Search Unity

Active/Selected styling

Discussion in 'UI Toolkit' started by cecarlsen, Jun 21, 2019.

  1. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    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?
     
  2. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    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: https://docs.unity3d.com/2019.3/Documentation/Manual/UIE-USS-Selectors.html under "Pseudo states".
     
    Last edited: Jun 28, 2019
    cecarlsen likes this.
  3. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    +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.
     
    RKar likes this.
  4. jonathanma_unity

    jonathanma_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    229
    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.
     
  5. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    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:
    Code (USS):
    1. #icon {
    2.     background-image: url(icon.png);
    3. }
    4. #icon:hover {
    5.     background-image: url(iconHover.png);
    6. }
    In C#/USS:
    Code (CSharp):
    1. button.style.backgroundImage = new Background(AssetDatabase.LoadAssetAtPath<Texture2D>(path));
    Code (USS):
    1. #icon:hover {
    2.     background-image: url(iconHover.png);
    3. }
    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.
     
    Nexer8 likes this.
  6. jonathanma_unity

    jonathanma_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    229
    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.
     
  7. mrSaig

    mrSaig

    Joined:
    Jan 14, 2010
    Posts:
    68
    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?
     
  8. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    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:
    Code (CSharp):
    1. #icon.icon--selected,
    2. #icon.icon--selected:hover {
    3.     background-image: url(iconSelected.png);
    4. }
    5.  
     
    RKar, DonLoquacious and mrSaig like this.
  9. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Should be possible to do this with pure c# without uss.
     
    Toolsmith, qcbf1 and mrSaig like this.
  10. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    You can track the hover state in c#, it's just way simpler in uss:
    Code (CSharp):
    1. icon.RegisterCallback<MouseEnterEvent>( (evt) => icon.style.backgroundImage = hoveredImage);
    2. icon.RegisterCallback<MouseLeaveEvent>( (evt) => icon.style.backgroundImage = normalImage);
    3.  
     
    DonLoquacious, Kamyker and mrSaig like this.
  11. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    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:
    Code (CSharp):
    1. myIcon.AddToClassList("icon--selected")
     
  12. Nexer8

    Nexer8

    Joined:
    Dec 10, 2017
    Posts:
    271
    Any updates on the C# Pseudo State API? It's been almost one year.
     
    Thimo_, qcbf1, MostHated and 3 others like this.
  13. digital-void

    digital-void

    Joined:
    May 7, 2018
    Posts:
    8
    @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...
     
    florianBrn, cecarlsen and Nexer8 like this.
  14. digital-void

    digital-void

    Joined:
    May 7, 2018
    Posts:
    8
    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:

    Code (CSharp):
    1. using System;
    2. using System.Reflection;
    3. using UnityEngine.UIElements;
    4.  
    5. namespace The.Dark.Side
    6. {
    7.     [Flags]
    8.     public enum PseudoStates
    9.     {
    10.         Active    = 1 << 0,
    11.         Hover     = 1 << 1,
    12.         Checked   = 1 << 3,
    13.         Disabled  = 1 << 5,
    14.         Focus     = 1 << 6,
    15.         Root      = 1 << 7,
    16.     }
    17.  
    18.     public static class UIUtilities
    19.     {
    20.         private static Action<VisualElement, PseudoStates>  s_SetPseudoStates;
    21.         private static Func<VisualElement, PseudoStates>    s_GetPseudoStates;
    22.         private static bool s_isSetup;
    23.  
    24.         public static void SetupPseudoStates()
    25.         {
    26.             Type veType = typeof(VisualElement);
    27.             string propName = "pseudoStates";
    28.             var propInfo = veType.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty);
    29.             Type psType = veType.Assembly.GetType("UnityEngine.UIElements.PseudoStates");
    30.             s_SetPseudoStates = (VisualElement ve, PseudoStates pseudoStates) =>
    31.             {
    32.                 propInfo.SetValue(ve, Enum.ToObject(psType, (int)pseudoStates));
    33.             };
    34.  
    35.             s_GetPseudoStates = (VisualElement ve) =>
    36.             {
    37.                 return (PseudoStates) (int) propInfo.GetValue(ve);
    38.             };
    39.  
    40.             s_isSetup = true;
    41.  
    42.         }
    43.  
    44.         public static PseudoStates GetPseudoStates(this VisualElement ve)
    45.         {
    46.             if ( !s_isSetup )
    47.             {
    48.                 SetupPseudoStates();
    49.             }
    50.             return s_GetPseudoStates(ve);
    51.         }
    52.  
    53.  
    54.         public static void SetPseudoStates(this VisualElement ve, PseudoStates pseudoStates)
    55.         {
    56.             if ( !s_isSetup )
    57.             {
    58.                 SetupPseudoStates();
    59.             }
    60.             s_SetPseudoStates(ve, pseudoStates);
    61.         }
    62.  
    63.         public static void AddPseudoState(this VisualElement ve, PseudoStates pseudoState)
    64.         {
    65.             if ( !s_isSetup )
    66.             {
    67.                 SetupPseudoStates();
    68.             }
    69.             var s = s_GetPseudoStates(ve);
    70.             s |= pseudoState;
    71.             s_SetPseudoStates(ve, s);
    72.         }
    73.  
    74.         public static void RemovePseudoState(this VisualElement ve, PseudoStates pseudoState)
    75.         {
    76.             if ( !s_isSetup )
    77.             {
    78.                 SetupPseudoStates();
    79.             }
    80.             var s = s_GetPseudoStates(ve);
    81.             s &= ~pseudoState;
    82.             s_SetPseudoStates(ve, s);
    83.         }
    84.  
    85.     }
    86. }
    87.  
    Usage is quite simple as it's an extension method to VisualElement
    Code (CSharp):
    1.  
    2. var ve = new VisualElement();
    3. ve.AddPseudoState(PseudoStates.Checked);
    4. //and
    5. ve.RemovePseudoState(PseudoStates.Checked);
    6.  
    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 ...
     
  15. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    You can get around this using css classes:
    Code (CSharp):
    1. .myToggleButton
    2. {
    3. background-color: blue;
    4. }
    5. .myToggleButton--checked
    6. {
    7. background-color: red;
    8. }
    9.  
    Then in c#
    Code (CSharp):
    1. class MyToggleButton
    2. {
    3.  
    4.   public SetChecked(bool value)
    5. {
    6.    EnableInClassList("myToggleButton--checked", value);
    7. )
    8. }
     
    Timboc likes this.
  16. detzt

    detzt

    Joined:
    Oct 6, 2018
    Posts:
    21
    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.
     
  17. Cameron_SM

    Cameron_SM

    Joined:
    Jun 1, 2009
    Posts:
    915
    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/do...e.versioning.requirespreviewfeaturesattribute
     
    Last edited: Jan 29, 2022
  18. Cameron_SM

    Cameron_SM

    Joined:
    Jun 1, 2009
    Posts:
    915
    @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.

    Code (CSharp):
    1. using System;
    2. using System.Reflection;
    3.  
    4. public static class ReflectionUtility
    5. {
    6.  
    7.     /// <summary>
    8.     /// Creates an untyped static delegate to get the property value. Result will need to be cast.
    9.     /// </summary>
    10.     /// <typeparam name="T">The type that declares the property you want to create a setter for.</typeparam>
    11.     /// <param name="propName">The name of the property in the declaring type.</param>
    12.     /// <returns>A func that can be given a target instance that will return the value for the property.</returns>
    13.     public static Func<TTarget, object> CreateStaticGetter<TTarget>(string propName) where TTarget : class
    14.     {
    15.         return CreateStaticGetter<TTarget>(typeof(TTarget).GetProperty(propName, propertyBindingFlags));
    16.     }
    17.  
    18.  
    19.     /// <summary>
    20.     /// Creates a static delegate to get the value of a property of an instance.
    21.     /// The delegate returned will get the property value boxed as an object.
    22.     /// </summary>
    23.     /// <typeparam name="TTarget">The type that declares the property you want to create a getter for.</typeparam>
    24.     /// <param name="propInfo">The property info of the desired type.</param>
    25.     /// <returns></returns>
    26.     public static Func<TTarget, object> CreateStaticGetter<TTarget>(PropertyInfo propInfo) where TTarget : class
    27.     {
    28.         // Make a method to call the boxed getter without explicit typing.
    29.         var helper = untypedPropGetterInfo.MakeGenericMethod(typeof(TTarget), propInfo.PropertyType);
    30.  
    31.         // Invoke and cast delegate.
    32.         return (Func<TTarget, object>)helper.Invoke(null, new object[] { propInfo });
    33.     }
    34.  
    35.     static Func<TTarget, object> CreateUntypedGetter<TTarget, TReturn>(PropertyInfo propInfo)
    36.     {
    37.         var getter = (Func<TTarget, TReturn>)Delegate.CreateDelegate(typeof(Func<TTarget, TReturn>), null, propInfo.GetMethod);
    38.         object boxedGetter(TTarget target) => getter(target); // implicit conversion of TReturn to object.
    39.         return boxedGetter;
    40.     }
    41.  
    42.  
    43.     /// <summary>
    44.     /// Creates an untyped static delegate to set the value of a property of an instance.
    45.     /// The delegate returned will not be type safe.
    46.     /// </summary>
    47.     /// <typeparam name="TTarget">The type that declares the property you want to create a setter for.</typeparam>
    48.     /// <param name="propName">The name of the property in the declaring type.</param>
    49.     /// <returns>An action that can be given a target instance and a value to set for the property.</returns>
    50.     public static Action<TTarget, object> CreateStaticSetter<TTarget>(string propName) where TTarget : class
    51.     {
    52.         return CreateStaticSetter<TTarget>(typeof(TTarget).GetProperty(propName, propertyBindingFlags));
    53.     }
    54.  
    55.  
    56.     /// <summary>
    57.     /// Creates an untyped static delegate to set the value of a property of an instance.
    58.     /// The delegate returned will not be type safe.
    59.     /// </summary>
    60.     /// <typeparam name="TTarget">The type that declares the property you want to create a setter for.</typeparam>
    61.     /// <param name="propInfo">The property info of the desired type.</param>
    62.     /// <returns>An action that can be given a target instance and a value to set for the property.</returns>
    63.     public static Action<T, object> CreateStaticSetter<T>(PropertyInfo propInfo) where T : class
    64.     {
    65.         // Make a method to call the boxed setter without explicit typing.
    66.         var helper = untypedPropSetterInfo.MakeGenericMethod(typeof(T), propInfo.PropertyType);
    67.         var d = helper.Invoke(null, new object[] { propInfo });
    68.         // Invoke and cast delegate.
    69.         return (Action<T, object>)d;
    70.     }
    71.  
    72.     static Action<TTarget, object> CreateUntypedSetter<TTarget, TParam>(PropertyInfo propInfo)
    73.     {
    74.         var setter = (Action<TTarget, TParam>)Delegate.CreateDelegate(typeof(Action<TTarget, TParam>), propInfo.SetMethod);
    75.         void boxedSetter(TTarget target, object param) => setter(target, (TParam)param); // explicitly casts param type when invoking
    76.         return boxedSetter;
    77.     }
    78. }
    I use it in extension methods class as follows (same namespace as unity)
    Code (CSharp):
    1.  
    2. using System;
    3.  
    4. namespace UnityEngine.UIElements
    5. {
    6.     [Flags]
    7.     public enum PseudoStates
    8.     {
    9.         Active    = 1 << 0,
    10.         Hover     = 1 << 1,
    11.         Checked   = 1 << 3,
    12.         Disabled  = 1 << 5,
    13.         Focus     = 1 << 6,
    14.         Root      = 1 << 7,
    15.     }
    16.  
    17.     public static class VisualElementExtensions
    18.     {
    19.    
    20.         static readonly Action<VisualElement, object> pseudoStatesSetter;
    21.         static readonly Func<VisualElement, object> pseudoStatesGetter;
    22.         static readonly string pseudoStatesPropName = "pseudoStates";
    23.  
    24.         static VisualElementExtensions()
    25.         {
    26.             pseudoStatesSetter = ReflectionUtility.CreateStaticSetter<VisualElement>(pseudoStatesPropName);
    27.             pseudoStatesGetter = ReflectionUtility.CreateStaticGetter<VisualElement>(pseudoStatesPropName);
    28.         }
    29.  
    30.         /// <summary>
    31.         /// Retrives the PseudoStates flags currently set on the element.
    32.         /// </summary>
    33.         public static PseudoStates GetPseudoStates(this VisualElement v) => (PseudoStates)pseudoStatesGetter(v);
    34.  
    35.         /// <summary>
    36.         /// Sets the active PseudoStates flags for the element.
    37.         /// </summary>
    38.         public static void SetPseudoStates(this VisualElement v, PseudoStates s) => pseudoStatesSetter(v, s);
    39.  
    40.         /// <summary>
    41.         /// Adds a PseudoStates flag to the element.
    42.         /// </summary>
    43.         public static void AddPseudoState(this VisualElement v, PseudoStates s)
    44.         {
    45.             var states = ((PseudoStates)pseudoStatesGetter(v)).SetFlags(s);
    46.             pseudoStatesSetter(v, states);
    47.         }
    48.  
    49.         /// <summary>
    50.         /// Removes a PseudoStates flag from the element.
    51.         /// </summary>
    52.         public static void RemovePseudoState(this VisualElement v, PseudoStates s)
    53.         {
    54.             var currentStates = (PseudoStates)pseudoStatesGetter(v);
    55.             var newStates =  currentStates.ClearFlags(s);
    56.             pseudoStatesSetter(v, newStates);
    57.         }
    58.  
    59.         /// Below pulled from Enum extensions class
    60.         public static T SetFlags<T>(this T value, T flags) where T : struct, IFormattable, IConvertible, IComparable
    61.         {
    62.             return value.SetFlags(flags, true);
    63.         }
    64.  
    65.  
    66.         public static T ClearFlags<T>(this T value, T flags) where T : struct, IFormattable, IConvertible, IComparable
    67.         {
    68.             return value.SetFlags(flags, false);
    69.         }
    70.  
    71.         static T SetFlags<T>(this T self, T flags, bool value)
    72.            where T : struct, IFormattable, IConvertible, IComparable
    73.         {
    74.             Debug.Assert(IsEnum<T>(true));
    75.             Debug.Assert(self.GetType() == flags.GetType());
    76.             var selfAsLong = Convert.ToInt64(self);
    77.             var flagAsLong = Convert.ToInt64(flags);
    78.             if (value)
    79.             {
    80.                 selfAsLong |= flagAsLong;
    81.             }
    82.             else
    83.             {
    84.                 selfAsLong &= ~flagAsLong;
    85.             }
    86.             return (T)Enum.ToObject(typeof(T), selfAsLong);
    87.         }
    88.     }
    89. }
    90.  
    Edit: Copied in the wrong methods, fixed now.
     
    Last edited: Feb 1, 2022
    PhantasmicDev and Nexer8 like this.
  19. Nexer8

    Nexer8

    Joined:
    Dec 10, 2017
    Posts:
    271
    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.
     
  20. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    @Nexer8 Can you file an issue with Help -> Report a Bug? We'll get on it.
     
    Nexer8 likes this.
  21. Nexer8

    Nexer8

    Joined:
    Dec 10, 2017
    Posts:
    271
    Done. Case 1406988.
     
  22. felipemullen

    felipemullen

    Joined:
    Mar 4, 2017
    Posts:
    44
    Just wanted to chime in that in 2022 there are many pseudo-classes supported


    Also, I humbly (though strongly) disagree that it is easier to change your styling by writing a monobehaviour with managed mouse events, when all you need is a single uss selector. To each their own.
     
  23. RiseBasti

    RiseBasti

    Joined:
    Nov 4, 2018
    Posts:
    33
    It's mid-2023 and we still can't set pseudo-classes ourselves? The solution provided for adding a "--active" class is a poor workaround and doesn't fit all needs.
    I have manipulator for drag&drop and I'd like to make it as generic as possible. Predefining an active class limits the ability to define this in uss.

    Or am I missing something here?
     
  24. PhantasmicDev

    PhantasmicDev

    Joined:
    Jul 10, 2013
    Posts:
    35
    Any update on this behavior? Im currently making controls for mobile such as a Touch Joysticks and a Touch Button (with different behaviors than the regular button) and I'd like to style these controls in the UI Builder using a style sheet, but in my C# code I cannot change the pseudo state of a control.

    I'd be cool with a generic Manipulator class that handles changing the internal pseudo states of a VisualElement if changing pseudo states is a delicate process but ideally it'd be nice to make the pseudoStates property in VisualElement a protected property instead of internal.