Merge "Clean up the code to build successfully on macOS" into devel/master
[platform/core/uifw/dali-core.git] / dali / integration-api / events / touch-event-combiner.cpp
1 /*
2  * Copyright (c) 2020 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/integration-api/events/touch-event-combiner.h>
20
21 // EXTERNAL INCLUDES
22 #include <algorithm>
23 #include <cmath> // abs<float>
24
25 // INTERNAL INCLUDES
26 #include <dali/integration-api/events/hover-event-integ.h>
27 #include <dali/integration-api/events/touch-event-integ.h>
28 #include <dali/public-api/common/dali-common.h>
29
30 namespace Dali
31 {
32 namespace Integration
33 {
34 namespace
35 {
36 const unsigned long DEFAULT_MINIMUM_MOTION_TIME(1u);
37 const Vector2       DEFAULT_MINIMUM_MOTION_DISTANCE(1.0f, 1.0f);
38 } // unnamed namespace
39
40 struct TouchEventCombiner::PointInfo
41 {
42   // Construction
43
44   /**
45    * Constructor
46    * @param[in]  touchPoint  The point to add.
47    * @param[in]  pointTime   The time of the point event.
48    */
49   PointInfo(const Point& touchPoint, uint32_t pointTime)
50   : point(touchPoint),
51     time(pointTime)
52   {
53   }
54
55   // Data
56
57   Point    point; ///< The point.
58   uint32_t time;  ///< The time the point event took place.
59 };
60
61 TouchEventCombiner::TouchEventCombiner()
62 : mMinMotionTime(DEFAULT_MINIMUM_MOTION_TIME),
63   mMinMotionDistance(DEFAULT_MINIMUM_MOTION_DISTANCE)
64 {
65 }
66
67 TouchEventCombiner::TouchEventCombiner(uint32_t minMotionTime, float minMotionXDistance, float minMotionYDistance)
68 : mMinMotionTime(minMotionTime),
69   mMinMotionDistance(minMotionXDistance, minMotionYDistance)
70 {
71   DALI_ASSERT_ALWAYS(minMotionXDistance >= 0.0f && minMotionYDistance >= 0.0f && "Negative values not allowed\n");
72 }
73
74 TouchEventCombiner::TouchEventCombiner(uint32_t minMotionTime, Vector2 minMotionDistance)
75 : mMinMotionTime(minMotionTime),
76   mMinMotionDistance(minMotionDistance)
77 {
78   DALI_ASSERT_ALWAYS(minMotionDistance.x >= 0.0f && minMotionDistance.y >= 0.0f && "Negative values not allowed\n");
79 }
80
81 TouchEventCombiner::~TouchEventCombiner() = default;
82
83 TouchEventCombiner::EventDispatchType TouchEventCombiner::GetNextTouchEvent(const Point& point, uint32_t time, TouchEvent& touchEvent, HoverEvent& hoverEvent)
84 {
85   TouchEventCombiner::EventDispatchType dispatchEvent(TouchEventCombiner::DISPATCH_NONE);
86   const PointState::Type                state    = point.GetState();
87   const int                             deviceId = point.GetDeviceId();
88
89   switch(state)
90   {
91     case PointState::STARTED:
92     {
93       touchEvent.time = time;
94       bool addToContainer(true);
95
96       // Iterate through already stored touch points and add to TouchEvent
97       for(PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter)
98       {
99         if(iter->point.GetDeviceId() != deviceId)
100         {
101           iter->point.SetState(PointState::STATIONARY);
102         }
103         else
104         {
105           // System has sent us two down points for the same point ID, update our stored data to latest.
106           // We do not want to emit another down event for this Point Device ID.
107
108           addToContainer = false;
109           iter->point    = point;
110           iter->time     = time;
111         }
112         touchEvent.AddPoint(iter->point);
113       }
114
115       // Add new touch point to the list and to the TouchEvent
116       if(addToContainer)
117       {
118         mPressedPoints.push_back(PointInfo(point, time));
119         touchEvent.AddPoint(point);
120         dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // Only dispatch touch event if just added to container
121
122         // Check whether hover event was dispatched previously
123         if(!mHoveredPoints.empty())
124         {
125           hoverEvent.time = time;
126
127           PointInfoContainer::iterator match(mHoveredPoints.end());
128           for(PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter)
129           {
130             if(deviceId == iter->point.GetDeviceId())
131             {
132               match = iter;
133               // Add new point to the HoverEvent
134               iter->point.SetState(PointState::FINISHED);
135               hoverEvent.AddPoint(iter->point);
136             }
137             else
138             {
139               iter->point.SetState(PointState::STATIONARY);
140               hoverEvent.AddPoint(iter->point);
141             }
142           }
143
144           if(match != mHoveredPoints.end())
145           {
146             mHoveredPoints.erase(match);
147             dispatchEvent = TouchEventCombiner::DISPATCH_BOTH; // We should only dispatch hover events if the point was actually hovered in this window
148           }
149         }
150       }
151
152       break;
153     }
154
155     case PointState::FINISHED:
156     {
157       touchEvent.time = time;
158
159       // Find pressed touch point in local list (while also adding the stored points to the touchEvent)
160       PointInfoContainer::iterator match(mPressedPoints.end());
161       for(PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter)
162       {
163         if(deviceId == iter->point.GetDeviceId())
164         {
165           match = iter;
166
167           // Add new point to the TouchEvent
168           touchEvent.AddPoint(point);
169         }
170         else
171         {
172           iter->point.SetState(PointState::STATIONARY);
173           touchEvent.AddPoint(iter->point);
174         }
175       }
176
177       if(match != mPressedPoints.end())
178       {
179         mPressedPoints.erase(match);
180         dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // We should only dispatch touch events if the point was actually pressed in this window
181
182         // Iterate through already stored touch points for HoverEvent and delete them
183         for(PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter)
184         {
185           if(iter->point.GetDeviceId() == deviceId)
186           {
187             iter = mHoveredPoints.erase(iter);
188           }
189         }
190       }
191       break;
192     }
193
194     case PointState::MOTION:
195     {
196       bool fromNewDeviceId = false;
197
198       if(!mPressedPoints.empty())
199       {
200         touchEvent.time = time;
201
202         bool                         ignore              = false;
203         PointInfoContainer::iterator match               = mPressedPoints.end();
204         const Vector2&               pointScreenPosition = point.GetScreenPosition();
205         for(PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter)
206         {
207           if(deviceId == iter->point.GetDeviceId())
208           {
209             uint32_t timeDiff(time - iter->time);
210
211             if(timeDiff < mMinMotionTime)
212             {
213               // Motion event sent too soon after previous event so ignore
214               ignore = true;
215               break;
216             }
217
218             const Vector2& currentScreenPosition = iter->point.GetScreenPosition();
219             if((std::abs(pointScreenPosition.x - currentScreenPosition.x) < mMinMotionDistance.x) &&
220                (std::abs(pointScreenPosition.y - currentScreenPosition.y) < mMinMotionDistance.y))
221             {
222               // Not enough positional change from last event so ignore
223               ignore = true;
224               break;
225             }
226
227             match = iter;
228
229             // Add new touch point to the TouchEvent
230             touchEvent.AddPoint(point);
231           }
232           else
233           {
234             iter->point.SetState(PointState::STATIONARY);
235             touchEvent.AddPoint(iter->point);
236           }
237         }
238
239         if(match != mPressedPoints.end())
240         {
241           PointInfo matchedPoint(point, time);
242           std::swap(*match, matchedPoint);
243
244           dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // Dispatch touch event
245         }
246         else if(!ignore)
247         {
248           fromNewDeviceId = true;
249         }
250       }
251
252       // Dispatch hover event if no previous down event received or the motion event comes from a new device ID
253       if(mPressedPoints.empty() || fromNewDeviceId)
254       {
255         hoverEvent.time = time;
256
257         // Iterate through already stored touch points and add to HoverEvent
258         bool                         ignore              = false;
259         PointInfoContainer::iterator match               = mHoveredPoints.end();
260         const Vector2&               pointScreenPosition = point.GetScreenPosition();
261         for(PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter)
262         {
263           if(iter->point.GetDeviceId() == deviceId)
264           {
265             uint32_t timeDiff(time - iter->time);
266
267             if(timeDiff < mMinMotionTime)
268             {
269               // Motion event sent too soon after previous event so ignore
270               ignore = true;
271               break;
272             }
273
274             const Vector2& currentScreenPosition = iter->point.GetScreenPosition();
275             if((std::abs(pointScreenPosition.x - currentScreenPosition.x) < mMinMotionDistance.x) &&
276                (std::abs(pointScreenPosition.y - currentScreenPosition.y) < mMinMotionDistance.y))
277             {
278               // Not enough positional change from last event so ignore
279               ignore = true;
280               break;
281             }
282
283             match = iter;
284
285             // Add new touch point to the HoverEvent
286             hoverEvent.AddPoint(point);
287           }
288           else
289           {
290             iter->point.SetState(PointState::STATIONARY);
291             hoverEvent.AddPoint(iter->point);
292           }
293         }
294
295         // Add new hover point to the list and to the HoverEvent
296         if(!ignore) // Only dispatch hover event when it should not be ignored
297         {
298           if(match == mHoveredPoints.end())
299           {
300             Point hoverPoint(point);
301             hoverPoint.SetState(PointState::STARTED); // The first hover event received
302             mHoveredPoints.push_back(PointInfo(hoverPoint, time));
303             hoverEvent.AddPoint(hoverPoint);
304           }
305           else
306           {
307             PointInfo matchedPoint(point, time);
308             std::swap(*match, matchedPoint);
309           }
310
311           if(dispatchEvent == TouchEventCombiner::DISPATCH_TOUCH)
312           {
313             dispatchEvent = TouchEventCombiner::DISPATCH_BOTH;
314           }
315           else
316           {
317             dispatchEvent = TouchEventCombiner::DISPATCH_HOVER;
318           }
319         }
320       }
321       break;
322     }
323
324     case PointState::INTERRUPTED:
325     {
326       Reset();
327
328       // We should still tell core about the interruption.
329       touchEvent.AddPoint(point);
330       hoverEvent.AddPoint(point);
331       dispatchEvent = TouchEventCombiner::DISPATCH_BOTH;
332       break;
333     }
334
335     default:
336       break;
337   }
338
339   return dispatchEvent;
340 }
341
342 void TouchEventCombiner::SetMinimumMotionTimeThreshold(uint32_t minTime)
343 {
344   mMinMotionTime = minTime;
345 }
346
347 void TouchEventCombiner::SetMinimumMotionDistanceThreshold(float minDistance)
348 {
349   DALI_ASSERT_ALWAYS(minDistance >= 0.0f && "Negative values not allowed\n");
350
351   mMinMotionDistance.x = mMinMotionDistance.y = minDistance;
352 }
353
354 void TouchEventCombiner::SetMinimumMotionDistanceThreshold(float minXDistance, float minYDistance)
355 {
356   DALI_ASSERT_ALWAYS(minXDistance >= 0.0f && minYDistance >= 0.0f && "Negative values not allowed\n");
357
358   mMinMotionDistance.x = minXDistance;
359   mMinMotionDistance.y = minYDistance;
360 }
361
362 void TouchEventCombiner::SetMinimumMotionDistanceThreshold(Vector2 minDistance)
363 {
364   DALI_ASSERT_ALWAYS(minDistance.x >= 0.0f && minDistance.y >= 0.0f && "Negative values not allowed\n");
365
366   mMinMotionDistance = minDistance;
367 }
368
369 unsigned long TouchEventCombiner::GetMinimumMotionTimeThreshold() const
370 {
371   return mMinMotionTime;
372 }
373
374 Vector2 TouchEventCombiner::GetMinimumMotionDistanceThreshold() const
375 {
376   return mMinMotionDistance;
377 }
378
379 void TouchEventCombiner::Reset()
380 {
381   mPressedPoints.clear();
382   mHoveredPoints.clear();
383 }
384
385 } // namespace Integration
386
387 } // namespace Dali