Fix the minimum pinch gesture distance
[platform/core/uifw/dali-core.git] / dali / internal / event / events / pinch-gesture / pinch-gesture-recognizer.cpp
1 /*
2  * Copyright (c) 2019 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 unsigned int MINIMUM_TOUCH_EVENTS_REQUIRED = 4;
43 const unsigned int MINIMUM_TOUCH_EVENTS_REQUIRED_AFTER_START = 4;
44
45 const float MINIMUM_DISTANCE_IN_MILLIINCH = 45.0f; // This value is used for measuring minimum pinch distance in pixel.
46 const float MINIMUM_DISTANCE_IN_PIXEL = 10.0f; // This value is for devices that do not provide a valid dpi value. (assumes 220dpi)
47
48 inline float GetDistance(const Integration::Point& point1, const Integration::Point& point2)
49 {
50   Vector2 vector(point1.GetScreenPosition() - point2.GetScreenPosition());
51   return vector.Length();
52 }
53
54 inline Vector2 GetCenterPoint(const Integration::Point& point1, const Integration::Point& point2)
55 {
56   return Vector2(point1.GetScreenPosition() + point2.GetScreenPosition()) * 0.5f;
57 }
58
59 inline bool IsValidDpi( const Vector2& dpi )
60 {
61   return dpi.x > 0.f && dpi.y > 0.f;
62 }
63
64 inline float GetDefaultMinimumPinchDistance( const Vector2& dpi )
65 {
66   return IsValidDpi( dpi ) ? ( ( MINIMUM_DISTANCE_IN_MILLIINCH * std::min( dpi.x, dpi.y ) ) / 1000.f ) : MINIMUM_DISTANCE_IN_PIXEL;
67 }
68
69 } // unnamed namespace
70
71 PinchGestureRecognizer::PinchGestureRecognizer( Observer& observer, Vector2 screenSize, Vector2 screenDpi, float minimumPinchDistance )
72 : GestureRecognizer( screenSize, Gesture::Pinch ),
73   mObserver( observer ),
74   mState( Clear ),
75   mTouchEvents(),
76   mDefaultMinimumDistanceDelta( GetDefaultMinimumPinchDistance( screenDpi ) ),
77   mStartingDistance( 0.0f )
78 {
79   SetMinimumPinchDistance( minimumPinchDistance );
80 }
81
82 PinchGestureRecognizer::~PinchGestureRecognizer()
83 {
84 }
85
86 void PinchGestureRecognizer::SetMinimumPinchDistance(float value)
87 {
88   mMinimumDistanceDelta = value >= 0.0f ? value : mDefaultMinimumDistanceDelta;
89 }
90
91 void PinchGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
92 {
93   int pointCount = event.GetPointCount();
94
95   switch (mState)
96   {
97     case Clear:
98     {
99       if (pointCount == 2)
100       {
101         // Change state to possible as we have two touch points.
102         mState = Possible;
103         mTouchEvents.push_back(event);
104       }
105       break;
106     }
107
108     case Possible:
109     {
110       if (pointCount != 2)
111       {
112         // We no longer have two touch points so change state back to Clear.
113         mState = Clear;
114         mTouchEvents.clear();
115       }
116       else
117       {
118         const Integration::Point& currentPoint1 = event.points[0];
119         const Integration::Point& currentPoint2 = event.points[1];
120
121         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
122         {
123           // One of our touch points has an Up event so change our state back to Clear.
124           mState = Clear;
125           mTouchEvents.clear();
126         }
127         else
128         {
129           mTouchEvents.push_back(event);
130
131           // We can only determine a pinch after a certain number of touch points have been collected.
132           if (mTouchEvents.size() >= MINIMUM_TOUCH_EVENTS_REQUIRED)
133           {
134             const Integration::Point& firstPoint1 = mTouchEvents[0].points[0];
135             const Integration::Point& firstPoint2 = mTouchEvents[0].points[1];
136
137             float firstDistance = GetDistance(firstPoint1, firstPoint2);
138             float currentDistance = GetDistance(currentPoint1, currentPoint2);
139             float distanceChanged = firstDistance - currentDistance;
140
141             // Check if distance has changed enough
142             if (fabsf(distanceChanged) > mMinimumDistanceDelta)
143             {
144               // Remove the first few events from the vector otherwise values are exaggerated
145               mTouchEvents.erase(mTouchEvents.begin(), mTouchEvents.end() - MINIMUM_TOUCH_EVENTS_REQUIRED_AFTER_START);
146
147               if ( !mTouchEvents.empty() )
148               {
149                 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
150
151                 // Send pinch started
152                 SendPinch(Gesture::Started, event);
153
154                 mState = Started;
155               }
156
157               mTouchEvents.clear();
158             }
159
160             if (mState == Possible)
161             {
162               // No pinch, so restart detection
163               mState = Clear;
164               mTouchEvents.clear();
165             }
166           }
167         }
168       }
169       break;
170     }
171
172     case Started:
173     {
174       if (pointCount != 2)
175       {
176         // Send pinch finished event
177         SendPinch(Gesture::Finished, event);
178
179         mState = Clear;
180         mTouchEvents.clear();
181       }
182       else
183       {
184         const Integration::Point& currentPoint1 = event.points[0];
185         const Integration::Point& currentPoint2 = event.points[1];
186
187         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
188         {
189           mTouchEvents.push_back(event);
190           // Send pinch finished event
191           SendPinch(Gesture::Finished, event);
192
193           mState = Clear;
194           mTouchEvents.clear();
195         }
196         else
197         {
198           mTouchEvents.push_back(event);
199
200           if (mTouchEvents.size() >= MINIMUM_TOUCH_EVENTS_REQUIRED_AFTER_START)
201           {
202             // Send pinch continuing
203             SendPinch(Gesture::Continuing, event);
204
205             mTouchEvents.clear();
206           }
207         }
208       }
209       break;
210     }
211   }
212 }
213
214 void PinchGestureRecognizer::Update(const GestureRequest& request)
215 {
216   // Nothing to do.
217 }
218
219 void PinchGestureRecognizer::SendPinch(Gesture::State state, const Integration::TouchEvent& currentEvent)
220 {
221   PinchGestureEvent gesture(state);
222
223   if ( !mTouchEvents.empty() )
224   {
225     const Integration::TouchEvent& firstEvent = mTouchEvents[0];
226
227     // Assert if we have been holding TouchEvents that do not have 2 points
228     DALI_ASSERT_DEBUG( firstEvent.GetPointCount() == 2 );
229
230     // We should use the current event in our calculations unless it does not have two points.
231     // If it does not have two points, then we should use the last point in mTouchEvents.
232     Integration::TouchEvent event( currentEvent );
233     if ( event.GetPointCount() != 2 )
234     {
235       event = *mTouchEvents.rbegin();
236     }
237
238     const Integration::Point& firstPoint1( firstEvent.points[0] );
239     const Integration::Point& firstPoint2( firstEvent.points[1] );
240     const Integration::Point& currentPoint1( event.points[0] );
241     const Integration::Point& currentPoint2( event.points[1] );
242
243     float firstDistance = GetDistance(firstPoint1, firstPoint2);
244     float currentDistance = GetDistance(currentPoint1, currentPoint2);
245     gesture.scale = currentDistance / mStartingDistance;
246
247     float distanceDelta = fabsf(firstDistance - currentDistance);
248     float timeDelta = static_cast<float> ( currentEvent.time - firstEvent.time );
249     gesture.speed = ( distanceDelta / timeDelta ) * 1000.0f;
250
251     gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
252   }
253   else
254   {
255     // Something has gone wrong, just cancel the gesture.
256     gesture.state = Gesture::Cancelled;
257   }
258
259   gesture.time = currentEvent.time;
260
261   if( mScene )
262   {
263     // Create another handle so the recognizer cannot be destroyed during process function
264     GestureRecognizerPtr recognizerHandle = this;
265
266     mObserver.Process(*mScene, gesture);
267   }
268 }
269
270 } // namespace Internal
271
272 } // namespace Dali