Reduce CC of HoverEventProcessor::ProcessHoverEvent 85/304585/8
authorAdeel Kazmi <adeel.kazmi@samsung.com>
Sat, 20 Jan 2024 13:01:38 +0000 (13:01 +0000)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Wed, 24 Jan 2024 22:02:25 +0000 (22:02 +0000)
Change-Id: Iec40f015ab571f4bdfe0b3b049c4770a5a4f328d

automated-tests/src/dali/utc-Dali-GeoHoverProcessing.cpp
automated-tests/src/dali/utc-Dali-GeoTouchProcessing.cpp
automated-tests/src/dali/utc-Dali-HoverProcessing.cpp
dali/internal/event/events/hover-event-processor.cpp
dali/internal/event/events/hover-event-processor.h

index 625788e..556ec78 100644 (file)
@@ -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
index 43944be..1cb4cfc 100644 (file)
@@ -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
index 720f7ab..e912937 100644 (file)
@@ -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;
+}
index 0aea6c2..e5cd845 100644 (file)
@@ -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 <dali/internal/event/render-tasks/render-task-impl.h>
 #include <dali/public-api/math/vector2.h>
 
-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<Dali::Internal::Actor*>& actorLists, c
   Dali::Actor consumedActor;
 
   std::list<Dali::Internal::Actor*>::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<Dali::Internal::Actor*>& 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<Dali::Internal::Actor*>& 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<Dali::Internal::Actor*>& 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<Dali::Internal::Actor*> 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<PointState::Type>(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<void*>(&hitTestResults.actor.GetBaseObject()) : NULL), (hitTestResults.actor ? hitTestResults.actor.GetProperty<std::string>(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<void*>(&hitTestResults.actor.GetBaseObject()) : NULL),
+                    (hitTestResults.actor ? hitTestResults.actor.GetProperty<std::string>(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<void*>(&localVars.primaryHitActor.GetBaseObject()) : NULL,
+                              localVars.primaryHitActor ? localVars.primaryHitActor.GetProperty<int32_t>(Dali::Actor::Property::ID) : -1,
+                              localVars.primaryHitActor ? localVars.primaryHitActor.GetProperty<std::string>(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<void*>(&localVars.consumedActor.GetBaseObject()) : NULL,
+                              localVars.consumedActor ? localVars.consumedActor.GetProperty<int32_t>(Dali::Actor::Property::ID) : -1,
+                              localVars.consumedActor ? localVars.consumedActor.GetProperty<std::string>(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<void*>(&primaryHitActor.GetBaseObject()) : NULL, primaryHitActor ? primaryHitActor.GetProperty<int32_t>(Dali::Actor::Property::ID) : -1, primaryHitActor ? primaryHitActor.GetProperty<std::string>(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<void*>(&consumedActor.GetBaseObject()) : NULL, consumedActor ? consumedActor.GetProperty<int32_t>(Dali::Actor::Property::ID) : -1, consumedActor ? consumedActor.GetProperty<std::string>(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<Dali::Internal::Actor*>::reverse_iterator rLastIter = mLastActorLists.rbegin();
-            for(; rLastIter != mLastActorLists.rend(); rLastIter++)
+            if(localVars.isGeometry)
             {
-              bool find = false;
-              std::list<Dali::Internal::Actor*>::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<Dali::Internal::Actor*>::reverse_iterator rLastIter = processor.mLastActorLists.rbegin();
+              for(; rLastIter != processor.mLastActorLists.rend(); rLastIter++)
               {
-                if(*rCandidateIter == *rLastIter)
+                bool                                                find           = false;
+                std::list<Dali::Internal::Actor*>::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<void*>(*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<void*>(*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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<Dali::Internal::Actor*> 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<Dali::Internal::Actor*> 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<Dali::Internal::Actor*> 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
index 5ec5002..45b48fb 100644 (file)
@@ -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<Dali::Internal::Actor*> mCandidateActorLists;     ///< Stores a list of actors that can be touched, from leaf actor to root.
-  std::list<Dali::Internal::Actor*> mLastActorLists;     ///< Stores a list of actors that can be touched, from leaf actor to root.
+  std::list<Dali::Internal::Actor*> mLastActorLists;          ///< Stores a list of actors that can be touched, from leaf actor to root.
+
+  struct Impl;
 };
 
 } // namespace Internal