From 700a947503dd58c1c8db5ed91c85b46f87d41b69 Mon Sep 17 00:00:00 2001 From: Adeel Kazmi Date: Sat, 20 Jan 2024 13:01:38 +0000 Subject: [PATCH] Reduce CC of HoverEventProcessor::ProcessHoverEvent Change-Id: Iec40f015ab571f4bdfe0b3b049c4770a5a4f328d --- .../src/dali/utc-Dali-GeoHoverProcessing.cpp | 84 ++- .../src/dali/utc-Dali-GeoTouchProcessing.cpp | 105 +++- .../src/dali/utc-Dali-HoverProcessing.cpp | 198 ++++++- .../event/events/hover-event-processor.cpp | 607 ++++++++++++--------- dali/internal/event/events/hover-event-processor.h | 28 +- 5 files changed, 713 insertions(+), 309 deletions(-) diff --git a/automated-tests/src/dali/utc-Dali-GeoHoverProcessing.cpp b/automated-tests/src/dali/utc-Dali-GeoHoverProcessing.cpp index 625788e..556ec78 100644 --- a/automated-tests/src/dali/utc-Dali-GeoHoverProcessing.cpp +++ b/automated-tests/src/dali/utc-Dali-GeoHoverProcessing.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -368,7 +368,7 @@ int UtcDaliGeoHoverParentConsumer(void) { TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -469,7 +469,7 @@ int UtcDaliGeoHoverInterruptedParentConsumer(void) { TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -595,7 +595,7 @@ int UtcDaliGeoHoverLeaveParentConsumer(void) { TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -705,7 +705,7 @@ int UtcDaliGeoHoverActorBecomesInsensitiveParentConsumer(void) { TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -803,7 +803,7 @@ int UtcDaliGeoHoverMultipleLayers(void) { TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); // Connect to actor's hovered signal SignalData data; @@ -927,7 +927,7 @@ int UtcDaliGeoHoverMultipleLayers(void) int UtcDaliGeoHoverMultipleRenderTasks(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage(application.GetScene()); Vector2 stageSize(stage.GetSize()); @@ -973,7 +973,7 @@ int UtcDaliGeoHoverMultipleRenderTasks(void) int UtcDaliGeoHoverMultipleRenderTasksWithChildLayer(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage(application.GetScene()); Vector2 stageSize(stage.GetSize()); @@ -1026,7 +1026,7 @@ int UtcDaliGeoHoverMultipleRenderTasksWithChildLayer(void) int UtcDaliGeoHoverOffscreenRenderTasks(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage(application.GetScene()); Vector2 stageSize(stage.GetSize()); @@ -1077,7 +1077,7 @@ int UtcDaliGeoHoverOffscreenRenderTasks(void) int UtcDaliGeoHoverMultipleRenderableActors(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage(application.GetScene()); Vector2 stageSize(stage.GetSize()); @@ -1249,7 +1249,7 @@ int UtcDaliGeoHoverActorUnStaged(void) int UtcDaliGeoHoverLeaveActorReadded(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage = application.GetScene(); @@ -1296,7 +1296,7 @@ int UtcDaliGeoHoverLeaveActorReadded(void) int UtcDaliGeoHoverClippingActor(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage = application.GetScene(); @@ -1349,7 +1349,7 @@ int UtcDaliGeoHoverClippingActor(void) int UtcDaliGeoHoverActorHide(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); Integration::Scene stage = application.GetScene(); @@ -1385,3 +1385,61 @@ int UtcDaliGeoHoverActorHide(void) END_TEST; } + +int UtcDaliGeoHoverEnsureDifferentConsumerReceivesInterrupted(void) +{ + // Interrupted event with a different consumer to previous event + + TestApplication application; + Integration::Scene scene = application.GetScene(); + scene.SetGeometryHittestEnabled(true); + + Actor parent = Actor::New(); + parent.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + parent.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + scene.Add(parent); + + Actor child = Actor::New(); + child.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + child.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + parent.Add(child); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Connect to parent's hover signal + SignalData dataParent; + HoverEventFunctor functorParent(dataParent); + parent.HoveredSignal().Connect(&application, functorParent); + + // Connect to child's hovered signal but do not consume + SignalData dataChildNoConsume; + HoverEventFunctor functorChildNoConsume(dataChildNoConsume, false); + child.HoveredSignal().Connect(&application, functorChildNoConsume); + + // Create a functor to consume the event of the child, but don't connect just yet + SignalData dataChildConsume; + HoverEventFunctor functorChildConsume(dataChildConsume); + + auto resetData = [&]() { dataParent.Reset(); dataChildNoConsume.Reset(); dataChildConsume.Reset(); }; + + // Emit a started + application.ProcessEvent(GenerateSingleHover(PointState::STARTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataParent.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildNoConsume.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(false, dataChildConsume.functorCalled, TEST_LOCATION); + resetData(); + + // Connect to child's hover event and consume so it's a different consumer on interrupted + child.HoveredSignal().Connect(&application, functorChildConsume); + + // Emit interrupted, all three methods should be called + application.ProcessEvent(GenerateSingleHover(PointState::INTERRUPTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataParent.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildNoConsume.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildConsume.functorCalled, TEST_LOCATION); + resetData(); + + END_TEST; +} \ No newline at end of file diff --git a/automated-tests/src/dali/utc-Dali-GeoTouchProcessing.cpp b/automated-tests/src/dali/utc-Dali-GeoTouchProcessing.cpp index 43944be..1cb4cfc 100644 --- a/automated-tests/src/dali/utc-Dali-GeoTouchProcessing.cpp +++ b/automated-tests/src/dali/utc-Dali-GeoTouchProcessing.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -559,13 +559,52 @@ int UtcDaliGeoTouchEventInterrupted(void) END_TEST; } +int UtcDaliGeoTouchEventNotConsumedInterrupted(void) +{ + TestApplication application; + + application.GetScene().SetGeometryHittestEnabled(true); + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + application.GetScene().Add(actor); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Connect to actor's touched signal + SignalData data; + TouchEventFunctor functor(data, false); + actor.TouchedSignal().Connect(&application, functor); + + // Emit a down signal + application.ProcessEvent(GenerateSingleTouch(PointState::DOWN, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, data.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(PointState::DOWN, data.receivedTouch.points[0].state, TEST_LOCATION); + data.Reset(); + + // Emit an interrupted signal, we should be signalled regardless of whether there is a hit or not even though we didn't consume + // as we still were the hit-actor in the last event. + application.ProcessEvent(GenerateSingleTouch(PointState::INTERRUPTED, Vector2(200.0f, 200.0f /* Outside actor */))); + DALI_TEST_EQUALS(true, data.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(PointState::INTERRUPTED, data.receivedTouch.points[0].state, TEST_LOCATION); + data.Reset(); + + // Emit another interrupted signal, our signal handler should not be called. + application.ProcessEvent(GenerateSingleTouch(PointState::INTERRUPTED, Vector2(200.0f, 200.0f))); + DALI_TEST_EQUALS(false, data.functorCalled, TEST_LOCATION); + END_TEST; +} + int UtcDaliGeoTouchEventParentConsumer(void) { TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -658,7 +697,7 @@ int UtcDaliGeoTouchEventInterruptedParentConsumer(void) application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -776,7 +815,7 @@ int UtcDaliGeoTouchEventActorBecomesInsensitiveParentConsumer(void) application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor actor = Actor::New(); actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -867,7 +906,7 @@ int UtcDaliGeoTouchEventMultipleLayers(void) application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); // Connect to actor's touched signal SignalData data; @@ -994,11 +1033,10 @@ int UtcDaliGeoTouchEventMultipleLayers(void) int UtcDaliGeoTouchEventMultipleRenderTasks(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); - Integration::Scene scene(application.GetScene()); Vector2 sceneSize(scene.GetSize()); @@ -1045,7 +1083,7 @@ int UtcDaliGeoTouchEventMultipleRenderTasks(void) int UtcDaliGeoTouchEventMultipleRenderTasksWithChildLayer(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); @@ -1102,7 +1140,7 @@ int UtcDaliGeoTouchEventMultipleRenderTasksWithChildLayer(void) int UtcDaliGeoTouchEventOffscreenRenderTasks(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); @@ -1155,7 +1193,7 @@ int UtcDaliGeoTouchEventOffscreenRenderTasks(void) int UtcDaliGeoTouchEventMultipleRenderableActors(void) { - TestApplication application; + TestApplication application; application.GetScene().SetGeometryHittestEnabled(true); @@ -1636,7 +1674,7 @@ int UtcDaliGeoTouchEventInterruptedDifferentConsumer(void) application.GetScene().SetGeometryHittestEnabled(true); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); Actor parent = Actor::New(); parent.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); @@ -2120,7 +2158,6 @@ int UtcDaliGeoTouchEventIntercept02(void) interceptData.Reset(); parentData.Reset(); - END_TEST; } @@ -2153,7 +2190,7 @@ int UtcDaliGeoTouchEventIntercept03(void) application.SendNotification(); application.Render(); - Actor rootActor(application.GetScene().GetRootLayer()); + Actor rootActor(application.GetScene().GetRootLayer()); // Connect to root actor's intercept touched signal SignalData sceneData; @@ -2171,7 +2208,6 @@ int UtcDaliGeoTouchEventIntercept03(void) DALI_TEST_EQUALS(true, sceneData.functorCalled, TEST_LOCATION); sceneData.Reset(); - END_TEST; } @@ -2458,3 +2494,44 @@ int UtcDaliGeoTouchEventDispatchTouchMotionPropertySet(void) END_TEST; } + +int UtcDaliGeoTouchDownDifferentUp(void) +{ + TestApplication application; + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + application.GetScene().Add(actor); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Connect to actor's touched signal + SignalData data; + TouchEventFunctor functor(data); + actor.TouchedSignal().Connect(&application, functor); + + // Emit a down signal + application.ProcessEvent(GenerateSingleTouch(PointState::DOWN, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, data.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(PointState::DOWN, data.receivedTouch.points[0].state, TEST_LOCATION); + data.Reset(); + + // Emit a signal outside the actor, we should NOT be signalled. + application.ProcessEvent(GenerateSingleTouch(PointState::MOTION, Vector2(200.0f, 200.0f /* Outside actor */))); + application.ProcessEvent(GenerateSingleTouch(PointState::MOTION, Vector2(210.0f, 200.0f /* Outside actor */))); + application.ProcessEvent(GenerateSingleTouch(PointState::MOTION, Vector2(220.0f, 200.0f /* Outside actor */))); + DALI_TEST_EQUALS(false, data.functorCalled, TEST_LOCATION); + data.Reset(); + + // Set the geometry hit test on + application.GetScene().SetGeometryHittestEnabled(true); + + // ...and emit an up event outside of the actor's bounds, we should get an interrupted signal + application.ProcessEvent(GenerateSingleTouch(PointState::UP, Vector2(200.0f, 200.0f))); + DALI_TEST_EQUALS(true, data.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(PointState::INTERRUPTED, data.receivedTouch.points[0].state, TEST_LOCATION); + END_TEST; +} \ No newline at end of file diff --git a/automated-tests/src/dali/utc-Dali-HoverProcessing.cpp b/automated-tests/src/dali/utc-Dali-HoverProcessing.cpp index 720f7ab..e912937 100644 --- a/automated-tests/src/dali/utc-Dali-HoverProcessing.cpp +++ b/automated-tests/src/dali/utc-Dali-HoverProcessing.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1404,3 +1404,199 @@ int UtcDaliHoverActorHide(void) END_TEST; } + +int UtcDaliHoverStartConsumerDifferentAtEnd(void) +{ + // Start a hover in one actor, continue it in another actor + // End hover in the second actor + // First actor should still get a hover finished call + TestApplication application; + Integration::Scene scene = application.GetScene(); + + Actor actor = Actor::New(); + actor.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + scene.Add(actor); + + Actor actor2 = Actor::New(); + actor2.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + actor2.SetProperty(Actor::Property::POSITION, Vector2(100.0f, 100.0f)); + actor2.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + scene.Add(actor2); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Connect to hover start actor's hovered signal + SignalData dataStartActor; + HoverEventFunctor functorStartActor(dataStartActor); + actor.HoveredSignal().Connect(&application, functorStartActor); + + // Connect to second actor's hovered signal + SignalData dataSecondActor; + HoverEventFunctor functorSecondActor(dataSecondActor); + actor2.HoveredSignal().Connect(&application, functorSecondActor); + + auto resetData = [&]() { dataStartActor.Reset(); dataSecondActor.Reset(); }; + + // Emit a started + application.ProcessEvent(GenerateSingleHover(PointState::STARTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataStartActor.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(false, dataSecondActor.functorCalled, TEST_LOCATION); + resetData(); + + // Emit a hover somewhere else + application.ProcessEvent(GenerateSingleHover(PointState::MOTION, Vector2(110.0f, 110.0f))); + DALI_TEST_EQUALS(false, dataStartActor.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataSecondActor.functorCalled, TEST_LOCATION); + resetData(); + + // Emit a hover end in the same point, the hover start actor should still be informed + application.ProcessEvent(GenerateSingleHover(PointState::FINISHED, Vector2(110.0f, 110.0f))); + DALI_TEST_EQUALS(true, dataStartActor.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataSecondActor.functorCalled, TEST_LOCATION); + resetData(); + + // Do it again with the geometry on + // Emit a started + application.ProcessEvent(GenerateSingleHover(PointState::STARTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataStartActor.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(false, dataSecondActor.functorCalled, TEST_LOCATION); + resetData(); + + // Emit a hover somewhere else + application.ProcessEvent(GenerateSingleHover(PointState::MOTION, Vector2(110.0f, 110.0f))); + DALI_TEST_EQUALS(false, dataStartActor.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataSecondActor.functorCalled, TEST_LOCATION); + resetData(); + + scene.SetGeometryHittestEnabled(true); + + // Emit a hover end in the same point, the hover start actor should still be informed + application.ProcessEvent(GenerateSingleHover(PointState::FINISHED, Vector2(110.0f, 110.0f))); + DALI_TEST_EQUALS(true, dataStartActor.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataSecondActor.functorCalled, TEST_LOCATION); + resetData(); + + END_TEST; +} + +int UtcDaliHoverEnsureDifferentConsumerReceivesInterrupted(void) +{ + // Interrupted event with a different consumer to previous event + + TestApplication application; + Integration::Scene scene = application.GetScene(); + + Actor parent = Actor::New(); + parent.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + parent.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + scene.Add(parent); + + Actor child = Actor::New(); + child.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + child.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + parent.Add(child); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Connect to parent's hover signal + SignalData dataParent; + HoverEventFunctor functorParent(dataParent); + parent.HoveredSignal().Connect(&application, functorParent); + + // Connect to child's hovered signal but do not consume + SignalData dataChildNoConsume; + HoverEventFunctor functorChildNoConsume(dataChildNoConsume, false); + child.HoveredSignal().Connect(&application, functorChildNoConsume); + + // Create a functor to consume the event of the child, but don't connect just yet + SignalData dataChildConsume; + HoverEventFunctor functorChildConsume(dataChildConsume); + + auto resetData = [&]() { dataParent.Reset(); dataChildNoConsume.Reset(); dataChildConsume.Reset(); }; + + // Emit a started + application.ProcessEvent(GenerateSingleHover(PointState::STARTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataParent.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildNoConsume.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(false, dataChildConsume.functorCalled, TEST_LOCATION); + resetData(); + + // Connect to child's hover event and consume so it's a different consumer on interrupted + child.HoveredSignal().Connect(&application, functorChildConsume); + + // Emit interrupted, all three methods should be called + application.ProcessEvent(GenerateSingleHover(PointState::INTERRUPTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataParent.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildNoConsume.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildConsume.functorCalled, TEST_LOCATION); + resetData(); + + END_TEST; +} + +int UtcDaliHoverEnsureDifferentConsumerReceivesLeave(void) +{ + // Motion event outside previous hit actor + // This event is consumed by a different actor + // The previous consumer's listener should still get called + + TestApplication application; + Integration::Scene scene = application.GetScene(); + + Actor parent = Actor::New(); + parent.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + parent.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + scene.Add(parent); + + Actor child = Actor::New(); + child.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f)); + child.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + parent.Add(child); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Connect to parent's hover signal + SignalData dataParent; + HoverEventFunctor functorParent(dataParent); + parent.HoveredSignal().Connect(&application, functorParent); + + // Connect to child's hovered signal but do not consume + SignalData dataChildNoConsume; + HoverEventFunctor functorChildNoConsume(dataChildNoConsume, false); + child.HoveredSignal().Connect(&application, functorChildNoConsume); + + // Create a functor to consume the event of the child, but don't connect just yet + SignalData dataChildConsume; + HoverEventFunctor functorChildConsume(dataChildConsume); + + auto resetData = [&]() { dataParent.Reset(); dataChildNoConsume.Reset(); dataChildConsume.Reset(); }; + + // Emit a started + application.ProcessEvent(GenerateSingleHover(PointState::STARTED, Vector2(10.0f, 10.0f))); + DALI_TEST_EQUALS(true, dataParent.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildNoConsume.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(false, dataChildConsume.functorCalled, TEST_LOCATION); + resetData(); + + // Connect to child's hover event and consume so it's a different consumer on interrupted + child.HoveredSignal().Connect(&application, functorChildConsume); + + // Also make last consumer insensitive + parent.SetProperty(Actor::Property::SENSITIVE, false); + + // Emit interrupted, all three methods should be called + application.ProcessEvent(GenerateSingleHover(PointState::MOTION, Vector2(110.0f, 110.0f))); + DALI_TEST_EQUALS(true, dataParent.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildNoConsume.functorCalled, TEST_LOCATION); + DALI_TEST_EQUALS(true, dataChildConsume.functorCalled, TEST_LOCATION); + resetData(); + + END_TEST; +} diff --git a/dali/internal/event/events/hover-event-processor.cpp b/dali/internal/event/events/hover-event-processor.cpp index 0aea6c2..e5cd845 100644 --- a/dali/internal/event/events/hover-event-processor.cpp +++ b/dali/internal/event/events/hover-event-processor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,9 +38,7 @@ #include #include -namespace Dali -{ -namespace Internal +namespace Dali::Internal { namespace { @@ -49,6 +47,28 @@ DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_PERFORMANCE_MARKER, false); Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_HOVER_PROCESSOR"); #endif // defined(DEBUG_ENABLED) +/** + * Structure for Variables used in the ProcessHoverEvent method. + */ +struct ProcessHoverEventVariables +{ + ProcessHoverEventVariables(bool geometry) + : isGeometry(geometry) + { + } + + const bool isGeometry; ///< Whether it's a geometry or not. + Actor* lastPrimaryHitActor{nullptr}; ///< The last primary hit-actor. + Actor* lastConsumedActor{nullptr}; ///< The last consuming actor. + HoverEventPtr hoverEvent; ///< The current hover-event-impl. + Dali::HoverEvent hoverEventHandle; ///< The handle to the hover-event-impl. + RenderTaskPtr currentRenderTask; ///< The current render-task. + Dali::Actor consumedActor; ///< The actor that consumed the event. + Dali::Actor primaryHitActor; ///< The actor that has been hit by the primary point. + Integration::Point* primaryPoint{nullptr}; ///< The primary point of the hit. + PointState::Type primaryPointState{PointState::STARTED}; ///< The state of the primary point. +}; + const char* TOUCH_POINT_STATE[PointState::INTERRUPTED + 1] = { "STARTED", @@ -62,7 +82,7 @@ const char* TOUCH_POINT_STATE[PointState::INTERRUPTED + 1] = bool ShouldEmitHoverEvent(const Actor& actorImpl, const Dali::HoverEvent& event) { PointState::Type state = event.GetState(0); - return actorImpl.GetHoverRequired() && (state!= PointState::MOTION || actorImpl.IsDispatchHoverMotion()); + return actorImpl.GetHoverRequired() && (state != PointState::MOTION || actorImpl.IsDispatchHoverMotion()); } /** @@ -117,7 +137,7 @@ Dali::Actor EmitGeoHoverSignals(std::list& actorLists, c Dali::Actor consumedActor; std::list::reverse_iterator rIter = actorLists.rbegin(); - for (; rIter != actorLists.rend(); rIter++) + for(; rIter != actorLists.rend(); rIter++) { Actor* actorImpl(*rIter); // Only emit the signal if the actor's hover signal has connections (or derived actor implementation requires hover). @@ -128,7 +148,7 @@ Dali::Actor EmitGeoHoverSignals(std::list& actorLists, c actorImpl->SetHoverState(hoverEvent.GetState(0)); // If hover event is newly entering the actor, update it to the started state. if(hoverEvent.GetState(0) == PointState::MOTION && - (currentState == PointState::FINISHED || currentState == PointState::INTERRUPTED || currentState == PointState::LEAVE)) + (currentState == PointState::FINISHED || currentState == PointState::INTERRUPTED || currentState == PointState::LEAVE)) { HoverEventPtr newHoverEvent = HoverEvent::Clone(GetImplementation(hoverEvent)); newHoverEvent->GetPoint(0).SetState(PointState::STARTED); @@ -151,7 +171,6 @@ Dali::Actor EmitGeoHoverSignals(std::list& actorLists, c return consumedActor; } - Dali::Actor AllocAndEmitHoverSignals(unsigned long time, Dali::Actor actor, const Integration::Point& point) { HoverEventPtr hoverEvent(new HoverEvent(time)); @@ -172,7 +191,6 @@ Dali::Actor GeoAllocAndEmitHoverSignals(std::list& actor return EmitGeoHoverSignals(actorLists, hoverEventHandle); } - /** * Changes the state of the primary point to leave and emits the hover signals */ @@ -249,72 +267,27 @@ uint32_t GetMilliSeconds() } // unnamed namespace -HoverEventProcessor::HoverEventProcessor(Scene& scene) -: mScene(scene), - mLastPrimaryHitActor(MakeCallback(this, &HoverEventProcessor::OnObservedActorDisconnected)) -{ - DALI_LOG_TRACE_METHOD(gLogFilter); -} - -HoverEventProcessor::~HoverEventProcessor() -{ - DALI_LOG_TRACE_METHOD(gLogFilter); -} - -void HoverEventProcessor::SendInterruptedHoverEvent(Dali::Internal::Actor* actor) +struct HoverEventProcessor::Impl { - if(actor && - (mLastPrimaryHitActor.GetActor() == actor || mLastConsumedActor.GetActor() == actor)) - { - Integration::Point point; - point.SetState(PointState::INTERRUPTED); - point.SetHitActor(Dali::Actor(actor)); - if(mScene.IsGeometryHittestEnabled()) - { - std::list actorLists; - actorLists.push_back(actor); - GeoAllocAndEmitHoverSignals(actorLists, 0, point); - } - else - { - AllocAndEmitHoverSignals(GetMilliSeconds(), point.GetHitActor(), point); - } - Clear(); - } -} - -void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event) -{ - DALI_LOG_TRACE_METHOD(gLogFilter); - DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty HoverEvent sent from Integration\n"); - - PointState::Type state = static_cast(event.points[0].GetState()); - - PRINT_HIERARCHY(gLogFilter); - - DALI_TRACE_SCOPE(gTraceFilter, "DALI_PROCESS_HOVER_EVENT"); - - bool isGeometry = mScene.IsGeometryHittestEnabled(); - - // Copy so we can add the results of a hit-test. - HoverEventPtr hoverEvent(new HoverEvent(event.time)); - - // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this - // and emit the stage signal as well. - - if(state == PointState::INTERRUPTED) + /** + * Emits an interrupted event while processing the latest hover event. + * @param[in/out] processor The hover-event-processor + * @param[in] isGeometry Whether it's a geometry or not + * @param[in] event The hover event that has occurred + */ + static inline void EmitInterruptedEvent(HoverEventProcessor& processor, const bool isGeometry, const Integration::HoverEvent& event) { Dali::Actor consumingActor; Integration::Point currentPoint(event.points[0]); - Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor()); + Actor* lastPrimaryHitActor(processor.mLastPrimaryHitActor.GetActor()); if(lastPrimaryHitActor) { Dali::Actor lastPrimaryHitActorHandle(lastPrimaryHitActor); currentPoint.SetHitActor(lastPrimaryHitActorHandle); if(isGeometry) { - consumingActor = GeoAllocAndEmitHoverSignals(mCandidateActorLists, event.time, currentPoint); + consumingActor = GeoAllocAndEmitHoverSignals(processor.mCandidateActorLists, event.time, currentPoint); } else { @@ -323,10 +296,10 @@ void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event } // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed). - Actor* lastConsumedActor(mLastConsumedActor.GetActor()); + Actor* lastConsumedActor(processor.mLastConsumedActor.GetActor()); if(lastConsumedActor && - lastConsumedActor != lastPrimaryHitActor && - lastConsumedActor != consumingActor) + lastConsumedActor != lastPrimaryHitActor && + lastConsumedActor != consumingActor) { Dali::Actor lastConsumedActorHandle(lastConsumedActor); currentPoint.SetHitActor(lastConsumedActorHandle); @@ -343,11 +316,11 @@ void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event } // Tell the hover-start consuming actor as well, if required - Actor* hoverStartConsumedActor(mHoverStartConsumedActor.GetActor()); + Actor* hoverStartConsumedActor(processor.mHoverStartConsumedActor.GetActor()); if(hoverStartConsumedActor && - hoverStartConsumedActor != lastPrimaryHitActor && - hoverStartConsumedActor != lastConsumedActor && - hoverStartConsumedActor != consumingActor) + hoverStartConsumedActor != lastPrimaryHitActor && + hoverStartConsumedActor != lastConsumedActor && + hoverStartConsumedActor != consumingActor) { Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor); currentPoint.SetHitActor(hoverStartConsumedActorHandle); @@ -363,264 +336,364 @@ void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event } } - Clear(); - mHoverStartConsumedActor.SetActor(nullptr); - return; // No need for hit testing + processor.Clear(); + processor.mHoverStartConsumedActor.SetActor(nullptr); } - // 2) Hit Testing. - - Dali::HoverEvent hoverEventHandle(hoverEvent.Get()); - - DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n"); - DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount()); - - RenderTaskPtr currentRenderTask; - bool firstPointParsed = false; - - for(auto&& currentPoint : event.points) + /** + * Performs a hit-test and sets the variables in processor and localVars appropriately. + * @param[in/out] processor The hover-event-processor + * @param[in/out] localVars The struct of stack variables used by ProcessHoverEvent + * @param[in] event The hover event that has occurred + */ + static inline void HitTest(HoverEventProcessor& processor, ProcessHoverEventVariables& localVars, const Integration::HoverEvent& event) { - HitTestAlgorithm::Results hitTestResults; - hitTestResults.eventTime = event.time; - ActorHoverableCheck actorHoverableCheck; - HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults, actorHoverableCheck, isGeometry); - - Integration::Point newPoint(currentPoint); - newPoint.SetHitActor(hitTestResults.actor); - newPoint.SetLocalPosition(hitTestResults.actorCoordinates); - - hoverEvent->AddPoint(newPoint); - - 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(&hitTestResults.actor.GetBaseObject()) : NULL), (hitTestResults.actor ? hitTestResults.actor.GetProperty(Dali::Actor::Property::NAME).c_str() : ""), hitTestResults.actorCoordinates.x, hitTestResults.actorCoordinates.y); - - // Only set the currentRenderTask for the primary hit actor. - if(!firstPointParsed) + bool firstPointParsed = false; + for(auto&& currentPoint : event.points) { - firstPointParsed = true; - currentRenderTask = hitTestResults.renderTask; - mCandidateActorLists = hitTestResults.actorLists; + HitTestAlgorithm::Results hitTestResults; + hitTestResults.eventTime = event.time; + ActorHoverableCheck actorHoverableCheck; + HitTestAlgorithm::HitTest(processor.mScene.GetSize(), processor.mScene.GetRenderTaskList(), processor.mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults, actorHoverableCheck, localVars.isGeometry); + + Integration::Point newPoint(currentPoint); + newPoint.SetHitActor(hitTestResults.actor); + newPoint.SetLocalPosition(hitTestResults.actorCoordinates); + + localVars.hoverEvent->AddPoint(newPoint); + + 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(&hitTestResults.actor.GetBaseObject()) : NULL), + (hitTestResults.actor ? hitTestResults.actor.GetProperty(Dali::Actor::Property::NAME).c_str() : ""), + hitTestResults.actorCoordinates.x, + hitTestResults.actorCoordinates.y); + + // Only set the currentRenderTask for the primary hit actor. + if(!firstPointParsed) + { + firstPointParsed = true; + localVars.currentRenderTask = hitTestResults.renderTask; + processor.mCandidateActorLists = hitTestResults.actorLists; + } } } - // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached. - - Integration::Point primaryPoint = hoverEvent->GetPoint(0); - Dali::Actor primaryHitActor = primaryPoint.GetHitActor(); - PointState::Type primaryPointState = primaryPoint.GetState(); - - // Emit the touch signal - Dali::Actor consumedActor; - if(currentRenderTask) + /** + * Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached. + * @param[in/out] processor The hover-event-processor + * @param[in/out] localVars The struct of stack variables used by ProcessHoverEvent + */ + static inline void DeliverEventsToActorAndParents(HoverEventProcessor& processor, ProcessHoverEventVariables& localVars) { - Dali::Actor hitActor = hoverEvent->GetHitActor(0); - - if(isGeometry) - { - consumedActor = EmitGeoHoverSignals(mCandidateActorLists, hoverEventHandle); - } - else + // Emit the touch signal + if(localVars.currentRenderTask) { - // If the actor is hit first, the hover is started. - if(hitActor && - mLastPrimaryHitActor.GetActor() != hitActor && - state == PointState::MOTION) + Dali::Actor hitActor = localVars.hoverEvent->GetHitActor(0); + + if(localVars.isGeometry) { - Actor* hitActorImpl = &GetImplementation(hitActor); - if(hitActorImpl->GetLeaveRequired()) + localVars.consumedActor = EmitGeoHoverSignals(processor.mCandidateActorLists, localVars.hoverEventHandle); + } + else + { + // If the actor is hit first, the hover is started. + if(hitActor && processor.mLastPrimaryHitActor.GetActor() != hitActor && localVars.primaryPointState == PointState::MOTION) { - hoverEvent->GetPoint(0).SetState(PointState::STARTED); + Actor* hitActorImpl = &GetImplementation(hitActor); + if(hitActorImpl->GetLeaveRequired()) + { + localVars.hoverEvent->GetPoint(0).SetState(PointState::STARTED); + } } + localVars.consumedActor = EmitHoverSignals(hitActor, localVars.hoverEventHandle); + } + + if(localVars.hoverEvent->GetPoint(0).GetState() != PointState::MOTION) + { + DALI_LOG_RELEASE_INFO("PrimaryHitActor:(%p), id(%d), name(%s), state(%s)\n", + localVars.primaryHitActor ? reinterpret_cast(&localVars.primaryHitActor.GetBaseObject()) : NULL, + localVars.primaryHitActor ? localVars.primaryHitActor.GetProperty(Dali::Actor::Property::ID) : -1, + localVars.primaryHitActor ? localVars.primaryHitActor.GetProperty(Dali::Actor::Property::NAME).c_str() : "", + TOUCH_POINT_STATE[localVars.hoverEvent->GetPoint(0).GetState()]); + DALI_LOG_RELEASE_INFO("ConsumedActor: (%p), id(%d), name(%s), state(%s)\n", + localVars.consumedActor ? reinterpret_cast(&localVars.consumedActor.GetBaseObject()) : NULL, + localVars.consumedActor ? localVars.consumedActor.GetProperty(Dali::Actor::Property::ID) : -1, + localVars.consumedActor ? localVars.consumedActor.GetProperty(Dali::Actor::Property::NAME).c_str() : "", + TOUCH_POINT_STATE[localVars.hoverEvent->GetPoint(0).GetState()]); } - consumedActor = EmitHoverSignals(hitActor, hoverEventHandle); } - if(hoverEvent->GetPoint(0).GetState() != PointState::MOTION) + if((localVars.primaryPointState == PointState::STARTED) && + (localVars.hoverEvent->GetPointCount() == 1) && + (localVars.consumedActor && GetImplementation(localVars.consumedActor).OnScene())) { - DALI_LOG_RELEASE_INFO("PrimaryHitActor:(%p), id(%d), name(%s), state(%s)\n", primaryHitActor ? reinterpret_cast(&primaryHitActor.GetBaseObject()) : NULL, primaryHitActor ? primaryHitActor.GetProperty(Dali::Actor::Property::ID) : -1, primaryHitActor ? primaryHitActor.GetProperty(Dali::Actor::Property::NAME).c_str() : "", TOUCH_POINT_STATE[hoverEvent->GetPoint(0).GetState()]); - DALI_LOG_RELEASE_INFO("ConsumedActor: (%p), id(%d), name(%s), state(%s)\n", consumedActor ? reinterpret_cast(&consumedActor.GetBaseObject()) : NULL, consumedActor ? consumedActor.GetProperty(Dali::Actor::Property::ID) : -1, consumedActor ? consumedActor.GetProperty(Dali::Actor::Property::NAME).c_str() : "", TOUCH_POINT_STATE[hoverEvent->GetPoint(0).GetState()]); + processor.mHoverStartConsumedActor.SetActor(&GetImplementation(localVars.consumedActor)); } } - if((primaryPointState == PointState::STARTED) && - (hoverEvent->GetPointCount() == 1) && - (consumedActor && GetImplementation(consumedActor).OnScene())) + /** + * Deliver Leave event to last hit or consuming actor if required. + * @param[in/out] processor The hover-event-processor + * @param[in/out] localVars The struct of stack variables used by ProcessHoverEvent + */ + static inline void DeliverLeaveEvent(HoverEventProcessor& processor, ProcessHoverEventVariables& localVars) { - mHoverStartConsumedActor.SetActor(&GetImplementation(consumedActor)); - } - - // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary - // hit actor. Also process the last consumed actor in the same manner. - - Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor()); - Actor* lastConsumedActor(mLastConsumedActor.GetActor()); - if((primaryPointState == PointState::STARTED) || (primaryPointState == PointState::MOTION) || (primaryPointState == PointState::FINISHED) || (primaryPointState == PointState::STATIONARY)) - { - if(mLastRenderTask) + if((localVars.primaryPointState == PointState::STARTED) || + (localVars.primaryPointState == PointState::MOTION) || + (localVars.primaryPointState == PointState::FINISHED) || + (localVars.primaryPointState == PointState::STATIONARY)) { - Dali::Actor leaveEventConsumer; - RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get(); - - if(lastPrimaryHitActor && - lastPrimaryHitActor != primaryHitActor && - lastPrimaryHitActor != consumedActor) + if(processor.mLastRenderTask) { - if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor)) + Dali::Actor leaveEventConsumer; + RenderTask& lastRenderTaskImpl = *processor.mLastRenderTask.Get(); + + if(localVars.lastPrimaryHitActor && + localVars.lastPrimaryHitActor != localVars.primaryHitActor && + localVars.lastPrimaryHitActor != localVars.consumedActor) { - if(isGeometry) + if(localVars.lastPrimaryHitActor->IsHittable() && IsActuallySensitive(localVars.lastPrimaryHitActor)) { - // This is a situation where actors who received a hover event must leave. - // Compare the lastActorList that received the hover event and the CandidateActorList that can receive the new hover event - // If the hover event can no longer be received, Leave is sent. - std::list::reverse_iterator rLastIter = mLastActorLists.rbegin(); - for(; rLastIter != mLastActorLists.rend(); rLastIter++) + if(localVars.isGeometry) { - bool find = false; - std::list::reverse_iterator rCandidateIter = mCandidateActorLists.rbegin(); - for(; rCandidateIter != mCandidateActorLists.rend(); rCandidateIter++) + // This is a situation where actors who received a hover event must leave. + // Compare the lastActorList that received the hover event and the CandidateActorList that can receive the new hover event + // If the hover event can no longer be received, Leave is sent. + std::list::reverse_iterator rLastIter = processor.mLastActorLists.rbegin(); + for(; rLastIter != processor.mLastActorLists.rend(); rLastIter++) { - if(*rCandidateIter == *rLastIter) + bool find = false; + std::list::reverse_iterator rCandidateIter = processor.mCandidateActorLists.rbegin(); + for(; rCandidateIter != processor.mCandidateActorLists.rend(); rCandidateIter++) + { + if(*rCandidateIter == *rLastIter) + { + find = true; + break; + } + } + if(!find) + { + DALI_LOG_RELEASE_INFO("LeaveActor(Hit): (%p) %d %s\n", reinterpret_cast(*rLastIter), (*rLastIter)->GetId(), (*rLastIter)->GetName().data()); + leaveEventConsumer = EmitHoverSignals(*rLastIter, lastRenderTaskImpl, localVars.hoverEvent, PointState::LEAVE, localVars.isGeometry); + } + // If the actor has been consumed, you do not need to proceed. + if(*rLastIter == localVars.lastConsumedActor) { - find = true; break; } } - if(!find) - { - DALI_LOG_RELEASE_INFO("LeaveActor(Hit): (%p) %d %s\n", reinterpret_cast(*rLastIter), (*rLastIter)->GetId(), (*rLastIter)->GetName().data()); - leaveEventConsumer = EmitHoverSignals(*rLastIter, lastRenderTaskImpl, hoverEvent, PointState::LEAVE, isGeometry); - } - // If the actor has been consumed, you do not need to proceed. - if(*rLastIter == lastConsumedActor) - { - break; - } + } + else if(localVars.lastPrimaryHitActor->GetLeaveRequired()) + { + // In the case of isGeometry, it is not propagated but only sent to actors who are not hittable. + DALI_LOG_RELEASE_INFO("LeaveActor(Hit): (%p) %d %s\n", reinterpret_cast(localVars.lastPrimaryHitActor), localVars.lastPrimaryHitActor->GetId(), localVars.lastPrimaryHitActor->GetName().data()); + leaveEventConsumer = EmitHoverSignals(processor.mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, localVars.hoverEvent, PointState::LEAVE, localVars.isGeometry); } } - else if(lastPrimaryHitActor->GetLeaveRequired()) + else if(localVars.primaryPointState != PointState::STARTED) { - // In the case of isGeometry, it is not propagated but only sent to actors who are not hittable. - DALI_LOG_RELEASE_INFO("LeaveActor(Hit): (%p) %d %s\n", reinterpret_cast(lastPrimaryHitActor), lastPrimaryHitActor->GetId(), lastPrimaryHitActor->GetName().data()); - leaveEventConsumer = EmitHoverSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::LEAVE, isGeometry); + // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one. + // An interrupted event is sent to allow some actors to go back to their original state (i.e. Button controls) + DALI_LOG_RELEASE_INFO("InterruptedActor(Hit): (%p) %d %s\n", reinterpret_cast(localVars.lastPrimaryHitActor), localVars.lastPrimaryHitActor->GetId(), localVars.lastPrimaryHitActor->GetName().data()); + leaveEventConsumer = EmitHoverSignals(processor.mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, localVars.hoverEvent, PointState::INTERRUPTED, localVars.isGeometry); } } - else if(primaryPointState != PointState::STARTED) - { - // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one. - // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls) - DALI_LOG_RELEASE_INFO("InterruptedActor(Hit): (%p) %d %s\n", reinterpret_cast(lastPrimaryHitActor), lastPrimaryHitActor->GetId(), lastPrimaryHitActor->GetName().data()); - leaveEventConsumer = EmitHoverSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED, isGeometry); - } - } - // Check if the motion event has been consumed by another actor's listener. In this case, the previously - // consumed actor's listeners may need to be informed (through a leave event). - // Further checks here to ensure we do not signal the same actor twice for the same event. - if(lastConsumedActor && - lastConsumedActor != consumedActor && - lastConsumedActor != lastPrimaryHitActor && - lastConsumedActor != primaryHitActor && - lastConsumedActor != leaveEventConsumer) - { - if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor)) + // Check if the motion event has been consumed by another actor's listener. In this case, the previously + // consumed actor's listeners may need to be informed (through a leave event). + // Further checks here to ensure we do not signal the same actor twice for the same event. + if(localVars.lastConsumedActor && + localVars.lastConsumedActor != localVars.consumedActor && + localVars.lastConsumedActor != localVars.lastPrimaryHitActor && + localVars.lastConsumedActor != localVars.primaryHitActor && + localVars.lastConsumedActor != leaveEventConsumer) { - if(lastConsumedActor->GetLeaveRequired() && !isGeometry) // For geometry, we have already sent leave. There is no need to send leave repeatedly. + if(localVars.lastConsumedActor->IsHittable() && IsActuallySensitive(localVars.lastConsumedActor)) + { + if(localVars.lastConsumedActor->GetLeaveRequired() && !localVars.isGeometry) // For geometry, we have already sent leave. There is no need to send leave repeatedly. + { + DALI_LOG_RELEASE_INFO("LeaveActor(Consume): (%p) %d %s\n", reinterpret_cast(localVars.lastConsumedActor), localVars.lastConsumedActor->GetId(), localVars.lastConsumedActor->GetName().data()); + EmitHoverSignals(localVars.lastConsumedActor, lastRenderTaskImpl, localVars.hoverEvent, PointState::LEAVE, localVars.isGeometry); + } + } + else if(localVars.primaryPointState != PointState::STARTED) { - DALI_LOG_RELEASE_INFO("LeaveActor(Consume): (%p) %d %s\n", reinterpret_cast(lastConsumedActor), lastConsumedActor->GetId(), lastConsumedActor->GetName().data()); - EmitHoverSignals(lastConsumedActor, lastRenderTaskImpl, hoverEvent, PointState::LEAVE, isGeometry); + // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one. + // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls) + DALI_LOG_RELEASE_INFO("InterruptedActor(Consume): (%p) %d %s\n", reinterpret_cast(localVars.lastConsumedActor), localVars.lastConsumedActor->GetId(), localVars.lastConsumedActor->GetName().data()); + EmitHoverSignals(processor.mLastConsumedActor.GetActor(), lastRenderTaskImpl, localVars.hoverEvent, PointState::INTERRUPTED, localVars.isGeometry); } } - else if(primaryPointState != PointState::STARTED) - { - // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one. - // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls) - DALI_LOG_RELEASE_INFO("InterruptedActor(Consume): (%p) %d %s\n", reinterpret_cast(lastConsumedActor), lastConsumedActor->GetId(), lastConsumedActor->GetName().data()); - EmitHoverSignals(mLastConsumedActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED, isGeometry); - } } } } - // 5) If our primary point is a FINISHED event, then the primary point (in multi-touch) will change next - // time so set our last primary actor to NULL. Do the same to the last consumed actor as well. - - if(primaryPointState == PointState::FINISHED) + /** + * Update the processor member appropriately by handling the final up event, and setting the last hit/consumed events etc. + * @param[in/out] processor The hover-event-processor + * @param[in/out] localVars The struct of stack variables used by ProcessHoverEvent + */ + static inline void UpdateMembersWithCurrentHitInformation(HoverEventProcessor& processor, ProcessHoverEventVariables& localVars) { - Clear(); - } - else - { - // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members. - if(primaryHitActor && GetImplementation(primaryHitActor).OnScene()) + // If our primary point is a FINISHED event, then the primary point (in multi-touch) will change next + // time so set our last primary actor to NULL. Do the same to the last consumed actor as well. + + if(localVars.primaryPointState == PointState::FINISHED) + { + processor.Clear(); + } + else { - mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor)); - // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene). - if(consumedActor && GetImplementation(consumedActor).OnScene()) + // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members. + if(localVars.primaryHitActor && GetImplementation(localVars.primaryHitActor).OnScene()) { - mLastConsumedActor.SetActor(&GetImplementation(consumedActor)); + processor.mLastPrimaryHitActor.SetActor(&GetImplementation(localVars.primaryHitActor)); + // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene). + if(localVars.consumedActor && GetImplementation(localVars.consumedActor).OnScene()) + { + processor.mLastConsumedActor.SetActor(&GetImplementation(localVars.consumedActor)); + } + else + { + processor.mLastConsumedActor.SetActor(nullptr); + } + + processor.mLastRenderTask = localVars.currentRenderTask; + processor.mLastActorLists = processor.mCandidateActorLists; } else { - mLastConsumedActor.SetActor(nullptr); + processor.Clear(); } - - mLastRenderTask = currentRenderTask; - mLastActorLists = mCandidateActorLists; - } - else - { - Clear(); } } - // 6) Emit an interrupted event to the hover-started actor if it hasn't consumed the FINISHED. - - if(hoverEvent->GetPointCount() == 1) // Only want the first hover started + /** + * Deliver an interrupted event to the hover started actor as required. + * @param[in/out] processor The hover-event-processor + * @param[in/out] localVars The struct of stack variables used by ProcessHoverEvent + * @param[in] event The hover event that has occurred + */ + static inline void DeliverInterruptedEventToHoverStartedActor(HoverEventProcessor& processor, ProcessHoverEventVariables& localVars, const Integration::HoverEvent& event) { - switch(primaryPointState) + if(localVars.hoverEvent->GetPointCount() == 1 && localVars.primaryPointState == PointState::FINISHED) // Only want the first hover started { - case PointState::FINISHED: + Actor* hoverStartConsumedActor(processor.mHoverStartConsumedActor.GetActor()); + if(hoverStartConsumedActor && + hoverStartConsumedActor != localVars.consumedActor && + hoverStartConsumedActor != localVars.lastPrimaryHitActor && + hoverStartConsumedActor != localVars.lastConsumedActor) { - Actor* hoverStartConsumedActor(mHoverStartConsumedActor.GetActor()); - if(hoverStartConsumedActor && - hoverStartConsumedActor != consumedActor && - hoverStartConsumedActor != lastPrimaryHitActor && - hoverStartConsumedActor != lastConsumedActor) + Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor); + Integration::Point primaryPoint = localVars.hoverEvent->GetPoint(0); + primaryPoint.SetHitActor(hoverStartConsumedActorHandle); + primaryPoint.SetState(PointState::INTERRUPTED); + if(localVars.isGeometry) { - Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor); - Integration::Point primaryPoint = hoverEvent->GetPoint(0); - primaryPoint.SetHitActor(hoverStartConsumedActorHandle); - primaryPoint.SetState(PointState::INTERRUPTED); - if(isGeometry) - { - std::list actorLists; - actorLists.push_back(hoverStartConsumedActor); - GeoAllocAndEmitHoverSignals(actorLists, event.time, primaryPoint); - } - else - { - AllocAndEmitHoverSignals(event.time, hoverStartConsumedActorHandle, primaryPoint); - } - - // Restore hover-event to original state - primaryPoint.SetHitActor(primaryHitActor); - primaryPoint.SetState(primaryPointState); + std::list actorLists; + actorLists.push_back(hoverStartConsumedActor); + GeoAllocAndEmitHoverSignals(actorLists, event.time, primaryPoint); + } + else + { + AllocAndEmitHoverSignals(event.time, hoverStartConsumedActorHandle, primaryPoint); } - mHoverStartConsumedActor.SetActor(nullptr); + // Restore hover-event to original state + primaryPoint.SetHitActor(localVars.primaryHitActor); + primaryPoint.SetState(localVars.primaryPointState); } - // No break, Fallthrough - case PointState::STARTED: - case PointState::MOTION: - case PointState::LEAVE: - case PointState::STATIONARY: - case PointState::INTERRUPTED: - { - // Ignore - break; - } + processor.mHoverStartConsumedActor.SetActor(nullptr); + } + } +}; + +HoverEventProcessor::HoverEventProcessor(Scene& scene) +: mScene(scene), + mLastPrimaryHitActor(MakeCallback(this, &HoverEventProcessor::OnObservedActorDisconnected)) +{ + DALI_LOG_TRACE_METHOD(gLogFilter); +} + +HoverEventProcessor::~HoverEventProcessor() +{ + DALI_LOG_TRACE_METHOD(gLogFilter); +} + +void HoverEventProcessor::SendInterruptedHoverEvent(Dali::Internal::Actor* actor) +{ + if(actor && + (mLastPrimaryHitActor.GetActor() == actor || mLastConsumedActor.GetActor() == actor)) + { + Integration::Point point; + point.SetState(PointState::INTERRUPTED); + point.SetHitActor(Dali::Actor(actor)); + if(mScene.IsGeometryHittestEnabled()) + { + std::list actorLists; + actorLists.push_back(actor); + GeoAllocAndEmitHoverSignals(actorLists, 0, point); + } + else + { + AllocAndEmitHoverSignals(GetMilliSeconds(), point.GetHitActor(), point); } + Clear(); + } +} + +void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event) +{ + DALI_LOG_TRACE_METHOD(gLogFilter); + DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty HoverEvent sent from Integration\n"); + + PRINT_HIERARCHY(gLogFilter); + + DALI_TRACE_SCOPE(gTraceFilter, "DALI_PROCESS_HOVER_EVENT"); + + ProcessHoverEventVariables localVars(mScene.IsGeometryHittestEnabled()); + + // Copy so we can add the results of a hit-test. + localVars.hoverEvent = new HoverEvent(event.time); + + // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this + // and emit the stage signal as well. + if(event.points[0].GetState() == PointState::INTERRUPTED) + { + Impl::EmitInterruptedEvent(*this, localVars.isGeometry, event); + return; // No need for hit testing } + + // 2) Hit Testing. + DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n"); + DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount()); + localVars.hoverEventHandle = Dali::HoverEvent(localVars.hoverEvent.Get()); + Impl::HitTest(*this, localVars, event); + + // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached. + localVars.primaryPoint = &localVars.hoverEvent->GetPoint(0); + localVars.primaryHitActor = localVars.primaryPoint->GetHitActor(); + localVars.primaryPointState = localVars.primaryPoint->GetState(); + Impl::DeliverEventsToActorAndParents(*this, localVars); + + // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary + // hit actor. Also process the last consumed actor in the same manner. + localVars.lastPrimaryHitActor = mLastPrimaryHitActor.GetActor(); + localVars.lastConsumedActor = mLastConsumedActor.GetActor(); + Impl::DeliverLeaveEvent(*this, localVars); + + // 5) Update the processor member appropriately. + Impl::UpdateMembersWithCurrentHitInformation(*this, localVars); + + // 6) Emit an interrupted event to the hover-started actor if it hasn't consumed the FINISHED. + Impl::DeliverInterruptedEventToHoverStartedActor(*this, localVars, event); } void HoverEventProcessor::Clear() @@ -636,6 +709,4 @@ void HoverEventProcessor::OnObservedActorDisconnected(Dali::Internal::Actor* act SendInterruptedHoverEvent(actor); } -} // namespace Internal - -} // namespace Dali +} // namespace Dali::Internal diff --git a/dali/internal/event/events/hover-event-processor.h b/dali/internal/event/events/hover-event-processor.h index 5ec5002..45b48fb 100644 --- a/dali/internal/event/events/hover-event-processor.h +++ b/dali/internal/event/events/hover-event-processor.h @@ -2,7 +2,7 @@ #define DALI_INTERNAL_HOVER_EVENT_PROCESSOR_H /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,13 +72,13 @@ public: */ void SendInterruptedHoverEvent(Dali::Internal::Actor* actor); -private: - // Undefined - HoverEventProcessor(const HoverEventProcessor&); - - // Undefined - HoverEventProcessor& operator=(const HoverEventProcessor& rhs); + // Movable but not copyable + HoverEventProcessor(const HoverEventProcessor&) = delete; + HoverEventProcessor(HoverEventProcessor&&) = default; + HoverEventProcessor& operator=(const HoverEventProcessor&) = delete; + HoverEventProcessor& operator=(HoverEventProcessor&&) = default; +private: /** * Clears the value. */ @@ -90,13 +90,15 @@ private: */ void OnObservedActorDisconnected(Dali::Internal::Actor* actor); - Scene& mScene; ///< Reference to the scene - ActorObserver mLastPrimaryHitActor; ///< Stores the last primary point hit actor - ActorObserver mLastConsumedActor; ///< Stores the last consumed actor - ActorObserver mHoverStartConsumedActor; ///< Stores the hover-start consumed actor - RenderTaskPtr mLastRenderTask; ///< The RenderTask used for the last hit actor + Scene& mScene; ///< Reference to the scene + ActorObserver mLastPrimaryHitActor; ///< Stores the last primary point hit actor + ActorObserver mLastConsumedActor; ///< Stores the last consumed actor + ActorObserver mHoverStartConsumedActor; ///< Stores the hover-start consumed actor + RenderTaskPtr mLastRenderTask; ///< The RenderTask used for the last hit actor std::list mCandidateActorLists; ///< Stores a list of actors that can be touched, from leaf actor to root. - std::list mLastActorLists; ///< Stores a list of actors that can be touched, from leaf actor to root. + std::list mLastActorLists; ///< Stores a list of actors that can be touched, from leaf actor to root. + + struct Impl; }; } // namespace Internal -- 2.7.4