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