Search Unity

  1. Unity 2020.1 has been released.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Feature Request Custom UXML Attributes

Discussion in 'UI Toolkit' started by BinaryCats, Aug 2, 2020.

  1. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    274
    Hello,

    I would like to propose a way to define my own Attributes for VisualElements that I have little to no control of (i.e. stock VisualElements, and those that may be created by content creators).

    The problem
    Currently if I want to extend the functionality of a VisualElement I would have to create my own VisualElement which inherits from the original with the new functionality. This increases the complexity of creating UI as there will be a lot of duplicate VisualElements with near similar names and functionality.

    The Solution
    I propose a similar the Custom Property Drawer, in which you define the valid VisualElement type and the name of the Attribute.
    [CustomUXMLAttribute(Typeof(UnityEngine.UIElements.Label), "format")]


    The class to which the attribute is donned upon, will handle the same functions as UxmlTraits for the VisualElement (in the above example label). I suspect this should inherit from a given class/interface which defines the required function calls.

    Code (CSharp):
    1.  
    2. [CustomUXMLAttribute(Typeof(UnityEngine.UIElements.Label), "format")]
    3. public new class FormatCustomUxmlTraits : CustomUxmlTraits
    4.     {
    5.         UxmlStringAttributeDescription AttributeDescription => new UxmlStringAttributeDescription { name = "format" };
    6.        
    7.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    8.         {
    9.              var value = AttributeDescription.GetValueFromBag(bag, cc);
    10.              if(value == "toLower")
    11.                   (UnityEngine.UIElements.Label)ve.text = (UnityEngine.UIElements.Label)ve.text.ToLower();
    12.         }
    13.     }
    14. }
    Here is an example of a CustomUXMLAttribute which mutates the text string of a label depending on what the value is set to.
    The UXML would be defined:
    Code (UXML):
    1. <engine:Label text="TeXt" format = toLower/>

    I hope you enjoy and consider my proposal. I have purposefully tried to make the example as simple as possible, I am aware the user could just define the text as lowercase. The use case, which lead me to this feature request would be more advanced which would include fetching and processing data which would then mutate values of VisualElements.

    Maybe the init of
    CustomUxmlTraits
    could just pass the value into it, as the name is already defined in the attribute parameters, I just wanted to keep the whole system familiar to the current coding style :)

    Thank you in advance
     
  2. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    274
    Revision 1
    After gathering some feedback from colleagues and friends; I want to change the proposal so
    CustomUXMLAttribute
    no longer defines the attribute name in the parameters. Instead, once defined the factory would call the
    CustomUXMLAttribute
    (for the given types), the same way it does to the UxmlTraits.

    This would enable you to check multiple custom attributes from one
    CustomUxmlTraits

    Code (CSharp):
    1. [CustomUXMLAttribute(Typeof(UnityEngine.UIElements.Label))]
    2. public new class FormatCustomUxmlTraits : CustomUxmlTraits
    3.     {
    4.         UxmlStringAttributeDescription FormatAttributeDescription => new UxmlStringAttributeDescription { name = "format" };
    5.         UxmlStringAttributeDescription AttributeDescription => new UxmlStringAttributeDescription { name = "maxLength" };
    6.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    7.         {
    8.              var value = FormatAttributeDescription.GetValueFromBag(bag, cc);
    9.              if(value == "toLower")
    10.                   (UnityEngine.UIElements.Label)ve.text = (UnityEngine.UIElements.Label)ve.text.ToLower();
    11.          
    12.            var value2 = AttributeDescription.GetValueFromBag(bag, cc);
    13.              if(value.Length >  value2)
    14.                     (UnityEngine.UIElements.Label)ve.text = (UnityEngine.UIElements.Label)ve.text.subString(0,value2);
    15.  
    16.  
    17.         }
    18.     }
    19. }

    I would also like to highlight that although my example adds a CustomXMLAttribute for a Label, if say I defined it as
    [CustomUXMLAttribute(Typeof(UnityEngine.UIElements.TextElement))]
    it would work for a all TextElement derived fields i.e. Label, Button, RepeatButton, ToolbarMenu, PopupWindow, ToolbarButton. Without my proposal, to handle Custom UXML Attributes it means you would have to define 6 custom Visual Elements, rather than just defining one CustomUXMLAttribute!
     
    Last edited: Aug 3, 2020
  3. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    859
    It's an interesting proposal, and we'll keep it in mind as refine the UxmlTraits API. However, we are also looking at other solutions for defining UXML Attributes that may change this pattern. It's not an ideal pattern and we've learned while working on the UI Builder that it's difficult to examine these attributes and modify them after the element has been created.

    So with that said, I'm curious to know more about the more complex uses cases you had in mind to see if there are alternate approaches that we could suggest. There are other options besides inheriting from an element if you want to augment the element.
     
  4. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    274
    Thanks for getting back to me uDamian. I'll be keen to know what the other solutions to define UXML Attributes will look like; I'll be sure to keep my eye out for changes, and update the proposal if needs be.

    To get specific to my needs, I am working on a Localisation plugin which extends functionality of the "standard" ingame text components. UIToolkit may not be "production" ready for ingame UI right now but (after building the plugin's interface with UItoolkit) I want to board the UIToolkit runtime train as soon as possible as I think it's the right horse to bet on :)

    I wish for my users to be able to define an asset (i.e. ScriptableObject) within the UXML, this asset will serve as a replacement for the text attribute. I then wish some code to load the asset, "grab the correct text" (which may be determined by other UXML attributes on the same element) and set the text on that Visual element - This should be done when the element is created via the factory.

    I foresee two hurdles with this plan:
    1. How does the user reference the asset? (what happens if it isnt loaded?)
    2. This could be done if I make custom Visual Elements for all the "Text elements" but this doesn't scale well, it has duplicated code and doesnt allow my users to create their own custom "Text Elements" which would just work with my plugin
    #1 is difficult, but I feel it is a solvable problem.

    #2 is what lead me to write this proposal; having a smooth and generic way to alter the "text attribute" of "Text Elements" would be the best workflow I could offer my users.

    I understand that this use case is very specific to me, however; I really do see this feature being on par with Custom Property Drawers in terms of expandability and usefulness to the entire UToolkit community!

    If you can think of any suggestions, or maybe different approaches I haven't thought about I would be glad to hear them :)
     
unityunity