Merge "Updated all code to new format" into devel/master
[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 {
223   DALI_LOG_TRACE_METHOD(gLogFilter);
224 }
225
226 TouchEventProcessor::~TouchEventProcessor()
227 {
228   DALI_LOG_TRACE_METHOD(gLogFilter);
229 }
230
231 bool TouchEventProcessor::ProcessTouchEvent(const Integration::TouchEvent& event)
232 {
233   DALI_LOG_TRACE_METHOD(gLogFilter);
234   DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty TouchEvent sent from Integration\n");
235
236   PRINT_HIERARCHY(gLogFilter);
237
238   // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this
239   //    and emit the stage signal as well.
240
241   if(event.points[0].GetState() == PointState::INTERRUPTED)
242   {
243     Dali::Actor        consumingActor;
244     Integration::Point currentPoint(event.points[0]);
245
246     Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
247     if(lastPrimaryHitActor)
248     {
249       Dali::Actor lastPrimaryHitActorHandle(lastPrimaryHitActor);
250       currentPoint.SetHitActor(lastPrimaryHitActorHandle);
251
252       consumingActor = AllocAndEmitTouchSignals(event.time, lastPrimaryHitActorHandle, currentPoint);
253     }
254
255     // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed).
256     Actor* lastConsumedActor(mLastConsumedActor.GetActor());
257     if(lastConsumedActor &&
258        lastConsumedActor != lastPrimaryHitActor &&
259        lastConsumedActor != consumingActor)
260     {
261       Dali::Actor lastConsumedActorHandle(lastConsumedActor);
262       currentPoint.SetHitActor(lastConsumedActorHandle);
263       AllocAndEmitTouchSignals(event.time, lastConsumedActorHandle, currentPoint);
264     }
265
266     // Tell the touch-down consuming actor as well, if required
267     Actor* touchDownConsumedActor(mTouchDownConsumedActor.GetActor());
268     if(touchDownConsumedActor &&
269        touchDownConsumedActor != lastPrimaryHitActor &&
270        touchDownConsumedActor != lastConsumedActor &&
271        touchDownConsumedActor != consumingActor)
272     {
273       Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
274
275       currentPoint.SetHitActor(touchDownConsumedActorHandle);
276       AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
277     }
278
279     mLastPrimaryHitActor.SetActor(nullptr);
280     mLastConsumedActor.SetActor(nullptr);
281     mCapturingTouchActor.SetActor(nullptr);
282     mTouchDownConsumedActor.SetActor(nullptr);
283     mLastRenderTask.Reset();
284
285     currentPoint.SetHitActor(Dali::Actor());
286
287     TouchEventPtr    touchEventImpl(new TouchEvent(event.time));
288     Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
289
290     touchEventImpl->AddPoint(currentPoint);
291
292     mScene.EmitTouchedSignal(touchEventHandle);
293     return false; // No need for hit testing & already an interrupted event so just return false
294   }
295
296   // 2) Hit Testing.
297   TouchEventPtr    touchEventImpl(new TouchEvent(event.time));
298   Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
299
300   DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n");
301   DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount());
302
303   RenderTaskPtr currentRenderTask;
304   bool          firstPointParsed = false;
305
306   for(auto&& currentPoint : event.points)
307   {
308     HitTestAlgorithm::Results hitTestResults;
309     if(!firstPointParsed)
310     {
311       firstPointParsed = true;
312       ParsePrimaryTouchPoint(hitTestResults, mCapturingTouchActor, mLastRenderTask, currentPoint, mScene);
313
314       // Only set the currentRenderTask for the primary hit actor.
315       currentRenderTask = hitTestResults.renderTask;
316     }
317     else
318     {
319       HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults);
320     }
321
322     Integration::Point newPoint(currentPoint);
323     newPoint.SetHitActor(hitTestResults.actor);
324     newPoint.SetLocalPosition(hitTestResults.actorCoordinates);
325
326     touchEventImpl->AddPoint(newPoint);
327
328     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);
329   }
330
331   // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
332
333   bool consumed = false;
334
335   // Emit the touch signal
336   Dali::Actor consumedActor;
337   if(currentRenderTask)
338   {
339     // Emit the intercept touch signal
340     Dali::Actor interceptedActor = EmitInterceptTouchSignals(touchEventImpl->GetPoint(0).GetHitActor(), touchEventHandle);
341     if(interceptedActor)
342     {
343       consumedActor = EmitTouchSignals(interceptedActor, touchEventHandle);
344     }
345     else
346     {
347       consumedActor = EmitTouchSignals(touchEventImpl->GetPoint(0).GetHitActor(), touchEventHandle);
348     }
349     consumed = consumedActor ? true : false;
350   }
351
352   Integration::Point& primaryPoint      = touchEventImpl->GetPoint(0);
353   Dali::Actor         primaryHitActor   = primaryPoint.GetHitActor();
354   PointState::Type    primaryPointState = primaryPoint.GetState();
355
356   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() : "");
357   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() : "");
358
359   if((primaryPointState == PointState::DOWN) &&
360      (touchEventImpl->GetPointCount() == 1) &&
361      (consumedActor && consumedActor.GetProperty<bool>(Dali::Actor::Property::CONNECTED_TO_SCENE)))
362   {
363     mTouchDownConsumedActor.SetActor(&GetImplementation(consumedActor));
364   }
365
366   // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
367   //    hit actor.  Also process the last consumed actor in the same manner.
368
369   Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
370   Actor* lastConsumedActor(mLastConsumedActor.GetActor());
371   if((primaryPointState == PointState::MOTION) || (primaryPointState == PointState::UP) || (primaryPointState == PointState::STATIONARY))
372   {
373     if(mLastRenderTask)
374     {
375       Dali::Actor leaveEventConsumer;
376       RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
377
378       if(lastPrimaryHitActor &&
379          lastPrimaryHitActor != primaryHitActor &&
380          lastPrimaryHitActor != consumedActor)
381       {
382         if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor))
383         {
384           if(lastPrimaryHitActor->GetLeaveRequired())
385           {
386             DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Hit):     (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
387             leaveEventConsumer = EmitTouchSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::LEAVE);
388           }
389         }
390         else
391         {
392           // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
393           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
394           DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Hit):     (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
395           leaveEventConsumer = EmitTouchSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::INTERRUPTED);
396         }
397       }
398
399       consumed |= leaveEventConsumer ? true : false;
400
401       // Check if the motion event has been consumed by another actor's listener.  In this case, the previously
402       // consumed actor's listeners may need to be informed (through a leave event).
403       // Further checks here to ensure we do not signal the same actor twice for the same event.
404       if(lastConsumedActor &&
405          lastConsumedActor != consumedActor &&
406          lastConsumedActor != lastPrimaryHitActor &&
407          lastConsumedActor != primaryHitActor &&
408          lastConsumedActor != leaveEventConsumer)
409       {
410         if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor))
411         {
412           if(lastConsumedActor->GetLeaveRequired())
413           {
414             DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
415             EmitTouchSignals(lastConsumedActor, lastRenderTaskImpl, touchEventImpl, PointState::LEAVE);
416           }
417         }
418         else
419         {
420           // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
421           // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
422           DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Consume):     (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
423           EmitTouchSignals(mLastConsumedActor.GetActor(), lastRenderTaskImpl, touchEventImpl, PointState::INTERRUPTED);
424         }
425       }
426     }
427   }
428
429   // 5) If our primary point is an Up event, then the primary point (in multi-touch) will change next
430   //    time so set our last primary actor to NULL.  Do the same to the last consumed actor as well.
431
432   if(primaryPointState == PointState::UP)
433   {
434     mLastPrimaryHitActor.SetActor(nullptr);
435     mLastConsumedActor.SetActor(nullptr);
436     mCapturingTouchActor.SetActor(nullptr);
437     mLastRenderTask.Reset();
438   }
439   else
440   {
441     // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members.
442     if(primaryHitActor && GetImplementation(primaryHitActor).OnScene())
443     {
444       mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor));
445
446       // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene).
447       if(consumedActor && GetImplementation(consumedActor).OnScene())
448       {
449         mLastConsumedActor.SetActor(&GetImplementation(consumedActor));
450       }
451       else
452       {
453         mLastConsumedActor.SetActor(nullptr);
454       }
455
456       mLastRenderTask = currentRenderTask;
457     }
458     else
459     {
460       mLastPrimaryHitActor.SetActor(nullptr);
461       mLastConsumedActor.SetActor(nullptr);
462       mCapturingTouchActor.SetActor(nullptr);
463       mLastRenderTask.Reset();
464     }
465   }
466
467   // 6) Emit an interrupted event to the touch-down actor if it hasn't consumed the up and
468   //    emit the stage touched event if required.
469
470   if(touchEventImpl->GetPointCount() == 1) // Only want the first touch and the last release
471   {
472     switch(primaryPointState)
473     {
474       case PointState::UP:
475       {
476         Actor* touchDownConsumedActor(mTouchDownConsumedActor.GetActor());
477         if(touchDownConsumedActor &&
478            touchDownConsumedActor != consumedActor &&
479            touchDownConsumedActor != lastPrimaryHitActor &&
480            touchDownConsumedActor != lastConsumedActor)
481         {
482           Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
483
484           Integration::Point currentPoint = touchEventImpl->GetPoint(0);
485           currentPoint.SetHitActor(touchDownConsumedActorHandle);
486           currentPoint.SetState(PointState::INTERRUPTED);
487
488           AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
489         }
490
491         mTouchDownConsumedActor.SetActor(nullptr);
492
493         DALI_FALLTHROUGH;
494       }
495
496       case PointState::DOWN:
497       {
498         mScene.EmitTouchedSignal(touchEventHandle);
499         break;
500       }
501
502       case PointState::MOTION:
503       case PointState::LEAVE:
504       case PointState::STATIONARY:
505       case PointState::INTERRUPTED:
506       {
507         // Ignore
508         break;
509       }
510     }
511   }
512
513   return consumed;
514 }
515
516 void TouchEventProcessor::OnObservedActorDisconnected(Actor* actor)
517 {
518   if(actor == mLastPrimaryHitActor.GetActor())
519   {
520     Dali::Actor actorHandle(actor);
521
522     Integration::Point point;
523     point.SetState(PointState::INTERRUPTED);
524     point.SetHitActor(actorHandle);
525
526     TouchEventPtr touchEventImpl(new TouchEvent);
527     touchEventImpl->AddPoint(point);
528     Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
529
530     Dali::Actor eventConsumer = EmitTouchSignals(actorHandle, touchEventHandle);
531
532     if(mLastConsumedActor.GetActor() != eventConsumer)
533     {
534       EmitTouchSignals(Dali::Actor(mLastConsumedActor.GetActor()), touchEventHandle);
535     }
536
537     // Do not set mLastPrimaryHitActor to NULL we may be iterating through its observers
538
539     mLastConsumedActor.SetActor(nullptr);
540     mLastRenderTask.Reset();
541   }
542 }
543
544 } // namespace Internal
545
546 } // namespace Dali