mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
Simplify mouse-over ancestry tracking
- Derive isMouseOverMeOrChildren() from the event dispatcher's committed mouse-over node instead of maintaining a transient flag on every node. - Remove the mouse-over tracking set, per-frame flag clearing, and hover bookkeeping side effects from hit testing. This makes hover ancestry available between scene updates while reducing SceneNode size and eliminating hash-set operations during overFind(). - Add coverage for persistent ancestry, sibling transitions, hover state synchronization, and side-effect-free hit testing.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Node*> mScheduledUpdate;
|
||||
UnorderedSet<Node*> mScheduledUpdateRemove;
|
||||
UnorderedSet<Node*> mMouseOverNodes;
|
||||
Float mDPI;
|
||||
|
||||
virtual void onSizeChange();
|
||||
|
||||
@@ -72,9 +72,6 @@ template <class TContainer> Node* UIItemContainer<TContainer>::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 );
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ) ) )
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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 ) ) )
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user