Added class to replace TouchEvent
[platform/core/uifw/dali-core.git] / dali / internal / event / events / touch-event-processor.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/internal/event/events/touch-event-processor.h>
20
21 #if defined(DEBUG_ENABLED)
22 #include <sstream>
23 #endif
24
25 // INTERNAL INCLUDES
26 #include <dali/public-api/events/touch-data.h>
27 #include <dali/public-api/math/vector2.h>
28 #include <dali/public-api/signals/callback.h>
29 #include <dali/integration-api/debug.h>
30 #include <dali/integration-api/events/touch-event-integ.h>
31 #include <dali/internal/event/actors/actor-impl.h>
32 #include <dali/internal/event/actors/layer-impl.h>
33 #include <dali/internal/event/common/stage-impl.h>
34 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
35 #include <dali/internal/event/events/multi-point-event-util.h>
36 #include <dali/internal/event/events/touch-data-impl.h>
37 #include <dali/internal/event/render-tasks/render-task-impl.h>
38
39 namespace Dali
40 {
41
42 namespace Internal
43 {
44
45 namespace
46 {
47
48 #if defined(DEBUG_ENABLED)
49 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TOUCH_PROCESSOR" );
50
51 const char * TOUCH_POINT_STATE[TouchPoint::Last] =
52 {
53   "Down",
54   "Up",
55   "Motion",
56   "Leave",
57   "Stationary",
58   "Interrupted",
59 };
60
61 #endif // defined(DEBUG_ENABLED)
62
63
64 /**
65  *  Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
66  */
67 Dali::Actor EmitTouchSignals( Dali::Actor actor, const TouchEvent& event, const Dali::TouchData& touchData )
68 {
69   Dali::Actor consumedActor;
70
71   if ( actor )
72   {
73     Dali::Actor oldParent( actor.GetParent() );
74
75     Actor& actorImpl( GetImplementation(actor) );
76
77     bool consumed( false );
78
79     // Only emit the signal if the actor's touch signal has connections (or derived actor implementation requires touch).
80     if ( actorImpl.GetTouchRequired() )
81     {
82       consumed = actorImpl.EmitTouchEventSignal( event, touchData );
83     }
84
85     if ( consumed )
86     {
87       // One of this actor's listeners has consumed the event so set this actor as the consumed actor.
88       consumedActor = Dali::Actor( &actorImpl );
89     }
90     else
91     {
92       // The actor may have been removed/reparented during the signal callbacks.
93       Dali::Actor parent = actor.GetParent();
94
95       if ( parent &&
96            (parent == oldParent) )
97       {
98         // One of the actor's parents may consumed the event and they should be set as the consumed actor.
99         consumedActor = EmitTouchSignals( parent, event, touchData );
100       }
101     }
102   }
103
104   return consumedActor;
105 }
106
107 Dali::Actor AllocAndEmitTouchSignals( unsigned long time,  Dali::Actor actor, const TouchPoint& point )
108 {
109   TouchEvent touchEvent( time );
110   IntrusivePtr< TouchData > touchData( new TouchData( time ) );
111   Dali::TouchData touchDataHandle( touchData.Get() );
112
113   touchEvent.points.push_back( point );
114   touchData->AddPoint( point );
115
116   return EmitTouchSignals( actor, touchEvent, touchDataHandle );
117 }
118
119
120 /**
121  * Changes the state of the primary point to leave and emits the touch signals
122  */
123 Dali::Actor EmitTouchSignals( Actor* actor, RenderTask& renderTask, const TouchEvent& originalEvent, TouchPoint::State state )
124 {
125   TouchEvent touchEvent( originalEvent );
126
127   DALI_ASSERT_DEBUG( NULL != actor && "NULL actor pointer" );
128   if( actor )
129   {
130     TouchPoint& primaryPoint = touchEvent.points[0];
131
132     actor->ScreenToLocal( renderTask, primaryPoint.local.x, primaryPoint.local.y, primaryPoint.screen.x, primaryPoint.screen.y );
133
134     primaryPoint.hitActor = Dali::Actor(actor);
135     primaryPoint.state = state;
136   }
137
138   IntrusivePtr< TouchData > touchData( new TouchData( touchEvent.time ) );
139   touchData->SetPoints( touchEvent.points );
140
141   return EmitTouchSignals( Dali::Actor(actor), touchEvent, Dali::TouchData( touchData.Get() ) );
142 }
143
144 } // unnamed namespace
145
146 TouchEventProcessor::TouchEventProcessor( Stage& stage )
147 : mStage( stage ),
148   mLastPrimaryHitActor( MakeCallback( this, &TouchEventProcessor::OnObservedActorDisconnected ) ),
149   mLastConsumedActor(),
150   mTouchDownConsumedActor(),
151   mLastRenderTask()
152 {
153   DALI_LOG_TRACE_METHOD( gLogFilter );
154 }
155
156 TouchEventProcessor::~TouchEventProcessor()
157 {
158   DALI_LOG_TRACE_METHOD( gLogFilter );
159 }
160
161 void TouchEventProcessor::ProcessTouchEvent( const Integration::TouchEvent& event )
162 {
163   DALI_LOG_TRACE_METHOD( gLogFilter );
164
165   DALI_ASSERT_ALWAYS( !event.points.empty() && "Empty TouchEvent sent from Integration\n" );
166
167   Stage& stage = mStage;
168
169   PRINT_HIERARCHY(gLogFilter);
170
171   // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this
172   //    and emit the stage signal as well.
173
174   if ( event.points[0].state == TouchPoint::Interrupted )
175   {
176     Dali::Actor consumingActor;
177     TouchPoint currentPoint( event.points[0] );
178
179     Actor* lastPrimaryHitActor( mLastPrimaryHitActor.GetActor() );
180     if ( lastPrimaryHitActor )
181     {
182       Dali::Actor lastPrimaryHitActorHandle( lastPrimaryHitActor );
183       currentPoint.hitActor = lastPrimaryHitActorHandle;
184
185       consumingActor = AllocAndEmitTouchSignals( event.time, lastPrimaryHitActorHandle, currentPoint );
186     }
187
188     // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed).
189     Actor* lastConsumedActor( mLastConsumedActor.GetActor() );
190     if ( lastConsumedActor &&
191          lastConsumedActor != lastPrimaryHitActor &&
192          lastConsumedActor != consumingActor )
193     {
194       Dali::Actor lastConsumedActorHandle( lastConsumedActor );
195       currentPoint.hitActor = lastConsumedActorHandle;
196       AllocAndEmitTouchSignals( event.time, lastConsumedActorHandle, currentPoint );
197     }
198
199     // Tell the touch-down consuming actor as well, if required
200     Actor* touchDownConsumedActor( mTouchDownConsumedActor.GetActor() );
201     if ( touchDownConsumedActor &&
202          touchDownConsumedActor != lastPrimaryHitActor &&
203          touchDownConsumedActor != lastConsumedActor &&
204          touchDownConsumedActor != consumingActor )
205     {
206       Dali::Actor touchDownConsumedActorHandle( touchDownConsumedActor );
207
208       currentPoint.hitActor = touchDownConsumedActorHandle;
209       AllocAndEmitTouchSignals( event.time, touchDownConsumedActorHandle, currentPoint );
210     }
211
212     mLastPrimaryHitActor.SetActor( NULL );
213     mLastConsumedActor.SetActor( NULL );
214     mTouchDownConsumedActor.SetActor( NULL );
215     mLastRenderTask.Reset();
216
217     currentPoint.hitActor.Reset();
218
219     TouchEvent touchEvent( event.time );
220     IntrusivePtr< TouchData > touchData( new TouchData( event.time ) );
221     Dali::TouchData touchDataHandle( touchData.Get() );
222
223     touchEvent.points.push_back( currentPoint );
224     touchData->AddPoint( currentPoint );
225
226     mStage.EmitTouchedSignal( touchEvent, touchDataHandle );
227
228     return; // No need for hit testing
229   }
230
231   // 2) Hit Testing.
232   TouchEvent touchEvent( event.time );
233   IntrusivePtr< TouchData > touchData( new TouchData( event.time ) );
234   Dali::TouchData touchDataHandle( touchData.Get() );
235
236   DALI_LOG_INFO( gLogFilter, Debug::Concise, "\n" );
237   DALI_LOG_INFO( gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount() );
238
239   Dali::RenderTask currentRenderTask;
240
241   for ( TouchPointContainerConstIterator iter = event.points.begin(), beginIter = event.points.begin(), endIter = event.points.end(); iter != endIter; ++iter )
242   {
243     HitTestAlgorithm::Results hitTestResults;
244     HitTestAlgorithm::HitTest( stage, iter->screen, hitTestResults );
245
246     TouchPoint newPoint( iter->deviceId, iter->state, iter->screen.x, iter->screen.y, hitTestResults.actorCoordinates.x, hitTestResults.actorCoordinates.y );
247     newPoint.hitActor = hitTestResults.actor;
248
249     touchEvent.points.push_back( newPoint );
250     touchData->AddPoint( newPoint );
251
252     DALI_LOG_INFO( gLogFilter, Debug::General, "  State(%s), Screen(%.0f, %.0f), HitActor(%p, %s), Local(%.2f, %.2f)\n",
253                    TOUCH_POINT_STATE[iter->state], iter->screen.x, iter->screen.y,
254                    ( hitTestResults.actor ? (void*)&hitTestResults.actor.GetBaseObject() : NULL ),
255                    ( hitTestResults.actor ? hitTestResults.actor.GetName().c_str() : "" ),
256                    hitTestResults.actorCoordinates.x, hitTestResults.actorCoordinates.y );
257
258     // Only set the currentRenderTask for the primary hit actor.
259     if ( iter == beginIter && hitTestResults.renderTask )
260     {
261       currentRenderTask = hitTestResults.renderTask;
262     }
263   }
264
265   // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
266
267   // Emit the touch signal
268   Dali::Actor consumedActor;
269   if ( currentRenderTask )
270   {
271     consumedActor = EmitTouchSignals( touchEvent.points[0].hitActor, touchEvent, touchDataHandle );
272   }
273
274   TouchPoint& primaryPoint = touchEvent.points[0];
275   Dali::Actor primaryHitActor = primaryPoint.hitActor;
276   TouchPoint::State primaryPointState = primaryPoint.state;
277
278   DALI_LOG_INFO( gLogFilter, Debug::Concise, "PrimaryHitActor:     (%p) %s\n", primaryPoint.hitActor ? (void*)&primaryPoint.hitActor.GetBaseObject() : NULL, primaryPoint.hitActor ? primaryPoint.hitActor.GetName().c_str() : "" );
279   DALI_LOG_INFO( gLogFilter, Debug::Concise, "ConsumedActor:       (%p) %s\n", consumedActor ? (void*)&consumedActor.GetBaseObject() : NULL, consumedActor ? consumedActor.GetName().c_str() : "" );
280
281   if ( ( primaryPointState == TouchPoint::Down ) &&
282        ( touchEvent.GetPointCount() == 1 ) &&
283        ( consumedActor && consumedActor.OnStage() ) )
284   {
285     mTouchDownConsumedActor.SetActor( &GetImplementation( consumedActor ) );
286   }
287
288   // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
289   //    hit actor.  Also process the last consumed actor in the same manner.
290
291   Actor* lastPrimaryHitActor( mLastPrimaryHitActor.GetActor() );
292   Actor* lastConsumedActor( mLastConsumedActor.GetActor() );
293   if( (primaryPointState == TouchPoint::Motion) || (primaryPointState == TouchPoint::Up) || (primaryPointState == TouchPoint::Stationary) )
294   {
295     if ( mLastRenderTask )
296     {
297       Dali::Actor leaveEventConsumer;
298       RenderTask& lastRenderTaskImpl( GetImplementation( mLastRenderTask ) );
299
300       if( lastPrimaryHitActor &&
301           lastPrimaryHitActor != primaryHitActor &&
302           lastPrimaryHitActor != consumedActor )
303       {
304         if( lastPrimaryHitActor->IsHittable() && IsActuallySensitive( lastPrimaryHitActor ) )
305         {
306           if ( lastPrimaryHitActor->GetLeaveRequired() )
307           {
308             DALI_LOG_INFO( gLogFilter, Debug::Concise, "LeaveActor(Hit):     (%p) %s\n", (void*)lastPrimaryHitActor, lastPrimaryHitActor->GetName().c_str() );
309             leaveEventConsumer = EmitTouchSignals( mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEvent, TouchPoint::Leave );
310           }
311         }
312         else
313         {
314           // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
315           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
316           DALI_LOG_INFO( gLogFilter, Debug::Concise, "InterruptedActor(Hit):     (%p) %s\n", (void*)lastPrimaryHitActor, lastPrimaryHitActor->GetName().c_str() );
317           leaveEventConsumer = EmitTouchSignals( mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEvent, TouchPoint::Interrupted );
318         }
319       }
320
321       // Check if the motion event has been consumed by another actor's listener.  In this case, the previously
322       // consumed actor's listeners may need to be informed (through a leave event).
323       // Further checks here to ensure we do not signal the same actor twice for the same event.
324       if ( lastConsumedActor &&
325            lastConsumedActor != consumedActor &&
326            lastConsumedActor != lastPrimaryHitActor &&
327            lastConsumedActor != primaryHitActor &&
328            lastConsumedActor != leaveEventConsumer )
329       {
330         if( lastConsumedActor->IsHittable() && IsActuallySensitive( lastConsumedActor ) )
331         {
332           if( lastConsumedActor->GetLeaveRequired() )
333           {
334             DALI_LOG_INFO( gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", (void*)lastConsumedActor, lastConsumedActor->GetName().c_str() );
335             EmitTouchSignals( lastConsumedActor, lastRenderTaskImpl, touchEvent, TouchPoint::Leave );
336           }
337         }
338         else
339         {
340           // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
341           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
342           DALI_LOG_INFO( gLogFilter, Debug::Concise, "InterruptedActor(Consume):     (%p) %s\n", (void*)lastConsumedActor, lastConsumedActor->GetName().c_str() );
343           EmitTouchSignals( mLastConsumedActor.GetActor(), lastRenderTaskImpl, touchEvent, TouchPoint::Interrupted );
344         }
345       }
346     }
347   }
348
349   // 5) If our primary point is an Up event, then the primary point (in multi-touch) will change next
350   //    time so set our last primary actor to NULL.  Do the same to the last consumed actor as well.
351
352   if ( primaryPointState == TouchPoint::Up )
353   {
354     mLastPrimaryHitActor.SetActor( NULL );
355     mLastConsumedActor.SetActor( NULL );
356     mLastRenderTask.Reset();
357   }
358   else
359   {
360     // The primaryHitActor may have been removed from the stage so ensure it is still on the stage before setting members.
361     if ( primaryHitActor && primaryHitActor.OnStage() )
362     {
363       mLastPrimaryHitActor.SetActor( &GetImplementation( primaryHitActor ) );
364
365       // Only observe the consumed actor if we have a primaryHitActor (check if it is still on stage).
366       if ( consumedActor && consumedActor.OnStage() )
367       {
368         mLastConsumedActor.SetActor( &GetImplementation( consumedActor ) );
369       }
370       else
371       {
372         mLastConsumedActor.SetActor( NULL );
373       }
374
375       mLastRenderTask = currentRenderTask;
376     }
377     else
378     {
379       mLastPrimaryHitActor.SetActor( NULL );
380       mLastConsumedActor.SetActor( NULL );
381       mLastRenderTask.Reset();
382     }
383   }
384
385   // 6) Emit an interrupted event to the touch-down actor if it hasn't consumed the up and
386   //    emit the stage touched event if required.
387
388   if ( touchEvent.GetPointCount() == 1 ) // Only want the first touch and the last release
389   {
390     switch ( primaryPointState )
391     {
392       case TouchPoint::Up:
393       {
394         Actor* touchDownConsumedActor( mTouchDownConsumedActor.GetActor() );
395         if ( touchDownConsumedActor &&
396              touchDownConsumedActor != consumedActor &&
397              touchDownConsumedActor != lastPrimaryHitActor &&
398              touchDownConsumedActor != lastConsumedActor )
399         {
400           Dali::Actor touchDownConsumedActorHandle( touchDownConsumedActor );
401
402           TouchPoint currentPoint = touchData->GetPoint( 0 );
403           currentPoint.hitActor = touchDownConsumedActorHandle;
404           currentPoint.state    = TouchPoint::Interrupted;
405
406           AllocAndEmitTouchSignals( event.time, touchDownConsumedActorHandle, currentPoint );
407         }
408
409         mTouchDownConsumedActor.SetActor( NULL );
410       }
411       // No break, Fallthrough
412
413       case TouchPoint::Down:
414       {
415         mStage.EmitTouchedSignal( touchEvent, touchDataHandle );
416         break;
417       }
418
419       case TouchPoint::Motion:
420       case TouchPoint::Leave:
421       case TouchPoint::Stationary:
422       case TouchPoint::Interrupted:
423       case TouchPoint::Last:
424       {
425         // Ignore
426         break;
427       }
428     }
429   }
430 }
431
432 void TouchEventProcessor::OnObservedActorDisconnected( Actor* actor )
433 {
434   if ( actor == mLastPrimaryHitActor.GetActor() )
435   {
436     Dali::Actor handle( actor );
437     TouchEvent touchEvent( 0 );
438     touchEvent.points.push_back( TouchPoint( 0, TouchPoint::Interrupted, 0.0f, 0.0f ) );
439     touchEvent.points[0].hitActor = handle;
440
441     IntrusivePtr< TouchData > touchData( new TouchData );
442     touchData->SetPoints( touchEvent.points );
443
444     Dali::TouchData touchDataHandle( touchData.Get() );
445     Dali::Actor eventConsumer = EmitTouchSignals( handle, touchEvent, touchDataHandle );
446
447     if ( mLastConsumedActor.GetActor() != eventConsumer )
448     {
449       EmitTouchSignals( Dali::Actor( mLastConsumedActor.GetActor() ), touchEvent, touchDataHandle );
450     }
451
452     // Do not set mLastPrimaryHitActor to NULL we may be iterating through its observers
453
454     mLastConsumedActor.SetActor( NULL );
455     mLastRenderTask.Reset();
456   }
457 }
458
459 } // namespace Internal
460
461 } // namespace Dali