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/internal/event/actors/actor-impl.h>
29 #include <dali/internal/event/actors/layer-impl.h>
30 #include <dali/internal/event/common/scene-impl.h>
31 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
32 #include <dali/internal/event/events/hover-event-impl.h>
33 #include <dali/internal/event/events/multi-point-event-util.h>
34 #include <dali/internal/event/render-tasks/render-task-impl.h>
35 #include <dali/public-api/math/vector2.h>
43 #if defined(DEBUG_ENABLED)
44 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_HOVER_PROCESSOR");
46 const char* TOUCH_POINT_STATE[PointState::INTERRUPTED + 1] =
56 #endif // defined(DEBUG_ENABLED)
59 * Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
61 Dali::Actor EmitHoverSignals(Dali::Actor actor, const Dali::HoverEvent& event)
63 Dali::Actor consumedActor;
67 Dali::Actor oldParent(actor.GetParent());
69 Actor& actorImpl(GetImplementation(actor));
73 // Only emit the signal if the actor's hover signal has connections (or derived actor implementation requires hover).
74 if(actorImpl.GetHoverRequired())
76 consumed = actorImpl.EmitHoverEventSignal(event);
81 // One of this actor's listeners has consumed the event so set this actor as the consumed actor.
82 consumedActor = Dali::Actor(&actorImpl);
86 // The actor may have been removed/reparented during the signal callbacks.
87 Dali::Actor parent = actor.GetParent();
90 (parent == oldParent))
92 // One of the actor's parents may consumed the event and they should be set as the consumed actor.
93 consumedActor = EmitHoverSignals(parent, event);
101 Dali::Actor AllocAndEmitHoverSignals(unsigned long time, Dali::Actor actor, const Integration::Point& point)
103 HoverEventPtr hoverEvent(new HoverEvent(time));
104 Dali::HoverEvent hoverEventHandle(hoverEvent.Get());
106 hoverEvent->AddPoint(point);
108 return EmitHoverSignals(actor, hoverEventHandle);
112 * Changes the state of the primary point to leave and emits the hover signals
114 Dali::Actor EmitHoverSignals(Actor* actor, RenderTask& renderTask, const HoverEventPtr& originalEvent, PointState::Type state)
116 HoverEventPtr hoverEvent = HoverEvent::Clone(*originalEvent.Get());
118 DALI_ASSERT_DEBUG(NULL != actor && "NULL actor pointer");
121 Integration::Point& primaryPoint = hoverEvent->GetPoint(0);
123 const Vector2& screenPosition = primaryPoint.GetScreenPosition();
124 Vector2 localPosition;
125 actor->ScreenToLocal(renderTask, localPosition.x, localPosition.y, screenPosition.x, screenPosition.y);
127 primaryPoint.SetLocalPosition(localPosition);
128 primaryPoint.SetHitActor(Dali::Actor(actor));
129 primaryPoint.SetState(state);
132 return EmitHoverSignals(Dali::Actor(actor), Dali::HoverEvent(hoverEvent.Get()));
136 * Used in the hit-test algorithm to check whether the actor is hoverable.
138 struct ActorHoverableCheck : public HitTestAlgorithm::HitTestInterface
140 bool IsActorHittable(Actor* actor) override
142 return actor->GetHoverRequired() && // Does the Application or derived actor type require a hover event?
143 actor->IsHittable(); // Is actor sensitive, visible and on the scene?
146 bool DescendActorHierarchy(Actor* actor) override
148 return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible.
149 actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either.
152 bool DoesLayerConsumeHit(Layer* layer) override
154 return layer->IsHoverConsumed();
157 bool ActorRequiresHitResultCheck(Actor* actor, Integration::Point point, Vector2 hitPointLocal, uint32_t timeStamp) override
159 // Hover event is always hit.
164 } // unnamed namespace
166 HoverEventProcessor::HoverEventProcessor(Scene& scene)
169 DALI_LOG_TRACE_METHOD(gLogFilter);
172 HoverEventProcessor::~HoverEventProcessor()
174 DALI_LOG_TRACE_METHOD(gLogFilter);
177 void HoverEventProcessor::ProcessHoverEvent(const Integration::HoverEvent& event)
179 DALI_LOG_TRACE_METHOD(gLogFilter);
180 DALI_ASSERT_ALWAYS(!event.points.empty() && "Empty HoverEvent sent from Integration\n");
182 PointState::Type state = static_cast<PointState::Type>(event.points[0].GetState());
184 PRINT_HIERARCHY(gLogFilter);
186 // Copy so we can add the results of a hit-test.
187 HoverEventPtr hoverEvent(new HoverEvent(event.time));
189 // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this
190 // and emit the stage signal as well.
192 if(state == PointState::INTERRUPTED)
194 Dali::Actor consumingActor;
195 Integration::Point currentPoint(event.points[0]);
197 Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
198 if(lastPrimaryHitActor)
200 Dali::Actor lastPrimaryHitActorHandle(lastPrimaryHitActor);
201 currentPoint.SetHitActor(lastPrimaryHitActorHandle);
202 consumingActor = AllocAndEmitHoverSignals(event.time, lastPrimaryHitActorHandle, currentPoint);
205 // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed).
206 Actor* lastConsumedActor(mLastConsumedActor.GetActor());
207 if(lastConsumedActor &&
208 lastConsumedActor != lastPrimaryHitActor &&
209 lastConsumedActor != consumingActor)
211 Dali::Actor lastConsumedActorHandle(lastConsumedActor);
212 currentPoint.SetHitActor(lastConsumedActorHandle);
213 AllocAndEmitHoverSignals(event.time, lastConsumedActorHandle, currentPoint);
216 // Tell the hover-start consuming actor as well, if required
217 Actor* hoverStartConsumedActor(mHoverStartConsumedActor.GetActor());
218 if(hoverStartConsumedActor &&
219 hoverStartConsumedActor != lastPrimaryHitActor &&
220 hoverStartConsumedActor != lastConsumedActor &&
221 hoverStartConsumedActor != consumingActor)
223 Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor);
224 currentPoint.SetHitActor(hoverStartConsumedActorHandle);
225 AllocAndEmitHoverSignals(event.time, hoverStartConsumedActorHandle, currentPoint);
228 mLastPrimaryHitActor.SetActor(nullptr);
229 mLastConsumedActor.SetActor(nullptr);
230 mHoverStartConsumedActor.SetActor(nullptr);
231 mLastRenderTask.Reset();
233 return; // No need for hit testing
238 Dali::HoverEvent hoverEventHandle(hoverEvent.Get());
240 DALI_LOG_INFO(gLogFilter, Debug::Concise, "\n");
241 DALI_LOG_INFO(gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount());
243 RenderTaskPtr currentRenderTask;
244 bool firstPointParsed = false;
246 for(auto&& currentPoint : event.points)
248 HitTestAlgorithm::Results hitTestResults;
249 ActorHoverableCheck actorHoverableCheck;
250 HitTestAlgorithm::HitTest(mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults, actorHoverableCheck);
252 Integration::Point newPoint(currentPoint);
253 newPoint.SetHitActor(hitTestResults.actor);
254 newPoint.SetLocalPosition(hitTestResults.actorCoordinates);
256 hoverEvent->AddPoint(newPoint);
258 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);
260 // Only set the currentRenderTask for the primary hit actor.
261 if(!firstPointParsed)
263 firstPointParsed = true;
264 currentRenderTask = hitTestResults.renderTask;
268 // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
270 // Emit the touch signal
271 Dali::Actor consumedActor;
272 if(currentRenderTask)
274 Dali::Actor hitActor = hoverEvent->GetHitActor(0);
275 // If the actor is hit first, the hover is started.
277 mLastPrimaryHitActor.GetActor() != hitActor &&
278 state == PointState::MOTION)
280 Actor* hitActorImpl = &GetImplementation(hitActor);
281 if(hitActorImpl->GetLeaveRequired())
283 hoverEvent->GetPoint(0).SetState(PointState::STARTED);
286 consumedActor = EmitHoverSignals(hitActor, hoverEventHandle);
289 Integration::Point primaryPoint = hoverEvent->GetPoint(0);
290 Dali::Actor primaryHitActor = primaryPoint.GetHitActor();
291 PointState::Type primaryPointState = primaryPoint.GetState();
293 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() : "");
294 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() : "");
296 if((primaryPointState == PointState::STARTED) &&
297 (hoverEvent->GetPointCount() == 1) &&
298 (consumedActor && GetImplementation(consumedActor).OnScene()))
300 mHoverStartConsumedActor.SetActor(&GetImplementation(consumedActor));
303 // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
304 // hit actor. Also process the last consumed actor in the same manner.
306 Actor* lastPrimaryHitActor(mLastPrimaryHitActor.GetActor());
307 Actor* lastConsumedActor(mLastConsumedActor.GetActor());
308 if((primaryPointState == PointState::STARTED) || (primaryPointState == PointState::MOTION) || (primaryPointState == PointState::FINISHED) || (primaryPointState == PointState::STATIONARY))
312 Dali::Actor leaveEventConsumer;
313 RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
315 if(lastPrimaryHitActor &&
316 lastPrimaryHitActor != primaryHitActor &&
317 lastPrimaryHitActor != consumedActor)
319 if(lastPrimaryHitActor->IsHittable() && IsActuallySensitive(lastPrimaryHitActor))
321 if(lastPrimaryHitActor->GetLeaveRequired())
323 DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Hit): (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
324 leaveEventConsumer = EmitHoverSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::LEAVE);
327 else if(primaryPointState != PointState::STARTED)
329 // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
330 // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
331 DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Hit): (%p) %s\n", reinterpret_cast<void*>(lastPrimaryHitActor), lastPrimaryHitActor->GetName().data());
332 leaveEventConsumer = EmitHoverSignals(mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED);
336 // Check if the motion event has been consumed by another actor's listener. In this case, the previously
337 // consumed actor's listeners may need to be informed (through a leave event).
338 // Further checks here to ensure we do not signal the same actor twice for the same event.
339 if(lastConsumedActor &&
340 lastConsumedActor != consumedActor &&
341 lastConsumedActor != lastPrimaryHitActor &&
342 lastConsumedActor != primaryHitActor &&
343 lastConsumedActor != leaveEventConsumer)
345 if(lastConsumedActor->IsHittable() && IsActuallySensitive(lastConsumedActor))
347 if(lastConsumedActor->GetLeaveRequired())
349 DALI_LOG_INFO(gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
350 EmitHoverSignals(lastConsumedActor, lastRenderTaskImpl, hoverEvent, PointState::LEAVE);
353 else if(primaryPointState != PointState::STARTED)
355 // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
356 // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
357 DALI_LOG_INFO(gLogFilter, Debug::Concise, "InterruptedActor(Consume): (%p) %s\n", reinterpret_cast<void*>(lastConsumedActor), lastConsumedActor->GetName().data());
358 EmitHoverSignals(mLastConsumedActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED);
364 // 5) If our primary point is a FINISHED event, then the primary point (in multi-touch) will change next
365 // time so set our last primary actor to NULL. Do the same to the last consumed actor as well.
367 if(primaryPointState == PointState::FINISHED)
369 mLastPrimaryHitActor.SetActor(nullptr);
370 mLastConsumedActor.SetActor(nullptr);
371 mLastRenderTask.Reset();
375 // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members.
376 if(primaryHitActor && GetImplementation(primaryHitActor).OnScene())
378 mLastPrimaryHitActor.SetActor(&GetImplementation(primaryHitActor));
380 // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene).
381 if(consumedActor && GetImplementation(consumedActor).OnScene())
383 mLastConsumedActor.SetActor(&GetImplementation(consumedActor));
387 mLastConsumedActor.SetActor(nullptr);
390 mLastRenderTask = currentRenderTask;
394 mLastPrimaryHitActor.SetActor(nullptr);
395 mLastConsumedActor.SetActor(nullptr);
396 mLastRenderTask.Reset();
400 // 6) Emit an interrupted event to the hover-started actor if it hasn't consumed the FINISHED.
402 if(hoverEvent->GetPointCount() == 1) // Only want the first hover started
404 switch(primaryPointState)
406 case PointState::FINISHED:
408 Actor* hoverStartConsumedActor(mHoverStartConsumedActor.GetActor());
409 if(hoverStartConsumedActor &&
410 hoverStartConsumedActor != consumedActor &&
411 hoverStartConsumedActor != lastPrimaryHitActor &&
412 hoverStartConsumedActor != lastConsumedActor)
414 Dali::Actor hoverStartConsumedActorHandle(hoverStartConsumedActor);
415 Integration::Point primaryPoint = hoverEvent->GetPoint(0);
416 primaryPoint.SetHitActor(hoverStartConsumedActorHandle);
417 primaryPoint.SetState(PointState::INTERRUPTED);
418 AllocAndEmitHoverSignals(event.time, hoverStartConsumedActorHandle, primaryPoint);
420 // Restore hover-event to original state
421 primaryPoint.SetHitActor(primaryHitActor);
422 primaryPoint.SetState(primaryPointState);
425 mHoverStartConsumedActor.SetActor(nullptr);
427 // No break, Fallthrough
429 case PointState::STARTED:
430 case PointState::MOTION:
431 case PointState::LEAVE:
432 case PointState::STATIONARY:
433 case PointState::INTERRUPTED:
442 } // namespace Internal