2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/event/events/hover-event-processor.h>
21 #if defined(DEBUG_ENABLED)
26 #include <dali/integration-api/debug.h>
27 #include <dali/integration-api/events/hover-event-integ.h>
28 #include <dali/integration-api/trace.h>
29 #include <dali/internal/event/actors/actor-impl.h>
30 #include <dali/internal/event/actors/layer-impl.h>
31 #include <dali/internal/event/common/scene-impl.h>
32 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
33 #include <dali/internal/event/events/hover-event-impl.h>
34 #include <dali/internal/event/events/multi-point-event-util.h>
35 #include <dali/internal/event/render-tasks/render-task-impl.h>
36 #include <dali/public-api/math/vector2.h>
44 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_PERFORMANCE_MARKER, false);
45 #if defined(DEBUG_ENABLED)
46 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_HOVER_PROCESSOR");
48 const char* TOUCH_POINT_STATE[PointState::INTERRUPTED + 1] =
58 #endif // defined(DEBUG_ENABLED)
61 * Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
63 Dali::Actor EmitHoverSignals(Dali::Actor actor, const Dali::HoverEvent& event)
65 Dali::Actor consumedActor;
69 Dali::Actor oldParent(actor.GetParent());
71 Actor& actorImpl(GetImplementation(actor));
75 // Only emit the signal if the actor's hover signal has connections (or derived actor implementation requires hover).
76 if(actorImpl.GetHoverRequired())
78 DALI_TRACE_SCOPE(gTraceFilter, "DALI_EMIT_HOVER_EVENT_SIGNAL");
79 consumed = actorImpl.EmitHoverEventSignal(event);
84 // One of this actor's listeners has consumed the event so set this actor as the consumed actor.
85 consumedActor = Dali::Actor(&actorImpl);
89 // The actor may have been removed/reparented during the signal callbacks.
90 Dali::Actor parent = actor.GetParent();
93 (parent == oldParent))
95 // One of the actor's parents may consumed the event and they should be set as the consumed actor.
96 consumedActor = EmitHoverSignals(parent, event);
101 return consumedActor;
104 Dali::Actor AllocAndEmitHoverSignals(unsigned long time, Dali::Actor actor, const Integration::Point& point)
106 HoverEventPtr hoverEvent(new HoverEvent(time));
107 Dali::HoverEvent hoverEventHandle(hoverEvent.Get());
109 hoverEvent->AddPoint(point);
111 return EmitHoverSignals(actor, hoverEventHandle);
115 * Changes the state of the primary point to leave and emits the hover signals
117 Dali::Actor EmitHoverSignals(Actor* actor, RenderTask& renderTask, const HoverEventPtr& originalEvent, PointState::Type state)
119 HoverEventPtr hoverEvent = HoverEvent::Clone(*originalEvent.Get());
121 DALI_ASSERT_DEBUG(NULL != actor && "NULL actor pointer");
124 Integration::Point& primaryPoint = hoverEvent->GetPoint(0);
126 const Vector2& screenPosition = primaryPoint.GetScreenPosition();
127 Vector2 localPosition;
128 actor->ScreenToLocal(renderTask, localPosition.x, localPosition.y, screenPosition.x, screenPosition.y);
130 primaryPoint.SetLocalPosition(localPosition);
131 primaryPoint.SetHitActor(Dali::Actor(actor));
132 primaryPoint.SetState(state);
135 return EmitHoverSignals(Dali::Actor(actor), Dali::HoverEvent(hoverEvent.Get()));
139 * Used in the hit-test algorithm to check whether the actor is hoverable.
141 struct ActorHoverableCheck : public HitTestAlgorithm::HitTestInterface
143 bool IsActorHittable(Actor* actor) override
145 return actor->GetHoverRequired() && // Does the Application or derived actor type require a hover event?
146 actor->IsHittable(); // Is actor sensitive, visible and on the scene?
149 bool DescendActorHierarchy(Actor* actor) override
151 return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible.
152 actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either.
155 bool DoesLayerConsumeHit(Layer* layer) override
157 return layer->IsHoverConsumed();
160 bool ActorRequiresHitResultCheck(Actor* actor, Integration::Point point, Vector2 hitPointLocal, uint32_t timeStamp) override
162 // Hover event is always hit.
167 } // unnamed namespace
169 HoverEventProcessor::HoverEventProcessor(Scene& scene)
172 DALI_LOG_TRACE_METHOD(gLogFilter);
175 HoverEventProcessor::~HoverEventProcessor()
177 DALI_LOG_TRACE_METHOD(gLogFilter);
180 void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event)
182 DALI_LOG_TRACE_METHOD(gLogFilter);
183 DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty HoverEvent sent from Integration\n");
185 PointState::Type state = static_cast<PointState::Type>(event.points[0].GetState());
187 PRINT_HIERARCHY(gLogFilter);
189 DALI_TRACE_SCOPE(gTraceFilter, "DALI_PROCESS_HOVER_EVENT");
191 // Copy so we can add the results of a hit-test.
192 HoverEventPtr hoverEvent(new HoverEvent(event.time));
194 // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this
195 // and emit the stage signal as well.
197 if(state == PointState::INTERRUPTED)
199 Dali::Actor consumingActor;
200 Integration::Point currentPoint(event.points[0]);
202 Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
203 if(lastPrimaryHitActor)
205 Dali::Actor lastPrimaryHitActorHandle(lastPrimaryHitActor);
206 currentPoint.SetHitActor(lastPrimaryHitActorHandle);
207 consumingActor = AllocAndEmitHoverSignals(event.time, lastPrimaryHitActorHandle, currentPoint);
210 // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed).
211 Actor* lastConsumedActor(mLastConsumedActor.GetActor());
212 if(lastConsumedActor &&
213 lastConsumedActor != lastPrimaryHitActor &&
214 lastConsumedActor != consumingActor)
216 Dali::Actor lastConsumedActorHandle(lastConsumedActor);
217 currentPoint.SetHitActor(lastConsumedActorHandle);
218 AllocAndEmitHoverSignals(event.time, lastConsumedActorHandle, currentPoint);
221 // Tell the hover-start consuming actor as well, if required
222 Actor* hoverStartConsumedActor(mHoverStartConsumedActor.GetActor());
223 if(hoverStartConsumedActor &&
224 hoverStartConsumedActor != lastPrimaryHitActor &&
225 hoverStartConsumedActor != lastConsumedActor &&
226 hoverStartConsumedActor != consumingActor)
228 Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor);
229 currentPoint.SetHitActor(hoverStartConsumedActorHandle);
230 AllocAndEmitHoverSignals(event.time, hoverStartConsumedActorHandle, currentPoint);
233 mLastPrimaryHitActor.SetActor(nullptr);
234 mLastConsumedActor.SetActor(nullptr);
235 mHoverStartConsumedActor.SetActor(nullptr);
236 mLastRenderTask.Reset();
238 return; // No need for hit testing
243 Dali::HoverEvent hoverEventHandle(hoverEvent.Get());
245 DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n");
246 DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount());
248 RenderTaskPtr currentRenderTask;
249 bool firstPointParsed = false;
251 for(auto&& currentPoint : event.points)
253 HitTestAlgorithm::Results hitTestResults;
254 ActorHoverableCheck actorHoverableCheck;
255 HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults, actorHoverableCheck);
257 Integration::Point newPoint(currentPoint);
258 newPoint.SetHitActor(hitTestResults.actor);
259 newPoint.SetLocalPosition(hitTestResults.actorCoordinates);
261 hoverEvent->AddPoint(newPoint);
263 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);
265 // Only set the currentRenderTask for the primary hit actor.
266 if(!firstPointParsed)
268 firstPointParsed = true;
269 currentRenderTask = hitTestResults.renderTask;
273 // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
275 // Emit the touch signal
276 Dali::Actor consumedActor;
277 if(currentRenderTask)
279 Dali::Actor hitActor = hoverEvent->GetHitActor(0);
280 // If the actor is hit first, the hover is started.
282 mLastPrimaryHitActor.GetActor() != hitActor &&
283 state == PointState::MOTION)
285 Actor* hitActorImpl = &GetImplementation(hitActor);
286 if(hitActorImpl->GetLeaveRequired())
288 hoverEvent->GetPoint(0).SetState(PointState::STARTED);
291 consumedActor = EmitHoverSignals(hitActor, hoverEventHandle);
294 Integration::Point primaryPoint = hoverEvent->GetPoint(0);
295 Dali::Actor primaryHitActor = primaryPoint.GetHitActor();
296 PointState::Type primaryPointState = primaryPoint.GetState();
298 DALI_LOG_INFO(gLogFilter, Debug::Concise, "PrimaryHitActor: (%p) %s\n", primaryHitActor ? reinterpret_cast<void*>(&primaryHitActor.GetBaseObject()) : NULL, primaryHitActor ? primaryHitActor.GetProperty<std::string>(Dali::Actor::Property::NAME).c_str() : "");
299 DALI_LOG_INFO(gLogFilter, Debug::Concise, "ConsumedActor: (%p) %s\n", consumedActor ? reinterpret_cast<void*>(&consumedActor.GetBaseObject()) : NULL, consumedActor ? consumedActor.GetProperty<std::string>(Dali::Actor::Property::NAME).c_str() : "");
301 if((primaryPointState == PointState::STARTED) &&
302 (hoverEvent->GetPointCount() == 1) &&
303 (consumedActor && GetImplementation(consumedActor).OnScene()))
305 mHoverStartConsumedActor.SetActor(&GetImplementation(consumedActor));
308 // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
309 // hit actor. Also process the last consumed actor in the same manner.
311 Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
312 Actor* lastConsumedActor(mLastConsumedActor.GetActor());
313 if((primaryPointState == PointState::STARTED) || (primaryPointState == PointState::MOTION) || (primaryPointState == PointState::FINISHED) || (primaryPointState == PointState::STATIONARY))
317 Dali::Actor leaveEventConsumer;
318 RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
320 if(lastPrimaryHitActor &&
321 lastPrimaryHitActor != primaryHitActor &&
322 lastPrimaryHitActor != consumedActor)
324 if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor))
326 if(lastPrimaryHitActor->GetLeaveRequired())
328 DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Hit): (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
329 leaveEventConsumer = EmitHoverSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::LEAVE);
332 else if(primaryPointState != PointState::STARTED)
334 // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
335 // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
336 DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Hit): (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
337 leaveEventConsumer = EmitHoverSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED);
341 // Check if the motion event has been consumed by another actor's listener. In this case, the previously
342 // consumed actor's listeners may need to be informed (through a leave event).
343 // Further checks here to ensure we do not signal the same actor twice for the same event.
344 if(lastConsumedActor &&
345 lastConsumedActor != consumedActor &&
346 lastConsumedActor != lastPrimaryHitActor &&
347 lastConsumedActor != primaryHitActor &&
348 lastConsumedActor != leaveEventConsumer)
350 if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor))
352 if(lastConsumedActor->GetLeaveRequired())
354 DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
355 EmitHoverSignals(lastConsumedActor, lastRenderTaskImpl, hoverEvent, PointState::LEAVE);
358 else if(primaryPointState != PointState::STARTED)
360 // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
361 // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
362 DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
363 EmitHoverSignals(mLastConsumedActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED);
369 // 5) If our primary point is a FINISHED event, then the primary point (in multi-touch) will change next
370 // time so set our last primary actor to NULL. Do the same to the last consumed actor as well.
372 if(primaryPointState == PointState::FINISHED)
374 mLastPrimaryHitActor.SetActor(nullptr);
375 mLastConsumedActor.SetActor(nullptr);
376 mLastRenderTask.Reset();
380 // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members.
381 if(primaryHitActor && GetImplementation(primaryHitActor).OnScene())
383 mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor));
385 // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene).
386 if(consumedActor && GetImplementation(consumedActor).OnScene())
388 mLastConsumedActor.SetActor(&GetImplementation(consumedActor));
392 mLastConsumedActor.SetActor(nullptr);
395 mLastRenderTask = currentRenderTask;
399 mLastPrimaryHitActor.SetActor(nullptr);
400 mLastConsumedActor.SetActor(nullptr);
401 mLastRenderTask.Reset();
405 // 6) Emit an interrupted event to the hover-started actor if it hasn't consumed the FINISHED.
407 if(hoverEvent->GetPointCount() == 1) // Only want the first hover started
409 switch(primaryPointState)
411 case PointState::FINISHED:
413 Actor* hoverStartConsumedActor(mHoverStartConsumedActor.GetActor());
414 if(hoverStartConsumedActor &&
415 hoverStartConsumedActor != consumedActor &&
416 hoverStartConsumedActor != lastPrimaryHitActor &&
417 hoverStartConsumedActor != lastConsumedActor)
419 Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor);
420 Integration::Point primaryPoint = hoverEvent->GetPoint(0);
421 primaryPoint.SetHitActor(hoverStartConsumedActorHandle);
422 primaryPoint.SetState(PointState::INTERRUPTED);
423 AllocAndEmitHoverSignals(event.time, hoverStartConsumedActorHandle, primaryPoint);
425 // Restore hover-event to original state
426 primaryPoint.SetHitActor(primaryHitActor);
427 primaryPoint.SetState(primaryPointState);
430 mHoverStartConsumedActor.SetActor(nullptr);
432 // No break, Fallthrough
434 case PointState::STARTED:
435 case PointState::MOTION:
436 case PointState::LEAVE:
437 case PointState::STATIONARY:
438 case PointState::INTERRUPTED:
447 } // namespace Internal