0256821daf68de90724888c2f2397753d3f2f31d
[platform/core/uifw/dali-core.git] / dali / internal / event / events / pan-gesture / pan-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/pan-gesture/pan-gesture-recognizer.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 #include <dali/public-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, Gesture::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
85   if (primaryPointState == PointState::INTERRUPTED)
86   {
87     if ( ( mState == Started ) || ( mState == Possible ) )
88     {
89       // If our pan had started and we are interrupted, then tell Core that pan is cancelled.
90       mTouchEvents.push_back(event);
91       SendPan(Gesture::Cancelled, event);
92     }
93     mState = Clear; // We should change our state to Clear.
94     mTouchEvents.clear();
95   }
96   else
97   {
98     switch (mState)
99     {
100       case Clear:
101       {
102         if ( ( primaryPointState == PointState::DOWN ) || ( primaryPointState == PointState::STATIONARY ) )
103         {
104           mPrimaryTouchDownLocation = event.points[0].GetScreenPosition();
105           mPrimaryTouchDownTime = event.time;
106           mMotionEvents = 0;
107           if (event.GetPointCount() == mMinimumTouchesRequired)
108           {
109             // We have satisfied the minimum touches required for a pan, tell core that a gesture may be possible and change our state accordingly.
110             mState = Possible;
111             SendPan(Gesture::Possible, event);
112           }
113
114           mTouchEvents.push_back(event);
115         }
116         break;
117       }
118
119       case Possible:
120       {
121         unsigned int pointCount(event.GetPointCount());
122         if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
123         {
124           if (primaryPointState == PointState::MOTION)
125           {
126             mTouchEvents.push_back(event);
127             mMotionEvents++;
128
129             Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
130
131             if ( ( mMotionEvents >= mMinimumMotionEvents ) &&
132                  ( delta.LengthSquared() >= static_cast<float>( mMinimumDistanceSquared ) ) )
133             {
134               // 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.
135               mState = Started;
136               SendPan(Gesture::Started, event);
137             }
138           }
139           else if (primaryPointState == PointState::UP)
140           {
141             Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
142             if (delta.LengthSquared() >= static_cast<float>( mMinimumDistanceSquared ) )
143             {
144               SendPan(Gesture::Started, event);
145               mTouchEvents.push_back(event);
146               SendPan(Gesture::Finished, event);
147             }
148             else
149             {
150               // If we have lifted the primary touch point then tell core the pan is cancelled and change our state to Clear.
151               SendPan(Gesture::Cancelled, event);
152             }
153             mState = Clear;
154             mTouchEvents.clear();
155           }
156         }
157         else
158         {
159           // We do not satisfy pan conditions, tell Core our Gesture has been cancelled.
160           SendPan(Gesture::Cancelled, event);
161
162           if (pointCount == 1 && primaryPointState == PointState::UP)
163           {
164             // If we have lifted the primary touch point, then change our state to Clear...
165             mState = Clear;
166             mTouchEvents.clear();
167           }
168           else
169           {
170             // ...otherwise change it to Failed.
171             mState = Failed;
172           }
173         }
174         break;
175       }
176
177       case Started:
178       {
179         mTouchEvents.push_back(event);
180
181         unsigned int pointCount(event.GetPointCount());
182         if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
183         {
184           switch (primaryPointState)
185           {
186             case PointState::MOTION:
187               // Pan is continuing, tell Core.
188               SendPan(Gesture::Continuing, event);
189               break;
190
191             case PointState::UP:
192               // Pan is finally finished when our primary point is lifted, tell Core and change our state to Clear.
193               mState = Clear;
194               SendPan(Gesture::Finished, event);
195               mTouchEvents.clear();
196               break;
197
198             case PointState::STATIONARY:
199               if (pointCount == mMinimumTouchesRequired)
200               {
201                 Integration::PointContainerConstIterator iter = event.points.begin() + 1; // We already know the state of the first point
202                 for(; iter != event.points.end(); ++iter)
203                 {
204                   if(iter->GetState() == PointState::UP)
205                   {
206                     // The number of touch points will be less than the minimum required.  Inform core and change our state to Finished.
207                     SendPan(Gesture::Finished, event);
208                     mState = Finished;
209                     break;
210                   }
211                 }
212               }
213               break;
214
215             default:
216               break;
217           }
218         }
219         else
220         {
221           // We have gone outside of the pan requirements, inform Core that the gesture is finished.
222           SendPan(Gesture::Finished, event);
223
224           if (pointCount == 1 && primaryPointState == PointState::UP)
225           {
226             // If this was the primary point being released, then we change our state back to Clear...
227             mState = Clear;
228             mTouchEvents.clear();
229           }
230           else
231           {
232             // ...otherwise we change it to Finished.
233             mState = Finished;
234           }
235         }
236         break;
237       }
238
239       case Finished:
240       case Failed:
241       {
242         if (primaryPointState == PointState::UP)
243         {
244           // Change our state back to clear when the primary touch point is released.
245           mState = Clear;
246           mTouchEvents.clear();
247         }
248         break;
249       }
250     }
251   }
252 }
253
254 void PanGestureRecognizer::Update(const GestureRequest& request)
255 {
256   const PanGestureRequest& pan = static_cast<const PanGestureRequest&>(request);
257
258   mMinimumTouchesRequired = pan.minTouches;
259   mMaximumTouchesRequired = pan.maxTouches;
260 }
261
262 void PanGestureRecognizer::SendPan(Gesture::State state, const Integration::TouchEvent& currentEvent)
263 {
264   PanGestureEvent gesture(state);
265   gesture.currentPosition = currentEvent.points[0].GetScreenPosition();
266   gesture.numberOfTouches = currentEvent.GetPointCount();
267
268   if ( mTouchEvents.size() > 1 )
269   {
270     // Get the second last event in the queue, the last one is the current event
271     const Integration::TouchEvent& previousEvent( *( mTouchEvents.rbegin() + 1 ) );
272
273     Vector2 previousPosition( mPreviousPosition );
274     uint32_t previousTime( previousEvent.time );
275
276     // If we've just started then we want to remove the threshold from Core calculations.
277     if ( state == Gesture::Started )
278     {
279       previousPosition = mPrimaryTouchDownLocation;
280       previousTime = mPrimaryTouchDownTime;
281
282       // If it's a slow pan, we do not want to phase in the threshold over the first few pan-events
283       // A slow pan is defined as one that starts the specified number of milliseconds after the down-event
284       if ( ( currentEvent.time - previousTime ) > MINIMUM_TIME_BEFORE_THRESHOLD_ADJUSTMENTS )
285       {
286         mThresholdAdjustmentsRemaining = mThresholdTotalAdjustments;
287         mThresholdAdjustmentPerFrame = ( gesture.currentPosition - previousPosition ) / static_cast<float>( mThresholdTotalAdjustments );
288       }
289       else
290       {
291         mThresholdAdjustmentsRemaining = 0;
292         mThresholdAdjustmentPerFrame = Vector2::ZERO;
293       }
294     }
295
296     gesture.previousPosition = previousPosition;
297     gesture.timeDelta = currentEvent.time - previousTime;
298
299     // Apply the threshold with a phased approach
300     if ( mThresholdAdjustmentsRemaining > 0 )
301     {
302       --mThresholdAdjustmentsRemaining;
303       gesture.currentPosition -= mThresholdAdjustmentPerFrame * static_cast<float>( mThresholdAdjustmentsRemaining );
304     }
305
306     mPreviousPosition = gesture.currentPosition;
307   }
308   else
309   {
310     gesture.previousPosition = gesture.currentPosition;
311     gesture.timeDelta = 0;
312   }
313
314   gesture.time = currentEvent.time;
315
316   if( mScene )
317   {
318     // Create another handle so the recognizer cannot be destroyed during process function
319     GestureRecognizerPtr recognizerHandle = this;
320
321     mObserver.Process(*mScene, gesture);
322   }
323 }
324
325 } // namespace Internal
326
327 } // namespace Dali