Add support for new accessibility actions
[platform/core/uifw/dali-adaptor.git] / adaptors / common / events / pinch-gesture-detector.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 "pinch-gesture-detector.h"
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 #include <dali/public-api/events/touch-point.h>
25 #include <dali/public-api/math/vector2.h>
26
27 #include <dali/integration-api/events/pinch-gesture-event.h>
28 #include <dali/integration-api/events/touch-event-integ.h>
29
30 // INTERNAL INCLUDES
31 #include <base/core-event-interface.h>
32
33 namespace Dali
34 {
35
36 namespace Internal
37 {
38
39 namespace Adaptor
40 {
41
42 namespace
43 {
44 const unsigned int MINIMUM_TOUCH_EVENTS_REQUIRED = 4;
45 const unsigned int MINIMUM_TOUCH_EVENTS_REQUIRED_AFTER_START = 4;
46
47 inline float GetDistance(const TouchPoint& point1, const TouchPoint& point2)
48 {
49   Vector2 vector(point1.screen - point2.screen);
50   return vector.Length();
51 }
52
53 inline float GetGradient(const TouchPoint& point1, const TouchPoint& point2)
54 {
55   return (point2.screen.y - point1.screen.y)
56          /
57          (point2.screen.x - point1.screen.x);
58 }
59
60 inline Vector2 GetCenterPoint(const TouchPoint& point1, const TouchPoint& point2)
61 {
62   return Vector2(point1.screen + point2.screen) * 0.5f;
63 }
64
65 } // unnamed namespace
66
67 PinchGestureDetector::PinchGestureDetector(CoreEventInterface& coreEventInterface, Vector2 screenSize, float minimumPinchDistance)
68 : GestureDetector(screenSize, Gesture::Pinch),
69   mCoreEventInterface(coreEventInterface),
70   mState(Clear),
71   mTouchEvents(),
72   mMinimumDistanceDelta(minimumPinchDistance),
73   mStartingDistance(0.0f)
74 {
75 }
76
77 PinchGestureDetector::~PinchGestureDetector()
78 {
79 }
80
81 void PinchGestureDetector::SetMinimumPinchDistance(float distance)
82 {
83   mMinimumDistanceDelta = distance;
84 }
85
86 void PinchGestureDetector::SendEvent(const Integration::TouchEvent& event)
87 {
88   int pointCount = event.GetPointCount();
89
90   switch (mState)
91   {
92     case Clear:
93     {
94       if (pointCount == 2)
95       {
96         // Change state to possible as we have two touch points.
97         mState = Possible;
98         mTouchEvents.push_back(event);
99       }
100       break;
101     }
102
103     case Possible:
104     {
105       if (pointCount != 2)
106       {
107         // We no longer have two touch points so change state back to Clear.
108         mState = Clear;
109         mTouchEvents.clear();
110       }
111       else
112       {
113         const TouchPoint& currentPoint1 = event.points[0];
114         const TouchPoint& currentPoint2 = event.points[1];
115
116         if (currentPoint1.state == TouchPoint::Up || currentPoint2.state == TouchPoint::Up)
117         {
118           // One of our touch points has an Up event so change our state back to Clear.
119           mState = Clear;
120           mTouchEvents.clear();
121         }
122         else
123         {
124           mTouchEvents.push_back(event);
125
126           // We can only determine a pinch after a certain number of touch points have been collected.
127           if (mTouchEvents.size() >= MINIMUM_TOUCH_EVENTS_REQUIRED)
128           {
129             const TouchPoint& firstPoint1 = mTouchEvents[0].points[0];
130             const TouchPoint& firstPoint2 = mTouchEvents[0].points[1];
131
132             float firstDistance = GetDistance(firstPoint1, firstPoint2);
133             float currentDistance = GetDistance(currentPoint1, currentPoint2);
134             float distanceChanged = firstDistance - currentDistance;
135
136             // Check if distance has changed enough
137             if (fabsf(distanceChanged) > mMinimumDistanceDelta)
138             {
139               // Remove the first few events from the vector otherwise values are exaggerated
140               mTouchEvents.erase(mTouchEvents.begin(), mTouchEvents.end() - MINIMUM_TOUCH_EVENTS_REQUIRED_AFTER_START);
141
142               if ( !mTouchEvents.empty() )
143               {
144                 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
145
146                 // Send pinch started
147                 SendPinch(Gesture::Started, event);
148
149                 mState = Started;
150               }
151
152               mTouchEvents.clear();
153             }
154
155             if (mState == Possible)
156             {
157               // No pinch, so restart detection
158               mState = Clear;
159               mTouchEvents.clear();
160             }
161           }
162         }
163       }
164       break;
165     }
166
167     case Started:
168     {
169       if (pointCount != 2)
170       {
171         // Send pinch finished event
172         SendPinch(Gesture::Finished, event);
173
174         mState = Clear;
175         mTouchEvents.clear();
176       }
177       else
178       {
179         const TouchPoint& currentPoint1 = event.points[0];
180         const TouchPoint& currentPoint2 = event.points[1];
181
182         if (currentPoint1.state == TouchPoint::Up || currentPoint2.state == TouchPoint::Up)
183         {
184           mTouchEvents.push_back(event);
185           // Send pinch finished event
186           SendPinch(Gesture::Finished, event);
187
188           mState = Clear;
189           mTouchEvents.clear();
190         }
191         else
192         {
193           mTouchEvents.push_back(event);
194
195           if (mTouchEvents.size() >= MINIMUM_TOUCH_EVENTS_REQUIRED_AFTER_START)
196           {
197             // Send pinch continuing
198             SendPinch(Gesture::Continuing, event);
199
200             mTouchEvents.clear();
201           }
202         }
203       }
204       break;
205     }
206   }
207 }
208
209 void PinchGestureDetector::Update(const Integration::GestureRequest& request)
210 {
211   // Nothing to do.
212 }
213
214 void PinchGestureDetector::SendPinch(Gesture::State state, const Integration::TouchEvent& currentEvent)
215 {
216   Integration::PinchGestureEvent gesture(state);
217
218   if ( !mTouchEvents.empty() )
219   {
220     const Integration::TouchEvent& firstEvent = mTouchEvents[0];
221
222     // Assert if we have been holding TouchEvents that do not have 2 points
223     DALI_ASSERT_DEBUG( firstEvent.GetPointCount() == 2 );
224
225     // We should use the current event in our calculations unless it does not have two points.
226     // If it does not have two points, then we should use the last point in mTouchEvents.
227     Integration::TouchEvent event( currentEvent );
228     if ( event.GetPointCount() != 2 )
229     {
230       event = *mTouchEvents.rbegin();
231     }
232
233     const TouchPoint& firstPoint1( firstEvent.points[0] );
234     const TouchPoint& firstPoint2( firstEvent.points[1] );
235     const TouchPoint& currentPoint1( event.points[0] );
236     const TouchPoint& currentPoint2( event.points[1] );
237
238     float firstDistance = GetDistance(firstPoint1, firstPoint2);
239     float currentDistance = GetDistance(currentPoint1, currentPoint2);
240     gesture.scale = currentDistance / mStartingDistance;
241
242     float distanceDelta = fabsf(firstDistance - currentDistance);
243     unsigned long timeDelta = currentEvent.time - firstEvent.time;
244     gesture.speed = (distanceDelta / timeDelta) * 1000.0f;
245
246     gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
247   }
248   else
249   {
250     // Something has gone wrong, just cancel the gesture.
251     gesture.state = Gesture::Cancelled;
252   }
253
254   gesture.time = currentEvent.time;
255
256   mCoreEventInterface.QueueCoreEvent(gesture);
257 }
258
259 } // namespace Adaptor
260
261 } // namespace Internal
262
263 } // namespace Dali