2 * Copyright (c) 2020 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/public-api/math/vector2.h>
27 #include <dali/integration-api/debug.h>
28 #include <dali/integration-api/events/hover-event-integ.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/multi-point-event-util.h>
34 #include <dali/internal/event/events/hover-event-impl.h>
35 #include <dali/internal/event/render-tasks/render-task-impl.h>
46 #if defined(DEBUG_ENABLED)
47 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_HOVER_PROCESSOR" );
49 const char * TOUCH_POINT_STATE[PointState::INTERRUPTED + 1] =
59 #endif // defined(DEBUG_ENABLED)
62 * Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
64 Dali::Actor EmitHoverSignals( Dali::Actor actor, const Dali::HoverEvent& event )
66 Dali::Actor consumedActor;
70 Dali::Actor oldParent( actor.GetParent() );
72 Actor& actorImpl( GetImplementation(actor) );
74 bool consumed( false );
76 // Only emit the signal if the actor's hover signal has connections (or derived actor implementation requires hover).
77 if ( actorImpl.GetHoverRequired() )
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();
161 } // unnamed namespace
163 HoverEventProcessor::HoverEventProcessor( Scene& scene )
166 DALI_LOG_TRACE_METHOD( gLogFilter );
169 HoverEventProcessor::~HoverEventProcessor()
171 DALI_LOG_TRACE_METHOD( gLogFilter );
174 void HoverEventProcessor::ProcessHoverEvent( const Integration::HoverEvent& event )
176 DALI_LOG_TRACE_METHOD( gLogFilter );
177 DALI_ASSERT_ALWAYS( !event.points.empty() && "Empty HoverEvent sent from Integration\n" );
179 PointState::Type state = static_cast< PointState::Type >( event.points[0].GetState() );
181 PRINT_HIERARCHY(gLogFilter);
183 // Copy so we can add the results of a hit-test.
184 HoverEventPtr hoverEvent( new HoverEvent( event.time ) );
186 // 1) Check if it is an interrupted event - we should inform our last primary hit actor about this
187 // and emit the stage signal as well.
189 if ( state == PointState::INTERRUPTED )
191 Dali::Actor consumingActor;
192 Integration::Point currentPoint( event.points[0] );
194 Actor* lastPrimaryHitActor( mLastPrimaryHitActor.GetActor() );
195 if ( lastPrimaryHitActor )
197 Dali::Actor lastPrimaryHitActorHandle( lastPrimaryHitActor );
198 currentPoint.SetHitActor( lastPrimaryHitActorHandle );
199 consumingActor = AllocAndEmitHoverSignals( event.time, lastPrimaryHitActorHandle, currentPoint );
202 // If the last consumed actor was different to the primary hit actor then inform it as well (if it has not already been informed).
203 Actor* lastConsumedActor( mLastConsumedActor.GetActor() );
204 if ( lastConsumedActor &&
205 lastConsumedActor != lastPrimaryHitActor &&
206 lastConsumedActor != consumingActor )
208 Dali::Actor lastConsumedActorHandle( lastConsumedActor );
209 currentPoint.SetHitActor( lastConsumedActorHandle );
210 AllocAndEmitHoverSignals( event.time, lastConsumedActorHandle, currentPoint );
213 // Tell the hover-start consuming actor as well, if required
214 Actor* hoverStartConsumedActor( mHoverStartConsumedActor.GetActor() );
215 if ( hoverStartConsumedActor &&
216 hoverStartConsumedActor != lastPrimaryHitActor &&
217 hoverStartConsumedActor != lastConsumedActor &&
218 hoverStartConsumedActor != consumingActor )
220 Dali::Actor hoverStartConsumedActorHandle( hoverStartConsumedActor );
221 currentPoint.SetHitActor( hoverStartConsumedActorHandle );
222 AllocAndEmitHoverSignals( event.time, hoverStartConsumedActorHandle, currentPoint );
225 mLastPrimaryHitActor.SetActor( nullptr );
226 mLastConsumedActor.SetActor( nullptr );
227 mHoverStartConsumedActor.SetActor( nullptr );
228 mLastRenderTask.Reset();
230 return; // No need for hit testing
235 Dali::HoverEvent hoverEventHandle( hoverEvent.Get() );
237 DALI_LOG_INFO( gLogFilter, Debug::Concise, "\n" );
238 DALI_LOG_INFO( gLogFilter, Debug::General, "Point(s): %d\n", event.GetPointCount() );
240 RenderTaskPtr currentRenderTask;
241 bool firstPointParsed = false;
243 for ( auto&& currentPoint : event.points )
245 HitTestAlgorithm::Results hitTestResults;
246 ActorHoverableCheck actorHoverableCheck;
247 HitTestAlgorithm::HitTest( mScene.GetSize(), mScene.GetRenderTaskList(), mScene.GetLayerList(), currentPoint.GetScreenPosition(), hitTestResults, actorHoverableCheck );
249 Integration::Point newPoint( currentPoint );
250 newPoint.SetHitActor( hitTestResults.actor );
251 newPoint.SetLocalPosition( hitTestResults.actorCoordinates );
253 hoverEvent->AddPoint( newPoint );
255 DALI_LOG_INFO( gLogFilter, Debug::General, " State(%s), Screen(%.0f, %.0f), HitActor(%p, %s), Local(%.2f, %.2f)\n",
256 TOUCH_POINT_STATE[currentPoint.GetState()], currentPoint.GetScreenPosition().x, currentPoint.GetScreenPosition().y,
257 ( hitTestResults.actor ? reinterpret_cast< void* >( &hitTestResults.actor.GetBaseObject() ) : NULL ),
258 ( hitTestResults.actor ? hitTestResults.actor.GetProperty< std::string >( Dali::Actor::Property::NAME ).c_str() : "" ),
259 hitTestResults.actorCoordinates.x, hitTestResults.actorCoordinates.y );
261 // Only set the currentRenderTask for the primary hit actor.
262 if( !firstPointParsed )
264 firstPointParsed = true;
265 currentRenderTask = hitTestResults.renderTask;
269 // 3) Recursively deliver events to the actor and its parents, until the event is consumed or the stage is reached.
271 // Emit the touch signal
272 Dali::Actor consumedActor;
273 if ( currentRenderTask )
275 consumedActor = EmitHoverSignals( hoverEvent->GetHitActor( 0 ), hoverEventHandle );
278 Integration::Point primaryPoint = hoverEvent->GetPoint( 0 );
279 Dali::Actor primaryHitActor = primaryPoint.GetHitActor();
280 PointState::Type primaryPointState = primaryPoint.GetState();
282 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() : "" );
283 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() : "" );
285 if ( ( primaryPointState == PointState::STARTED ) &&
286 ( hoverEvent->GetPointCount() == 1 ) &&
287 ( consumedActor && GetImplementation( consumedActor ).OnScene() ) )
289 mHoverStartConsumedActor.SetActor( &GetImplementation( consumedActor ) );
292 // 4) Check if the last primary hit actor requires a leave event and if it was different to the current primary
293 // hit actor. Also process the last consumed actor in the same manner.
295 Actor* lastPrimaryHitActor( mLastPrimaryHitActor.GetActor() );
296 Actor* lastConsumedActor( mLastConsumedActor.GetActor() );
297 if( (primaryPointState == PointState::MOTION) || (primaryPointState == PointState::FINISHED) || (primaryPointState == PointState::STATIONARY) )
299 if ( mLastRenderTask )
301 Dali::Actor leaveEventConsumer;
302 RenderTask& lastRenderTaskImpl = *mLastRenderTask.Get();
304 if( lastPrimaryHitActor &&
305 lastPrimaryHitActor != primaryHitActor &&
306 lastPrimaryHitActor != consumedActor )
308 if( lastPrimaryHitActor->IsHittable() && IsActuallySensitive( lastPrimaryHitActor ) )
310 if ( lastPrimaryHitActor->GetLeaveRequired() )
312 DALI_LOG_INFO( gLogFilter, Debug::Concise, "LeaveActor(Hit): (%p) %s\n", reinterpret_cast< void* >( lastPrimaryHitActor ), lastPrimaryHitActor->GetName().data() );
313 leaveEventConsumer = EmitHoverSignals( mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::LEAVE );
318 // At this point mLastPrimaryHitActor was touchable and sensitive in the previous touch event process but is not in the current one.
319 // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
320 DALI_LOG_INFO( gLogFilter, Debug::Concise, "InterruptedActor(Hit): (%p) %s\n", reinterpret_cast< void* >( lastPrimaryHitActor ), lastPrimaryHitActor->GetName().data() );
321 leaveEventConsumer = EmitHoverSignals( mLastPrimaryHitActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED );
325 // Check if the motion event has been consumed by another actor's listener. In this case, the previously
326 // consumed actor's listeners may need to be informed (through a leave event).
327 // Further checks here to ensure we do not signal the same actor twice for the same event.
328 if ( lastConsumedActor &&
329 lastConsumedActor != consumedActor &&
330 lastConsumedActor != lastPrimaryHitActor &&
331 lastConsumedActor != primaryHitActor &&
332 lastConsumedActor != leaveEventConsumer )
334 if( lastConsumedActor->IsHittable() && IsActuallySensitive( lastConsumedActor ) )
336 if( lastConsumedActor->GetLeaveRequired() )
338 DALI_LOG_INFO( gLogFilter, Debug::Concise, "LeaveActor(Consume): (%p) %s\n", reinterpret_cast< void* >( lastConsumedActor ), lastConsumedActor->GetName().data() );
339 EmitHoverSignals( lastConsumedActor, lastRenderTaskImpl, hoverEvent, PointState::LEAVE );
344 // At this point mLastConsumedActor was touchable and sensitive in the previous touch event process but is not in the current one.
345 // An interrupted event is send to allow some actors to go back to their original state (i.e. Button controls)
346 DALI_LOG_INFO( gLogFilter, Debug::Concise, "InterruptedActor(Consume): (%p) %s\n", reinterpret_cast< void* >( lastConsumedActor ), lastConsumedActor->GetName().data() );
347 EmitHoverSignals( mLastConsumedActor.GetActor(), lastRenderTaskImpl, hoverEvent, PointState::INTERRUPTED );
353 // 5) If our primary point is a FINISHED event, then the primary point (in multi-touch) will change next
354 // time so set our last primary actor to NULL. Do the same to the last consumed actor as well.
356 if ( primaryPointState == PointState::FINISHED )
358 mLastPrimaryHitActor.SetActor( nullptr );
359 mLastConsumedActor.SetActor( nullptr );
360 mLastRenderTask.Reset();
364 // The primaryHitActor may have been removed from the scene so ensure it is still on the scene before setting members.
365 if ( primaryHitActor && GetImplementation( primaryHitActor ).OnScene() )
367 mLastPrimaryHitActor.SetActor( &GetImplementation( primaryHitActor ) );
369 // Only observe the consumed actor if we have a primaryHitActor (check if it is still on the scene).
370 if ( consumedActor && GetImplementation( consumedActor ).OnScene() )
372 mLastConsumedActor.SetActor( &GetImplementation( consumedActor ) );
376 mLastConsumedActor.SetActor( nullptr );
379 mLastRenderTask = currentRenderTask;
383 mLastPrimaryHitActor.SetActor( nullptr );
384 mLastConsumedActor.SetActor( nullptr );
385 mLastRenderTask.Reset();
389 // 6) Emit an interrupted event to the hover-started actor if it hasn't consumed the FINISHED.
391 if ( hoverEvent->GetPointCount() == 1 ) // Only want the first hover started
393 switch ( primaryPointState )
395 case PointState::FINISHED:
397 Actor* hoverStartConsumedActor( mHoverStartConsumedActor.GetActor() );
398 if ( hoverStartConsumedActor &&
399 hoverStartConsumedActor != consumedActor &&
400 hoverStartConsumedActor != lastPrimaryHitActor &&
401 hoverStartConsumedActor != lastConsumedActor )
403 Dali::Actor hoverStartConsumedActorHandle( hoverStartConsumedActor );
404 Integration::Point primaryPoint = hoverEvent->GetPoint( 0 );
405 primaryPoint.SetHitActor( hoverStartConsumedActorHandle );
406 primaryPoint.SetState( PointState::INTERRUPTED );
407 AllocAndEmitHoverSignals( event.time, hoverStartConsumedActorHandle, primaryPoint );
409 // Restore hover-event to original state
410 primaryPoint.SetHitActor( primaryHitActor );
411 primaryPoint.SetState( primaryPointState );
414 mHoverStartConsumedActor.SetActor( nullptr );
416 // No break, Fallthrough
418 case PointState::STARTED:
419 case PointState::MOTION:
420 case PointState::LEAVE:
421 case PointState::STATIONARY:
422 case PointState::INTERRUPTED:
431 } // namespace Internal