808c4adf766807be9a7d97b0dd442be1472f3065
[platform/core/uifw/dali-core.git] / dali / internal / event / events / pinch-gesture / pinch-gesture-recognizer.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/event/events/pinch-gesture/pinch-gesture-recognizer.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 #include <dali/public-api/events/touch-point.h>
25 #include <dali/public-api/math/vector2.h>
26
27 #include <dali/internal/event/events/pinch-gesture/pinch-gesture-event.h>
28 #include <dali/integration-api/events/touch-event-integ.h>
29 #include <dali/internal/event/common/scene-impl.h>
30
31 // INTERNAL INCLUDES
32
33
34 namespace Dali
35 {
36
37 namespace Internal
38 {
39
40 namespace
41 {
42 const float MINIMUM_DISTANCE_IN_MILLIINCH = 45.0f; // This value is used for measuring minimum pinch distance in pixel.
43 const float MINIMUM_DISTANCE_IN_PIXEL = 10.0f; // This value is for devices that do not provide a valid dpi value. (assumes 220dpi)
44
45 inline float GetDistance(const Integration::Point& point1, const Integration::Point& point2)
46 {
47   Vector2 vector(point1.GetScreenPosition() - point2.GetScreenPosition());
48   return vector.Length();
49 }
50
51 inline Vector2 GetCenterPoint(const Integration::Point& point1, const Integration::Point& point2)
52 {
53   return Vector2(point1.GetScreenPosition() + point2.GetScreenPosition()) * 0.5f;
54 }
55
56 inline bool IsValidDpi( const Vector2& dpi )
57 {
58   return dpi.x > 0.f && dpi.y > 0.f;
59 }
60
61 inline float GetDefaultMinimumPinchDistance( const Vector2& dpi )
62 {
63   return IsValidDpi( dpi ) ? ( ( MINIMUM_DISTANCE_IN_MILLIINCH * std::min( dpi.x, dpi.y ) ) / 1000.f ) : MINIMUM_DISTANCE_IN_PIXEL;
64 }
65
66 } // unnamed namespace
67
68 PinchGestureRecognizer::PinchGestureRecognizer( Observer& observer, Vector2 screenSize, Vector2 screenDpi, float minimumPinchDistance, uint32_t minimumTouchEvents, uint32_t minimumTouchEventsAfterStart )
69 : GestureRecognizer( screenSize, Gesture::Pinch ),
70   mObserver( observer ),
71   mState( Clear ),
72   mTouchEvents(),
73   mDefaultMinimumDistanceDelta( GetDefaultMinimumPinchDistance( screenDpi ) ),
74   mStartingDistance( 0.0f ),
75   mMinimumTouchEvents( minimumTouchEvents ),
76   mMinimumTouchEventsAfterStart( minimumTouchEventsAfterStart )
77 {
78   SetMinimumPinchDistance( minimumPinchDistance );
79 }
80
81 PinchGestureRecognizer::~PinchGestureRecognizer()
82 {
83 }
84
85 void PinchGestureRecognizer::SetMinimumPinchDistance(float value)
86 {
87   mMinimumDistanceDelta = value >= 0.0f ? value : mDefaultMinimumDistanceDelta;
88 }
89
90 void PinchGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
91 {
92   int pointCount = event.GetPointCount();
93
94   switch (mState)
95   {
96     case Clear:
97     {
98       if (pointCount == 2)
99       {
100         // Change state to possible as we have two touch points.
101         mState = Possible;
102         mTouchEvents.push_back(event);
103       }
104       break;
105     }
106
107     case Possible:
108     {
109       if (pointCount != 2)
110       {
111         // We no longer have two touch points so change state back to Clear.
112         mState = Clear;
113         mTouchEvents.clear();
114       }
115       else
116       {
117         const Integration::Point& currentPoint1 = event.points[0];
118         const Integration::Point& currentPoint2 = event.points[1];
119
120         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP || currentPoint1.GetState() == PointState::INTERRUPTED)
121         {
122           // One of our touch points has an Up event so change our state back to Clear.
123           mState = Clear;
124           mTouchEvents.clear();
125         }
126         else
127         {
128           mTouchEvents.push_back(event);
129
130           // We can only determine a pinch after a certain number of touch points have been collected.
131           if( mTouchEvents.size() >= mMinimumTouchEvents )
132           {
133             const Integration::Point& firstPoint1 = mTouchEvents[0].points[0];
134             const Integration::Point& firstPoint2 = mTouchEvents[0].points[1];
135
136             float firstDistance = GetDistance(firstPoint1, firstPoint2);
137             float currentDistance = GetDistance(currentPoint1, currentPoint2);
138             float distanceChanged = firstDistance - currentDistance;
139
140             // Check if distance has changed enough
141             if (fabsf(distanceChanged) > mMinimumDistanceDelta)
142             {
143               // Remove the first few events from the vector otherwise values are exaggerated
144               mTouchEvents.erase( mTouchEvents.begin(), mTouchEvents.end() - mMinimumTouchEvents );
145
146               if ( !mTouchEvents.empty() )
147               {
148                 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
149
150                 // Send pinch started
151                 SendPinch(Gesture::Started, event);
152
153                 mState = Started;
154               }
155
156               mTouchEvents.clear();
157             }
158
159             if (mState == Possible)
160             {
161               // No pinch, so restart detection
162               mState = Clear;
163               mTouchEvents.clear();
164             }
165           }
166         }
167       }
168       break;
169     }
170
171     case Started:
172     {
173       if(event.points[0].GetState() == PointState::INTERRUPTED)
174       {
175         // System interruption occurred, pinch should be cancelled
176         mTouchEvents.clear();
177         SendPinch(Gesture::Cancelled, event);
178         mState = Clear;
179         mTouchEvents.clear();
180       }
181       else if (pointCount != 2)
182       {
183         // Send pinch finished event
184         SendPinch(Gesture::Finished, event);
185
186         mState = Clear;
187         mTouchEvents.clear();
188       }
189       else
190       {
191         const Integration::Point& currentPoint1 = event.points[0];
192         const Integration::Point& currentPoint2 = event.points[1];
193
194         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
195         {
196           mTouchEvents.push_back(event);
197           // Send pinch finished event
198           SendPinch(Gesture::Finished, event);
199
200           mState = Clear;
201           mTouchEvents.clear();
202         }
203         else
204         {
205           mTouchEvents.push_back(event);
206
207           if( mTouchEvents.size() >= mMinimumTouchEventsAfterStart )
208           {
209             // Send pinch continuing
210             SendPinch(Gesture::Continuing, event);
211
212             mTouchEvents.clear();
213           }
214         }
215       }
216       break;
217     }
218   }
219 }
220
221 void PinchGestureRecognizer::Update(const GestureRequest& request)
222 {
223   // Nothing to do.
224 }
225
226 void PinchGestureRecognizer::SendPinch(Gesture::State state, const Integration::TouchEvent& currentEvent)
227 {
228   PinchGestureEvent gesture(state);
229
230   if ( !mTouchEvents.empty() )
231   {
232     const Integration::TouchEvent& firstEvent = mTouchEvents[0];
233
234     // Assert if we have been holding TouchEvents that do not have 2 points
235     DALI_ASSERT_DEBUG( firstEvent.GetPointCount() == 2 );
236
237     // We should use the current event in our calculations unless it does not have two points.
238     // If it does not have two points, then we should use the last point in mTouchEvents.
239     Integration::TouchEvent event( currentEvent );
240     if ( event.GetPointCount() != 2 )
241     {
242       event = *mTouchEvents.rbegin();
243     }
244
245     const Integration::Point& firstPoint1( firstEvent.points[0] );
246     const Integration::Point& firstPoint2( firstEvent.points[1] );
247     const Integration::Point& currentPoint1( event.points[0] );
248     const Integration::Point& currentPoint2( event.points[1] );
249
250     float firstDistance = GetDistance(firstPoint1, firstPoint2);
251     float currentDistance = GetDistance(currentPoint1, currentPoint2);
252     gesture.scale = currentDistance / mStartingDistance;
253
254     float distanceDelta = fabsf(firstDistance - currentDistance);
255     float timeDelta = static_cast<float> ( currentEvent.time - firstEvent.time );
256     gesture.speed = ( distanceDelta / timeDelta ) * 1000.0f;
257
258     gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
259   }
260   else
261   {
262     // Something has gone wrong, just cancel the gesture.
263     gesture.state = Gesture::Cancelled;
264   }
265
266   gesture.time = currentEvent.time;
267
268   if( mScene )
269   {
270     // Create another handle so the recognizer cannot be destroyed during process function
271     GestureRecognizerPtr recognizerHandle = this;
272
273     mObserver.Process(*mScene, gesture);
274   }
275 }
276
277 void PinchGestureRecognizer::SetMinimumTouchEvents( uint32_t value )
278 {
279   mMinimumTouchEvents = value;
280 }
281
282 void PinchGestureRecognizer::SetMinimumTouchEventsAfterStart( uint32_t value )
283 {
284   mMinimumTouchEventsAfterStart = value;
285 }
286
287 } // namespace Internal
288
289 } // namespace Dali