2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/event/events/touch-event-processor.h>
21 #if defined(DEBUG_ENABLED)
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>
45 #if defined(DEBUG_ENABLED)
46 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TOUCH_PROCESSOR");
48 const char* TOUCH_POINT_STATE[6] =
58 #endif // defined(DEBUG_ENABLED)
60 Dali::Actor EmitInterceptTouchSignals(Dali::Actor actor, const Dali::TouchEvent& touchEvent)
62 Dali::Actor interceptedActor;
66 Dali::Actor parent = actor.GetParent();
69 // Recursively deliver events to the actor and its parents for intercept touch event.
70 interceptedActor = EmitInterceptTouchSignals(parent, touchEvent);
75 bool intercepted = false;
76 Actor& actorImpl(GetImplementation(actor));
77 if(actorImpl.GetInterceptTouchRequired())
79 intercepted = actorImpl.EmitInterceptTouchEventSignal(touchEvent);
82 interceptedActor = Dali::Actor(&actorImpl);
88 return interceptedActor;
92 * Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
94 Dali::Actor EmitTouchSignals(Dali::Actor actor, const Dali::TouchEvent& touchEvent)
96 Dali::Actor consumedActor;
100 Dali::Actor oldParent(actor.GetParent());
102 Actor& actorImpl(GetImplementation(actor));
104 bool consumed(false);
106 // Only emit the signal if the actor's touch signal has connections (or derived actor implementation requires touch).
107 if(actorImpl.GetTouchRequired())
109 consumed = actorImpl.EmitTouchEventSignal(touchEvent);
114 // One of this actor's listeners has consumed the event so set this actor as the consumed actor.
115 consumedActor = Dali::Actor(&actorImpl);
119 // The actor may have been removed/reparented during the signal callbacks.
120 Dali::Actor parent = actor.GetParent();
123 (parent == oldParent))
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);
131 return consumedActor;
134 Dali::Actor AllocAndEmitTouchSignals(unsigned long time, Dali::Actor actor, const Integration::Point& point)
136 TouchEventPtr touchEvent(new TouchEvent(time));
137 Dali::TouchEvent touchEventHandle(touchEvent.Get());
139 touchEvent->AddPoint(point);
141 return EmitTouchSignals(actor, touchEventHandle);
145 * Changes the state of the primary point to leave and emits the touch signals
147 Dali::Actor EmitTouchSignals(Actor* actor, RenderTask& renderTask, const TouchEventPtr& originalTouchEvent, PointState::Type state)
149 Dali::Actor consumingActor;
153 TouchEventPtr touchEventImpl = TouchEvent::Clone(*originalTouchEvent.Get());
155 Integration::Point& primaryPoint = touchEventImpl->GetPoint(0);
157 const Vector2& screenPosition = primaryPoint.GetScreenPosition();
158 Vector2 localPosition;
159 actor->ScreenToLocal(renderTask, localPosition.x, localPosition.y, screenPosition.x, screenPosition.y);
161 primaryPoint.SetLocalPosition(localPosition);
162 primaryPoint.SetHitActor(Dali::Actor(actor));
163 primaryPoint.SetState(state);
165 consumingActor = EmitTouchSignals(Dali::Actor(actor), Dali::TouchEvent(touchEventImpl.Get()));
168 return consumingActor;
172 * @brief Parses the primary touch point by performing a hit-test if necessary
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
180 void ParsePrimaryTouchPoint(
181 HitTestAlgorithm::Results& hitTestResults,
182 ActorObserver& capturingTouchActorObserver,
183 const RenderTaskPtr& lastRenderTask,
184 const Integration::Point& currentPoint,
185 const Internal::Scene& scene)
187 Actor* capturingTouchActor = capturingTouchActorObserver.GetActor();
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)
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);
199 HitTestAlgorithm::HitTest(scene.GetSize(), scene.GetRenderTaskList(), scene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults);
201 if(currentPoint.GetState() == PointState::STARTED && hitTestResults.actor)
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())
207 capturingTouchActorObserver.SetActor(hitActor);
213 } // unnamed namespace
215 TouchEventProcessor::TouchEventProcessor(Scene& scene)
217 mLastPrimaryHitActor(MakeCallback(this, &TouchEventProcessor::OnObservedActorDisconnected)),
218 mLastConsumedActor(),
219 mCapturingTouchActor(),
220 mTouchDownConsumedActor(),
222 mLastPrimaryPointState(PointState::FINISHED)
224 DALI_LOG_TRACE_METHOD(gLogFilter);
227 TouchEventProcessor::~TouchEventProcessor()
229 DALI_LOG_TRACE_METHOD(gLogFilter);
232 bool TouchEventProcessor::ProcessTouchEvent(const Integration::TouchEvent& event)
234 DALI_LOG_TRACE_METHOD(gLogFilter);
235 DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty TouchEvent sent from Integration\n");
237 PRINT_HIERARCHY(gLogFilter);
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.
242 if(event.points[0].GetState() == PointState::INTERRUPTED)
244 Dali::Actor consumingActor;
245 Integration::Point currentPoint(event.points[0]);
247 Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
248 if(lastPrimaryHitActor)
250 Dali::Actor lastPrimaryHitActorHandle(lastPrimaryHitActor);
251 currentPoint.SetHitActor(lastPrimaryHitActorHandle);
253 consumingActor = AllocAndEmitTouchSignals(event.time, lastPrimaryHitActorHandle, currentPoint);
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)
262 Dali::Actor lastConsumedActorHandle(lastConsumedActor);
263 currentPoint.SetHitActor(lastConsumedActorHandle);
264 AllocAndEmitTouchSignals(event.time, lastConsumedActorHandle, currentPoint);
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)
274 Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
276 currentPoint.SetHitActor(touchDownConsumedActorHandle);
277 AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
280 mLastPrimaryHitActor.SetActor(nullptr);
281 mLastConsumedActor.SetActor(nullptr);
282 mCapturingTouchActor.SetActor(nullptr);
283 mTouchDownConsumedActor.SetActor(nullptr);
284 mLastRenderTask.Reset();
285 mLastPrimaryPointState = PointState::FINISHED;
287 currentPoint.SetHitActor(Dali::Actor());
289 TouchEventPtr touchEventImpl(new TouchEvent(event.time));
290 Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
292 touchEventImpl->AddPoint(currentPoint);
294 mScene.EmitTouchedSignal(touchEventHandle);
295 return false; // No need for hit testing & already an interrupted event so just return false
299 TouchEventPtr touchEventImpl(new TouchEvent(event.time));
300 Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
302 DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n");
303 DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount());
305 RenderTaskPtr currentRenderTask;
306 bool firstPointParsed = false;
308 for(auto&& currentPoint : event.points)
310 HitTestAlgorithm::Results hitTestResults;
311 hitTestResults.point = currentPoint;
312 hitTestResults.eventTime = event.time;
313 if(!firstPointParsed)
315 firstPointParsed = true;
316 ParsePrimaryTouchPoint(hitTestResults, mCapturingTouchActor, mLastRenderTask, currentPoint, mScene);
318 // Only set the currentRenderTask for the primary hit actor.
319 currentRenderTask = hitTestResults.renderTask;
323 HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults);
326 Integration::Point newPoint(currentPoint);
327 newPoint.SetHitActor(hitTestResults.actor);
328 newPoint.SetLocalPosition(hitTestResults.actorCoordinates);
330 touchEventImpl->AddPoint(newPoint);
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);
335 // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
337 bool consumed = false;
339 // Emit the touch signal
340 Dali::Actor consumedActor;
342 Integration::Point& primaryPoint = touchEventImpl->GetPoint(0);
343 Dali::Actor primaryHitActor = primaryPoint.GetHitActor();
344 PointState::Type primaryPointState = primaryPoint.GetState();
346 if(currentRenderTask)
348 // Emit the intercept touch signal
349 Dali::Actor interceptedActor = EmitInterceptTouchSignals(primaryHitActor, touchEventHandle);
352 // If the child is being touched, INTERRUPTED is sent.
353 if(mLastPrimaryHitActor.GetActor() &&
354 mLastPrimaryHitActor.GetActor() != interceptedActor &&
356 mLastPrimaryPointState != PointState::FINISHED)
358 EmitTouchSignals(mLastPrimaryHitActor.GetActor(), *mLastRenderTask.Get(), touchEventImpl, PointState::INTERRUPTED);
361 consumedActor = EmitTouchSignals(interceptedActor, touchEventHandle);
365 consumedActor = EmitTouchSignals(primaryHitActor, touchEventHandle);
367 consumed = consumedActor ? true : false;
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() : "");
373 if((primaryPointState == PointState::DOWN) &&
374 (touchEventImpl->GetPointCount() == 1) &&
375 (consumedActor && consumedActor.GetProperty<bool>(Dali::Actor::Property::CONNECTED_TO_SCENE)))
377 mTouchDownConsumedActor.SetActor(&GetImplementation(consumedActor));
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.
383 Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
384 Actor* lastConsumedActor(mLastConsumedActor.GetActor());
385 if((primaryPointState == PointState::MOTION) || (primaryPointState == PointState::UP) || (primaryPointState == PointState::STATIONARY))
389 Dali::Actor leaveEventConsumer;
390 RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
392 if(lastPrimaryHitActor &&
393 lastPrimaryHitActor != primaryHitActor &&
394 lastPrimaryHitActor != consumedActor)
396 if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor))
398 if(lastPrimaryHitActor->GetLeaveRequired())
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);
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);
413 consumed |= leaveEventConsumer ? true : false;
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)
424 if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor))
426 if(lastConsumedActor->GetLeaveRequired())
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);
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);
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)
447 mLastPrimaryHitActor.SetActor(nullptr);
448 mLastConsumedActor.SetActor(nullptr);
449 mCapturingTouchActor.SetActor(nullptr);
450 mLastRenderTask.Reset();
451 mLastPrimaryPointState = PointState::FINISHED;
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())
458 mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor));
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())
463 mLastConsumedActor.SetActor(&GetImplementation(consumedActor));
467 mLastConsumedActor.SetActor(nullptr);
470 mLastRenderTask = currentRenderTask;
471 mLastPrimaryPointState = primaryPointState;
475 mLastPrimaryHitActor.SetActor(nullptr);
476 mLastConsumedActor.SetActor(nullptr);
477 mCapturingTouchActor.SetActor(nullptr);
478 mLastRenderTask.Reset();
479 mLastPrimaryPointState = PointState::FINISHED;
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.
486 if(touchEventImpl->GetPointCount() == 1) // Only want the first touch and the last release
488 switch(primaryPointState)
492 Actor* touchDownConsumedActor(mTouchDownConsumedActor.GetActor());
493 if(touchDownConsumedActor &&
494 touchDownConsumedActor != consumedActor &&
495 touchDownConsumedActor != lastPrimaryHitActor &&
496 touchDownConsumedActor != lastConsumedActor)
498 Dali::Actor touchDownConsumedActorHandle(touchDownConsumedActor);
500 Integration::Point currentPoint = touchEventImpl->GetPoint(0);
501 currentPoint.SetHitActor(touchDownConsumedActorHandle);
502 currentPoint.SetState(PointState::INTERRUPTED);
504 AllocAndEmitTouchSignals(event.time, touchDownConsumedActorHandle, currentPoint);
507 mTouchDownConsumedActor.SetActor(nullptr);
512 case PointState::DOWN:
514 mScene.EmitTouchedSignal(touchEventHandle);
518 case PointState::MOTION:
519 case PointState::LEAVE:
520 case PointState::STATIONARY:
521 case PointState::INTERRUPTED:
532 void TouchEventProcessor::OnObservedActorDisconnected(Actor* actor)
534 if(actor == mLastPrimaryHitActor.GetActor())
536 Dali::Actor actorHandle(actor);
538 Integration::Point point;
539 point.SetState(PointState::INTERRUPTED);
540 point.SetHitActor(actorHandle);
542 TouchEventPtr touchEventImpl(new TouchEvent);
543 touchEventImpl->AddPoint(point);
544 Dali::TouchEvent touchEventHandle(touchEventImpl.Get());
546 Dali::Actor eventConsumer = EmitTouchSignals(actorHandle, touchEventHandle);
548 if(mLastConsumedActor.GetActor() != eventConsumer)
550 EmitTouchSignals(Dali::Actor(mLastConsumedActor.GetActor()), touchEventHandle);
553 // Do not set mLastPrimaryHitActor to NULL we may be iterating through its observers
555 mLastConsumedActor.SetActor(nullptr);
556 mLastRenderTask.Reset();
557 mLastPrimaryPointState = PointState::FINISHED;
561 } // namespace Internal