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