diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index 795807b4f..121a609e1 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -62,7 +62,6 @@ enum NodeFlags { NODE_FLAG_MOUSEOVER = ( 1 << 7 ), NODE_FLAG_HAS_FOCUS = ( 1 << 8 ), NODE_FLAG_SELECTED = ( 1 << 9 ), - NODE_FLAG_MOUSEOVER_ME_OR_CHILD = ( 1 << 10 ), NODE_FLAG_DRAGGING = ( 1 << 11 ), NODE_FLAG_SKIN_OWNER = ( 1 << 12 ), NODE_FLAG_TOUCH_DRAGGING = ( 1 << 13 ), @@ -633,12 +632,22 @@ class EE_API Node : public Transformable { inline bool isMouseOver() const { return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER ); } /** - * @brief Checks if the mouse is over this node or any of its children. + * @brief Checks if this node is the current mouse-over target or one of its ancestors. + * + * This reflects the event dispatcher's last completed hit test and remains valid between scene + * updates. * * @return True if the mouse is over this node or any descendant, false otherwise. */ inline bool isMouseOverMeOrChildren() const { - return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER_ME_OR_CHILD ); + EventDispatcher* dispatcher = getEventDispatcher(); + Node* overNode = dispatcher ? dispatcher->getMouseOverNode() : nullptr; + while ( overNode ) { + if ( overNode == this ) + return true; + overNode = overNode->mParentNode; + } + return false; } /** diff --git a/include/eepp/scene/scenenode.hpp b/include/eepp/scene/scenenode.hpp index 2a56e74c1..1b8d0939e 100644 --- a/include/eepp/scene/scenenode.hpp +++ b/include/eepp/scene/scenenode.hpp @@ -351,23 +351,6 @@ class EE_API SceneNode : public Node { */ bool isSubscribedForScheduledUpdate( Node* node ); - /** - * @brief Adds a node to the mouse-over tracking list. - * - * This is used internally to track which nodes are currently under - * the mouse cursor. - * - * @param node Pointer to the node to track. - */ - void addMouseOverNode( Node* node ); - - /** - * @brief Removes a node from the mouse-over tracking list. - * - * @param node Pointer to the node to stop tracking. - */ - void removeMouseOverNode( Node* node ); - /** * @brief Gets whether all children are updated each frame. * @@ -442,7 +425,6 @@ class EE_API SceneNode : public Node { Time mElapsed; UnorderedSet mScheduledUpdate; UnorderedSet mScheduledUpdateRemove; - UnorderedSet mMouseOverNodes; Float mDPI; virtual void onSizeChange(); diff --git a/include/eepp/ui/uiitemcontainer.hpp b/include/eepp/ui/uiitemcontainer.hpp index 6102cadd1..4cbb63dff 100644 --- a/include/eepp/ui/uiitemcontainer.hpp +++ b/include/eepp/ui/uiitemcontainer.hpp @@ -72,9 +72,6 @@ template Node* UIItemContainer::overFind( const V updateWorldPolygon(); if ( mWorldBounds.contains( Point ) && mPoly.pointInside( Point ) ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); - for ( Uint32 i = tParent->mVisibleFirst; i <= tParent->mVisibleLast; i++ ) { if ( NULL != tParent->mItems[i] ) { Node* ChildOver = tParent->mItems[i]->overFind( Point ); diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index 187108ffd..c075cda9c 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -41,9 +41,6 @@ Node::~Node() { if ( mNodeFlags & NODE_FLAG_SCHEDULED_UPDATE ) mSceneNode->unsubscribeScheduledUpdate( this ); - - if ( isMouseOverMeOrChildren() ) - mSceneNode->removeMouseOverNode( this ); } childDeleteAll(); @@ -291,8 +288,6 @@ void Node::update( const Time& time ) { childLoop->update( time ); childLoop = childLoop->mNext; } - - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 0 ); } void Node::sendMouseEvent( const Uint32& event, const Vector2i& pos, const Uint32& flags ) { @@ -973,9 +968,6 @@ Node* Node::overFind( const Vector2f& point ) { updateWorldPolygon(); if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); - Node* child = mChildLast; while ( NULL != child ) { diff --git a/src/eepp/scene/scenenode.cpp b/src/eepp/scene/scenenode.cpp index 392c40ea3..bf9dc2e02 100644 --- a/src/eepp/scene/scenenode.cpp +++ b/src/eepp/scene/scenenode.cpp @@ -160,14 +160,8 @@ void SceneNode::update( const Time& time ) { node->scheduledUpdate( time ); } - if ( mUpdateAllChildren ) { + if ( mUpdateAllChildren ) Node::update( time ); - } else { - for ( auto& nodeOver : mMouseOverNodes ) - nodeOver->writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 0 ); - } - - mMouseOverNodes.clear(); } void SceneNode::onSizeChange() { @@ -488,14 +482,6 @@ bool SceneNode::isSubscribedForScheduledUpdate( Node* node ) { return mScheduledUpdate.count( node ) > 0; } -void SceneNode::addMouseOverNode( Node* node ) { - mMouseOverNodes.insert( node ); -} - -void SceneNode::removeMouseOverNode( Node* node ) { - mMouseOverNodes.erase( node ); -} - bool SceneNode::getUpdateAllChildren() const { return mUpdateAllChildren; } diff --git a/src/eepp/ui/uihtmlwidget.cpp b/src/eepp/ui/uihtmlwidget.cpp index 1e36c2bfb..9905af8b5 100644 --- a/src/eepp/ui/uihtmlwidget.cpp +++ b/src/eepp/ui/uihtmlwidget.cpp @@ -858,9 +858,6 @@ Node* UIHTMLWidget::overFind( const Vector2f& point ) { updateWorldPolygon(); if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); - const auto& sortedChildren = getPaintOrder(); // Drawing and hit-testing share one sequence; reverse it so the last painted node wins. diff --git a/src/eepp/ui/uiroot.cpp b/src/eepp/ui/uiroot.cpp index 572fa3918..be46e4c5e 100644 --- a/src/eepp/ui/uiroot.cpp +++ b/src/eepp/ui/uiroot.cpp @@ -29,11 +29,6 @@ Node* UIRoot::overFind( const Vector2f& point ) { child = child->getPrevNode(); } - if ( nullptr != pOver || selfHit ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); - } - if ( nullptr == pOver && selfHit ) pOver = this; } diff --git a/src/eepp/ui/uitableview.cpp b/src/eepp/ui/uitableview.cpp index b5ea6218d..38936a666 100644 --- a/src/eepp/ui/uitableview.cpp +++ b/src/eepp/ui/uitableview.cpp @@ -94,8 +94,6 @@ Node* UITableView::overFind( const Vector2f& point ) { ConditionalLock l( getModel() != nullptr, getModel() ? &getModel()->resourceMutex() : nullptr ); updateWorldPolygon(); if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); if ( mHScroll->isVisible() && ( pOver = mHScroll->overFind( point ) ) ) return pOver; if ( mVScroll->isVisible() && ( pOver = mVScroll->overFind( point ) ) ) diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index 2e30942b8..d5bd17a82 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -725,9 +725,6 @@ Node* UITextSpan::overFind( const Vector2f& point ) { } if ( hit ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); - Node* child = mChildLast; while ( NULL != child ) { diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index bce885270..b3e786cdb 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -447,8 +447,6 @@ Node* UITreeView::overFind( const Vector2f& point ) { if ( mEnabled && mVisible ) { updateWorldPolygon(); if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) { - writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); - mSceneNode->addMouseOverNode( this ); if ( mHScroll->isVisible() && ( pOver = mHScroll->overFind( point ) ) ) return pOver; if ( mVScroll->isVisible() && ( pOver = mVScroll->overFind( point ) ) ) diff --git a/src/tests/unit_tests/uiscenenode_tests.cpp b/src/tests/unit_tests/uiscenenode_tests.cpp index 38cd8615b..b6f806e37 100644 --- a/src/tests/unit_tests/uiscenenode_tests.cpp +++ b/src/tests/unit_tests/uiscenenode_tests.cpp @@ -35,6 +35,65 @@ UTEST( UISceneNode, CssPointerCursorUsesHandCursor ) { EXPECT_STREQ( Cursor::toName( Cursor::Arrow ), "arrow" ); } +UTEST( UISceneNode, MouseOverAncestryUsesCommittedOverNode ) { + UIApplication app( + WindowSettings{ 320, 240, "Mouse Over Ancestry" }, + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1.f ) ); + auto* scene = app.getUI(); + auto* parent = UIWidget::New(); + parent->setPixelsSize( 200, 100 ); + parent->setParent( scene->getRoot() ); + auto* firstChild = UIWidget::New(); + firstChild->setPixelsSize( 80, 80 ); + firstChild->setParent( parent ); + auto* secondChild = UIWidget::New(); + secondChild->setPixelsPosition( 100, 0 ); + secondChild->setPixelsSize( 80, 80 ); + secondChild->setParent( parent ); + scene->flushDirtyStyleAndLayout(); + + Input* input = app.getWindow()->getInput(); + input->setMousePos( firstChild->convertToWorldSpace( { 20.f, 20.f } ).asInt() ); + SceneManager::instance()->update(); + + EXPECT_EQ( scene->getEventDispatcher()->getMouseOverNode(), firstChild ); + EXPECT_TRUE( firstChild->isMouseOverMeOrChildren() ); + EXPECT_TRUE( firstChild->isMouseOver() ); + EXPECT_TRUE( parent->isMouseOverMeOrChildren() ); + EXPECT_TRUE( parent->isMouseOver() ); + EXPECT_FALSE( secondChild->isMouseOverMeOrChildren() ); + EXPECT_FALSE( secondChild->isMouseOver() ); + + const Vector2i secondChildPosition = secondChild->convertToWorldSpace( { 20.f, 20.f } ).asInt(); + EXPECT_EQ( scene->overFind( secondChildPosition.asFloat() ), secondChild ); + EXPECT_EQ( scene->getEventDispatcher()->getMouseOverNode(), firstChild ); + EXPECT_TRUE( firstChild->isMouseOverMeOrChildren() ); + EXPECT_TRUE( firstChild->isMouseOver() ); + EXPECT_FALSE( secondChild->isMouseOverMeOrChildren() ); + EXPECT_FALSE( secondChild->isMouseOver() ); + + input->setMousePos( secondChildPosition ); + SceneManager::instance()->update(); + + EXPECT_EQ( scene->getEventDispatcher()->getMouseOverNode(), secondChild ); + EXPECT_FALSE( firstChild->isMouseOverMeOrChildren() ); + EXPECT_FALSE( firstChild->isMouseOver() ); + EXPECT_TRUE( secondChild->isMouseOverMeOrChildren() ); + EXPECT_TRUE( secondChild->isMouseOver() ); + EXPECT_TRUE( parent->isMouseOverMeOrChildren() ); + EXPECT_TRUE( parent->isMouseOver() ); + + input->setMousePos( { 280, 180 } ); + SceneManager::instance()->update(); + + EXPECT_FALSE( firstChild->isMouseOverMeOrChildren() ); + EXPECT_FALSE( firstChild->isMouseOver() ); + EXPECT_FALSE( secondChild->isMouseOverMeOrChildren() ); + EXPECT_FALSE( secondChild->isMouseOver() ); + EXPECT_FALSE( parent->isMouseOverMeOrChildren() ); + EXPECT_FALSE( parent->isMouseOver() ); +} + UTEST( UISceneNode, ScopedContextBindsNodesAndRestoresNestedScene ) { auto* engine = Engine::instance(); auto* window = engine->getCurrentWindow();