Removing Dali::TouchEvent
[platform/core/uifw/dali-adaptor.git] / dali / internal / accessibility / common / accessibility-gesture-detector.cpp
1 /*
2  * Copyright (c) 2019 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 <dali/internal/accessibility/common/accessibility-gesture-detector.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23 #include <dali/integration-api/events/touch-event-integ.h>
24
25
26 namespace Dali
27 {
28
29 namespace Internal
30 {
31
32 namespace Adaptor
33 {
34
35 namespace
36 {
37   const float MINIMUM_MOTION_DISTANCE_BEFORE_PAN( 15.0f );
38   const float MINIMUM_MOTION_DISTANCE_BEFORE_PAN_SQUARED( MINIMUM_MOTION_DISTANCE_BEFORE_PAN * MINIMUM_MOTION_DISTANCE_BEFORE_PAN );
39   const float MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO( 2.0f / 3.0f );
40   const unsigned long MAXIMUM_TIME_DIFF_ALLOWED( 500 );
41   const unsigned long MINIMUM_TIME_BEFORE_THRESHOLD_ADJUSTMENTS( 100 );
42   const unsigned int MINIMUM_MOTION_EVENTS_BEFORE_PAN(2);
43   const unsigned int MINIMUM_TOUCHES_BEFORE_PAN(1);
44   const unsigned int MAXIMUM_TOUCHES_BEFORE_PAN(1);
45 } // unnamed namespace
46
47
48 AccessibilityGestureDetector::AccessibilityGestureDetector()
49 : mState( Clear ),
50   mScene(nullptr),
51   mGestureHandler(nullptr),
52   mPanning(false),
53   mThresholdAdjustmentsRemaining( 0 ),
54   mThresholdTotalAdjustments( MINIMUM_MOTION_DISTANCE_BEFORE_PAN * MINIMUM_MOTION_DISTANCE_TO_THRESHOLD_ADJUSTMENTS_RATIO ),
55   mPrimaryTouchDownTime( 0 ),
56   mMinimumTouchesRequired( MINIMUM_TOUCHES_BEFORE_PAN ),
57   mMaximumTouchesRequired( MAXIMUM_TOUCHES_BEFORE_PAN ),
58   mMinimumDistanceSquared( MINIMUM_MOTION_DISTANCE_BEFORE_PAN_SQUARED ),
59   mMinimumMotionEvents( MINIMUM_MOTION_EVENTS_BEFORE_PAN ),
60   mMotionEvents( 0 )
61 {
62 }
63
64 AccessibilityGestureDetector::~AccessibilityGestureDetector()
65 {
66 }
67
68 void AccessibilityGestureDetector::SetGestureHandler(AccessibilityGestureHandler& handler)
69 {
70   mGestureHandler = &handler;
71 }
72
73 void AccessibilityGestureDetector::EmitPan(const AccessibilityGestureEvent gesture)
74 {
75   if( mGestureHandler )
76   {
77     if(gesture.state == AccessibilityGestureEvent::Started)
78     {
79       mPanning = true;
80     }
81
82     if( mPanning )
83     {
84       mGestureHandler->HandlePanGesture(gesture);
85
86       if( (gesture.state == AccessibilityGestureEvent::Finished) ||
87           (gesture.state == AccessibilityGestureEvent::Cancelled) )
88       {
89         mPanning = false;
90       }
91     }
92   }
93 }
94
95 void AccessibilityGestureDetector::SendEvent(const Integration::TouchEvent& event)
96 {
97   PointState::Type primaryPointState(event.points[0].GetState());
98
99   if (primaryPointState == PointState::INTERRUPTED)
100   {
101     if ( ( mState == Started ) || ( mState == Possible ) )
102     {
103       // If our pan had started and we are interrupted, then tell Core that pan is cancelled.
104       mTouchEvents.push_back(event);
105       SendPan(AccessibilityGestureEvent::Cancelled, event);
106     }
107     mState = Clear; // We should change our state to Clear.
108     mTouchEvents.clear();
109   }
110   else
111   {
112     switch (mState)
113     {
114       case Clear:
115       {
116         if ( ( primaryPointState == PointState::DOWN ) || ( primaryPointState == PointState::STATIONARY ) )
117         {
118           mPrimaryTouchDownLocation = event.points[0].GetScreenPosition();
119           mPrimaryTouchDownTime = event.time;
120           mMotionEvents = 0;
121           if (event.GetPointCount() == mMinimumTouchesRequired)
122           {
123             // We have satisfied the minimum touches required for a pan, tell core that a gesture may be possible and change our state accordingly.
124             mState = Possible;
125             SendPan(AccessibilityGestureEvent::Possible, event);
126           }
127
128           mTouchEvents.push_back(event);
129         }
130         break;
131       }
132
133       case Possible:
134       {
135         unsigned int pointCount(event.GetPointCount());
136         if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
137         {
138           if (primaryPointState == PointState::MOTION)
139           {
140             mTouchEvents.push_back(event);
141             mMotionEvents++;
142
143             Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
144
145             if ( ( mMotionEvents >= mMinimumMotionEvents ) &&
146                  ( delta.LengthSquared() >= static_cast<float>( mMinimumDistanceSquared ) ) )
147             {
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               mState = Started;
150               SendPan(AccessibilityGestureEvent::Started, event);
151             }
152           }
153           else if (primaryPointState == PointState::UP)
154           {
155             Vector2 delta(event.points[0].GetScreenPosition() - mPrimaryTouchDownLocation);
156             if (delta.LengthSquared() >= static_cast<float>( mMinimumDistanceSquared ) )
157             {
158               SendPan(AccessibilityGestureEvent::Started, event);
159               mTouchEvents.push_back(event);
160               SendPan(AccessibilityGestureEvent::Finished, event);
161             }
162             else
163             {
164               // If we have lifted the primary touch point then tell core the pan is cancelled and change our state to Clear.
165               SendPan(AccessibilityGestureEvent::Cancelled, event);
166             }
167             mState = Clear;
168             mTouchEvents.clear();
169           }
170         }
171         else
172         {
173           // We do not satisfy pan conditions, tell Core our Gesture has been cancelled.
174           SendPan(AccessibilityGestureEvent::Cancelled, event);
175
176           if (pointCount == 1 && primaryPointState == PointState::UP)
177           {
178             // If we have lifted the primary touch point, then change our state to Clear...
179             mState = Clear;
180             mTouchEvents.clear();
181           }
182           else
183           {
184             // ...otherwise change it to Failed.
185             mState = Failed;
186           }
187         }
188         break;
189       }
190
191       case Started:
192       {
193         mTouchEvents.push_back(event);
194
195         unsigned int pointCount(event.GetPointCount());
196         if ( (pointCount >= mMinimumTouchesRequired)&&(pointCount <= mMaximumTouchesRequired) )
197         {
198           switch (primaryPointState)
199           {
200             case PointState::MOTION:
201               // Pan is continuing, tell Core.
202               SendPan(AccessibilityGestureEvent::Continuing, event);
203               break;
204
205             case PointState::UP:
206               // Pan is finally finished when our primary point is lifted, tell Core and change our state to Clear.
207               SendPan(AccessibilityGestureEvent::Finished, event);
208               mState = Clear;
209               mTouchEvents.clear();
210               break;
211
212             case PointState::STATIONARY:
213               if (pointCount == mMinimumTouchesRequired)
214               {
215                 Integration::PointContainerConstIterator iter = event.points.begin() + 1; // We already know the state of the first point
216                 for(; iter != event.points.end(); ++iter)
217                 {
218                   if(iter->GetState() == PointState::UP)
219                   {
220                     // The number of touch points will be less than the minimum required.  Inform core and change our state to Finished.
221                     SendPan(AccessibilityGestureEvent::Finished, event);
222                     mState = Finished;
223                     break;
224                   }
225                 }
226               }
227               break;
228
229             default:
230               break;
231           }
232         }
233         else
234         {
235           // We have gone outside of the pan requirements, inform Core that the gesture is finished.
236           SendPan(AccessibilityGestureEvent::Finished, event);
237
238           if (pointCount == 1 && primaryPointState == PointState::UP)
239           {
240             // If this was the primary point being released, then we change our state back to Clear...
241             mState = Clear;
242             mTouchEvents.clear();
243           }
244           else
245           {
246             // ...otherwise we change it to Finished.
247             mState = Finished;
248           }
249         }
250         break;
251       }
252
253       case Finished:
254       case Failed:
255       {
256         if (primaryPointState == PointState::UP)
257         {
258           // Change our state back to clear when the primary touch point is released.
259           mState = Clear;
260           mTouchEvents.clear();
261         }
262         break;
263       }
264     }
265   }
266 }
267
268 void AccessibilityGestureDetector::SendPan(AccessibilityGestureEvent::State state, const Integration::TouchEvent& currentEvent)
269 {
270   AccessibilityGestureEvent gesture(state);
271   gesture.currentPosition = currentEvent.points[0].GetScreenPosition();
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     uint32_t previousTime( previousEvent.time );
281
282     // If we've just started then we want to remove the threshold from Core calculations.
283     if ( state == AccessibilityGestureEvent::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 ) / static_cast<float>( 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 * static_cast<float>( 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