[dali_2.3.33] Merge branch 'devel/master'
[platform/core/uifw/dali-core.git] / dali / internal / event / events / long-press-gesture / long-press-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/long-press-gesture/long-press-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/integration-api/platform-abstraction.h>
29 #include <dali/internal/event/common/thread-local-storage.h>
30 #include <dali/internal/event/events/gesture-requests.h>
31
32 namespace Dali
33 {
34 namespace Internal
35 {
36 namespace
37 {
38 // TODO: Set these according to DPI
39 const float MAXIMUM_MOTION_ALLOWED = 60.0f;
40
41 } // unnamed namespace
42
43 LongPressGestureRecognizer::LongPressGestureRecognizer(Observer& observer, Vector2 screenSize, const LongPressGestureRequest& request, uint32_t minimumHoldingTime)
44 : GestureRecognizer(screenSize, GestureType::LONG_PRESS),
45   mObserver(observer),
46   mState(CLEAR),
47   mMinimumTouchesRequired(request.minTouches),
48   mMaximumTouchesRequired(request.maxTouches),
49   mTouchTime(0),
50   mTimerId(0),
51   mMinimumHoldingTime(minimumHoldingTime)
52 {
53 }
54
55 LongPressGestureRecognizer::~LongPressGestureRecognizer()
56 {
57   if(mTimerId != 0 && ThreadLocalStorage::Created())
58   {
59     Dali::Integration::PlatformAbstraction& platformAbstraction = ThreadLocalStorage::Get().GetPlatformAbstraction();
60     platformAbstraction.CancelTimer(mTimerId);
61   }
62 }
63
64 void LongPressGestureRecognizer::SendEvent(const Integration::TouchEvent& event)
65 {
66   unsigned int                            pointCount(event.GetPointCount());
67   Dali::Integration::PlatformAbstraction& platformAbstraction = ThreadLocalStorage::Get().GetPlatformAbstraction();
68   GestureRecognizerPtr                    ptr(this); // To keep us from being destroyed during the life-time of this method
69
70   switch(mState)
71   {
72     // CLEAR: Wait till one point touches the screen before starting timer.
73     case CLEAR:
74     {
75       const Integration::Point& point = event.points[0];
76
77       if(point.GetState() == PointState::DOWN)
78       {
79         mTouchPositions.clear();
80         mTouchPositions[point.GetDeviceId()] = point.GetScreenPosition();
81
82         mTouchTime = event.time;
83
84         if(mTimerId != 0)
85         {
86           platformAbstraction.CancelTimer(mTimerId);
87         }
88         mTimerId = platformAbstraction.StartTimer(mMinimumHoldingTime, MakeCallback(this, &LongPressGestureRecognizer::TimerCallback));
89
90         // A long press gesture may be possible, tell Core about this and change state to TOUCHED.
91         mState = TOUCHED;
92         EmitGesture(GestureState::POSSIBLE);
93       }
94
95       break;
96     }
97
98     // TOUCHED: Monitor movement and addition/removal of points.
99     case TOUCHED:
100     {
101       if(pointCount > mMaximumTouchesRequired)
102       {
103         // A long press did not occur, tell Core that it was cancelled and change state to FAILED.
104         EmitGesture(GestureState::CANCELLED);
105         mTouchPositions.clear();
106         platformAbstraction.CancelTimer(mTimerId);
107         mTimerId = 0;
108         mState   = FAILED;
109         break;
110       }
111
112       bool endLoop(false);
113
114       for(Integration::PointContainerConstIterator iter = event.points.begin(), endIter = event.points.end();
115           iter != endIter && !endLoop;
116           ++iter)
117       {
118         switch(iter->GetState())
119         {
120           // add point.
121           case PointState::DOWN:
122           {
123             mTouchPositions[iter->GetDeviceId()] = iter->GetScreenPosition();
124             break;
125           }
126
127           // remove point.
128           case PointState::UP:
129           case PointState::INTERRUPTED:
130           {
131             // System has interrupted us, long press is not possible, inform Core
132             EmitGesture(GestureState::CANCELLED);
133             mTouchPositions.clear();
134             platformAbstraction.CancelTimer(mTimerId);
135             mTimerId = 0;
136             mState   = (pointCount == 1) ? CLEAR : FAILED; // Change state to CLEAR if only one point, FAILED otherwise.
137             endLoop  = true;
138             break;
139           }
140
141           case PointState::MOTION:
142           {
143             const Vector2 touchPosition(mTouchPositions[iter->GetDeviceId()] - iter->GetScreenPosition());
144             float         distanceSquared = touchPosition.LengthSquared();
145
146             if(distanceSquared > (MAXIMUM_MOTION_ALLOWED * MAXIMUM_MOTION_ALLOWED))
147             {
148               // We have moved more than the allowable motion for a long press gesture. Inform Core and change state to FAILED.
149               EmitGesture(GestureState::CANCELLED);
150               platformAbstraction.CancelTimer(mTimerId);
151               mTimerId = 0;
152               mState   = FAILED;
153               endLoop  = true;
154             }
155             break;
156           }
157
158           case PointState::STATIONARY:
159           case PointState::LEAVE:
160           {
161             break;
162           }
163         }
164       }
165       break;
166     }
167
168     // FAILED/FINISHED: Monitor the touches, waiting for all touches to be released.
169     case FAILED:
170     case FINISHED:
171     {
172       // eventually the final touch point will be removed, marking the end of this gesture.
173       if(pointCount == 1)
174       {
175         PointState::Type primaryPointState = event.points[0].GetState();
176
177         if((primaryPointState == PointState::UP) || (primaryPointState == PointState::INTERRUPTED))
178         {
179           if(mState == FINISHED)
180           {
181             // When the last touch point is lifted, we should inform the Core that the Long press has finished.
182             EmitGesture(GestureState::FINISHED);
183           }
184           mTouchPositions.clear();
185           mState = CLEAR; // Reset state to clear when last touch point is lifted.
186         }
187       }
188       break;
189     }
190   }
191 }
192
193 void LongPressGestureRecognizer::CancelEvent()
194 {
195   if(mState != CLEAR)
196   {
197     if(mTimerId != 0 && ThreadLocalStorage::Created())
198     {
199       Dali::Integration::PlatformAbstraction& platformAbstraction = ThreadLocalStorage::Get().GetPlatformAbstraction();
200       platformAbstraction.CancelTimer(mTimerId);
201     }
202     if(mState == FINISHED)
203     {
204       EmitGesture(GestureState::FINISHED);
205     }
206     else
207     {
208       EmitGesture(GestureState::CANCELLED);
209     }
210     mTouchPositions.clear();
211     mTimerId = 0;
212     mState = CLEAR;
213   }
214 }
215
216 void LongPressGestureRecognizer::Update(const GestureRequest& request)
217 {
218   const LongPressGestureRequest& longPress = static_cast<const LongPressGestureRequest&>(request);
219
220   mMinimumTouchesRequired = longPress.minTouches;
221   mMaximumTouchesRequired = longPress.maxTouches;
222 }
223
224 void LongPressGestureRecognizer::SetMinimumHoldingTime(uint32_t time)
225 {
226   mMinimumHoldingTime = time;
227 }
228
229 bool LongPressGestureRecognizer::TimerCallback()
230 {
231   EmitGesture(GestureState::STARTED);
232
233   mState = FINISHED;
234
235   mTimerId = 0;
236
237   return false;
238 }
239
240 void LongPressGestureRecognizer::EmitGesture(GestureState state)
241 {
242   unsigned int touchPoints(static_cast<unsigned int>(mTouchPositions.size()));
243
244   // We should tell Core about the POSSIBLE and CANCELLED states regardless of whether we have satisfied long press requirements.
245   if((state == GestureState::POSSIBLE) ||
246      (state == GestureState::CANCELLED) ||
247      (touchPoints >= mMinimumTouchesRequired))
248   {
249     LongPressGestureEvent longPress(state);
250     longPress.numberOfTouches = touchPoints;
251
252     for(std::map<int, Vector2>::iterator iter = mTouchPositions.begin(), endIter = mTouchPositions.end();
253         iter != endIter;
254         ++iter)
255     {
256       longPress.point += iter->second;
257     }
258     longPress.point /= static_cast<float>(touchPoints);
259
260     longPress.time = mTouchTime;
261     if(state != GestureState::POSSIBLE)
262     {
263       longPress.time += mMinimumHoldingTime;
264     }
265     longPress.sourceType = mSourceType;
266     longPress.sourceData = mSourceData;
267
268     if(mScene)
269     {
270       // Create another handle so the recognizer cannot be destroyed during process function
271       GestureRecognizerPtr recognizerHandle = this;
272
273       mObserver.Process(*mScene, longPress);
274     }
275   }
276 }
277
278 } // namespace Internal
279
280 } // namespace Dali