Merge "Add UserInteractionEnabled property on actor for controlling user interaction...
[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     hitTestResults.point = currentPoint;
312     hitTestResults.eventTime = event.time;
313     if(!firstPointParsed)
314     {
315       firstPointParsed = true;
316       ParsePrimaryTouchPoint(hitTestResults, mCapturingTouchActor, mLastRenderTask, currentPoint, mScene);
317
318       // Only set the currentRenderTask for the primary hit actor.
319       currentRenderTask = hitTestResults.renderTask;
320     }
321     else
322     {
323       HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults);
324     }
325
326     Integration::Point newPoint(currentPoint);
327     newPoint.SetHitActor(hitTestResults.actor);
328     newPoint.SetLocalPosition(hitTestResults.actorCoordinates);
329
330     touchEventImpl->AddPoint(newPoint);
331
332     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);
333   }
334
335   // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
336
337   bool consumed = false;
338
339   // Emit the touch signal
340   Dali::Actor consumedActor;
341
342   Integration::Point& primaryPoint      = touchEventImpl->GetPoint(0);
343   Dali::Actor         primaryHitActor   = primaryPoint.GetHitActor();
344   PointState::Type    primaryPointState = primaryPoint.GetState();
345
346   if(currentRenderTask)
347   {
348     // Emit the intercept touch signal
349     Dali::Actor interceptedActor = EmitInterceptTouchSignals(primaryHitActor, touchEventHandle);
350     if(interceptedActor)
351     {
352       // If the child is being touched, INTERRUPTED is sent.
353       if(mLastPrimaryHitActor.GetActor() &&
354          mLastPrimaryHitActor.GetActor() != interceptedActor &&
355          mLastRenderTask &&
356          mLastPrimaryPointState != PointState::FINISHED)
357       {
358         EmitTouchSignals(mLastPrimaryHitActor.GetActor(), *mLastRenderTask.Get(), touchEventImpl, PointState::INTERRUPTED);
359       }
360
361       consumedActor = EmitTouchSignals(interceptedActor, touchEventHandle);
362     }
363     else
364     {
365       consumedActor = EmitTouchSignals(primaryHitActor, touchEventHandle);
366     }
367     consumed = consumedActor ? true : false;
368   }
369
370   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() : "");
371   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() : "");
372
373   if((primaryPointState == PointState::DOWN) &&
374      (touchEventImpl->GetPointCount() == 1) &&
375      (consumedActor && consumedActor.GetProperty<bool>(Dali::Actor::Property::CONNECTED_TO_SCENE)))
376   {
377     mTouchDownConsumedActor.SetActor(&GetImplementation(consumedActor));
378   }
379
380   // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
381   //    hit actor.  Also process the last consumed actor in the same manner.
382
383   Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
384   Actor* lastConsumedActor(mLastConsumedActor.GetActor());
385   if((primaryPointState == PointState::MOTION) || (primaryPointState == PointState::UP) || (primaryPointState == PointState::STATIONARY))
386   {
387     if(mLastRenderTask)
388     {
389       Dali::Actor leaveEventConsumer;
390       RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
391
392       if(lastPrimaryHitActor &&
393          lastPrimaryHitActor != primaryHitActor &&
394          lastPrimaryHitActor != consumedActor)
395       {
396         if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor))
397         {
398           if(lastPrimaryHitActor->GetLeaveRequired())
399           {
400             DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Hit):     (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
401             leaveEventConsumer = EmitTouchSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::LEAVE);
402           }
403         }
404         else
405         {
406           // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
407           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
408           DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Hit):     (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
409           leaveEventConsumer = EmitTouchSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::INTERRUPTED);
410         }
411       }
412
413       consumed |= leaveEventConsumer ? true : false;
414
415       // Check if the motion event has been consumed by another actor's listener.  In this case, the previously
416       // consumed actor's listeners may need to be informed (through a leave event).
417       // Further checks here to ensure we do not signal the same actor twice for the same event.
418       if(lastConsumedActor &&
419          lastConsumedActor != consumedActor &&
420          lastConsumedActor != lastPrimaryHitActor &&
421          lastConsumedActor != primaryHitActor &&
422          lastConsumedActor != leaveEventConsumer)
423       {
424         if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor))
425         {
426           if(lastConsumedActor->GetLeaveRequired())
427           {
428             DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
429             EmitTouchSignals(lastConsumedActor, lastRenderTaskImpl, touchEventImpl, PointState::LEAVE);
430           }
431         }
432         else
433         {
434           // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
435           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
436           DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Consume):     (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
437           EmitTouchSignals(mLastConsumedActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::INTERRUPTED);
438         }
439       }
440     }
441   }
442
443   // 5) If our primary point is an Up event, then the primary point (in multi-touch) will change next
444   //    time so set our last primary actor to NULL.  Do the same to the last consumed actor as well.
445   if(primaryPointState == PointState::UP)
446   {
447     mLastPrimaryHitActor.SetActor(nullptr);
448     mLastConsumedActor.SetActor(nullptr);
449     mCapturingTouchActor.SetActor(nullptr);
450     mLastRenderTask.Reset();
451     mLastPrimaryPointState = PointState::FINISHED;
452   }
453   else
454   {
455     // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members.
456     if(primaryHitActor && GetImplementation(primaryHitActor).OnScene())
457     {
458       mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor));
459
460       // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene).
461       if(consumedActor && GetImplementation(consumedActor).OnScene())
462       {
463         mLastConsumedActor.SetActor(&GetImplementation(consumedActor));
464       }
465       else
466       {
467         mLastConsumedActor.SetActor(nullptr);
468       }
469
470       mLastRenderTask        = currentRenderTask;
471       mLastPrimaryPointState = primaryPointState;
472     }
473     else
474     {
475       mLastPrimaryHitActor.SetActor(nullptr);
476       mLastConsumedActor.SetActor(nullptr);
477       mCapturingTouchActor.SetActor(nullptr);
478       mLastRenderTask.Reset();
479       mLastPrimaryPointState = PointState::FINISHED;
480     }
481   }
482
483   // 6) Emit an interrupted event to the touch-down actor if it hasn't consumed the up and
484   //    emit the stage touched event if required.
485
486   if(touchEventImpl->GetPointCount() == 1) // Only want the first touch and the last release
487   {
488     switch(primaryPointState)
489     {
490       case PointState::UP:
491       {
492         Actor* touchDownConsumedActor(mTouchDownConsumedActor.GetActor());
493         if(touchDownConsumedActor &&
494            touchDownConsumedActor != consumedActor &&
495            touchDownConsumedActor != lastPrimaryHitActor &&
496            touchDownConsumedActor != lastConsumedActor)
497         {
498           Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
499
500           Integration::Point currentPoint = touchEventImpl->GetPoint(0);
501           currentPoint.SetHitActor(touchDownConsumedActorHandle);
502           currentPoint.SetState(PointState::INTERRUPTED);
503
504           AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
505         }
506
507         mTouchDownConsumedActor.SetActor(nullptr);
508
509         DALI_FALLTHROUGH;
510       }
511
512       case PointState::DOWN:
513       {
514         mScene.EmitTouchedSignal(touchEventHandle);
515         break;
516       }
517
518       case PointState::MOTION:
519       case PointState::LEAVE:
520       case PointState::STATIONARY:
521       case PointState::INTERRUPTED:
522       {
523         // Ignore
524         break;
525       }
526     }
527   }
528
529   return consumed;
530 }
531
532 void TouchEventProcessor::OnObservedActorDisconnected(Actor* actor)
533 {
534   if(actor == mLastPrimaryHitActor.GetActor())
535   {
536     Dali::Actor actorHandle(actor);
537
538     Integration::Point point;
539     point.SetState(PointState::INTERRUPTED);
540     point.SetHitActor(actorHandle);
541
542     TouchEventPtr touchEventImpl(new TouchEvent);
543     touchEventImpl->AddPoint(point);
544     Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
545
546     Dali::Actor eventConsumer = EmitTouchSignals(actorHandle, touchEventHandle);
547
548     if(mLastConsumedActor.GetActor() != eventConsumer)
549     {
550       EmitTouchSignals(Dali::Actor(mLastConsumedActor.GetActor()), touchEventHandle);
551     }
552
553     // Do not set mLastPrimaryHitActor to NULL we may be iterating through its observers
554
555     mLastConsumedActor.SetActor(nullptr);
556     mLastRenderTask.Reset();
557     mLastPrimaryPointState = PointState::FINISHED;
558   }
559 }
560
561 } // namespace Internal
562
563 } // namespace Dali