Is there a native way to style active/selected elements (for example like in CSS “className:active”) … or should I manually add and remove a “selected” style from CSS inside a manipulator using AddToClassList and RemoveFromClassList?
Hello,
This is technically supported from the style sheets but there is no public API to set those states (pseudo states). Right now you’ll have to use the class list to achieve what you need.
However if you use the Clickable
manipulator it will set the “:active” state.
The full list of existing pseudo states is detailed here: Unity - Manual: USS selectors under “Pseudo states”.
+1 to add public API.
Not sure if bug but after setting backgroundImage in C# the background-image in “:hover” state defined in uss stops working.
Hi Kamyker, this is expected behavior since setting style with C# is considered “inline”, this means that they have the highest specificity and cannot be set in USS afterwards.
Yes but I’m not setting any value for :hover state in C# as it’s not even possible. Not sure if I was clear enough.
In USS:
#icon {
background-image: url(icon.png);
}
#icon:hover {
background-image: url(iconHover.png);
}
In C#/USS:
button.style.backgroundImage = new Background(AssetDatabase.LoadAssetAtPath<Texture2D>(path));
#icon:hover {
background-image: url(iconHover.png);
}
Is it because setting button.style.backgroundImage clears info for all the states? If so then it’s pretty weird and could be split to button.styles[PseudoStates.Hover] etc.
When you do this this basically take over all USS. The element is assigned this background image and will keep it unless you change it from C#. Even though “#icon:hover” will match the style set from C# wins because it has higher specificity…
If you remove that line it should work as expected.
Yeah a public api for this would be nice … just wanted to set some kind of visual feedback on a button to show that it is selected!
any Workaround for that?
in c#, you can Add/Remove classnames on your button and drive the styling from uss:
From c#: myIcon.AddToClassList("icon--selected")
and in uss:
#icon.icon--selected,
#icon.icon--selected:hover {
background-image: url(iconSelected.png);
}
Should be possible to do this with pure c# without uss.
You can track the hover state in c#, it’s just way simpler in uss:
icon.RegisterCallback<MouseEnterEvent>( (evt) => icon.style.backgroundImage = hoveredImage);
icon.RegisterCallback<MouseLeaveEvent>( (evt) => icon.style.backgroundImage = normalImage);
I like this more than uss and find it simpler than c#/uss. Especially as it doesn’t use string references.
Code below breaks after uss class name change, can’t use intellisense code completion, prone to typos etc:
myIcon.AddToClassList("icon--selected")
Any updates on the C# Pseudo State API? It’s been almost one year.
@uMathieu
I’ve got to chime in…
How are we supposed to write custom controls if such core features are kept internal - and no API is provided?
Especially hiding away the visualInput and PseudoStates …
If it’s not intended to be extended, I recon you should definitely say so in the docs.
If I’d known beforehand, I would have vetoed using UIElements in our project.
Imho keeping an UI System closed is a real killjoy…
4wiw - I needed :checked for a ToggleButton - (not a toggle with a checkbox)
Having no possibility to set :checked made this impossible - so … so I went to the Dark Side … and used reflections…
Those of you who really really need it - here it is…
But before you open this - it’s like copying a save game of a rouge-like … you’ll never feel clean again afterwards…
You have been warned:
using System;
using System.Reflection;
using UnityEngine.UIElements;
namespace The.Dark.Side
{
[Flags]
public enum PseudoStates
{
Active = 1 << 0,
Hover = 1 << 1,
Checked = 1 << 3,
Disabled = 1 << 5,
Focus = 1 << 6,
Root = 1 << 7,
}
public static class UIUtilities
{
private static Action<VisualElement, PseudoStates> s_SetPseudoStates;
private static Func<VisualElement, PseudoStates> s_GetPseudoStates;
private static bool s_isSetup;
public static void SetupPseudoStates()
{
Type veType = typeof(VisualElement);
string propName = "pseudoStates";
var propInfo = veType.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty);
Type psType = veType.Assembly.GetType("UnityEngine.UIElements.PseudoStates");
s_SetPseudoStates = (VisualElement ve, PseudoStates pseudoStates) =>
{
propInfo.SetValue(ve, Enum.ToObject(psType, (int)pseudoStates));
};
s_GetPseudoStates = (VisualElement ve) =>
{
return (PseudoStates) (int) propInfo.GetValue(ve);
};
s_isSetup = true;
}
public static PseudoStates GetPseudoStates(this VisualElement ve)
{
if ( !s_isSetup )
{
SetupPseudoStates();
}
return s_GetPseudoStates(ve);
}
public static void SetPseudoStates(this VisualElement ve, PseudoStates pseudoStates)
{
if ( !s_isSetup )
{
SetupPseudoStates();
}
s_SetPseudoStates(ve, pseudoStates);
}
public static void AddPseudoState(this VisualElement ve, PseudoStates pseudoState)
{
if ( !s_isSetup )
{
SetupPseudoStates();
}
var s = s_GetPseudoStates(ve);
s |= pseudoState;
s_SetPseudoStates(ve, s);
}
public static void RemovePseudoState(this VisualElement ve, PseudoStates pseudoState)
{
if ( !s_isSetup )
{
SetupPseudoStates();
}
var s = s_GetPseudoStates(ve);
s &= ~pseudoState;
s_SetPseudoStates(ve, s);
}
}
}
Usage is quite simple as it’s an extension method to VisualElement
var ve = new VisualElement();
ve.AddPseudoState(PseudoStates.Checked);
//and
ve.RemovePseudoState(PseudoStates.Checked);
I used the same name for the flags enum - so that if - one day in the bright future - pseudostates will be public or protected - it’ll be easy to just remove this hacky reflections and use the extension methods with the real stuff
So this would be the only File that would need some adjustments …
You can get around this using css classes:
.myToggleButton
{
background-color: blue;
}
.myToggleButton--checked
{
background-color: red;
}
Then in c#
class MyToggleButton
{
public SetChecked(bool value)
{
EnableInClassList("myToggleButton--checked", value);
)
}
For the Toggle
element, there is a working :checked
uss class.
If you don’t want a Checkbox, you can hide it with display: none
, add further elements as children of the Toggle, or style it however you like.
If a dev is diving this deep into UIElements they already know they can hack around this limitation using classes - the issue is we shouldn’t have to because it creates extra overheads and technical debt down the chain where we have to support and educate designers/artists that in order to style some UI elements they need to use :checked but on others they have to use –checked instead.
Maybe that shouldn’t be be the officially recommended solution by unity.
I know there’s overhead and cost on Unity’s end to making anything public but in this case you’re kinda pushing the burden of maintaining documentation and caveats onto your customers.
Maybe when Unity progress to .Net 6 you could consider making less of the API internal but instead decorating with [RequiresPreviewFeatures] and making it so developers must opt-in to using said decorated APIs via the editor (which would cause the C# assemblies Unity generates for user code to have the required flag to use APIs decorated with that attribute). Developers then know they take on a risk that the API surface may change shape or disappear completely and you can gather metrics on usage from cloud build repos to prioritise what parts of the API should be officially made public and have documentation added. Details here: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.versioning.requirespreviewfeaturesattribute
@digital-void I did the same. You can speed that reflected code up by an order of 600x if you use Delegate.CreateDelegate to generate actions and funcs instead of using the slow reflection invoke. I’ve included some of the methods from my reflection utility class below to demonstrate:
Please note: Only tested on Mono scripting backend. IL2CPP and AOT compiled runtimes sometimes makes these hacks perform worse than standard reflection so profile the changes if your builds are configured that way.
using System;
using System.Reflection;
public static class ReflectionUtility
{
/// <summary>
/// Creates an untyped static delegate to get the property value. Result will need to be cast.
/// </summary>
/// <typeparam name="T">The type that declares the property you want to create a setter for.</typeparam>
/// <param name="propName">The name of the property in the declaring type.</param>
/// <returns>A func that can be given a target instance that will return the value for the property.</returns>
public static Func<TTarget, object> CreateStaticGetter<TTarget>(string propName) where TTarget : class
{
return CreateStaticGetter<TTarget>(typeof(TTarget).GetProperty(propName, propertyBindingFlags));
}
/// <summary>
/// Creates a static delegate to get the value of a property of an instance.
/// The delegate returned will get the property value boxed as an object.
/// </summary>
/// <typeparam name="TTarget">The type that declares the property you want to create a getter for.</typeparam>
/// <param name="propInfo">The property info of the desired type.</param>
/// <returns></returns>
public static Func<TTarget, object> CreateStaticGetter<TTarget>(PropertyInfo propInfo) where TTarget : class
{
// Make a method to call the boxed getter without explicit typing.
var helper = untypedPropGetterInfo.MakeGenericMethod(typeof(TTarget), propInfo.PropertyType);
// Invoke and cast delegate.
return (Func<TTarget, object>)helper.Invoke(null, new object[] { propInfo });
}
static Func<TTarget, object> CreateUntypedGetter<TTarget, TReturn>(PropertyInfo propInfo)
{
var getter = (Func<TTarget, TReturn>)Delegate.CreateDelegate(typeof(Func<TTarget, TReturn>), null, propInfo.GetMethod);
object boxedGetter(TTarget target) => getter(target); // implicit conversion of TReturn to object.
return boxedGetter;
}
/// <summary>
/// Creates an untyped static delegate to set the value of a property of an instance.
/// The delegate returned will not be type safe.
/// </summary>
/// <typeparam name="TTarget">The type that declares the property you want to create a setter for.</typeparam>
/// <param name="propName">The name of the property in the declaring type.</param>
/// <returns>An action that can be given a target instance and a value to set for the property.</returns>
public static Action<TTarget, object> CreateStaticSetter<TTarget>(string propName) where TTarget : class
{
return CreateStaticSetter<TTarget>(typeof(TTarget).GetProperty(propName, propertyBindingFlags));
}
/// <summary>
/// Creates an untyped static delegate to set the value of a property of an instance.
/// The delegate returned will not be type safe.
/// </summary>
/// <typeparam name="TTarget">The type that declares the property you want to create a setter for.</typeparam>
/// <param name="propInfo">The property info of the desired type.</param>
/// <returns>An action that can be given a target instance and a value to set for the property.</returns>
public static Action<T, object> CreateStaticSetter<T>(PropertyInfo propInfo) where T : class
{
// Make a method to call the boxed setter without explicit typing.
var helper = untypedPropSetterInfo.MakeGenericMethod(typeof(T), propInfo.PropertyType);
var d = helper.Invoke(null, new object[] { propInfo });
// Invoke and cast delegate.
return (Action<T, object>)d;
}
static Action<TTarget, object> CreateUntypedSetter<TTarget, TParam>(PropertyInfo propInfo)
{
var setter = (Action<TTarget, TParam>)Delegate.CreateDelegate(typeof(Action<TTarget, TParam>), propInfo.SetMethod);
void boxedSetter(TTarget target, object param) => setter(target, (TParam)param); // explicitly casts param type when invoking
return boxedSetter;
}
}
I use it in extension methods class as follows (same namespace as unity)
using System;
namespace UnityEngine.UIElements
{
[Flags]
public enum PseudoStates
{
Active = 1 << 0,
Hover = 1 << 1,
Checked = 1 << 3,
Disabled = 1 << 5,
Focus = 1 << 6,
Root = 1 << 7,
}
public static class VisualElementExtensions
{
static readonly Action<VisualElement, object> pseudoStatesSetter;
static readonly Func<VisualElement, object> pseudoStatesGetter;
static readonly string pseudoStatesPropName = "pseudoStates";
static VisualElementExtensions()
{
pseudoStatesSetter = ReflectionUtility.CreateStaticSetter<VisualElement>(pseudoStatesPropName);
pseudoStatesGetter = ReflectionUtility.CreateStaticGetter<VisualElement>(pseudoStatesPropName);
}
/// <summary>
/// Retrives the PseudoStates flags currently set on the element.
/// </summary>
public static PseudoStates GetPseudoStates(this VisualElement v) => (PseudoStates)pseudoStatesGetter(v);
/// <summary>
/// Sets the active PseudoStates flags for the element.
/// </summary>
public static void SetPseudoStates(this VisualElement v, PseudoStates s) => pseudoStatesSetter(v, s);
/// <summary>
/// Adds a PseudoStates flag to the element.
/// </summary>
public static void AddPseudoState(this VisualElement v, PseudoStates s)
{
var states = ((PseudoStates)pseudoStatesGetter(v)).SetFlags(s);
pseudoStatesSetter(v, states);
}
/// <summary>
/// Removes a PseudoStates flag from the element.
/// </summary>
public static void RemovePseudoState(this VisualElement v, PseudoStates s)
{
var currentStates = (PseudoStates)pseudoStatesGetter(v);
var newStates = currentStates.ClearFlags(s);
pseudoStatesSetter(v, newStates);
}
/// Below pulled from Enum extensions class
public static T SetFlags<T>(this T value, T flags) where T : struct, IFormattable, IConvertible, IComparable
{
return value.SetFlags(flags, true);
}
public static T ClearFlags<T>(this T value, T flags) where T : struct, IFormattable, IConvertible, IComparable
{
return value.SetFlags(flags, false);
}
static T SetFlags<T>(this T self, T flags, bool value)
where T : struct, IFormattable, IConvertible, IComparable
{
Debug.Assert(IsEnum<T>(true));
Debug.Assert(self.GetType() == flags.GetType());
var selfAsLong = Convert.ToInt64(self);
var flagAsLong = Convert.ToInt64(flags);
if (value)
{
selfAsLong |= flagAsLong;
}
else
{
selfAsLong &= ~flagAsLong;
}
return (T)Enum.ToObject(typeof(T), selfAsLong);
}
}
}
Edit: Copied in the wrong methods, fixed now.
This breaks when using mouse capture. If you capture the mouse, move it over an element, then release the mouse, the element will not know it is being hovered. MouseEnter is never called.
What should happen is MouseEnter being called “late” when the mouse is released. This would also match web behavior, where pointer states “catch up” on pointer release.
@Nexer8 Can you file an issue with Help → Report a Bug? We’ll get on it.