[dali_2.3.5] Merge branch 'devel/master'
[platform/core/uifw/dali-core.git] / dali / internal / event / events / pinch-gesture / pinch-gesture-recognizer.cpp
1 /*
2  * Copyright (c) 2022 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/event/events/pinch-gesture/pinch-gesture-recognizer.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 #include <dali/devel-api/events/touch-point.h>
25 #include <dali/public-api/math/vector2.h>
26
27 #include <dali/integration-api/events/touch-event-integ.h>
28 #include <dali/internal/event/common/scene-impl.h>
29 #include <dali/internal/event/events/pinch-gesture/pinch-gesture-event.h>
30
31 // INTERNAL INCLUDES
32
33 namespace Dali
34 {
35 namespace Internal
36 {
37 namespace
38 {
39 const float MINIMUM_DISTANCE_IN_MILLIINCH = 45.0f; // This value is used for measuring minimum pinch distance in pixel.
40 const float MINIMUM_DISTANCE_IN_PIXEL     = 10.0f; // This value is for devices that do not provide a valid dpi value. (assumes 220dpi)
41
42 inline float GetDistance(const Integration::Point& point1, const Integration::Point& point2)
43 {
44   Vector2 vector(point1.GetScreenPosition() - point2.GetScreenPosition());
45   return vector.Length();
46 }
47
48 inline Vector2 GetCenterPoint(const Integration::Point& point1, const Integration::Point& point2)
49 {
50   return Vector2(point1.GetScreenPosition() + point2.GetScreenPosition()) * 0.5f;
51 }
52
53 inline bool IsValidDpi(const Vector2& dpi)
54 {
55   return dpi.x > 0.f && dpi.y > 0.f;
56 }
57
58 inline float GetDefaultMinimumPinchDistance(const Vector2& dpi)
59 {
60   return IsValidDpi(dpi) ? ((MINIMUM_DISTANCE_IN_MILLIINCH * std::min(dpi.x, dpi.y)) / 1000.f) : MINIMUM_DISTANCE_IN_PIXEL;
61 }
62
63 } // unnamed namespace
64
65 PinchGestureRecognizer::PinchGestureRecognizer(Observer& observer, Vector2 screenSize, Vector2 screenDpi, float minimumPinchDistance, uint32_t minimumTouchEvents, uint32_t minimumTouchEventsAfterStart)
66 : GestureRecognizer(screenSize, GestureType::PINCH),
67   mObserver(observer),
68   mState(CLEAR),
69   mTouchEvents(),
70   mDefaultMinimumDistanceDelta(GetDefaultMinimumPinchDistance(screenDpi)),
71   mStartingDistance(0.0f),
72   mMinimumTouchEvents(minimumTouchEvents),
73   mMinimumTouchEventsAfterStart(minimumTouchEventsAfterStart)
74 {
75   SetMinimumPinchDistance(minimumPinchDistance);
76 }
77
78 PinchGestureRecognizer::~PinchGestureRecognizer() = default;
79
80 void PinchGestureRecognizer::SetMinimumPinchDistance(float value)
81 {
82   mMinimumDistanceDelta = value >= 0.0f ? value : mDefaultMinimumDistanceDelta;
83 }
84
85 void PinchGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
86 {
87   int                  pointCount = event.GetPointCount();
88   GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
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 Integration::Point& currentPoint1 = event.points[0];
114         const Integration::Point& currentPoint2 = event.points[1];
115
116         if(currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP || currentPoint1.GetState() == PointState::INTERRUPTED)
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() >= mMinimumTouchEvents)
128           {
129             const Integration::Point& firstPoint1 = mTouchEvents[0].points[0];
130             const Integration::Point& 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() - mMinimumTouchEvents);
141
142               if(!mTouchEvents.empty())
143               {
144                 mStartingDistance = GetDistance(mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1]);
145
146                 // Send pinch started
147                 SendPinch(GestureState::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(event.points[0].GetState() == PointState::INTERRUPTED)
170       {
171         // System interruption occurred, pinch should be cancelled
172         mTouchEvents.clear();
173         SendPinch(GestureState::CANCELLED, event);
174         mState = CLEAR;
175         mTouchEvents.clear();
176       }
177       else if(pointCount != 2)
178       {
179         // Send pinch finished event
180         SendPinch(GestureState::FINISHED, event);
181
182         mState = CLEAR;
183         mTouchEvents.clear();
184       }
185       else
186       {
187         const Integration::Point& currentPoint1 = event.points[0];
188         const Integration::Point& currentPoint2 = event.points[1];
189
190         if(currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP)
191         {
192           mTouchEvents.push_back(event);
193           // Send pinch finished event
194           SendPinch(GestureState::FINISHED, event);
195
196           mState = CLEAR;
197           mTouchEvents.clear();
198         }
199         else
200         {
201           mTouchEvents.push_back(event);
202
203           if(mTouchEvents.size() >= mMinimumTouchEventsAfterStart)
204           {
205             // Send pinch continuing
206             SendPinch(GestureState::CONTINUING, event);
207
208             mTouchEvents.clear();
209           }
210         }
211       }
212       break;
213     }
214   }
215 }
216
217 void PinchGestureRecognizer::Update(const GestureRequest& request)
218 {
219   // Nothing to do.
220 }
221
222 void PinchGestureRecognizer::SendPinch(GestureState state, const Integration::TouchEvent& currentEvent)
223 {
224   PinchGestureEvent gesture(state);
225
226   if(!mTouchEvents.empty())
227   {
228     const Integration::TouchEvent& firstEvent = mTouchEvents[0];
229
230     // Assert if we have been holding TouchEvents that do not have 2 points
231     DALI_ASSERT_DEBUG(firstEvent.GetPointCount() == 2);
232
233     // We should use the current event in our calculations unless it does not have two points.
234     // If it does not have two points, then we should use the last point in mTouchEvents.
235     Integration::TouchEvent event(currentEvent);
236     if(event.GetPointCount() != 2)
237     {
238       event = *mTouchEvents.rbegin();
239     }
240
241     const Integration::Point& firstPoint1(firstEvent.points[0]);
242     const Integration::Point& firstPoint2(firstEvent.points[1]);
243     const Integration::Point& currentPoint1(event.points[0]);
244     const Integration::Point& currentPoint2(event.points[1]);
245
246     float firstDistance   = GetDistance(firstPoint1, firstPoint2);
247     float currentDistance = GetDistance(currentPoint1, currentPoint2);
248     gesture.scale         = currentDistance / mStartingDistance;
249
250     float distanceDelta = fabsf(firstDistance - currentDistance);
251     float timeDelta     = static_cast<float>(currentEvent.time - firstEvent.time);
252     gesture.speed       = (distanceDelta / timeDelta) * 1000.0f;
253
254     gesture.centerPoint = GetCenterPoint(currentPoint1, currentPoint2);
255   }
256   else
257   {
258     // Something has gone wrong, just cancel the gesture.
259     gesture.state = GestureState::CANCELLED;
260   }
261
262   gesture.time       = currentEvent.time;
263   gesture.sourceType = mSourceType;
264   gesture.sourceData = mSourceData;
265
266   if(mScene)
267   {
268     // Create another handle so the recognizer cannot be destroyed during process function
269     GestureRecognizerPtr recognizerHandle = this;
270
271     mObserver.Process(*mScene, gesture);
272   }
273 }
274
275 void PinchGestureRecognizer::SetMinimumTouchEvents(uint32_t value)
276 {
277   mMinimumTouchEvents = value;
278 }
279
280 void PinchGestureRecognizer::SetMinimumTouchEventsAfterStart(uint32_t value)
281 {
282   mMinimumTouchEventsAfterStart = value;
283 }
284
285 } // namespace Internal
286
287 } // namespace Dali