X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali%2Finternal%2Fevent%2Fevents%2Fhit-test-algorithm-impl.cpp;h=604f71ee8fc56dfdaaaf87ba1e348e298c76893b;hb=21b6e70ef1243610d8d1df0cc8dd765b279b20fd;hp=7ebf2563a7661b47568f91803fc5c0edb21d75f9;hpb=308967f3c2a3d7942a40e8dbe6463a85ce0cb389;p=platform%2Fcore%2Fuifw%2Fdali-core.git diff --git a/dali/internal/event/events/hit-test-algorithm-impl.cpp b/dali/internal/event/events/hit-test-algorithm-impl.cpp index 7ebf256..604f71e 100644 --- a/dali/internal/event/events/hit-test-algorithm-impl.cpp +++ b/dali/internal/event/events/hit-test-algorithm-impl.cpp @@ -1,35 +1,38 @@ -// -// Copyright (c) 2014 Samsung Electronics Co., Ltd. -// -// Licensed under the Flora License, Version 1.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://floralicense.org/license/ -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an AS IS BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +/* + * Copyright (c) 2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ // CLASS HEADER #include // INTERNAL INCLUDES #include +#include #include #include #include #include #include +#include #include #include -#include #include #include #include +#include #include #include @@ -52,54 +55,100 @@ struct HitActor x( 0 ), y( 0 ), distance( std::numeric_limits::max() ), - overlay( false ) + depth( std::numeric_limits::min() ) { - } Actor *actor; ///< the actor hit. (if actor hit, then initialised) float x; ///< x position of hit (only valid if actor valid) float y; ///< y position of hit (only valid if actor valid) float distance; ///< distance from ray origin to hit actor - bool overlay; ///< true if the hit actor is an overlay + int depth; ///< depth index of this actor }; /** - * The function to be used in the hit-test algorithm to check whether the actor is touchable. - * It is used by the touch event and gesture processor. + * Creates an Actor handle so that a HitTestFunction provided via the public API can be called. */ -bool IsActorTouchableFunction(Dali::Actor actor, Dali::HitTestAlgorithm::TraverseType type) +struct HitTestFunctionWrapper : public HitTestInterface { - bool hittable = false; + /** + * Constructor + * + * @param[in] func HitTestFunction to call with an Actor handle. + */ + HitTestFunctionWrapper( Dali::HitTestAlgorithm::HitTestFunction func ) + : mFunc( func ) + { + } - switch (type) + virtual bool IsActorHittable( Actor* actor ) { - case Dali::HitTestAlgorithm::CHECK_ACTOR: - { - if( GetImplementation(actor).GetTouchRequired() && // Does the Application or derived actor type require a touch event? - GetImplementation(actor).IsHittable() ) // Is actor sensitive, visible and on the scene? - { - hittable = true; - } - break; - } - case Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE: + return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::CHECK_ACTOR ); + } + + virtual bool DescendActorHierarchy( Actor* actor ) + { + return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE ); + } + + virtual bool DoesLayerConsumeHit( Layer* layer ) + { + // Layer::IsTouchConsumed() focuses on touch only. Here we are a wrapper for the public-api + // where the caller may want to check for something completely different. + // TODO: Should provide a means to let caller decide. For now do not allow layers to consume + return false; + } + + Dali::HitTestAlgorithm::HitTestFunction mFunc; +}; + +/** + * Used in the hit-test algorithm to check whether the actor is touchable. + * It is used by the touch event processor. + */ +struct ActorTouchableCheck : public HitTestInterface +{ + virtual bool IsActorHittable( Actor* actor ) + { + return actor->GetTouchRequired() && // Does the Application or derived actor type require a touch event? + actor->IsHittable(); // Is actor sensitive, visible and on the scene? + } + + virtual bool DescendActorHierarchy( Actor* actor ) + { + return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible. + actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either. + } + + virtual bool DoesLayerConsumeHit( Layer* layer ) + { + return layer->IsTouchConsumed(); + } +}; + +/** + * Check to see if the actor we're about to hit test is exclusively owned by another rendertask? + */ +bool IsActorExclusiveToAnotherRenderTask( const Actor& actor, + const RenderTask& renderTask, + const Vector< RenderTaskList::Exclusive >& exclusives ) + +{ + if ( exclusives.Size() ) + { + for ( Vector< RenderTaskList::Exclusive >::Iterator exclusiveIt = exclusives.Begin(); exclusives.End() != exclusiveIt; ++exclusiveIt ) { - if( actor.IsVisible() && // Actor is visible, if not visible then none of its children are visible. - actor.IsSensitive() ) // Actor is sensitive, if insensitive none of its children should be hittable either. + if ( exclusiveIt->renderTaskPtr != &renderTask ) { - hittable = true; + if ( exclusiveIt->actorPtr == &actor ) + { + return true; + } } - break; - } - default: - { - break; } } - - return hittable; + return false; } /** @@ -108,26 +157,29 @@ bool IsActorTouchableFunction(Dali::Actor actor, Dali::HitTestAlgorithm::Travers * Hit-Testing each Actor, noting the distance from the Ray-Origin (3D origin * of touch vector). The closest Hit-Tested Actor is that which is returned. * Exceptions to this rule are: - * - If the Actor is an overlay then it is considered closer than all previous - * overlays encountered in the hit test traversal. * - When comparing against renderable parents, if Actor is the same distance * or closer than it's renderable parent, then it takes priority. */ HitActor HitTestWithinLayer( Actor& actor, + const RenderTask& renderTask, + const Vector< RenderTaskList::Exclusive >& exclusives, const Vector4& rayOrigin, const Vector4& rayDir, - bool worldOverlay, float& nearClippingPlane, float& farClippingPlane, - Dali::HitTestAlgorithm::HitTestFunction func, + HitTestInterface& hitCheck, bool& stencilOnLayer, bool& stencilHit, - bool parentIsStencil ) + bool parentIsStencil, + bool layerIs3d ) { - worldOverlay |= actor.IsOverlay(); - HitActor hit; + if ( IsActorExclusiveToAnotherRenderTask( actor, renderTask, exclusives ) ) + { + return hit; + } + // Children should inherit the stencil draw mode bool isStencil = parentIsStencil; @@ -138,11 +190,11 @@ HitActor HitTestWithinLayer( Actor& actor, } // If we are a stencil or hittable... - if ( isStencil || func(Dali::Actor(&actor), Dali::HitTestAlgorithm::CHECK_ACTOR) ) // Is actor hittable + if ( isStencil || hitCheck.IsActorHittable( &actor ) ) { Vector3 size( actor.GetCurrentSize() ); - if ( size.x > 0.0f && size.y > 0.0f && // Ensure the actor has a valid size. + if ( size.x > 0.0f && size.y > 0.0f && // Ensure the actor has a valid size. actor.RaySphereTest( rayOrigin, rayDir ) ) // Perform quicker ray sphere test to see if our ray is close to the actor. { Vector4 hitPointLocal; @@ -164,15 +216,30 @@ HitActor HitTestWithinLayer( Actor& actor, hit.x = hitPointLocal.x; hit.y = hitPointLocal.y; hit.distance = distance; - hit.overlay = worldOverlay; + hit.depth = actor.GetHierarchyDepth() * Dali::Layer::TREE_DEPTH_MULTIPLIER; + + if ( actor.GetRendererCount() > 0 ) + { + //Get renderer with maximum depth + int rendererMaxDepth(actor.GetRendererAt( 0 ).Get()->GetDepthIndex()); + for( unsigned int i(1); iGetDepthIndex(); + if( depth > rendererMaxDepth ) + { + rendererMaxDepth = depth; + } + } + hit.depth += rendererMaxDepth; + } } } } } } - // If there is a stencil on this layer and we've also registered a hit, then don't both searching any children - if ( stencilHit && hit.actor ) + // If we are a stencil (or a child of a stencil) and we have already ascertained that the stencil has been hit then there is no need to hit-test the children of this stencil-actor + if ( isStencil && stencilHit ) { return hit; } @@ -182,36 +249,53 @@ HitActor HitTestWithinLayer( Actor& actor, if( actor.GetChildCount() > 0 ) { childHit.distance = std::numeric_limits::max(); - Dali::ActorContainer& children = actor.GetChildrenInternal(); + childHit.depth = std::numeric_limits::min(); + ActorContainer& children = actor.GetChildrenInternal(); // Hit test ALL children and calculate their distance. bool parentIsRenderable = actor.IsRenderable(); - for (Dali::ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter) + for( ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter ) { // Descend tree only if... - if ( !iter->IsLayer() && // Child is NOT a layer, hit testing current layer only or Child is not a layer and we've inherited the stencil draw mode - ( isStencil || func(*iter, Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE ) ) ) // Child is visible and sensitive, otherwise none of its children should be hittable. + if ( !(*iter)->IsLayer() && // Child is NOT a layer, hit testing current layer only or Child is not a layer and we've inherited the stencil draw mode + ( isStencil || hitCheck.DescendActorHierarchy( ( *iter ).Get() ) ) ) // We are a stencil OR we can descend into child hierarchy { - HitActor currentHit( HitTestWithinLayer( GetImplementation(*iter), rayOrigin, rayDir, worldOverlay, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, isStencil ) ); - - // If Current child is an overlay, then it takes priority. - // If it is not an overlay, and the previously hit sibling is also not an overlay, then closest takes priority. - // (last overlay sibling has priority as is rendered on top) - if ( currentHit.distance >= 0.f && (currentHit.overlay || (!childHit.overlay && currentHit.distance < childHit.distance) ) ) + HitActor currentHit( HitTestWithinLayer( (*iter->Get()), + renderTask, + exclusives, + rayOrigin, + rayDir, + nearClippingPlane, + farClippingPlane, + hitCheck, + stencilOnLayer, + stencilHit, + isStencil, + layerIs3d) ); + + bool updateChildHit = false; + if ( currentHit.distance >= 0.0f ) { - if ( !parentIsRenderable ) + if( layerIs3d ) { - // If our parent is not renderable, then child should be hit regardless of distance. - childHit = currentHit; + updateChildHit = ( ( currentHit.depth > childHit.depth ) || + ( ( currentHit.depth == childHit.depth ) && ( currentHit.distance < childHit.distance ) ) ); } - else if ( currentHit.overlay || (!hit.overlay && currentHit.distance <= hit.distance) ) + else { - // If our parent is renderable, then child should only be hit if it is an overlay, or if it is closer than a non-overlay. - // (child overlay has priority as is rendered on top of it's parent) - childHit = currentHit; + updateChildHit = currentHit.depth >= childHit.depth; } } + + if ( updateChildHit ) + { + if( !parentIsRenderable || currentHit.depth > hit.depth || + ( layerIs3d && ( currentHit.depth == hit.depth && currentHit.distance < hit.distance )) ) + { + childHit = currentHit; + } + } } } } @@ -243,7 +327,7 @@ bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor ) /** * Returns true if the layer and all of the layer's parents are visible and sensitive. */ -inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, Dali::HitTestAlgorithm::HitTestFunction func ) +inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, HitTestInterface& hitCheck ) { bool hittable( true ); @@ -264,9 +348,11 @@ inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, if(hittable) { Actor* actor( &layer ); + + // Ensure that we can descend into the layer's (or any of its parent's) hierarchy. while ( actor && hittable ) { - if ( !(func(Dali::Actor(actor), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE)) ) // Layer (or its Parent) is NOT visible and sensitive, so our layer is not either. + if ( ! hitCheck.DescendActorHierarchy( actor ) ) { hittable = false; break; @@ -291,11 +377,13 @@ void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, f /** * Hit test a RenderTask */ -bool HitTestRenderTask( LayerList& layers, +bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives, + Stage& stage, + LayerList& layers, RenderTask& renderTask, Vector2 screenCoordinates, Results& results, - Dali::HitTestAlgorithm::HitTestFunction func ) + HitTestInterface& hitCheck ) { if ( renderTask.IsHittable( screenCoordinates ) ) { @@ -337,34 +425,66 @@ bool HitTestRenderTask( LayerList& layers, HitActor hit; bool stencilOnLayer = false; bool stencilHit = false; - const Vector2& stageSize = Stage::GetCurrent()->GetSize(); + bool layerConsumesHit = false; + + const Vector2& stageSize = stage.GetSize(); for (int i=layers.GetLayerCount()-1; i>=0 && !(hit.actor); --i) { Layer* layer( layers.GetLayer(i) ); - HitActor previousHit = hit; stencilOnLayer = false; stencilHit = false; // Ensure layer is touchable (also checks whether ancestors are also touchable) - if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, func ) ) + if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, hitCheck ) ) { // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy if ( sourceActorDepth == static_cast(i) ) { // Recursively hit test the source actor & children, without crossing into other layers. - hit = HitTestWithinLayer( *sourceActor, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, false ); + hit = HitTestWithinLayer( *sourceActor, + renderTask, + exclusives, + results.rayOrigin, + results.rayDirection, + nearClippingPlane, + farClippingPlane, + hitCheck, + stencilOnLayer, + stencilHit, + false, + layer->GetBehavior() == Dali::Layer::LAYER_3D); } else if ( IsWithinSourceActors( *sourceActor, *layer ) ) { // Recursively hit test all the actors, without crossing into other layers. - hit = HitTestWithinLayer( *layer, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, false ); + hit = HitTestWithinLayer( *layer, + renderTask, + exclusives, + results.rayOrigin, + results.rayDirection, + nearClippingPlane, + farClippingPlane, + hitCheck, + stencilOnLayer, + stencilHit, + false, + layer->GetBehavior() == Dali::Layer::LAYER_3D); } - // If a stencil on this layer hasn't been hit, then discard hit results for this layer - if ( stencilOnLayer && !stencilHit ) + + // If a stencil on this layer hasn't been hit, then discard hit results for this layer if our current hit actor is renderable + if ( stencilOnLayer && !stencilHit && + hit.actor && hit.actor->IsRenderable() ) { - hit = previousHit; + hit = previousHit; + } + + // If this layer is set to consume the hit, then do not check any layers behind it + if ( hitCheck.DoesLayerConsumeHit( layer ) ) + { + layerConsumesHit = true; + break; } } } @@ -376,6 +496,10 @@ bool HitTestRenderTask( LayerList& layers, results.actorCoordinates.y = hit.y; return true; // Success } + else if ( layerConsumesHit ) + { + return true; // Also success if layer is consuming the hit + } } } } @@ -384,17 +508,21 @@ bool HitTestRenderTask( LayerList& layers, /** * Iterate through RenderTaskList and perform hit test. + * + * @return true if we have a hit, false otherwise */ - -void HitTestForEachRenderTask( LayerList& layers, +bool HitTestForEachRenderTask( Stage& stage, + LayerList& layers, RenderTaskList& taskList, const Vector2& screenCoordinates, Results& results, - Dali::HitTestAlgorithm::HitTestFunction func ) + HitTestInterface& hitCheck ) { RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks(); RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend(); + const Vector< RenderTaskList::Exclusive >& exclusives = taskList.GetExclusivesList(); + // Check onscreen tasks before offscreen ones, hit test order should be reverse of draw order (see ProcessRenderTasks() where offscreen tasks are drawn first). // on screen @@ -416,10 +544,10 @@ void HitTestForEachRenderTask( LayerList& layers, } } - if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, func ) ) + if ( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) ) { - // Exit when an actor is hit - return; // don't bother checking off screen tasks + // Return true when an actor is hit (or layer in our render-task consumes the hit) + return true; // don't bother checking off screen tasks } } @@ -441,32 +569,40 @@ void HitTestForEachRenderTask( LayerList& layers, continue; } - if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, func ) ) + if ( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) ) { - // Exit when an actor is hit - break; + // Return true when an actor is hit (or a layer in our render-task consumes the hit) + return true; } } } + return false; } } // unnamed namespace -void HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func ) +bool HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func ) { + bool wasHit( false ); // Hit-test the regular on-stage actors RenderTaskList& taskList = stage.GetRenderTaskList(); LayerList& layerList = stage.GetLayerList(); Results hitTestResults; - HitTestForEachRenderTask( layerList, taskList, screenCoordinates, hitTestResults, func ); - - results.actor = hitTestResults.actor; - results.actorCoordinates = hitTestResults.actorCoordinates; + HitTestFunctionWrapper hitTestFunctionWrapper( func ); + if ( HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) ) + { + results.actor = hitTestResults.actor; + results.actorCoordinates = hitTestResults.actorCoordinates; + wasHit = true; + } + return wasHit; } -void HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results ) +bool HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface ) { + bool wasHit( false ); + // Hit-test the system-overlay actors first SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal(); @@ -475,26 +611,41 @@ void HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results ) RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks(); LayerList& overlayLayerList = systemOverlay->GetLayerList(); - HitTestForEachRenderTask( overlayLayerList, overlayTaskList, screenCoordinates, results, IsActorTouchableFunction ); + wasHit = HitTestForEachRenderTask( stage, overlayLayerList, overlayTaskList, screenCoordinates, results, hitTestInterface ); } // Hit-test the regular on-stage actors - if ( !results.actor ) + if ( !wasHit ) { RenderTaskList& taskList = stage.GetRenderTaskList(); LayerList& layerList = stage.GetLayerList(); - HitTestForEachRenderTask( layerList, taskList, screenCoordinates, results, IsActorTouchableFunction ); + wasHit = HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, results, hitTestInterface ); } + return wasHit; +} + +bool HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results ) +{ + ActorTouchableCheck actorTouchableCheck; + return HitTest( stage, screenCoordinates, results, actorTouchableCheck ); } -void HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates, +bool HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func ) { + bool wasHit( false ); Results hitTestResults; - HitTestRenderTask( stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, func ); - results.actor = hitTestResults.actor; - results.actorCoordinates = hitTestResults.actorCoordinates; + + const Vector< RenderTaskList::Exclusive >& exclusives = stage.GetRenderTaskList().GetExclusivesList(); + HitTestFunctionWrapper hitTestFunctionWrapper( func ); + if ( HitTestRenderTask( exclusives, stage, stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) ) + { + results.actor = hitTestResults.actor; + results.actorCoordinates = hitTestResults.actorCoordinates; + wasHit = true; + } + return wasHit; } } // namespace HitTestAlgorithm