Set pseudo-state style from C# script

I know I can override the style applied to a VisualElement by a USS stylesheet with an inline style from script, e.g.,

this.style.backgroundColor = UnityEngine.Color.blue;

I also know that I can reset an inline style by calling

this.style.backgroundColor = StyleKeyword.Null;

How do I do the same for the, e.g., :hover pseudo state?

Hi!
At the moment, there isn’t a public way to get or set the pseudo state on an element.

The workaround is to use classes instead if you need to diverge from the way hover/checked/focus work right now.

This way I would have to refactor a lot of code, which would will take several hours, so I’m wondering: Is there a way to customize the behavior in another way, e.g., with registering to UIToolkit events?

What would be the correct events to listen to (I’m thinking of PointerEnter and PointerLeave, maybe?)?

And what would be the correct timing to ensure that all currently applied uss styles are already resolved and only then the custom styles in C# are applied?

Oh, sorry, I misunderstood your question. To set the style “on hover” from code, yes, you will need to register to some events, namely:

This is done automatically for you: styles are always resolved and then, if a style property was set from code, it will take precedence. I would avoid setting the styles when receiving a GeometryChangedEvent, as those could lead to having to do multiple passes on the layout.

Hope this helps!

Thank you for your help!

This does not completely solve my issue about the timing. In my code I set the background-color property to some value from a USS stylesheet by adding a class to it. In the next line I modify the alpha value (dynamic property, independent of the color class and can’t be done in the stylesheet).

A.AddToClassList("red-bg");
var bgColor = A.resolvedStyle.background;
bgColor.a = 0.25f;
A.style.backgroundColor = bgColor;

However, at that point (2nd line ff) the style is not yet resolved.

Is there a StyleResolved event (or sth equivalent) I can listen to and where I can execute the last three lines in the callback?

element.resolvedStyle will include what is written to the element.style properties. So if we had an event where you change the style based on the resolvedStyle, you would probably end up with the wrong value in the end. To make this work, you would need access to element.computedStyle, but that is not exposed.

I believe the styles themselves should be resolved by the time LateUpdate() is called.

Any updates?

I could work around this by just using my own styles instead of pseudo-styles for when it’s relevant, but it seems like some things like pseudo-styles are buried deep inside the default runtime theme, and matching them will be clunky.

1 Like

Sadly, no updates, but we’re still aware that it is needed.

Any hints for how we could hack it? Who owns the hovered state? Is it in the event system, or in the panel, or in the visual element itself, or?

The pseudo-states are owned by the visual elements and can be changed for many reasons. For most of the pointer related states, it is done through the event system. In the case of the collection views, it is usually manually driven because a different visual element can be used the a given list item and we need to be able to transfer the pseudo-state correctly. There are other use-cases as well.

As to how to hack it, I usually just go with my own styles and accept the downsides you mentioned. If I wanted to change that, I would probably use reflection to get/set the pseudo-states. In cost cases, it should be fine.

1 Like

I’ve created a reflection helper to handle this problem.

Though I will say it is a serious problem. Take for example the Clickable Manipulator or the Abstract PointerManipulator.
These are public classes you’d hope to be able to extend, but so much of their internal logic depends on directly editing the pseudo-state. Of all the features in UI Toolkit that are internal and should be exposed this is one of the worst offenders.

I’ve thrown in my helpers to not just be complaining. They could use some optimization, but this is a good place to start.

public static int GetPsuedoState(this VisualElement element)
{                       
    return (int)element.GetType().GetProperty("pseudoStates", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(element);
}

public static void AddPsuedoState(this VisualElement element, int state)
{
    int result = element.GetPsuedoState() | state;
    var enumType = element.GetType().GetProperty("pseudoStates", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(element).GetType();
    if (enumType != null && enumType.IsEnum)
    {
        object enumValue = Enum.ToObject(enumType, result);
        element.GetType().GetProperty("pseudoStates", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(element, enumValue);
    }
    else
    {
        Debug.Log("pseudoStates is not enum");
    }
}

public static void RemovePsuedoState(this VisualElement element, int state)
{
    int result = element.GetPsuedoState();
    result &= ~state;
    var enumType = element.GetType().GetProperty("pseudoStates", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(element).GetType();
    if (enumType != null && enumType.IsEnum)
    {
        object enumValue = Enum.ToObject(enumType, result);
        element.GetType().GetProperty("pseudoStates", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(element, enumValue);
    }
    else
    {
        Debug.Log("pseudoStates is not enum");
    }
}

public static bool HasPseudoFlag(this VisualElement element, int flag)
{
    int result = element.GetPsuedoState();
    return (result & flag) == flag;
}
1 Like