Dali 3D User Interface Engine
pan-gesture-detector-base.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014 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
20 
21 // EXTERNAL INCLUDES
22 #include <cmath>
23 
25 
28 
29 // INTERNAL INCLUDES
31 
32 namespace Dali
33 {
34 
35 namespace Internal
36 {
37 
38 namespace Adaptor
39 {
40 
41 namespace
42 {
43 const float MINIMUM_MOTION_DISTANCE_BEFORE_PAN( 15.0f );
44 const float MINIMUM_MOTION_DISTANCE_BEFORE_PAN_SQUARED( MINIMUM_MOTION_DISTANCE_BEFORE_PAN * MINIMUM_MOTION_DISTANCE_BEFORE_PAN );
46 const unsigned long MAXIMUM_TIME_DIFF_ALLOWED( 500 );
47 const unsigned long MINIMUM_TIME_BEFORE_THRESHOLD_ADJUSTMENTS( 100 );
48 const unsigned int MINIMUM_MOTION_EVENTS_BEFORE_PAN(2);
49 } // unnamed namespace
50 
51 PanGestureDetectorBase::PanGestureDetectorBase(Vector2 screenSize, const Integration::PanGestureRequest& request, EnvironmentOptions* environmentOptions)
52 : GestureDetector( screenSize, Gesture::Pan ),
53  mState( Clear ),
54  mThresholdAdjustmentsRemaining( 0 ),
56  mPrimaryTouchDownTime( 0 ),
57  mMinimumTouchesRequired( request.minTouches ),
58  mMaximumTouchesRequired( request.maxTouches ),
59  mMinimumDistanceSquared( MINIMUM_MOTION_DISTANCE_BEFORE_PAN_SQUARED ),
60  mMinimumMotionEvents( MINIMUM_MOTION_EVENTS_BEFORE_PAN ),
61  mMotionEvents( 0 )
62 {
63  if ( environmentOptions )
64  {
65  int minimumDistance = environmentOptions->GetMinimumPanDistance();
66  if ( minimumDistance >= 0 )
67  {
68  mMinimumDistanceSquared = minimumDistance * minimumDistance;
69 
70  // Usually, we do not want to apply the threshold straight away, but phased over the first few pans
71  // Set our distance to threshold adjustments ratio here.
73  }
74 
75  int minimumEvents = environmentOptions->GetMinimumPanEvents();
76  if ( minimumEvents >= 1 )
77  {
78  mMinimumMotionEvents = minimumEvents - 1; // Down is the first event
79  }
80  }
81 }
82 
84 {
85 }
86 
87 void PanGestureDetectorBase::SendEvent(const Integration::TouchEvent& event)
88 {
89  TouchPoint::State primaryPointState(event.points[0].state);
90 
91  if (primaryPointState == TouchPoint::Interrupted)
92  {
93  if ( ( mState == Started ) || ( mState == Possible ) )
94  {
95  // If our pan had started and we are interrupted, then tell Core that pan is cancelled.
96  mTouchEvents.push_back(event);
98  }
99  mState = Clear; // We should change our state to Clear.
100  mTouchEvents.clear();
101  }
102  else
103  {
104  switch (mState)
105  {
106  case Clear:
107  {
108  if (primaryPointState == TouchPoint::Down)
109  {
110  mPrimaryTouchDownLocation = event.points[0].screen;
111  mPrimaryTouchDownTime = event.time;
112  mMotionEvents = 0;
113  if (event.GetPointCount() == mMinimumTouchesRequired)
114  {
115  // We have satisfied the minimum touches required for a pan, tell core that a gesture may be possible and change our state accordingly.
116  mState = Possible;
117  SendPan(Gesture::Possible, event);
118  }
119 
120  mTouchEvents.push_back(event);
121  }
122  break;
123  }
124 
125  case Possible:
126  {
127  unsigned int pointCount(event.GetPointCount());
128  if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
129  {
130  if (primaryPointState == TouchPoint::Motion)
131  {
132  mTouchEvents.push_back(event);
133  mMotionEvents++;
134 
135  Vector2 delta(event.points[0].screen - mPrimaryTouchDownLocation);
136 
137  if ( ( mMotionEvents >= mMinimumMotionEvents ) &&
138  ( delta.LengthSquared() >= mMinimumDistanceSquared ) )
139  {
140  // 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.
141  mState = Started;
142  SendPan(Gesture::Started, event);
143  }
144  }
145  else if (primaryPointState == TouchPoint::Up)
146  {
147  Vector2 delta(event.points[0].screen - mPrimaryTouchDownLocation);
149  {
150  SendPan(Gesture::Started, event);
151  mTouchEvents.push_back(event);
152  SendPan(Gesture::Finished, event);
153  }
154  else
155  {
156  // If we have lifted the primary touch point then tell core the pan is cancelled and change our state to Clear.
157  SendPan(Gesture::Cancelled, event);
158  }
159  mState = Clear;
160  mTouchEvents.clear();
161  }
162  }
163  else
164  {
165  // We do not satisfy pan conditions, tell Core our Gesture has been cancelled.
166  SendPan(Gesture::Cancelled, event);
167 
168  if (pointCount == 1 && primaryPointState == TouchPoint::Up)
169  {
170  // If we have lifted the primary touch point, then change our state to Clear...
171  mState = Clear;
172  mTouchEvents.clear();
173  }
174  else
175  {
176  // ...otherwise change it to Failed.
177  mState = Failed;
178  }
179  }
180  break;
181  }
182 
183  case Started:
184  {
185  mTouchEvents.push_back(event);
186 
187  unsigned int pointCount(event.GetPointCount());
188  if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
189  {
190  switch (primaryPointState)
191  {
192  case TouchPoint::Motion:
193  // Pan is continuing, tell Core.
195  break;
196 
197  case TouchPoint::Up:
198  // Pan is finally finished when our primary point is lifted, tell Core and change our state to Clear.
199  SendPan(Gesture::Finished, event);
200  mState = Clear;
201  mTouchEvents.clear();
202  break;
203 
205  if (pointCount == mMinimumTouchesRequired)
206  {
207  std::vector<TouchPoint>::const_iterator iter = event.points.begin() + 1; // We already know the state of the first point
208  for(; iter != event.points.end(); ++iter)
209  {
210  if(iter->state == TouchPoint::Up)
211  {
212  // The number of touch points will be less than the minimum required. Inform core and change our state to Finished.
213  SendPan(Gesture::Finished, event);
214  mState = Finished;
215  break;
216  }
217  }
218  }
219  break;
220 
221  default:
222  break;
223  }
224  }
225  else
226  {
227  // We have gone outside of the pan requirements, inform Core that the gesture is finished.
228  SendPan(Gesture::Finished, event);
229 
230  if (pointCount == 1 && primaryPointState == TouchPoint::Up)
231  {
232  // If this was the primary point being released, then we change our state back to Clear...
233  mState = Clear;
234  mTouchEvents.clear();
235  }
236  else
237  {
238  // ...otherwise we change it to Finished.
239  mState = Finished;
240  }
241  }
242  break;
243  }
244 
245  case Finished:
246  case Failed:
247  {
248  if (primaryPointState == TouchPoint::Up)
249  {
250  // Change our state back to clear when the primary touch point is released.
251  mState = Clear;
252  mTouchEvents.clear();
253  }
254  break;
255  }
256  }
257  }
258 }
259 
260 void PanGestureDetectorBase::Update(const Integration::GestureRequest& request)
261 {
262  const Integration::PanGestureRequest& pan = static_cast<const Integration::PanGestureRequest&>(request);
263 
264  mMinimumTouchesRequired = pan.minTouches;
265  mMaximumTouchesRequired = pan.maxTouches;
266 }
267 
268 void PanGestureDetectorBase::SendPan(Gesture::State state, const Integration::TouchEvent& currentEvent)
269 {
270  Integration::PanGestureEvent gesture(state);
271  gesture.currentPosition = currentEvent.points[0].screen;
272  gesture.numberOfTouches = currentEvent.GetPointCount();
273 
274  if ( mTouchEvents.size() > 1 )
275  {
276  // Get the second last event in the queue, the last one is the current event
277  const Integration::TouchEvent& previousEvent( *( mTouchEvents.rbegin() + 1 ) );
278 
279  Vector2 previousPosition( mPreviousPosition );
280  unsigned long previousTime( previousEvent.time );
281 
282  // If we've just started then we want to remove the threshold from Core calculations.
283  if ( state == Gesture::Started )
284  {
285  previousPosition = mPrimaryTouchDownLocation;
286  previousTime = mPrimaryTouchDownTime;
287 
288  // If it's a slow pan, we do not want to phase in the threshold over the first few pan-events
289  // A slow pan is defined as one that starts the specified number of milliseconds after the down-event
290  if ( ( currentEvent.time - previousTime ) > MINIMUM_TIME_BEFORE_THRESHOLD_ADJUSTMENTS )
291  {
293  mThresholdAdjustmentPerFrame = ( gesture.currentPosition - previousPosition ) / mThresholdTotalAdjustments;
294  }
295  else
296  {
299  }
300  }
301 
302  gesture.previousPosition = previousPosition;
303  gesture.timeDelta = currentEvent.time - previousTime;
304 
305  // Apply the threshold with a phased approach
307  {
310  }
311 
312  mPreviousPosition = gesture.currentPosition;
313  }
314  else
315  {
316  gesture.previousPosition = gesture.currentPosition;
317  gesture.timeDelta = 0;
318  }
319 
320  gesture.time = currentEvent.time;
321 
322  EmitPan(gesture);
323 }
324 
325 } // namespace Adaptor
326 
327 } // namespace Internal
328 
329 } // namespace Dali
Dali Docs Home
Read more about Dali