[dali_2.3.31] Merge branch '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, bool isMultiTouchEvent)
84 {
85   TouchEventCombiner::EventDispatchType dispatchEvent(TouchEventCombiner::DISPATCH_NONE);
86   const PointState::Type                state    = point.GetState();
87   const int                             deviceId = point.GetDeviceId();
88   const Device::Class::Type             deviceType = point.GetDeviceClass();
89
90   switch(state)
91   {
92     case PointState::STARTED:
93     {
94       touchEvent.time = time;
95       bool addToContainer(true);
96
97       // Iterate through already stored touch points and add to TouchEvent
98       for(PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter)
99       {
100         if(iter->point.GetDeviceId() != deviceId)
101         {
102           if(!isMultiTouchEvent)
103           {
104             iter->point.SetState(PointState::STATIONARY);
105           }
106         }
107         else
108         {
109           // System has sent us two down points for the same point ID, update our stored data to latest.
110           // We do not want to emit another down event for this Point Device ID.
111
112           addToContainer = false;
113           iter->point    = point;
114           iter->time     = time;
115         }
116         touchEvent.AddPoint(iter->point);
117       }
118
119       // Add new touch point to the list and to the TouchEvent
120       if(addToContainer)
121       {
122         mPressedPoints.push_back(PointInfo(point, time));
123         touchEvent.AddPoint(point);
124         dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // Only dispatch touch event if just added to container
125
126         // Check whether hover event was dispatched previously
127         if(!mHoveredPoints.empty())
128         {
129           hoverEvent.time = time;
130
131           PointInfoContainer::iterator match(mHoveredPoints.end());
132           for(PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter)
133           {
134             if(deviceId == iter->point.GetDeviceId())
135             {
136               match = iter;
137               // Add new point to the HoverEvent
138               iter->point.SetState(PointState::FINISHED);
139               hoverEvent.AddPoint(iter->point);
140             }
141             else
142             {
143               iter->point.SetState(PointState::STATIONARY);
144               hoverEvent.AddPoint(iter->point);
145             }
146           }
147
148           if(match != mHoveredPoints.end())
149           {
150             mHoveredPoints.erase(match);
151             dispatchEvent = TouchEventCombiner::DISPATCH_BOTH; // We should only dispatch hover events if the point was actually hovered in this window
152           }
153         }
154       }
155
156       break;
157     }
158
159     case PointState::FINISHED:
160     {
161       touchEvent.time = time;
162
163       // Find pressed touch point in local list (while also adding the stored points to the touchEvent)
164       PointInfoContainer::iterator match(mPressedPoints.end());
165       for(PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter)
166       {
167         if(deviceId == iter->point.GetDeviceId())
168         {
169           match = iter;
170
171           // Add new point to the TouchEvent
172           touchEvent.AddPoint(point);
173         }
174         else
175         {
176           if(!isMultiTouchEvent)
177           {
178             iter->point.SetState(PointState::STATIONARY);
179           }
180           touchEvent.AddPoint(iter->point);
181         }
182       }
183
184       if(match != mPressedPoints.end())
185       {
186         mPressedPoints.erase(match);
187         dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // We should only dispatch touch events if the point was actually pressed in this window
188
189         // Iterate through already stored touch points for HoverEvent and delete them
190         for(PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter)
191         {
192           if(iter->point.GetDeviceId() == deviceId)
193           {
194             iter = mHoveredPoints.erase(iter);
195           }
196         }
197
198         if(deviceType == Device::Class::Type::MOUSE)
199         {
200           hoverEvent.time = time;
201           Point hoverPoint(point);
202           hoverPoint.SetState(PointState::STARTED); // The first hover event received
203           mHoveredPoints.push_back(PointInfo(hoverPoint, time));
204           hoverEvent.AddPoint(hoverPoint);
205           dispatchEvent = TouchEventCombiner::DISPATCH_BOTH;
206         }
207       }
208       break;
209     }
210
211     case PointState::MOTION:
212     {
213       bool fromNewDeviceId = false;
214
215       if(!mPressedPoints.empty())
216       {
217         touchEvent.time = time;
218
219         bool                         ignore              = false;
220         PointInfoContainer::iterator match               = mPressedPoints.end();
221         const Vector2&               pointScreenPosition = point.GetScreenPosition();
222         for(PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter)
223         {
224           if(deviceId == iter->point.GetDeviceId())
225           {
226             uint32_t timeDiff(time - iter->time);
227
228             if(timeDiff < mMinMotionTime)
229             {
230               // Motion event sent too soon after previous event so ignore
231               ignore = true;
232               break;
233             }
234
235             const Vector2& currentScreenPosition = iter->point.GetScreenPosition();
236             if((std::abs(pointScreenPosition.x - currentScreenPosition.x) < mMinMotionDistance.x) &&
237                (std::abs(pointScreenPosition.y - currentScreenPosition.y) < mMinMotionDistance.y))
238             {
239               // Not enough positional change from last event so ignore
240               ignore = true;
241               break;
242             }
243
244             match = iter;
245
246             // Add new touch point to the TouchEvent
247             touchEvent.AddPoint(point);
248           }
249           else
250           {
251             if(!isMultiTouchEvent)
252             {
253               iter->point.SetState(PointState::STATIONARY);
254             }
255             touchEvent.AddPoint(iter->point);
256           }
257         }
258
259         if(match != mPressedPoints.end())
260         {
261           PointInfo matchedPoint(point, time);
262           std::swap(*match, matchedPoint);
263
264           dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // Dispatch touch event
265         }
266         else if(!ignore)
267         {
268           fromNewDeviceId = true;
269         }
270       }
271
272       // Dispatch hover event if no previous down event received or the motion event comes from a new device ID
273       // On touch down the MOTION -> STARTED event received at the same time. In this case, unnecessary hoverEvent may be triggered by the MOTION event.
274       // So, in case of touch device, it is ignored.
275       if((mPressedPoints.empty() || fromNewDeviceId) && deviceType != Device::Class::Type::TOUCH)
276       {
277         hoverEvent.time = time;
278
279         // Iterate through already stored touch points and add to HoverEvent
280         bool                         ignore              = false;
281         PointInfoContainer::iterator match               = mHoveredPoints.end();
282         const Vector2&               pointScreenPosition = point.GetScreenPosition();
283         for(PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter)
284         {
285           if(iter->point.GetDeviceId() == deviceId)
286           {
287             uint32_t timeDiff(time - iter->time);
288
289             if(timeDiff < mMinMotionTime)
290             {
291               // Motion event sent too soon after previous event so ignore
292               ignore = true;
293               break;
294             }
295
296             const Vector2& currentScreenPosition = iter->point.GetScreenPosition();
297             if((std::abs(pointScreenPosition.x - currentScreenPosition.x) < mMinMotionDistance.x) &&
298                (std::abs(pointScreenPosition.y - currentScreenPosition.y) < mMinMotionDistance.y))
299             {
300               // Not enough positional change from last event so ignore
301               ignore = true;
302               break;
303             }
304
305             match = iter;
306
307             // Add new touch point to the HoverEvent
308             hoverEvent.AddPoint(point);
309           }
310           else
311           {
312             if(!isMultiTouchEvent)
313             {
314               iter->point.SetState(PointState::STATIONARY);
315             }
316             hoverEvent.AddPoint(iter->point);
317           }
318         }
319
320         // Add new hover point to the list and to the HoverEvent
321         if(!ignore) // Only dispatch hover event when it should not be ignored
322         {
323           if(match == mHoveredPoints.end())
324           {
325             Point hoverPoint(point);
326             hoverPoint.SetState(PointState::STARTED); // The first hover event received
327             mHoveredPoints.push_back(PointInfo(hoverPoint, time));
328             hoverEvent.AddPoint(hoverPoint);
329           }
330           else
331           {
332             PointInfo matchedPoint(point, time);
333             std::swap(*match, matchedPoint);
334           }
335
336           if(dispatchEvent == TouchEventCombiner::DISPATCH_TOUCH)
337           {
338             dispatchEvent = TouchEventCombiner::DISPATCH_BOTH;
339           }
340           else
341           {
342             dispatchEvent = TouchEventCombiner::DISPATCH_HOVER;
343           }
344         }
345       }
346       break;
347     }
348
349     case PointState::INTERRUPTED:
350     {
351       // We should still tell core about the interruption.
352       if(!mPressedPoints.empty())
353       {
354         touchEvent.AddPoint(point);
355         dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH;
356       }
357       if((!mHoveredPoints.empty()))
358       {
359         hoverEvent.AddPoint(point);
360         dispatchEvent = dispatchEvent == TouchEventCombiner::DISPATCH_TOUCH ? TouchEventCombiner::DISPATCH_BOTH : TouchEventCombiner::DISPATCH_HOVER;
361       }
362       Reset();
363       break;
364     }
365
366     default:
367       break;
368   }
369
370   return dispatchEvent;
371 }
372
373 void TouchEventCombiner::SetMinimumMotionTimeThreshold(uint32_t minTime)
374 {
375   mMinMotionTime = minTime;
376 }
377
378 void TouchEventCombiner::SetMinimumMotionDistanceThreshold(float minDistance)
379 {
380   DALI_ASSERT_ALWAYS(minDistance >= 0.0f && "Negative values not allowed\n");
381
382   mMinMotionDistance.x = mMinMotionDistance.y = minDistance;
383 }
384
385 void TouchEventCombiner::SetMinimumMotionDistanceThreshold(float minXDistance, float minYDistance)
386 {
387   DALI_ASSERT_ALWAYS(minXDistance >= 0.0f && minYDistance >= 0.0f && "Negative values not allowed\n");
388
389   mMinMotionDistance.x = minXDistance;
390   mMinMotionDistance.y = minYDistance;
391 }
392
393 void TouchEventCombiner::SetMinimumMotionDistanceThreshold(Vector2 minDistance)
394 {
395   DALI_ASSERT_ALWAYS(minDistance.x >= 0.0f && minDistance.y >= 0.0f && "Negative values not allowed\n");
396
397   mMinMotionDistance = minDistance;
398 }
399
400 unsigned long TouchEventCombiner::GetMinimumMotionTimeThreshold() const
401 {
402   return mMinMotionTime;
403 }
404
405 Vector2 TouchEventCombiner::GetMinimumMotionDistanceThreshold() const
406 {
407   return mMinMotionDistance;
408 }
409
410 void TouchEventCombiner::Reset()
411 {
412   mPressedPoints.clear();
413   mHoveredPoints.clear();
414 }
415
416 } // namespace Integration
417
418 } // namespace Dali