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