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