Merge "LLVM/Emscripten fixes" into devel/master
[platform/core/uifw/dali-core.git] / dali / integration-api / events / touch-event-combiner.cpp
1 /*
2  * Copyright (c) 2014 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 TouchPoint& touchPoint, unsigned long pointTime )
52   : point( touchPoint ),
53     time( pointTime )
54   {
55   }
56
57   // Data
58
59   TouchPoint point;   ///< The point.
60   unsigned long 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( unsigned long 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( unsigned long 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 TouchPoint& point, unsigned long time, TouchEvent& touchEvent, HoverEvent& hoverEvent )
88 {
89   TouchEventCombiner::EventDispatchType dispatchEvent( TouchEventCombiner::DispatchNone );
90
91   switch ( point.state )
92   {
93     case TouchPoint::Started:
94     {
95       touchEvent.time = time;
96       bool addToContainer( true );
97
98       // Iterate through already stored touch points and add to TouchEvent
99       for ( PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter )
100       {
101         if ( iter->point.deviceId != point.deviceId )
102         {
103           iter->point.state = TouchPoint::Stationary;
104         }
105         else
106         {
107           // System has sent us two down points for the same point ID, update our stored data to latest.
108           // We do not want to emit another down event for this Point Device ID.
109
110           addToContainer = false;
111           iter->point = point;
112           iter->time = time;
113         }
114         touchEvent.AddPoint( iter->point );
115       }
116
117       // Add new touch point to the list and to the TouchEvent
118       if (addToContainer)
119       {
120         mPressedPoints.push_back( PointInfo( point, time ) );
121         touchEvent.AddPoint( point );
122         dispatchEvent = TouchEventCombiner::DispatchTouch; // Only dispatch touch event if just added to container
123
124         // Check whether hover event was dispatched previously
125         if ( !mHoveredPoints.empty() )
126         {
127           hoverEvent.time = time;
128
129           PointInfoContainer::iterator match( mHoveredPoints.end() );
130           for ( PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter )
131           {
132             if ( point.deviceId == iter->point.deviceId )
133             {
134               match = iter;
135               // Add new point to the HoverEvent
136               iter->point.state = TouchPoint::Finished;
137               hoverEvent.AddPoint( iter->point );
138             }
139             else
140             {
141               iter->point.state = TouchPoint::Stationary;
142               hoverEvent.AddPoint( iter->point );
143             }
144           }
145
146           if ( match != mHoveredPoints.end() )
147           {
148             mHoveredPoints.erase( match );
149             dispatchEvent = TouchEventCombiner::DispatchBoth; // We should only dispatch hover events if the point was actually hovered in this window
150           }
151         }
152       }
153
154       break;
155     }
156
157     case TouchPoint::Finished:
158     {
159       touchEvent.time = time;
160
161       // Find pressed touch point in local list (while also adding the stored points to the touchEvent)
162       PointInfoContainer::iterator match( mPressedPoints.end() );
163       for ( PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter )
164       {
165         if ( point.deviceId == iter->point.deviceId )
166         {
167           match = iter;
168
169           // Add new point to the TouchEvent
170           touchEvent.AddPoint( point );
171         }
172         else
173         {
174           iter->point.state = TouchPoint::Stationary;
175           touchEvent.AddPoint( iter->point );
176         }
177       }
178
179       if ( match != mPressedPoints.end() )
180       {
181         mPressedPoints.erase( match );
182         dispatchEvent = TouchEventCombiner::DispatchTouch; // We should only dispatch touch events if the point was actually pressed in this window
183
184         // Iterate through already stored touch points for HoverEvent and delete them
185         for ( PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter )
186         {
187           if ( iter->point.deviceId == point.deviceId )
188           {
189             iter = mHoveredPoints.erase( iter );
190           }
191         }
192       }
193       break;
194     }
195
196     case TouchPoint::Motion:
197     {
198       bool fromNewDeviceId = false;
199
200       if ( !mPressedPoints.empty() )
201       {
202         touchEvent.time = time;
203
204         bool ignore = false;
205         PointInfoContainer::iterator match = mPressedPoints.end();
206         for ( PointInfoContainer::iterator iter = mPressedPoints.begin(), endIter = mPressedPoints.end(); iter != endIter; ++iter )
207         {
208           if ( point.deviceId == iter->point.deviceId )
209           {
210             unsigned long timeDiff( time - iter->time );
211
212             if ( timeDiff < mMinMotionTime )
213             {
214               // Motion event sent too soon after previous event so ignore
215               ignore = true;
216               break;
217             }
218
219             if ( ( std::abs( point.screen.x - iter->point.screen.x ) < mMinMotionDistance.x ) &&
220                  ( std::abs( point.screen.y - iter->point.screen.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.state = TouchPoint::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::DispatchTouch; // 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         for ( PointInfoContainer::iterator iter = mHoveredPoints.begin(), endIter = mHoveredPoints.end(); iter != endIter; ++iter )
261         {
262           if ( iter->point.deviceId == point.deviceId )
263           {
264             unsigned long timeDiff( time - iter->time );
265
266             if ( timeDiff < mMinMotionTime )
267             {
268               // Motion event sent too soon after previous event so ignore
269               ignore = true;
270               break;
271             }
272
273             if ( ( std::abs( point.screen.x - iter->point.screen.x ) < mMinMotionDistance.x ) &&
274                  ( std::abs( point.screen.y - iter->point.screen.y ) < mMinMotionDistance.y ) )
275             {
276               // Not enough positional change from last event so ignore
277               ignore = true;
278               break;
279             }
280
281             match = iter;
282
283             // Add new touch point to the HoverEvent
284             hoverEvent.AddPoint( point );
285           }
286           else
287           {
288             iter->point.state = TouchPoint::Stationary;
289             hoverEvent.AddPoint( iter->point );
290           }
291         }
292
293         // Add new hover point to the list and to the HoverEvent
294         if ( !ignore ) // Only dispatch hover event when it should not be ignored
295         {
296           if( match == mHoveredPoints.end() )
297           {
298             TouchPoint hoverPoint(point);
299             hoverPoint.state = TouchPoint::Started; // The first hover event received
300             mHoveredPoints.push_back( PointInfo( hoverPoint, time ) );
301             hoverEvent.AddPoint( hoverPoint );
302           }
303           else
304           {
305             PointInfo matchedPoint( point, time );
306             std::swap( *match, matchedPoint );
307           }
308
309           if(dispatchEvent == TouchEventCombiner::DispatchTouch)
310           {
311             dispatchEvent = TouchEventCombiner::DispatchBoth;
312           }
313           else
314           {
315             dispatchEvent = TouchEventCombiner::DispatchHover;
316           }
317         }
318       }
319       break;
320     }
321
322     case TouchPoint::Interrupted:
323     {
324       Reset();
325
326       // We should still tell core about the interruption.
327       touchEvent.AddPoint( point );
328       hoverEvent.AddPoint( point );
329       dispatchEvent = TouchEventCombiner::DispatchBoth;
330       break;
331     }
332
333     default:
334       break;
335   }
336
337   return dispatchEvent;
338 }
339
340 void TouchEventCombiner::SetMinimumMotionTimeThreshold( unsigned long minTime )
341 {
342   mMinMotionTime = minTime;
343 }
344
345 void TouchEventCombiner::SetMinimumMotionDistanceThreshold( float minDistance )
346 {
347   DALI_ASSERT_ALWAYS( minDistance >= 0.0f && "Negative values not allowed\n" );
348
349   mMinMotionDistance.x = mMinMotionDistance.y = minDistance;
350 }
351
352 void TouchEventCombiner::SetMinimumMotionDistanceThreshold( float minXDistance, float minYDistance )
353 {
354   DALI_ASSERT_ALWAYS( minXDistance >= 0.0f && minYDistance >= 0.0f && "Negative values not allowed\n" );
355
356   mMinMotionDistance.x = minXDistance;
357   mMinMotionDistance.y = minYDistance;
358 }
359
360 void TouchEventCombiner::SetMinimumMotionDistanceThreshold( Vector2 minDistance )
361 {
362   DALI_ASSERT_ALWAYS( minDistance.x >= 0.0f && minDistance.y >= 0.0f && "Negative values not allowed\n" );
363
364   mMinMotionDistance = minDistance;
365 }
366
367 unsigned long TouchEventCombiner::GetMinimumMotionTimeThreshold() const
368 {
369   return mMinMotionTime;
370 }
371
372 Vector2 TouchEventCombiner::GetMinimumMotionDistanceThreshold() const
373 {
374   return mMinMotionDistance;
375 }
376
377 void TouchEventCombiner::Reset()
378 {
379   mPressedPoints.clear();
380   mHoveredPoints.clear();
381 }
382
383 } // namespace Integration
384
385 } // namespace Dali