Merge "Clean up the code to build successfully on macOS" into devel/master
[platform/core/uifw/dali-core.git] / dali / internal / event / events / pan-gesture / pan-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/pan-gesture/pan-gesture-recognizer.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 #include <dali/devel-api/events/touch-point.h>
25
26 #include <dali/integration-api/events/touch-event-integ.h>
27
28 // INTERNAL INCLUDES
29 #include <dali/internal/event/common/scene-impl.h>
30 #include <dali/internal/event/events/pan-gesture/pan-gesture-event.h>
31 #include <dali/internal/event/events/gesture-requests.h>
32 namespace Dali
33 {
34
35 namespace Internal
36 {
37
38 namespace
39 {
40 const float MINIMUM_MOTION_DISTANCE_BEFORE_PAN( 15.0f );
41 const float MINIMUM_MOTION_DISTANCE_BEFORE_PAN_SQUARED( MINIMUM_MOTION_DISTANCE_BEFORE_PAN * MINIMUM_MOTION_DISTANCE_BEFORE_PAN );
42 const float MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO( 2.0f / 3.0f );
43 const unsigned long MINIMUM_TIME_BEFORE_THRESHOLD_ADJUSTMENTS( 100 );
44 const unsigned int MINIMUM_MOTION_EVENTS_BEFORE_PAN(2);
45 } // unnamed namespace
46
47 PanGestureRecognizer::PanGestureRecognizer( Observer& observer, Vector2 screenSize, const PanGestureRequest& request, int32_t minimumDistance, int32_t minimumPanEvents )
48 : GestureRecognizer( screenSize, GestureType::PAN ),
49   mObserver( observer ),
50   mState( CLEAR ),
51   mThresholdAdjustmentsRemaining( 0 ),
52   mThresholdTotalAdjustments( static_cast<unsigned int>( MINIMUM_MOTION_DISTANCE_BEFORE_PAN * MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO ) ),
53   mPrimaryTouchDownTime( 0 ),
54   mMinimumTouchesRequired( request.minTouches ),
55   mMaximumTouchesRequired( request.maxTouches ),
56   mMinimumDistanceSquared( static_cast<unsigned int>( MINIMUM_MOTION_DISTANCE_BEFORE_PAN_SQUARED ) ),
57   mMinimumMotionEvents( MINIMUM_MOTION_EVENTS_BEFORE_PAN ),
58   mMotionEvents( 0 )
59 {
60   if ( minimumDistance >= 0 )
61   {
62     mMinimumDistanceSquared = minimumDistance * minimumDistance;
63
64     // Usually, we do not want to apply the threshold straight away, but phased over the first few pans
65     // Set our distance to threshold adjustments ratio here.
66     float fMinimumDistance = static_cast<float>( minimumDistance );
67     mThresholdTotalAdjustments = static_cast<unsigned int>( fMinimumDistance * MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO );
68   }
69
70   if ( minimumPanEvents >= 1 )
71   {
72     mMinimumMotionEvents = minimumPanEvents - 1; // Down is the first event
73   }
74 }
75
76 PanGestureRecognizer::~PanGestureRecognizer() = default;
77
78 void PanGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
79 {
80   PointState::Type primaryPointState(event.points[0].GetState());
81   GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
82
83   if (primaryPointState == PointState::INTERRUPTED)
84   {
85     if ( ( mState == STARTED ) || ( mState == POSSIBLE ) )
86     {
87       // If our pan had started and we are interrupted, then tell Core that pan is cancelled.
88       mTouchEvents.push_back(event);
89       SendPan(GestureState::CANCELLED, event);
90     }
91     mState = CLEAR; // We should change our state to CLEAR.
92     mTouchEvents.clear();
93   }
94   else
95   {
96     switch (mState)
97     {
98       case CLEAR:
99       {
100         if ( ( primaryPointState == PointState::DOWN ) || ( primaryPointState == PointState::STATIONARY ) || ( primaryPointState == PointState::MOTION ))
101         {
102           mPrimaryTouchDownLocation = event.points[0].GetScreenPosition();
103           mPrimaryTouchDownTime = event.time;
104           mMotionEvents = 0;
105           if (event.GetPointCount() == mMinimumTouchesRequired)
106           {
107             // We have satisfied the minimum touches required for a pan, tell core that a gesture may be possible and change our state accordingly.
108             mState = POSSIBLE;
109             SendPan(GestureState::POSSIBLE, event);
110           }
111
112           mTouchEvents.push_back(event);
113         }
114         break;
115       }
116
117       case POSSIBLE:
118       {
119         unsigned int pointCount(event.GetPointCount());
120         if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
121         {
122           if (primaryPointState == PointState::MOTION)
123           {
124             mTouchEvents.push_back(event);
125             mMotionEvents++;
126
127             Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
128
129             if ( ( mMotionEvents >= mMinimumMotionEvents ) &&
130                  ( delta.LengthSquared() >= static_cast<float>( mMinimumDistanceSquared ) ) )
131             {
132               // If the touch point(s) have moved enough distance to be considered a pan, then tell Core that the pan gesture has started and change our state accordingly.
133               mState = STARTED;
134               SendPan(GestureState::STARTED, event);
135             }
136           }
137           else if (primaryPointState == PointState::UP)
138           {
139             Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
140             if (delta.LengthSquared() >= static_cast<float>( mMinimumDistanceSquared ) )
141             {
142               SendPan(GestureState::STARTED, event);
143               mTouchEvents.push_back(event);
144               SendPan(GestureState::FINISHED, event);
145             }
146             else
147             {
148               // If we have lifted the primary touch point then tell core the pan is cancelled and change our state to CLEAR.
149               SendPan(GestureState::CANCELLED, event);
150             }
151             mState = CLEAR;
152             mTouchEvents.clear();
153           }
154         }
155         else
156         {
157           // We do not satisfy pan conditions, tell Core our Gesture has been cancelled.
158           SendPan(GestureState::CANCELLED, event);
159
160           if (pointCount == 1 && primaryPointState == PointState::UP)
161           {
162             // If we have lifted the primary touch point, then change our state to CLEAR...
163             mState = CLEAR;
164             mTouchEvents.clear();
165           }
166           else
167           {
168             // ...otherwise change it to FAILED.
169             mState = FAILED;
170           }
171         }
172         break;
173       }
174
175       case STARTED:
176       {
177         mTouchEvents.push_back(event);
178
179         unsigned int pointCount(event.GetPointCount());
180         if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
181         {
182           switch (primaryPointState)
183           {
184             case PointState::MOTION:
185               // Pan is continuing, tell Core.
186               SendPan(GestureState::CONTINUING, event);
187               break;
188
189             case PointState::UP:
190               // Pan is finally finished when our primary point is lifted, tell Core and change our state to CLEAR.
191               mState = CLEAR;
192               SendPan(GestureState::FINISHED, event);
193               mTouchEvents.clear();
194               break;
195
196             case PointState::STATIONARY:
197               if (pointCount == mMinimumTouchesRequired)
198               {
199                 Integration::PointContainerConstIterator iter = event.points.begin() + 1; // We already know the state of the first point
200                 for(; iter != event.points.end(); ++iter)
201                 {
202                   if(iter->GetState() == PointState::UP)
203                   {
204                     // The number of touch points will be less than the minimum required.  Inform core and change our state to FINISHED.
205                     SendPan(GestureState::FINISHED, event);
206                     mState = FINISHED;
207                     break;
208                   }
209                 }
210               }
211               break;
212
213             default:
214               break;
215           }
216         }
217         else
218         {
219           // We have gone outside of the pan requirements, inform Core that the gesture is finished.
220           SendPan(GestureState::FINISHED, event);
221
222           if (pointCount == 1 && primaryPointState == PointState::UP)
223           {
224             // If this was the primary point being released, then we change our state back to CLEAR...
225             mState = CLEAR;
226             mTouchEvents.clear();
227           }
228           else
229           {
230             // ...otherwise we change it to FINISHED.
231             mState = FINISHED;
232           }
233         }
234         break;
235       }
236
237       case FINISHED:
238       case FAILED:
239       {
240         if (primaryPointState == PointState::UP)
241         {
242           // Change our state back to clear when the primary touch point is released.
243           mState = CLEAR;
244           mTouchEvents.clear();
245         }
246         break;
247       }
248     }
249   }
250 }
251
252 void PanGestureRecognizer::Update(const GestureRequest& request)
253 {
254   const PanGestureRequest& pan = static_cast<const PanGestureRequest&>(request);
255
256   mMinimumTouchesRequired = pan.minTouches;
257   mMaximumTouchesRequired = pan.maxTouches;
258 }
259
260 void PanGestureRecognizer::SendPan(GestureState state, const Integration::TouchEvent& currentEvent)
261 {
262   PanGestureEvent gesture(state);
263   gesture.currentPosition = currentEvent.points[0].GetScreenPosition();
264   gesture.numberOfTouches = currentEvent.GetPointCount();
265
266   if ( mTouchEvents.size() > 1 )
267   {
268     // Get the second last event in the queue, the last one is the current event
269     const Integration::TouchEvent& previousEvent( *( mTouchEvents.rbegin() + 1 ) );
270
271     Vector2 previousPosition( mPreviousPosition );
272     uint32_t previousTime( previousEvent.time );
273
274     // If we've just started then we want to remove the threshold from Core calculations.
275     if ( state == GestureState::STARTED )
276     {
277       previousPosition = mPrimaryTouchDownLocation;
278       previousTime = mPrimaryTouchDownTime;
279
280       // If it's a slow pan, we do not want to phase in the threshold over the first few pan-events
281       // A slow pan is defined as one that starts the specified number of milliseconds after the down-event
282       if ( ( currentEvent.time - previousTime ) > MINIMUM_TIME_BEFORE_THRESHOLD_ADJUSTMENTS )
283       {
284         mThresholdAdjustmentsRemaining = mThresholdTotalAdjustments;
285         mThresholdAdjustmentPerFrame = ( gesture.currentPosition - previousPosition ) / static_cast<float>( mThresholdTotalAdjustments );
286       }
287       else
288       {
289         mThresholdAdjustmentsRemaining = 0;
290         mThresholdAdjustmentPerFrame = Vector2::ZERO;
291       }
292     }
293
294     gesture.previousPosition = previousPosition;
295     gesture.timeDelta = currentEvent.time - previousTime;
296
297     // Apply the threshold with a phased approach
298     if ( mThresholdAdjustmentsRemaining > 0 )
299     {
300       --mThresholdAdjustmentsRemaining;
301       gesture.currentPosition -= mThresholdAdjustmentPerFrame * static_cast<float>( mThresholdAdjustmentsRemaining );
302     }
303
304     mPreviousPosition = gesture.currentPosition;
305   }
306   else
307   {
308     gesture.previousPosition = gesture.currentPosition;
309     gesture.timeDelta = 0;
310   }
311
312   gesture.time = currentEvent.time;
313
314   if( mScene )
315   {
316     // Create another handle so the recognizer cannot be destroyed during process function
317     GestureRecognizerPtr recognizerHandle = this;
318
319     mObserver.Process(*mScene, gesture);
320   }
321 }
322
323 } // namespace Internal
324
325 } // namespace Dali