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