2 * Copyright (c) 2021 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/pinch-gesture/pinch-gesture-recognizer.h>
24 #include <dali/devel-api/events/touch-point.h>
25 #include <dali/public-api/math/vector2.h>
27 #include <dali/integration-api/events/touch-event-integ.h>
28 #include <dali/internal/event/common/scene-impl.h>
29 #include <dali/internal/event/events/pinch-gesture/pinch-gesture-event.h>
39 const float MINIMUM_DISTANCE_IN_MILLIINCH = 45.0f; // This value is used for measuring minimum pinch distance in pixel.
40 const float MINIMUM_DISTANCE_IN_PIXEL = 10.0f; // This value is for devices that do not provide a valid dpi value. (assumes 220dpi)
42 inline float GetDistance(const Integration::Point& point1, const Integration::Point& point2)
44 Vector2 vector(point1.GetScreenPosition() - point2.GetScreenPosition());
45 return vector.Length();
48 inline Vector2 GetCenterPoint(const Integration::Point& point1, const Integration::Point& point2)
50 return Vector2(point1.GetScreenPosition() + point2.GetScreenPosition()) * 0.5f;
53 inline bool IsValidDpi(const Vector2& dpi)
55 return dpi.x > 0.f && dpi.y > 0.f;
58 inline float GetDefaultMinimumPinchDistance(const Vector2& dpi)
60 return IsValidDpi(dpi) ? ((MINIMUM_DISTANCE_IN_MILLIINCH * std::min(dpi.x, dpi.y)) / 1000.f) : MINIMUM_DISTANCE_IN_PIXEL;
63 } // unnamed namespace
65 PinchGestureRecognizer::PinchGestureRecognizer(Observer& observer, Vector2 screenSize, Vector2 screenDpi, float minimumPinchDistance, uint32_t minimumTouchEvents, uint32_t minimumTouchEventsAfterStart)
66 : GestureRecognizer(screenSize, GestureType::PINCH),
70 mDefaultMinimumDistanceDelta(GetDefaultMinimumPinchDistance(screenDpi)),
71 mStartingDistance(0.0f),
72 mMinimumTouchEvents(minimumTouchEvents),
73 mMinimumTouchEventsAfterStart(minimumTouchEventsAfterStart)
75 SetMinimumPinchDistance(minimumPinchDistance);
78 PinchGestureRecognizer::~PinchGestureRecognizer() = default;
80 void PinchGestureRecognizer::SetMinimumPinchDistance(float value)
82 mMinimumDistanceDelta = value >= 0.0f ? value : mDefaultMinimumDistanceDelta;
85 void PinchGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
87 int pointCount = event.GetPointCount();
88 GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
96 // Change state to possible as we have two touch points.
98 mTouchEvents.push_back(event);
107 // We no longer have two touch points so change state back to CLEAR.
109 mTouchEvents.clear();
113 const Integration::Point& currentPoint1 = event.points[0];
114 const Integration::Point& currentPoint2 = event.points[1];
116 if(currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP || currentPoint1.GetState() == PointState::INTERRUPTED)
118 // One of our touch points has an Up event so change our state back to CLEAR.
120 mTouchEvents.clear();
124 mTouchEvents.push_back(event);
126 // We can only determine a pinch after a certain number of touch points have been collected.
127 if(mTouchEvents.size() >= mMinimumTouchEvents)
129 const Integration::Point& firstPoint1 = mTouchEvents[0].points[0];
130 const Integration::Point& firstPoint2 = mTouchEvents[0].points[1];
132 float firstDistance = GetDistance(firstPoint1, firstPoint2);
133 float currentDistance = GetDistance(currentPoint1, currentPoint2);
134 float distanceChanged = firstDistance - currentDistance;
136 // Check if distance has changed enough
137 if(fabsf(distanceChanged) > mMinimumDistanceDelta)
139 // Remove the first few events from the vector otherwise values are exaggerated
140 mTouchEvents.erase(mTouchEvents.begin(), mTouchEvents.end() - mMinimumTouchEvents);
142 if(!mTouchEvents.empty())
144 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
146 // Send pinch started
147 SendPinch(GestureState::STARTED, event);
152 mTouchEvents.clear();
155 if(mState == POSSIBLE)
157 // No pinch, so restart detection
159 mTouchEvents.clear();
169 if(event.points[0].GetState() == PointState::INTERRUPTED)
171 // System interruption occurred, pinch should be cancelled
172 mTouchEvents.clear();
173 SendPinch(GestureState::CANCELLED, event);
175 mTouchEvents.clear();
177 else if(pointCount != 2)
179 // Send pinch finished event
180 SendPinch(GestureState::FINISHED, event);
183 mTouchEvents.clear();
187 const Integration::Point& currentPoint1 = event.points[0];
188 const Integration::Point& currentPoint2 = event.points[1];
190 if(currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
192 mTouchEvents.push_back(event);
193 // Send pinch finished event
194 SendPinch(GestureState::FINISHED, event);
197 mTouchEvents.clear();
201 mTouchEvents.push_back(event);
203 if(mTouchEvents.size() >= mMinimumTouchEventsAfterStart)
205 // Send pinch continuing
206 SendPinch(GestureState::CONTINUING, event);
208 mTouchEvents.clear();
217 void PinchGestureRecognizer::Update(const GestureRequest& request)
222 void PinchGestureRecognizer::SendPinch(GestureState state, const Integration::TouchEvent& currentEvent)
224 PinchGestureEvent gesture(state);
226 if(!mTouchEvents.empty())
228 const Integration::TouchEvent& firstEvent = mTouchEvents[0];
230 // Assert if we have been holding TouchEvents that do not have 2 points
231 DALI_ASSERT_DEBUG(firstEvent.GetPointCount() == 2);
233 // We should use the current event in our calculations unless it does not have two points.
234 // If it does not have two points, then we should use the last point in mTouchEvents.
235 Integration::TouchEvent event(currentEvent);
236 if(event.GetPointCount() != 2)
238 event = *mTouchEvents.rbegin();
241 const Integration::Point& firstPoint1(firstEvent.points[0]);
242 const Integration::Point& firstPoint2(firstEvent.points[1]);
243 const Integration::Point& currentPoint1(event.points[0]);
244 const Integration::Point& currentPoint2(event.points[1]);
246 float firstDistance = GetDistance(firstPoint1, firstPoint2);
247 float currentDistance = GetDistance(currentPoint1, currentPoint2);
248 gesture.scale = currentDistance / mStartingDistance;
250 float distanceDelta = fabsf(firstDistance - currentDistance);
251 float timeDelta = static_cast<float>(currentEvent.time - firstEvent.time);
252 gesture.speed = (distanceDelta / timeDelta) * 1000.0f;
254 gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
258 // Something has gone wrong, just cancel the gesture.
259 gesture.state = GestureState::CANCELLED;
262 gesture.time = currentEvent.time;
266 // Create another handle so the recognizer cannot be destroyed during process function
267 GestureRecognizerPtr recognizerHandle = this;
269 mObserver.Process(*mScene, gesture);
273 void PinchGestureRecognizer::SetMinimumTouchEvents(uint32_t value)
275 mMinimumTouchEvents = value;
278 void PinchGestureRecognizer::SetMinimumTouchEventsAfterStart(uint32_t value)
280 mMinimumTouchEventsAfterStart = value;
283 } // namespace Internal