If the parent is intercepted, the child is informed that it is INTERRUPTED.
[platform/core/uifw/dali-core.git] / dali / internal / event / events / touch-event-processor.cpp
1 /*
2  * Copyright (c) 2021 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/integration-api/debug.h>
27 #include <dali/integration-api/events/touch-event-integ.h>
28 #include <dali/internal/event/actors/actor-impl.h>
29 #include <dali/internal/event/actors/layer-impl.h>
30 #include <dali/internal/event/common/scene-impl.h>
31 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
32 #include <dali/internal/event/events/multi-point-event-util.h>
33 #include <dali/internal/event/events/touch-event-impl.h>
34 #include <dali/internal/event/render-tasks/render-task-impl.h>
35 #include <dali/public-api/events/touch-event.h>
36 #include <dali/public-api/math/vector2.h>
37 #include <dali/public-api/signals/callback.h>
38
39 namespace Dali
40 {
41 namespace Internal
42 {
43 namespace
44 {
45 #if defined(DEBUG_ENABLED)
46 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TOUCH_PROCESSOR");
47
48 const char* TOUCH_POINT_STATE[6] =
49   {
50     "DOWN",
51     "UP",
52     "MOTION",
53     "LEAVE",
54     "STATIONARY",
55     "INTERRUPTED",
56 };
57
58 #endif // defined(DEBUG_ENABLED)
59
60 Dali::Actor EmitInterceptTouchSignals(Dali::Actor actor, const Dali::TouchEvent& touchEvent)
61 {
62   Dali::Actor interceptedActor;
63
64   if(actor)
65   {
66     Dali::Actor parent = actor.GetParent();
67     if(parent)
68     {
69       // Recursively deliver events to the actor and its parents for intercept touch event.
70       interceptedActor = EmitInterceptTouchSignals(parent, touchEvent);
71     }
72
73     if(!interceptedActor)
74     {
75       bool   intercepted = false;
76       Actor& actorImpl(GetImplementation(actor));
77       if(actorImpl.GetInterceptTouchRequired())
78       {
79         intercepted = actorImpl.EmitInterceptTouchEventSignal(touchEvent);
80         if(intercepted)
81         {
82           interceptedActor = Dali::Actor(&actorImpl);
83         }
84       }
85     }
86   }
87
88   return interceptedActor;
89 }
90
91 /**
92  *  Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
93  */
94 Dali::Actor EmitTouchSignals(Dali::Actor actor, const Dali::TouchEvent& touchEvent)
95 {
96   Dali::Actor consumedActor;
97
98   if(actor)
99   {
100     Dali::Actor oldParent(actor.GetParent());
101
102     Actor& actorImpl(GetImplementation(actor));
103
104     bool consumed(false);
105
106     // Only emit the signal if the actor's touch signal has connections (or derived actor implementation requires touch).
107     if(actorImpl.GetTouchRequired())
108     {
109       consumed = actorImpl.EmitTouchEventSignal(touchEvent);
110     }
111
112     if(consumed)
113     {
114       // One of this actor's listeners has consumed the event so set this actor as the consumed actor.
115       consumedActor = Dali::Actor(&actorImpl);
116     }
117     else
118     {
119       // The actor may have been removed/reparented during the signal callbacks.
120       Dali::Actor parent = actor.GetParent();
121
122       if(parent &&
123          (parent == oldParent))
124       {
125         // One of the actor's parents may consumed the event and they should be set as the consumed actor.
126         consumedActor = EmitTouchSignals(parent, touchEvent);
127       }
128     }
129   }
130
131   return consumedActor;
132 }
133
134 Dali::Actor AllocAndEmitTouchSignals(unsigned long time, Dali::Actor actor, const Integration::Point& point)
135 {
136   TouchEventPtr    touchEvent(new TouchEvent(time));
137   Dali::TouchEvent touchEventHandle(touchEvent.Get());
138
139   touchEvent->AddPoint(point);
140
141   return EmitTouchSignals(actor, touchEventHandle);
142 }
143
144 /**
145  * Changes the state of the primary point to leave and emits the touch signals
146  */
147 Dali::Actor EmitTouchSignals(Actor* actor, RenderTask& renderTask, const TouchEventPtr& originalTouchEvent, PointState::Type state)
148 {
149   Dali::Actor consumingActor;
150
151   if(actor)
152   {
153     TouchEventPtr touchEventImpl = TouchEvent::Clone(*originalTouchEvent.Get());
154
155     Integration::Point& primaryPoint = touchEventImpl->GetPoint(0);
156
157     const Vector2& screenPosition = primaryPoint.GetScreenPosition();
158     Vector2        localPosition;
159     actor->ScreenToLocal(renderTask, localPosition.x, localPosition.y, screenPosition.x, screenPosition.y);
160
161     primaryPoint.SetLocalPosition(localPosition);
162     primaryPoint.SetHitActor(Dali::Actor(actor));
163     primaryPoint.SetState(state);
164
165     consumingActor = EmitTouchSignals(Dali::Actor(actor), Dali::TouchEvent(touchEventImpl.Get()));
166   }
167
168   return consumingActor;
169 }
170
171 /**
172  * @brief Parses the primary touch point by performing a hit-test if necessary
173  *
174  * @param[out] hitTestResults The hit test results are put into this variable
175  * @param[in/out] capturingTouchActorObserver The observer for the capturing touch actor member
176  * @param[in] lastRenderTask The last render task member
177  * @param[in] currentPoint The current point information
178  * @param[in] scene The scene that this touch is related to
179  */
180 void ParsePrimaryTouchPoint(
181   HitTestAlgorithm::Results& hitTestResults,
182   ActorObserver&             capturingTouchActorObserver,
183   const RenderTaskPtr&       lastRenderTask,
184   const Integration::Point&  currentPoint,
185   const Internal::Scene&     scene)
186 {
187   Actor* capturingTouchActor = capturingTouchActorObserver.GetActor();
188
189   // We only set the capturing touch actor when the first touch-started actor captures all touch so if it's set, just use it
190   if(capturingTouchActor && lastRenderTask)
191   {
192     hitTestResults.actor          = Dali::Actor(capturingTouchActor);
193     hitTestResults.renderTask     = lastRenderTask;
194     const Vector2& screenPosition = currentPoint.GetScreenPosition();
195     capturingTouchActor->ScreenToLocal(*lastRenderTask, hitTestResults.actorCoordinates.x, hitTestResults.actorCoordinates.y, screenPosition.x, screenPosition.y);
196   }
197   else
198   {
199     HitTestAlgorithm::HitTest(scene.GetSize(), scene.GetRenderTaskList(), scene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults);
200
201     if(currentPoint.GetState() == PointState::STARTED && hitTestResults.actor)
202     {
203       // If we've just started touch, then check whether the actor has requested to capture all touch events
204       Actor* hitActor = &GetImplementation(hitTestResults.actor);
205       if(hitActor->CapturesAllTouchAfterStart())
206       {
207         capturingTouchActorObserver.SetActor(hitActor);
208       }
209     }
210   }
211 }
212
213 } // unnamed namespace
214
215 TouchEventProcessor::TouchEventProcessor(Scene& scene)
216 : mScene(scene),
217   mLastPrimaryHitActor(MakeCallback(this, &TouchEventProcessor::OnObservedActorDisconnected)),
218   mLastConsumedActor(),
219   mCapturingTouchActor(),
220   mTouchDownConsumedActor(),
221   mLastRenderTask(),
222   mLastPrimaryPointState(PointState::FINISHED)
223 {
224   DALI_LOG_TRACE_METHOD(gLogFilter);
225 }
226
227 TouchEventProcessor::~TouchEventProcessor()
228 {
229   DALI_LOG_TRACE_METHOD(gLogFilter);
230 }
231
232 bool TouchEventProcessor::ProcessTouchEvent(const Integration::TouchEvent& event)
233 {
234   DALI_LOG_TRACE_METHOD(gLogFilter);
235   DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty TouchEvent sent from Integration\n");
236
237   PRINT_HIERARCHY(gLogFilter);
238
239   // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this
240   //    and emit the stage signal as well.
241
242   if(event.points[0].GetState() == PointState::INTERRUPTED)
243   {
244     Dali::Actor        consumingActor;
245     Integration::Point currentPoint(event.points[0]);
246
247     Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
248     if(lastPrimaryHitActor)
249     {
250       Dali::Actor lastPrimaryHitActorHandle(lastPrimaryHitActor);
251       currentPoint.SetHitActor(lastPrimaryHitActorHandle);
252
253       consumingActor = AllocAndEmitTouchSignals(event.time, lastPrimaryHitActorHandle, currentPoint);
254     }
255
256     // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed).
257     Actor* lastConsumedActor(mLastConsumedActor.GetActor());
258     if(lastConsumedActor &&
259        lastConsumedActor != lastPrimaryHitActor &&
260        lastConsumedActor != consumingActor)
261     {
262       Dali::Actor lastConsumedActorHandle(lastConsumedActor);
263       currentPoint.SetHitActor(lastConsumedActorHandle);
264       AllocAndEmitTouchSignals(event.time, lastConsumedActorHandle, currentPoint);
265     }
266
267     // Tell the touch-down consuming actor as well, if required
268     Actor* touchDownConsumedActor(mTouchDownConsumedActor.GetActor());
269     if(touchDownConsumedActor &&
270        touchDownConsumedActor != lastPrimaryHitActor &&
271        touchDownConsumedActor != lastConsumedActor &&
272        touchDownConsumedActor != consumingActor)
273     {
274       Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
275
276       currentPoint.SetHitActor(touchDownConsumedActorHandle);
277       AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
278     }
279
280     mLastPrimaryHitActor.SetActor(nullptr);
281     mLastConsumedActor.SetActor(nullptr);
282     mCapturingTouchActor.SetActor(nullptr);
283     mTouchDownConsumedActor.SetActor(nullptr);
284     mLastRenderTask.Reset();
285     mLastPrimaryPointState = PointState::FINISHED;
286
287     currentPoint.SetHitActor(Dali::Actor());
288
289     TouchEventPtr    touchEventImpl(new TouchEvent(event.time));
290     Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
291
292     touchEventImpl->AddPoint(currentPoint);
293
294     mScene.EmitTouchedSignal(touchEventHandle);
295     return false; // No need for hit testing & already an interrupted event so just return false
296   }
297
298   // 2) Hit Testing.
299   TouchEventPtr    touchEventImpl(new TouchEvent(event.time));
300   Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
301
302   DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n");
303   DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount());
304
305   RenderTaskPtr currentRenderTask;
306   bool          firstPointParsed = false;
307
308   for(auto&& currentPoint : event.points)
309   {
310     HitTestAlgorithm::Results hitTestResults;
311     if(!firstPointParsed)
312     {
313       firstPointParsed = true;
314       ParsePrimaryTouchPoint(hitTestResults, mCapturingTouchActor, mLastRenderTask, currentPoint, mScene);
315
316       // Only set the currentRenderTask for the primary hit actor.
317       currentRenderTask = hitTestResults.renderTask;
318     }
319     else
320     {
321       HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults);
322     }
323
324     Integration::Point newPoint(currentPoint);
325     newPoint.SetHitActor(hitTestResults.actor);
326     newPoint.SetLocalPosition(hitTestResults.actorCoordinates);
327
328     touchEventImpl->AddPoint(newPoint);
329
330     DALI_LOG_INFO(gLogFilter, Debug::General, "  State(%s), Screen(%.0f, %.0f), HitActor(%p, %s), Local(%.2f, %.2f)\n", TOUCH_POINT_STATE[currentPoint.GetState()], currentPoint.GetScreenPosition().x, currentPoint.GetScreenPosition().y, (hitTestResults.actor ? reinterpret_cast<void*>(&hitTestResults.actor.GetBaseObject()) : NULL), (hitTestResults.actor ? hitTestResults.actor.GetProperty<std::string>(Dali::Actor::Property::NAME).c_str() : ""), hitTestResults.actorCoordinates.x, hitTestResults.actorCoordinates.y);
331   }
332
333   // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
334
335   bool consumed = false;
336
337   // Emit the touch signal
338   Dali::Actor consumedActor;
339
340   Integration::Point& primaryPoint      = touchEventImpl->GetPoint(0);
341   Dali::Actor         primaryHitActor   = primaryPoint.GetHitActor();
342   PointState::Type    primaryPointState = primaryPoint.GetState();
343
344   if(currentRenderTask)
345   {
346     // Emit the intercept touch signal
347     Dali::Actor interceptedActor = EmitInterceptTouchSignals(primaryHitActor, touchEventHandle);
348     if(interceptedActor)
349     {
350       // If the child is being touched, INTERRUPTED is sent.
351       if(mLastPrimaryHitActor.GetActor() &&
352          mLastPrimaryHitActor.GetActor() != interceptedActor &&
353          mLastRenderTask &&
354          mLastPrimaryPointState != PointState::FINISHED)
355       {
356         EmitTouchSignals(mLastPrimaryHitActor.GetActor(), *mLastRenderTask.Get(), touchEventImpl, PointState::INTERRUPTED);
357       }
358
359       consumedActor = EmitTouchSignals(interceptedActor, touchEventHandle);
360     }
361     else
362     {
363       consumedActor = EmitTouchSignals(primaryHitActor, touchEventHandle);
364     }
365     consumed = consumedActor ? true : false;
366   }
367
368   DALI_LOG_INFO(gLogFilter, Debug::Concise, "PrimaryHitActor:     (%p) %s\n", primaryHitActor ? reinterpret_cast<void*>(&primaryHitActor.GetBaseObject()) : NULL, primaryHitActor ? primaryHitActor.GetProperty<std::string>(Dali::Actor::Property::NAME).c_str() : "");
369   DALI_LOG_INFO(gLogFilter, Debug::Concise, "ConsumedActor:       (%p) %s\n", consumedActor ? reinterpret_cast<void*>(&consumedActor.GetBaseObject()) : NULL, consumedActor ? consumedActor.GetProperty<std::string>(Dali::Actor::Property::NAME).c_str() : "");
370
371   if((primaryPointState == PointState::DOWN) &&
372      (touchEventImpl->GetPointCount() == 1) &&
373      (consumedActor && consumedActor.GetProperty<bool>(Dali::Actor::Property::CONNECTED_TO_SCENE)))
374   {
375     mTouchDownConsumedActor.SetActor(&GetImplementation(consumedActor));
376   }
377
378   // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
379   //    hit actor.  Also process the last consumed actor in the same manner.
380
381   Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
382   Actor* lastConsumedActor(mLastConsumedActor.GetActor());
383   if((primaryPointState == PointState::MOTION) || (primaryPointState == PointState::UP) || (primaryPointState == PointState::STATIONARY))
384   {
385     if(mLastRenderTask)
386     {
387       Dali::Actor leaveEventConsumer;
388       RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
389
390       if(lastPrimaryHitActor &&
391          lastPrimaryHitActor != primaryHitActor &&
392          lastPrimaryHitActor != consumedActor)
393       {
394         if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor))
395         {
396           if(lastPrimaryHitActor->GetLeaveRequired())
397           {
398             DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Hit):     (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
399             leaveEventConsumer = EmitTouchSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::LEAVE);
400           }
401         }
402         else
403         {
404           // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
405           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
406           DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Hit):     (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
407           leaveEventConsumer = EmitTouchSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::INTERRUPTED);
408         }
409       }
410
411       consumed |= leaveEventConsumer ? true : false;
412
413       // Check if the motion event has been consumed by another actor's listener.  In this case, the previously
414       // consumed actor's listeners may need to be informed (through a leave event).
415       // Further checks here to ensure we do not signal the same actor twice for the same event.
416       if(lastConsumedActor &&
417          lastConsumedActor != consumedActor &&
418          lastConsumedActor != lastPrimaryHitActor &&
419          lastConsumedActor != primaryHitActor &&
420          lastConsumedActor != leaveEventConsumer)
421       {
422         if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor))
423         {
424           if(lastConsumedActor->GetLeaveRequired())
425           {
426             DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
427             EmitTouchSignals(lastConsumedActor, lastRenderTaskImpl, touchEventImpl, PointState::LEAVE);
428           }
429         }
430         else
431         {
432           // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
433           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
434           DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Consume):     (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
435           EmitTouchSignals(mLastConsumedActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::INTERRUPTED);
436         }
437       }
438     }
439   }
440
441   // 5) If our primary point is an Up event, then the primary point (in multi-touch) will change next
442   //    time so set our last primary actor to NULL.  Do the same to the last consumed actor as well.
443   if(primaryPointState == PointState::UP)
444   {
445     mLastPrimaryHitActor.SetActor(nullptr);
446     mLastConsumedActor.SetActor(nullptr);
447     mCapturingTouchActor.SetActor(nullptr);
448     mLastRenderTask.Reset();
449     mLastPrimaryPointState = PointState::FINISHED;
450   }
451   else
452   {
453     // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members.
454     if(primaryHitActor && GetImplementation(primaryHitActor).OnScene())
455     {
456       mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor));
457
458       // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene).
459       if(consumedActor && GetImplementation(consumedActor).OnScene())
460       {
461         mLastConsumedActor.SetActor(&GetImplementation(consumedActor));
462       }
463       else
464       {
465         mLastConsumedActor.SetActor(nullptr);
466       }
467
468       mLastRenderTask        = currentRenderTask;
469       mLastPrimaryPointState = primaryPointState;
470     }
471     else
472     {
473       mLastPrimaryHitActor.SetActor(nullptr);
474       mLastConsumedActor.SetActor(nullptr);
475       mCapturingTouchActor.SetActor(nullptr);
476       mLastRenderTask.Reset();
477       mLastPrimaryPointState = PointState::FINISHED;
478     }
479   }
480
481   // 6) Emit an interrupted event to the touch-down actor if it hasn't consumed the up and
482   //    emit the stage touched event if required.
483
484   if(touchEventImpl->GetPointCount() == 1) // Only want the first touch and the last release
485   {
486     switch(primaryPointState)
487     {
488       case PointState::UP:
489       {
490         Actor* touchDownConsumedActor(mTouchDownConsumedActor.GetActor());
491         if(touchDownConsumedActor &&
492            touchDownConsumedActor != consumedActor &&
493            touchDownConsumedActor != lastPrimaryHitActor &&
494            touchDownConsumedActor != lastConsumedActor)
495         {
496           Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
497
498           Integration::Point currentPoint = touchEventImpl->GetPoint(0);
499           currentPoint.SetHitActor(touchDownConsumedActorHandle);
500           currentPoint.SetState(PointState::INTERRUPTED);
501
502           AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
503         }
504
505         mTouchDownConsumedActor.SetActor(nullptr);
506
507         DALI_FALLTHROUGH;
508       }
509
510       case PointState::DOWN:
511       {
512         mScene.EmitTouchedSignal(touchEventHandle);
513         break;
514       }
515
516       case PointState::MOTION:
517       case PointState::LEAVE:
518       case PointState::STATIONARY:
519       case PointState::INTERRUPTED:
520       {
521         // Ignore
522         break;
523       }
524     }
525   }
526
527   return consumed;
528 }
529
530 void TouchEventProcessor::OnObservedActorDisconnected(Actor* actor)
531 {
532   if(actor == mLastPrimaryHitActor.GetActor())
533   {
534     Dali::Actor actorHandle(actor);
535
536     Integration::Point point;
537     point.SetState(PointState::INTERRUPTED);
538     point.SetHitActor(actorHandle);
539
540     TouchEventPtr touchEventImpl(new TouchEvent);
541     touchEventImpl->AddPoint(point);
542     Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
543
544     Dali::Actor eventConsumer = EmitTouchSignals(actorHandle, touchEventHandle);
545
546     if(mLastConsumedActor.GetActor() != eventConsumer)
547     {
548       EmitTouchSignals(Dali::Actor(mLastConsumedActor.GetActor()), touchEventHandle);
549     }
550
551     // Do not set mLastPrimaryHitActor to NULL we may be iterating through its observers
552
553     mLastConsumedActor.SetActor(nullptr);
554     mLastRenderTask.Reset();
555     mLastPrimaryPointState = PointState::FINISHED;
556   }
557 }
558
559 } // namespace Internal
560
561 } // namespace Dali