2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/event/events/pan-gesture/pan-gesture-recognizer.h>
25 #include <dali/devel-api/events/touch-point.h>
27 #include <dali/integration-api/events/touch-event-integ.h>
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>
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);
45 uint32_t GetMilliSeconds()
47 // Get the time of a monotonic clock since its epoch.
48 auto epoch = std::chrono::steady_clock::now().time_since_epoch();
50 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
52 return static_cast<uint32_t>(duration.count());
54 } // unnamed namespace
56 PanGestureRecognizer::PanGestureRecognizer(Observer& observer, Vector2 screenSize, const PanGestureRequest& request, int32_t minimumDistance, int32_t minimumPanEvents)
57 : GestureRecognizer(screenSize, GestureType::PAN),
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),
68 mMaximumMotionEventAge(request.maxMotionEventAge),
71 if(minimumDistance >= 0)
73 mMinimumDistanceSquared = minimumDistance * minimumDistance;
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);
81 if(minimumPanEvents >= 1)
83 mMinimumMotionEvents = minimumPanEvents - 1; // Down is the first event
87 PanGestureRecognizer::~PanGestureRecognizer() = default;
89 void PanGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
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
94 if(primaryPointState == PointState::INTERRUPTED)
96 if((mState == STARTED) || (mState == POSSIBLE))
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);
102 mState = CLEAR; // We should change our state to CLEAR.
103 mTouchEvents.clear();
104 mPrimaryDeviceId = -1;
112 if((primaryPointState == PointState::DOWN) || (primaryPointState == PointState::STATIONARY) || (primaryPointState == PointState::MOTION))
114 mPrimaryTouchDownLocation = mPreviousPosition = event.points[0].GetScreenPosition();
115 mPrimaryTouchDownTime = event.time;
118 uint32_t pointCount(event.GetPointCount());
119 if(pointCount == mMinimumTouchesRequired)
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))
128 mTouchEvents.push_back(event);
135 uint32_t pointCount(event.GetPointCount());
136 if((pointCount >= mMinimumTouchesRequired) && (pointCount <= mMaximumTouchesRequired))
138 if(primaryPointState == PointState::MOTION)
140 mTouchEvents.push_back(event);
143 Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
145 if((mMotionEvents >= mMinimumMotionEvents) &&
146 (delta.LengthSquared() >= static_cast<float>(mMinimumDistanceSquared)))
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))
156 else if(primaryPointState == PointState::UP)
158 Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
159 if(delta.LengthSquared() >= static_cast<float>(mMinimumDistanceSquared))
161 SendPan(GestureState::STARTED, event);
162 mTouchEvents.push_back(event);
163 SendPan(GestureState::FINISHED, event);
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);
171 mTouchEvents.clear();
176 // We do not satisfy pan conditions, tell Core our Gesture has been cancelled.
177 SendPan(GestureState::CANCELLED, event);
179 if(pointCount == 1 && primaryPointState == PointState::UP)
181 // If we have lifted the primary touch point, then change our state to CLEAR...
183 mTouchEvents.clear();
187 // ...otherwise change it to FAILED.
196 mTouchEvents.push_back(event);
198 unsigned int pointCount(event.GetPointCount());
199 if((pointCount >= mMinimumTouchesRequired) && (pointCount <= mMaximumTouchesRequired))
201 switch(primaryPointState)
203 case PointState::MOTION:
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)
209 // Too old event. Skip it.
210 mTouchEvents.pop_back();
214 // Pan is continuing, tell Core.
215 SendPan(GestureState::CONTINUING, event);
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))
225 mTouchEvents.clear();
229 case PointState::STATIONARY:
230 if(pointCount == mMinimumTouchesRequired)
232 Integration::PointContainerConstIterator iter = event.points.begin() + 1; // We already know the state of the first point
233 for(; iter != event.points.end(); ++iter)
235 if(iter->GetState() == PointState::UP)
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))
254 // We have gone outside of the pan requirements, inform Core that the gesture is finished.
255 SendPan(GestureState::FINISHED, event);
257 if(pointCount == 1 && primaryPointState == PointState::UP)
259 // If this was the primary point being released, then we change our state back to CLEAR...
261 mTouchEvents.clear();
262 mPrimaryDeviceId = -1;
266 // ...otherwise we change it to FINISHED.
276 if(primaryPointState == PointState::UP)
278 // Change our state back to clear when the primary touch point is released.
280 mTouchEvents.clear();
281 mPrimaryDeviceId = -1;
289 void PanGestureRecognizer::CancelEvent()
291 if(mState != CLEAR && mTouchEvents.size() > 0)
293 const Integration::TouchEvent& previousEvent(*(mTouchEvents.rbegin()));
294 SendPan(GestureState::CANCELLED, previousEvent);
296 mTouchEvents.clear();
297 mPrimaryDeviceId = -1;
301 void PanGestureRecognizer::Update(const GestureRequest& request)
303 const PanGestureRequest& pan = static_cast<const PanGestureRequest&>(request);
305 mMinimumTouchesRequired = pan.minTouches;
306 mMaximumTouchesRequired = pan.maxTouches;
307 mMaximumMotionEventAge = pan.maxMotionEventAge;
310 bool PanGestureRecognizer::SendPan(GestureState state, const Integration::TouchEvent& currentEvent)
312 PanGestureEvent gesture(state);
313 gesture.currentPosition = currentEvent.points[0].GetScreenPosition();
314 gesture.numberOfTouches = currentEvent.GetPointCount();
316 if(mTouchEvents.size() > 1)
318 // Get the second last event in the queue, the last one is the current event
319 const Integration::TouchEvent& previousEvent(*(mTouchEvents.rbegin() + 1));
321 Vector2 previousPosition(mPreviousPosition);
322 uint32_t previousTime(previousEvent.time);
324 // If we've just started then we want to remove the threshold from Core calculations.
325 if(state == GestureState::STARTED)
327 previousPosition = mPrimaryTouchDownLocation;
328 previousTime = mPrimaryTouchDownTime;
329 mPrimaryDeviceId = currentEvent.points[0].GetDeviceId();
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)
335 mThresholdAdjustmentsRemaining = mThresholdTotalAdjustments;
336 mThresholdAdjustmentPerFrame = (gesture.currentPosition - previousPosition) / static_cast<float>(mThresholdTotalAdjustments);
340 mThresholdAdjustmentsRemaining = 0;
341 mThresholdAdjustmentPerFrame = Vector2::ZERO;
344 else if (state == GestureState::CONTINUING && mPrimaryDeviceId != currentEvent.points[0].GetDeviceId())
349 gesture.previousPosition = previousPosition;
350 gesture.timeDelta = currentEvent.time - previousTime;
352 // Apply the threshold with a phased approach
353 if(mThresholdAdjustmentsRemaining > 0)
355 --mThresholdAdjustmentsRemaining;
356 gesture.currentPosition -= mThresholdAdjustmentPerFrame * static_cast<float>(mThresholdAdjustmentsRemaining);
359 mPreviousPosition = gesture.currentPosition;
363 gesture.previousPosition = gesture.currentPosition;
364 gesture.timeDelta = 0;
367 gesture.time = currentEvent.time;
368 gesture.sourceType = mSourceType;
369 gesture.sourceData = mSourceData;
373 // Create another handle so the recognizer cannot be destroyed during process function
374 GestureRecognizerPtr recognizerHandle = this;
376 mObserver.Process(*mScene, gesture);
381 void PanGestureRecognizer::SetMinimumDistance(int32_t minimumDistance)
383 if(minimumDistance >= 0)
385 mMinimumDistanceSquared = minimumDistance * minimumDistance;
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);
394 void PanGestureRecognizer::SetMinimumPanEvents(int32_t minimumPanEvents)
396 if(minimumPanEvents >= 1)
398 mMinimumMotionEvents = minimumPanEvents - 1; // Down is the first event
402 } // namespace Internal