Merge "Clean up the code to build successfully on macOS" into devel/master
[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, GestureType::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() = default;
82
83 void PinchGestureRecognizer::SetMinimumPinchDistance(float value)
84 {
85   mMinimumDistanceDelta = value >= 0.0f ? value : mDefaultMinimumDistanceDelta;
86 }
87
88 void PinchGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
89 {
90   int pointCount = event.GetPointCount();
91   GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
92
93   switch (mState)
94   {
95     case CLEAR:
96     {
97       if (pointCount == 2)
98       {
99         // Change state to possible as we have two touch points.
100         mState = POSSIBLE;
101         mTouchEvents.push_back(event);
102       }
103       break;
104     }
105
106     case POSSIBLE:
107     {
108       if (pointCount != 2)
109       {
110         // We no longer have two touch points so change state back to CLEAR.
111         mState = CLEAR;
112         mTouchEvents.clear();
113       }
114       else
115       {
116         const Integration::Point& currentPoint1 = event.points[0];
117         const Integration::Point& currentPoint2 = event.points[1];
118
119         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP || currentPoint1.GetState() == PointState::INTERRUPTED)
120         {
121           // One of our touch points has an Up event so change our state back to CLEAR.
122           mState = CLEAR;
123           mTouchEvents.clear();
124         }
125         else
126         {
127           mTouchEvents.push_back(event);
128
129           // We can only determine a pinch after a certain number of touch points have been collected.
130           if( mTouchEvents.size() >= mMinimumTouchEvents )
131           {
132             const Integration::Point& firstPoint1 = mTouchEvents[0].points[0];
133             const Integration::Point& firstPoint2 = mTouchEvents[0].points[1];
134
135             float firstDistance = GetDistance(firstPoint1, firstPoint2);
136             float currentDistance = GetDistance(currentPoint1, currentPoint2);
137             float distanceChanged = firstDistance - currentDistance;
138
139             // Check if distance has changed enough
140             if (fabsf(distanceChanged) > mMinimumDistanceDelta)
141             {
142               // Remove the first few events from the vector otherwise values are exaggerated
143               mTouchEvents.erase( mTouchEvents.begin(), mTouchEvents.end() - mMinimumTouchEvents );
144
145               if ( !mTouchEvents.empty() )
146               {
147                 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
148
149                 // Send pinch started
150                 SendPinch(GestureState::STARTED, event);
151
152                 mState = STARTED;
153               }
154
155               mTouchEvents.clear();
156             }
157
158             if (mState == POSSIBLE)
159             {
160               // No pinch, so restart detection
161               mState = CLEAR;
162               mTouchEvents.clear();
163             }
164           }
165         }
166       }
167       break;
168     }
169
170     case STARTED:
171     {
172       if(event.points[0].GetState() == PointState::INTERRUPTED)
173       {
174         // System interruption occurred, pinch should be cancelled
175         mTouchEvents.clear();
176         SendPinch(GestureState::CANCELLED, event);
177         mState = CLEAR;
178         mTouchEvents.clear();
179       }
180       else if (pointCount != 2)
181       {
182         // Send pinch finished event
183         SendPinch(GestureState::FINISHED, event);
184
185         mState = CLEAR;
186         mTouchEvents.clear();
187       }
188       else
189       {
190         const Integration::Point& currentPoint1 = event.points[0];
191         const Integration::Point& currentPoint2 = event.points[1];
192
193         if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
194         {
195           mTouchEvents.push_back(event);
196           // Send pinch finished event
197           SendPinch(GestureState::FINISHED, event);
198
199           mState = CLEAR;
200           mTouchEvents.clear();
201         }
202         else
203         {
204           mTouchEvents.push_back(event);
205
206           if( mTouchEvents.size() >= mMinimumTouchEventsAfterStart )
207           {
208             // Send pinch continuing
209             SendPinch(GestureState::CONTINUING, event);
210
211             mTouchEvents.clear();
212           }
213         }
214       }
215       break;
216     }
217   }
218 }
219
220 void PinchGestureRecognizer::Update(const GestureRequest& request)
221 {
222   // Nothing to do.
223 }
224
225 void PinchGestureRecognizer::SendPinch(GestureState state, const Integration::TouchEvent& currentEvent)
226 {
227   PinchGestureEvent gesture(state);
228
229   if ( !mTouchEvents.empty() )
230   {
231     const Integration::TouchEvent& firstEvent = mTouchEvents[0];
232
233     // Assert if we have been holding TouchEvents that do not have 2 points
234     DALI_ASSERT_DEBUG( firstEvent.GetPointCount() == 2 );
235
236     // We should use the current event in our calculations unless it does not have two points.
237     // If it does not have two points, then we should use the last point in mTouchEvents.
238     Integration::TouchEvent event( currentEvent );
239     if ( event.GetPointCount() != 2 )
240     {
241       event = *mTouchEvents.rbegin();
242     }
243
244     const Integration::Point& firstPoint1( firstEvent.points[0] );
245     const Integration::Point& firstPoint2( firstEvent.points[1] );
246     const Integration::Point& currentPoint1( event.points[0] );
247     const Integration::Point& currentPoint2( event.points[1] );
248
249     float firstDistance = GetDistance(firstPoint1, firstPoint2);
250     float currentDistance = GetDistance(currentPoint1, currentPoint2);
251     gesture.scale = currentDistance / mStartingDistance;
252
253     float distanceDelta = fabsf(firstDistance - currentDistance);
254     float timeDelta = static_cast<float> ( currentEvent.time - firstEvent.time );
255     gesture.speed = ( distanceDelta / timeDelta ) * 1000.0f;
256
257     gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
258   }
259   else
260   {
261     // Something has gone wrong, just cancel the gesture.
262     gesture.state = GestureState::CANCELLED;
263   }
264
265   gesture.time = currentEvent.time;
266
267   if( mScene )
268   {
269     // Create another handle so the recognizer cannot be destroyed during process function
270     GestureRecognizerPtr recognizerHandle = this;
271
272     mObserver.Process(*mScene, gesture);
273   }
274 }
275
276 void PinchGestureRecognizer::SetMinimumTouchEvents( uint32_t value )
277 {
278   mMinimumTouchEvents = value;
279 }
280
281 void PinchGestureRecognizer::SetMinimumTouchEventsAfterStart( uint32_t value )
282 {
283   mMinimumTouchEventsAfterStart = value;
284 }
285
286 } // namespace Internal
287
288 } // namespace Dali