7d1df48587692500b5e6df4a7f9e79d606f92fa9
[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/devel-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, Dali::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   GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
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 || currentPoint1.GetState() == PointState::INTERRUPTED)
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() >= mMinimumTouchEvents )
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() - mMinimumTouchEvents );
146
147               if ( !mTouchEvents.empty() )
148               {
149                 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
150
151                 // Send pinch started
152                 SendPinch(Dali::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(event.points[0].GetState() == PointState::INTERRUPTED)
175       {
176         // System interruption occurred, pinch should be cancelled
177         mTouchEvents.clear();
178         SendPinch(Dali::Gesture::Cancelled, event);
179         mState = Clear;
180         mTouchEvents.clear();
181       }
182       else if (pointCount != 2)
183       {
184         // Send pinch finished event
185         SendPinch(Dali::Gesture::Finished, event);
186
187         mState = Clear;
188         mTouchEvents.clear();
189       }
190       else
191       {
192         const Integration::Point& currentPoint1 = event.points[0];
193         const Integration::Point& currentPoint2 = event.points[1];
194
195         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
196         {
197           mTouchEvents.push_back(event);
198           // Send pinch finished event
199           SendPinch(Dali::Gesture::Finished, event);
200
201           mState = Clear;
202           mTouchEvents.clear();
203         }
204         else
205         {
206           mTouchEvents.push_back(event);
207
208           if( mTouchEvents.size() >= mMinimumTouchEventsAfterStart )
209           {
210             // Send pinch continuing
211             SendPinch(Dali::Gesture::Continuing, event);
212
213             mTouchEvents.clear();
214           }
215         }
216       }
217       break;
218     }
219   }
220 }
221
222 void PinchGestureRecognizer::Update(const GestureRequest& request)
223 {
224   // Nothing to do.
225 }
226
227 void PinchGestureRecognizer::SendPinch(Gesture::State state, const Integration::TouchEvent& currentEvent)
228 {
229   PinchGestureEvent gesture(state);
230
231   if ( !mTouchEvents.empty() )
232   {
233     const Integration::TouchEvent& firstEvent = mTouchEvents[0];
234
235     // Assert if we have been holding TouchEvents that do not have 2 points
236     DALI_ASSERT_DEBUG( firstEvent.GetPointCount() == 2 );
237
238     // We should use the current event in our calculations unless it does not have two points.
239     // If it does not have two points, then we should use the last point in mTouchEvents.
240     Integration::TouchEvent event( currentEvent );
241     if ( event.GetPointCount() != 2 )
242     {
243       event = *mTouchEvents.rbegin();
244     }
245
246     const Integration::Point& firstPoint1( firstEvent.points[0] );
247     const Integration::Point& firstPoint2( firstEvent.points[1] );
248     const Integration::Point& currentPoint1( event.points[0] );
249     const Integration::Point& currentPoint2( event.points[1] );
250
251     float firstDistance = GetDistance(firstPoint1, firstPoint2);
252     float currentDistance = GetDistance(currentPoint1, currentPoint2);
253     gesture.scale = currentDistance / mStartingDistance;
254
255     float distanceDelta = fabsf(firstDistance - currentDistance);
256     float timeDelta = static_cast<float> ( currentEvent.time - firstEvent.time );
257     gesture.speed = ( distanceDelta / timeDelta ) * 1000.0f;
258
259     gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
260   }
261   else
262   {
263     // Something has gone wrong, just cancel the gesture.
264     gesture.state = Dali::Gesture::Cancelled;
265   }
266
267   gesture.time = currentEvent.time;
268
269   if( mScene )
270   {
271     // Create another handle so the recognizer cannot be destroyed during process function
272     GestureRecognizerPtr recognizerHandle = this;
273
274     mObserver.Process(*mScene, gesture);
275   }
276 }
277
278 void PinchGestureRecognizer::SetMinimumTouchEvents( uint32_t value )
279 {
280   mMinimumTouchEvents = value;
281 }
282
283 void PinchGestureRecognizer::SetMinimumTouchEventsAfterStart( uint32_t value )
284 {
285   mMinimumTouchEventsAfterStart = value;
286 }
287
288 } // namespace Internal
289
290 } // namespace Dali