2 * Copyright (c) 2020 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/internal/event/events/pinch-gesture/pinch-gesture-event.h>
28 #include <dali/integration-api/events/touch-event-integ.h>
29 #include <dali/internal/event/common/scene-impl.h>
42 const float MINIMUM_DISTANCE_IN_MILLIINCH = 45.0f; // This value is used for measuring minimum pinch distance in pixel.
43 const float MINIMUM_DISTANCE_IN_PIXEL = 10.0f; // This value is for devices that do not provide a valid dpi value. (assumes 220dpi)
45 inline float GetDistance(const Integration::Point& point1, const Integration::Point& point2)
47 Vector2 vector(point1.GetScreenPosition() - point2.GetScreenPosition());
48 return vector.Length();
51 inline Vector2 GetCenterPoint(const Integration::Point& point1, const Integration::Point& point2)
53 return Vector2(point1.GetScreenPosition() + point2.GetScreenPosition()) * 0.5f;
56 inline bool IsValidDpi( const Vector2& dpi )
58 return dpi.x > 0.f && dpi.y > 0.f;
61 inline float GetDefaultMinimumPinchDistance( const Vector2& dpi )
63 return IsValidDpi( dpi ) ? ( ( MINIMUM_DISTANCE_IN_MILLIINCH * std::min( dpi.x, dpi.y ) ) / 1000.f ) : MINIMUM_DISTANCE_IN_PIXEL;
66 } // unnamed namespace
68 PinchGestureRecognizer::PinchGestureRecognizer( Observer& observer, Vector2 screenSize, Vector2 screenDpi, float minimumPinchDistance, uint32_t minimumTouchEvents, uint32_t minimumTouchEventsAfterStart )
69 : GestureRecognizer( screenSize, GestureType::PINCH ),
70 mObserver( observer ),
73 mDefaultMinimumDistanceDelta( GetDefaultMinimumPinchDistance( screenDpi ) ),
74 mStartingDistance( 0.0f ),
75 mMinimumTouchEvents( minimumTouchEvents ),
76 mMinimumTouchEventsAfterStart( minimumTouchEventsAfterStart )
78 SetMinimumPinchDistance( minimumPinchDistance );
81 PinchGestureRecognizer::~PinchGestureRecognizer() = default;
83 void PinchGestureRecognizer::SetMinimumPinchDistance(float value)
85 mMinimumDistanceDelta = value >= 0.0f ? value : mDefaultMinimumDistanceDelta;
88 void PinchGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
90 int pointCount = event.GetPointCount();
91 GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
99 // Change state to possible as we have two touch points.
101 mTouchEvents.push_back(event);
110 // We no longer have two touch points so change state back to CLEAR.
112 mTouchEvents.clear();
116 const Integration::Point& currentPoint1 = event.points[0];
117 const Integration::Point& currentPoint2 = event.points[1];
119 if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP || currentPoint1.GetState() == PointState::INTERRUPTED)
121 // One of our touch points has an Up event so change our state back to CLEAR.
123 mTouchEvents.clear();
127 mTouchEvents.push_back(event);
129 // We can only determine a pinch after a certain number of touch points have been collected.
130 if( mTouchEvents.size() >= mMinimumTouchEvents )
132 const Integration::Point& firstPoint1 = mTouchEvents[0].points[0];
133 const Integration::Point& firstPoint2 = mTouchEvents[0].points[1];
135 float firstDistance = GetDistance(firstPoint1, firstPoint2);
136 float currentDistance = GetDistance(currentPoint1, currentPoint2);
137 float distanceChanged = firstDistance - currentDistance;
139 // Check if distance has changed enough
140 if (fabsf(distanceChanged) > mMinimumDistanceDelta)
142 // Remove the first few events from the vector otherwise values are exaggerated
143 mTouchEvents.erase( mTouchEvents.begin(), mTouchEvents.end() - mMinimumTouchEvents );
145 if ( !mTouchEvents.empty() )
147 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
149 // Send pinch started
150 SendPinch(GestureState::STARTED, event);
155 mTouchEvents.clear();
158 if (mState == POSSIBLE)
160 // No pinch, so restart detection
162 mTouchEvents.clear();
172 if(event.points[0].GetState() == PointState::INTERRUPTED)
174 // System interruption occurred, pinch should be cancelled
175 mTouchEvents.clear();
176 SendPinch(GestureState::CANCELLED, event);
178 mTouchEvents.clear();
180 else if (pointCount != 2)
182 // Send pinch finished event
183 SendPinch(GestureState::FINISHED, event);
186 mTouchEvents.clear();
190 const Integration::Point& currentPoint1 = event.points[0];
191 const Integration::Point& currentPoint2 = event.points[1];
193 if (currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
195 mTouchEvents.push_back(event);
196 // Send pinch finished event
197 SendPinch(GestureState::FINISHED, event);
200 mTouchEvents.clear();
204 mTouchEvents.push_back(event);
206 if( mTouchEvents.size() >= mMinimumTouchEventsAfterStart )
208 // Send pinch continuing
209 SendPinch(GestureState::CONTINUING, event);
211 mTouchEvents.clear();
220 void PinchGestureRecognizer::Update(const GestureRequest& request)
225 void PinchGestureRecognizer::SendPinch(GestureState state, const Integration::TouchEvent& currentEvent)
227 PinchGestureEvent gesture(state);
229 if ( !mTouchEvents.empty() )
231 const Integration::TouchEvent& firstEvent = mTouchEvents[0];
233 // Assert if we have been holding TouchEvents that do not have 2 points
234 DALI_ASSERT_DEBUG( firstEvent.GetPointCount() == 2 );
236 // We should use the current event in our calculations unless it does not have two points.
237 // If it does not have two points, then we should use the last point in mTouchEvents.
238 Integration::TouchEvent event( currentEvent );
239 if ( event.GetPointCount() != 2 )
241 event = *mTouchEvents.rbegin();
244 const Integration::Point& firstPoint1( firstEvent.points[0] );
245 const Integration::Point& firstPoint2( firstEvent.points[1] );
246 const Integration::Point& currentPoint1( event.points[0] );
247 const Integration::Point& currentPoint2( event.points[1] );
249 float firstDistance = GetDistance(firstPoint1, firstPoint2);
250 float currentDistance = GetDistance(currentPoint1, currentPoint2);
251 gesture.scale = currentDistance / mStartingDistance;
253 float distanceDelta = fabsf(firstDistance - currentDistance);
254 float timeDelta = static_cast<float> ( currentEvent.time - firstEvent.time );
255 gesture.speed = ( distanceDelta / timeDelta ) * 1000.0f;
257 gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
261 // Something has gone wrong, just cancel the gesture.
262 gesture.state = GestureState::CANCELLED;
265 gesture.time = currentEvent.time;
269 // Create another handle so the recognizer cannot be destroyed during process function
270 GestureRecognizerPtr recognizerHandle = this;
272 mObserver.Process(*mScene, gesture);
276 void PinchGestureRecognizer::SetMinimumTouchEvents( uint32_t value )
278 mMinimumTouchEvents = value;
281 void PinchGestureRecognizer::SetMinimumTouchEventsAfterStart( uint32_t value )
283 mMinimumTouchEventsAfterStart = value;
286 } // namespace Internal