Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    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:
    317
    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
     
    Last edited: Mar 12, 2021
    redwyre and Xarbrough like this.
  2. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    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:
    1,231
    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.
     
    RKar likes this.
  4. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    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 :)
     
    RKar likes this.
  5. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Well, Localization is definitely not something specific to you. We know it's an area we need to focus on soon. Our initial thoughts on the subject involve using the bindings system instead of custom UXML attributes to push localization strings onto elements. This means, in UXML, you would just use regular English (or another default language) for the text attributes, give each element you wish to localize a
    binding-path
    attribute that would be the "localization id" of the string in the localization backend.

    You can already sort do this with existing public APIs in the Editor using the Editor's serialization system (based on ScriptableObject) along with UI Toolkit's existing bindings system. But of course, you need this at runtime so for that we don't have much built-in.

    However, the approach I describe could be implemented on top the universal
    binding-path
    attribute that exists on all VisualElements. You could implement your own binding system that looks for specially named elements, looks at their binding-path, and sets their text property accordingly. The benefit of this approach is that the complexity can stay at a root-level element instead of trying to add smarts to many different types of elements - so the custom VisualElement approach starts to make more sense.
     
  6. RKar

    RKar

    Joined:
    Mar 6, 2019
    Posts:
    22
    It is not convenient to fill out. I use a composite key: root-window-login, root-profile-login. I would like to make a window for defining keys. It would list all the text fields for the selected UXML asset. And user can select a localization key for each. But it looks like I can't change the original asset from the outside.
     
  7. MoruganKodi

    MoruganKodi

    Joined:
    Feb 11, 2015
    Posts:
    79
    Would the XName/XAttribute approach of System.Xml.Linq not be suitable? ( assuming the value of the attribute is always a string)
     
  8. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    @uDamian how did all this turn out? The last post is from 3 years ago...

    In 2022.x LTS is it possible to create attributes on an existing control (built-in) without having to create a custom control inheriting from each one?

    My case is very similar, I would like that to a Label I can put an attribute "localization-key" (or something similar) and that the code of the custom attribute obtains from the scene somehow a MonoBehaviour that is the one that has the data with the texts and passing that key it returns me the text.

    Is it possible to do something like that? With uGUI we had something similar with a MonoBehaviour with a field of type string (for the key) that we added to a gameObject with a label of ugui/textmeshpro that was in charge to manage changes of language etc of automatic maner and to assign the corresponding text.
     
    achimmihca likes this.
  9. achimmihca

    achimmihca

    Joined:
    Feb 13, 2016
    Posts:
    279
    I am also looking for a way to associate VisualElements with custom data.

    For example, HTML provides data attributes:

    > HTML is designed with extensibility in mind for data that should be associated with a particular element but need not have any defined meaning.

    Data attributes are used (among others) by UI frameworks such as Bootstrap.

    The very same could be done for UXML.
    Such a simple thing. But it would make UI Toolkit more versatile, more adaptable for custom use cases and implementations.
     
    bdovaz likes this.
  10. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    Bump @uDamian @HugoBD-Unity @SimonDufour

    Answer, please.

    Thanks!
     
  11. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Hi! There has indeed been an update on this topic: new runtime bindings system. Here's the main doc for the feature:
    https://docs.unity3d.com/2023.2/Documentation/Manual/UIE-get-started-runtime-binding.html

    While you still won't be able to simply add "a data component" onto an existing element type, what you could do instead is use custom bindings to bind custom data to an element without having to create a new element type:
    https://docs.unity3d.com/2023.2/Documentation/Manual/UIE-runtime-binding-custom-types.html

    And more video-first intro to binding was made for the last Unite:


    Hope that helps.
     
    spiney199 and bdovaz like this.
  12. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    @uDamian so in 2022.x LTS we have to continue to inherit from a control no? Thanks.