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?
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.
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).
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.
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.
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.
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;
}