Renaming of enum values for coding standards compliance.
[platform/core/uifw/dali-core.git] / dali / integration-api / events / touch-event-combiner.cpp
1 /*
2  * Copyright (c) 2019 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/touch-event-integ.h>
27 #include <dali/integration-api/events/hover-event-integ.h>
28 #include <dali/public-api/common/dali-common.h>
29
30 namespace Dali
31 {
32
33 namespace Integration
34 {
35
36 namespace
37 {
38 const unsigned long DEFAULT_MINIMUM_MOTION_TIME( 1u );
39 const Vector2 DEFAULT_MINIMUM_MOTION_DISTANCE( 1.0f, 1.0f );
40 } // unnamed namespace
41
42 struct TouchEventCombiner::PointInfo
43 {
44   // Construction
45
46   /**
47    * Constructor
48    * @param[in]  touchPoint  The point to add.
49    * @param[in]  pointTime   The time of the point event.
50    */
51   PointInfo( const Point& touchPoint, uint32_t pointTime )
52   : point( touchPoint ),
53     time( pointTime )
54   {
55   }
56
57   // Data
58
59   Point point;        ///< The point.
60   uint32_t time; ///< The time the point event took place.
61 };
62
63 TouchEventCombiner::TouchEventCombiner()
64 : mMinMotionTime( DEFAULT_MINIMUM_MOTION_TIME ),
65   mMinMotionDistance( DEFAULT_MINIMUM_MOTION_DISTANCE )
66 {
67 }
68
69 TouchEventCombiner::TouchEventCombiner( uint32_t minMotionTime, float minMotionXDistance, float minMotionYDistance )
70 : mMinMotionTime( minMotionTime ),
71   mMinMotionDistance( minMotionXDistance, minMotionYDistance )
72 {
73   DALI_ASSERT_ALWAYS( minMotionXDistance >= 0.0f && minMotionYDistance >= 0.0f && "Negative values not allowed\n" );
74 }
75
76 TouchEventCombiner::TouchEventCombiner( uint32_t minMotionTime, Vector2 minMotionDistance )
77 : mMinMotionTime( minMotionTime ),
78   mMinMotionDistance( minMotionDistance )
79 {
80   DALI_ASSERT_ALWAYS( minMotionDistance.x >= 0.0f && minMotionDistance.y >= 0.0f && "Negative values not allowed\n" );
81 }
82
83 TouchEventCombiner::~TouchEventCombiner()
84 {
85 }
86
87 TouchEventCombiner::EventDispatchType TouchEventCombiner::GetNextTouchEvent( const Point& point, uint32_t time, TouchEvent& touchEvent, HoverEvent& hoverEvent )
88 {
89   TouchEventCombiner::EventDispatchType dispatchEvent( TouchEventCombiner::DISPATCH_NONE );
90   const PointState::Type state = point.GetState();
91   const int deviceId = point.GetDeviceId();
92
93   switch ( state )
94   {
95     case PointState::STARTED:
96     {
97       touchEvent.time = time;
98       bool addToContainer( true );
99
100       // Iterate through already stored touch points and add to TouchEvent
101       for ( PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter )
102       {
103         if ( iter->point.GetDeviceId() != deviceId )
104         {
105           iter->point.SetState( PointState::STATIONARY );
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           iter->point.SetState( PointState::STATIONARY );
177           touchEvent.AddPoint( iter->point );
178         }
179       }
180
181       if ( match != mPressedPoints.end() )
182       {
183         mPressedPoints.erase( match );
184         dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // We should only dispatch touch events if the point was actually pressed in this window
185
186         // Iterate through already stored touch points for HoverEvent and delete them
187         for ( PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter )
188         {
189           if ( iter->point.GetDeviceId() == deviceId )
190           {
191             iter = mHoveredPoints.erase( iter );
192           }
193         }
194       }
195       break;
196     }
197
198     case PointState::MOTION:
199     {
200       bool fromNewDeviceId = false;
201
202       if ( !mPressedPoints.empty() )
203       {
204         touchEvent.time = time;
205
206         bool ignore = false;
207         PointInfoContainer::iterator match = mPressedPoints.end();
208         const Vector2& pointScreenPosition = point.GetScreenPosition();
209         for ( PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter )
210         {
211           if ( deviceId == iter->point.GetDeviceId() )
212           {
213             uint32_t timeDiff( time - iter->time );
214
215             if ( timeDiff < mMinMotionTime )
216             {
217               // Motion event sent too soon after previous event so ignore
218               ignore = true;
219               break;
220             }
221
222             const Vector2& currentScreenPosition = iter->point.GetScreenPosition();
223             if ( ( std::abs( pointScreenPosition.x - currentScreenPosition.x ) < mMinMotionDistance.x ) &&
224                  ( std::abs( pointScreenPosition.y - currentScreenPosition.y ) < mMinMotionDistance.y ) )
225             {
226               // Not enough positional change from last event so ignore
227               ignore = true;
228               break;
229             }
230
231             match = iter;
232
233             // Add new touch point to the TouchEvent
234             touchEvent.AddPoint( point );
235           }
236           else
237           {
238             iter->point.SetState( PointState::STATIONARY );
239             touchEvent.AddPoint( iter->point );
240           }
241         }
242
243         if ( match != mPressedPoints.end() )
244         {
245           PointInfo matchedPoint( point, time );
246           std::swap( *match, matchedPoint );
247
248           dispatchEvent = TouchEventCombiner::DISPATCH_TOUCH; // Dispatch touch event
249         }
250         else if(!ignore)
251         {
252           fromNewDeviceId = true;
253         }
254       }
255
256       // Dispatch hover event if no previous down event received or the motion event comes from a new device ID
257       if(mPressedPoints.empty() || fromNewDeviceId)
258       {
259         hoverEvent.time = time;
260
261         // Iterate through already stored touch points and add to HoverEvent
262         bool ignore = false;
263         PointInfoContainer::iterator match = mHoveredPoints.end();
264         const Vector2& pointScreenPosition = point.GetScreenPosition();
265         for ( PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter )
266         {
267           if ( iter->point.GetDeviceId() == deviceId )
268           {
269             uint32_t timeDiff( time - iter->time );
270
271             if ( timeDiff < mMinMotionTime )
272             {
273               // Motion event sent too soon after previous event so ignore
274               ignore = true;
275               break;
276             }
277
278             const Vector2& currentScreenPosition = iter->point.GetScreenPosition();
279             if ( ( std::abs( pointScreenPosition.x - currentScreenPosition.x ) < mMinMotionDistance.x ) &&
280                  ( std::abs( pointScreenPosition.y - currentScreenPosition.y ) < mMinMotionDistance.y ) )
281             {
282               // Not enough positional change from last event so ignore
283               ignore = true;
284               break;
285             }
286
287             match = iter;
288
289             // Add new touch point to the HoverEvent
290             hoverEvent.AddPoint( point );
291           }
292           else
293           {
294             iter->point.SetState( PointState::STATIONARY );
295             hoverEvent.AddPoint( iter->point );
296           }
297         }
298
299         // Add new hover point to the list and to the HoverEvent
300         if ( !ignore ) // Only dispatch hover event when it should not be ignored
301         {
302           if( match == mHoveredPoints.end() )
303           {
304             Point hoverPoint(point);
305             hoverPoint.SetState( PointState::STARTED ); // The first hover event received
306             mHoveredPoints.push_back( PointInfo( hoverPoint, time ) );
307             hoverEvent.AddPoint( hoverPoint );
308           }
309           else
310           {
311             PointInfo matchedPoint( point, time );
312             std::swap( *match, matchedPoint );
313           }
314
315           if(dispatchEvent == TouchEventCombiner::DISPATCH_TOUCH)
316           {
317             dispatchEvent = TouchEventCombiner::DISPATCH_BOTH;
318           }
319           else
320           {
321             dispatchEvent = TouchEventCombiner::DISPATCH_HOVER;
322           }
323         }
324       }
325       break;
326     }
327
328     case PointState::INTERRUPTED:
329     {
330       Reset();
331
332       // We should still tell core about the interruption.
333       touchEvent.AddPoint( point );
334       hoverEvent.AddPoint( point );
335       dispatchEvent = TouchEventCombiner::DISPATCH_BOTH;
336       break;
337     }
338
339     default:
340       break;
341   }
342
343   return dispatchEvent;
344 }
345
346 void TouchEventCombiner::SetMinimumMotionTimeThreshold( uint32_t minTime )
347 {
348   mMinMotionTime = minTime;
349 }
350
351 void TouchEventCombiner::SetMinimumMotionDistanceThreshold( float minDistance )
352 {
353   DALI_ASSERT_ALWAYS( minDistance >= 0.0f && "Negative values not allowed\n" );
354
355   mMinMotionDistance.x = mMinMotionDistance.y = minDistance;
356 }
357
358 void TouchEventCombiner::SetMinimumMotionDistanceThreshold( float minXDistance, float minYDistance )
359 {
360   DALI_ASSERT_ALWAYS( minXDistance >= 0.0f && minYDistance >= 0.0f && "Negative values not allowed\n" );
361
362   mMinMotionDistance.x = minXDistance;
363   mMinMotionDistance.y = minYDistance;
364 }
365
366 void TouchEventCombiner::SetMinimumMotionDistanceThreshold( Vector2 minDistance )
367 {
368   DALI_ASSERT_ALWAYS( minDistance.x >= 0.0f && minDistance.y >= 0.0f && "Negative values not allowed\n" );
369
370   mMinMotionDistance = minDistance;
371 }
372
373 unsigned long TouchEventCombiner::GetMinimumMotionTimeThreshold() const
374 {
375   return mMinMotionTime;
376 }
377
378 Vector2 TouchEventCombiner::GetMinimumMotionDistanceThreshold() const
379 {
380   return mMinMotionDistance;
381 }
382
383 void TouchEventCombiner::Reset()
384 {
385   mPressedPoints.clear();
386   mHoveredPoints.clear();
387 }
388
389 } // namespace Integration
390
391 } // namespace Dali