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.
This commit is contained in:
Martín Lucas Golini
2026-07-30 16:47:52 -03:00
parent 246161be7e
commit ae0c5ffef0
7 changed files with 57 additions and 4 deletions
+1 -1
View File
@@ -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 )
+15 -3
View File
@@ -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*> UIWidget::querySelectorAll( const std::string& selector )
return querySelectorAll( CSS::StyleSheetSelector( selector ) );
}
Cursor::Type UIWidget::getCursor() const {
return static_cast<Cursor::Type>( mCursor );
}
std::vector<PropertyId> 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<Uint8>( Cursor::fromName( attribute.getValue() ) );
if ( mSceneNode && getEventDispatcher() &&
getEventDispatcher()->getMouseOverNode() == this )
mSceneNode->setCursor( getCursor() );
break;
case PropertyId::BackgroundPositionX:
setBackgroundPositionX( attribute.value(), attribute.getIndex() );
+31
View File
@@ -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;
}
@@ -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(),
@@ -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() {