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