Merge branch 'devel/master(1.1.1)' into tizen
[platform/core/uifw/dali-adaptor.git] / adaptors / common / events / pan-gesture-detector-base.cpp
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
19 #include "pan-gesture-detector-base.h"
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 #include <dali/public-api/events/touch-point.h>
25
26 #include <dali/integration-api/events/gesture-requests.h>
27 #include <dali/integration-api/events/touch-event-integ.h>
28
29 // INTERNAL INCLUDES
30 #include <base/environment-options.h>
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 );
45 const float MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO( 2.0f / 3.0f );
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 ),
55   mThresholdTotalAdjustments( MINIMUM_MOTION_DISTANCE_BEFORE_PAN * MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO ),
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.
72       mThresholdTotalAdjustments = minimumDistance * MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO;
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
83 PanGestureDetectorBase::~PanGestureDetectorBase()
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);
97       SendPan(Gesture::Cancelled, 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);
148             if(delta.LengthSquared() >= mMinimumDistanceSquared)
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.
194               SendPan(Gesture::Continuing, event);
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
204             case TouchPoint::Stationary:
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       {
292         mThresholdAdjustmentsRemaining = mThresholdTotalAdjustments;
293         mThresholdAdjustmentPerFrame = ( gesture.currentPosition - previousPosition ) / mThresholdTotalAdjustments;
294       }
295       else
296       {
297         mThresholdAdjustmentsRemaining = 0;
298         mThresholdAdjustmentPerFrame = Vector2::ZERO;
299       }
300     }
301
302     gesture.previousPosition = previousPosition;
303     gesture.timeDelta = currentEvent.time - previousTime;
304
305     // Apply the threshold with a phased approach
306     if ( mThresholdAdjustmentsRemaining > 0 )
307     {
308       --mThresholdAdjustmentsRemaining;
309       gesture.currentPosition -= mThresholdAdjustmentPerFrame * mThresholdAdjustmentsRemaining;
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