From ae0c5ffef0d03506404b068bc999170d51a2d680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 30 Jul 2026 16:47:52 -0300 Subject: [PATCH] Fix CSS pointer cursor handling on hovered widgets Map the standard CSS pointer value to the hand cursor and retain the computed cursor in each UIWidget instead of mutating the scene during style application. Apply the retained cursor when a widget becomes the hover target, restore the arrow when leaving the UI, and make cursor inheritance available to descendants such as inline SVG elements. Store cursor state in an existing UIWidget padding gap and centralize cursor serialization in Cursor::toName(). Add regressions for pointer parsing, cursor serialization, inheritance, and the SS64 search button SVG. --- include/eepp/ui/uiwidget.hpp | 4 +++ include/eepp/window/cursor.hpp | 2 ++ src/eepp/ui/css/stylesheetspecification.cpp | 2 +- src/eepp/ui/uiwidget.cpp | 18 ++++++++++-- src/eepp/window/cursor.cpp | 31 +++++++++++++++++++++ src/tests/unit_tests/uihtml_float_tests.cpp | 2 ++ src/tests/unit_tests/uiscenenode_tests.cpp | 2 ++ 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index 002d99949..1416bad91 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -625,6 +625,9 @@ class EE_API UIWidget : public UINode { */ virtual bool applyProperty( const StyleSheetProperty& attribute ); + /** @return The cursor resolved from this widget's current style. */ + Cursor::Type getCursor() const; + void propagateInheritedProperty( const CSS::StyleSheetProperty& property ); /** @@ -1518,6 +1521,7 @@ class EE_API UIWidget : public UINode { mutable Float mMinIntrinsicWidth{ 0 }; mutable Float mMaxIntrinsicWidth{ 0 }; mutable bool mIntrinsicWidthsDirty{ true }; + Uint8 mCursor{ static_cast( Cursor::Arrow ) }; Uint8 mMarginAuto{ 0 }; void calculateAutoMargin(); diff --git a/include/eepp/window/cursor.hpp b/include/eepp/window/cursor.hpp index f8b99cb06..bf3867526 100644 --- a/include/eepp/window/cursor.hpp +++ b/include/eepp/window/cursor.hpp @@ -36,6 +36,8 @@ class EE_API Cursor { static Cursor::Type fromName( std::string name ); + static const char* toName( Cursor::Type cursor ); + /** @enum SysType list the system cursors that can be used */ enum SysType { SysArrow = 0, /**< Arrow */ diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index dc218d6f3..89a817e38 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -212,7 +212,7 @@ void StyleSheetSpecification::registerDefaultProperties() { .setType( PropertyType::NumberLength ) .setRelativeTarget( PropertyRelativeTarget::ContainingBlockHeight ); registerProperty( "opacity", "" ).setType( PropertyType::NumberFloat ); - registerProperty( "cursor", "arrow" ); + registerProperty( "cursor", "arrow", true ); registerProperty( "text", "" ).setType( PropertyType::String ); registerProperty( "text-transform", "", true ).setType( PropertyType::String ); registerProperty( "color", "", true ) diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index 0c16ec591..5c25a2ada 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -509,7 +509,10 @@ Uint32 UIWidget::onMouseOver( const Vector2i& position, const Uint32& flags ) { } } - return UINode::onMouseOver( position, flags ); + Uint32 result = UINode::onMouseOver( position, flags ); + if ( mSceneNode ) + mSceneNode->setCursor( getCursor() ); + return result; } Uint32 UIWidget::onMouseLeave( const Vector2i& Pos, const Uint32& Flags ) { @@ -519,6 +522,8 @@ Uint32 UIWidget::onMouseLeave( const Vector2i& Pos, const Uint32& Flags ) { NULL != mTooltip && !mTooltip->dontAutoHideOnMouseMove() ) { mTooltip->hide(); } + if ( NULL != eventDispatcher && eventDispatcher->getMouseOverNode() == nullptr && mSceneNode ) + mSceneNode->setCursor( Cursor::Arrow ); return UINode::onMouseLeave( Pos, Flags ); } @@ -1691,6 +1696,10 @@ std::vector UIWidget::querySelectorAll( const std::string& selector ) return querySelectorAll( CSS::StyleSheetSelector( selector ) ); } +Cursor::Type UIWidget::getCursor() const { + return static_cast( mCursor ); +} + std::vector UIWidget::getPropertiesImplemented() const { return { PropertyId::X, PropertyId::Y, @@ -1832,7 +1841,7 @@ std::string UIWidget::getPropertyString( const PropertyDefinition* propertyDef, case PropertyId::Opacity: return String::fromFloat( getAlpha() / 255.f ); case PropertyId::Cursor: - return "arrow"; + return Cursor::toName( getCursor() ); case PropertyId::Visible: return isVisible() ? "true" : "false"; case PropertyId::Enabled: @@ -2410,7 +2419,10 @@ bool UIWidget::applyProperty( const StyleSheetProperty& attribute ) { break; } case PropertyId::Cursor: - mSceneNode->setCursor( Cursor::fromName( attribute.getValue() ) ); + mCursor = static_cast( Cursor::fromName( attribute.getValue() ) ); + if ( mSceneNode && getEventDispatcher() && + getEventDispatcher()->getMouseOverNode() == this ) + mSceneNode->setCursor( getCursor() ); break; case PropertyId::BackgroundPositionX: setBackgroundPositionX( attribute.value(), attribute.getIndex() ); diff --git a/src/eepp/window/cursor.cpp b/src/eepp/window/cursor.cpp index f3711bf7d..1f32a5ff3 100644 --- a/src/eepp/window/cursor.cpp +++ b/src/eepp/window/cursor.cpp @@ -88,6 +88,37 @@ Cursor::Type Cursor::fromName( std::string name ) { return Arrow; } +const char* Cursor::toName( Cursor::Type cursor ) { + switch ( cursor ) { + case Hand: + return "hand"; + case IBeam: + return "ibeam"; + case Wait: + return "wait"; + case Crosshair: + return "crosshair"; + case WaitArrow: + return "waitarrow"; + case SizeNWSE: + return "sizenwse"; + case SizeNESW: + return "sizenesw"; + case SizeWE: + return "sizewe"; + case SizeNS: + return "sizens"; + case SizeAll: + return "sizeall"; + case NoCursor: + return "nocursor"; + case Arrow: + case CursorCount: + return "arrow"; + } + return "arrow"; +} + const String::HashType& Cursor::getId() const { return mId; } diff --git a/src/tests/unit_tests/uihtml_float_tests.cpp b/src/tests/unit_tests/uihtml_float_tests.cpp index 8eb8405ba..ad1cdf699 100644 --- a/src/tests/unit_tests/uihtml_float_tests.cpp +++ b/src/tests/unit_tests/uihtml_float_tests.cpp @@ -650,6 +650,8 @@ UTEST( UIHTMLFloat, ss64BlockAnchorsAndSvgAtPixelDensity2 ) { ASSERT_TRUE( input != nullptr ); ASSERT_TRUE( searchButton != nullptr ); ASSERT_TRUE( svg != nullptr ); + EXPECT_EQ( searchButton->getCursor(), Cursor::Hand ); + EXPECT_EQ( svg->getCursor(), Cursor::Hand ); EXPECT_NEAR( input->getPixelsSize().getHeight(), 74.5f, 1.f ); EXPECT_NEAR( searchButton->getPixelsSize().getHeight(), 69.f, 1.f ); EXPECT_LE( searchButton->getPixelsPosition().y + searchButton->getPixelsSize().getHeight(), diff --git a/src/tests/unit_tests/uiscenenode_tests.cpp b/src/tests/unit_tests/uiscenenode_tests.cpp index aaf144b92..d3ceb0963 100644 --- a/src/tests/unit_tests/uiscenenode_tests.cpp +++ b/src/tests/unit_tests/uiscenenode_tests.cpp @@ -23,6 +23,8 @@ using namespace EE::UI; UTEST( UISceneNode, CssPointerCursorUsesHandCursor ) { EXPECT_EQ( Cursor::fromName( "pointer" ), Cursor::Hand ); EXPECT_EQ( Cursor::fromName( "POINTER" ), Cursor::Hand ); + EXPECT_STREQ( Cursor::toName( Cursor::Hand ), "hand" ); + EXPECT_STREQ( Cursor::toName( Cursor::Arrow ), "arrow" ); } static UISceneNode* init_test_scene_node() {