Search Unity

General feedback on things I found when playing a bit with UIElements

Discussion in 'UI Toolkit' started by bdovaz, Apr 27, 2019.

  1. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    @uDamian

    When I use the window to create the 3 files (.cs, .uss and .uxml) there are two "bugs":
    - It creates the *.cs without respecting the project line ending settings. I get UNIX line ending and I have configured to OS Native.
    - It requires to be inside a "Editor" folder but if I'm using *.asmdef files it should be required.

    Is it possible to embed a *.uss (or inline styles) inside a *.uxml file?

    Is it possible to hide a VisualElement entirely? Using "visible" property only make it "hidden", not "collapsed". I workaround it changing "maxHeight" to 0 but it's really ugly.

    I tried to migrate a custom editor window from IMGUI using GUILayout to UIElements but I see that with IMGUI there is a default margin that it's not applied in UIElements:
    - A margin between the window edges and controls.
    - A margin between controls on a horizontal or vertical layout group.

    That's okay but we need to know that behaviour change so we can adjust it and make a 1:1 conversion applying the required margin.

    I think that you should add more examples if you want developers to switch from IMGUI to UIElements.

    Thinking on the future runtime implementation:
    - Is it or will be possible to register callbacks on *.uxml files? Like WPF commands and command parameter bindings. https://wpf.2000things.com/2014/06/09/1089-adding-a-parameter-to-a-command/
    - How "powerful" binding are or will it be? I'm used to WPF where you have concepts like "Converters", bindings relative to a DataContext where you can do things like binding to a property from another control.
    - Why are bindings coupled to SerializedObject class? Is not possible to have a POCO object on our own that implements standard interfaces like INotifiedPropertyChanged?

    I would like to make a real MVVM and that's why I have that concerns.

    I also miss syntax coloring and autocomplete features when working with *.uxml and *.uss files. It's a waste of time having to go to docs all the time. I don't know if you have on your roadmap having and embed editor for this but I think that it's better to create extensions for the most popular IDEs like VS or VS Code so we can have a syntax coloring and autocomplete features.

    I will post in this thread as soon as I find more things to improve.
     
    Last edited: Apr 28, 2019
    RKar and Creta_Park like this.
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    I've never actually used the window creator because of this issue. First thing I ran into.

    Also one of the first things I ran into when trying to make an inspector change options when toggling a field.
     
  3. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    Hello, there is no need to tag individual UI team members as we have a process to monitor this channel :)

    Before we look into each individual piece of feedback, Can you confirm you are using 2019.1 ?
     
  4. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    Sorry, I'm used to writing threads that get lost in the forum because it's not read by anyone on the staff.

    Of course, I'm on 2019.1, that's why I have started playing with it and considering rewriting some organization codebase custom windows or inspectors.
     
  5. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    @N3uRo : Let me try to respond to most your questions:

    Both issues you mention in the window creator seem valid. I created bug tickets to track them.

    Don't hesitate to file bugs directly using Help->Report a bug...

    You can use the style attribute this way:
    <ui:Label name="my-label" text="" style="background-color:blue;/>

    or link to an external uss, which currently needs to be in your resources folder:

    <UXML xmlns:ui="UnityEngine.UIElements">
    <ui:VisualElement name="content">
    <Style path="Styles/MyStyleSheet"/>
    </ui:VisualElement>
    </UXML>


    You can use display:none in your uss, or in c#:
    myVisualElement.style.display=DisplayStyle.None;



    Agreed.

    These are valid concerns. We are currently looking into ways to make bindings more powerful and more easily extensible, both for the editor and in the future runtime version.

    As for syntax highlighting and autocomplete, you can have it with uxml files this way:
    In the project pane, right-click and select "Update UIElements Schema" this will create a new folder named "UIElementsSchema" next to your Assets folder. You can then add the generated dtds to you favorite editor.

    For css, I personally use standard css syntax highlighing with Notepadd++, but I agree it's lacking for cases where uss diverges from the css standard. I don't think providing a extensions or configuration files for most external editors is in our roadmap, but I agree having one as a starting point would help.

    Keep that feedback coming!
     
  6. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    @uMathieu

    I was at Unite Copenhagen and I saw the UIBuilder session so I tried Unity 2020.1 alpha with UIElements and UIBuilder.

    I still see a lot of things still missing in UIElements.

    Bindings

    It's still very basic. Seeing the code I see that it only allows to make binding of the field that is considered "main" (Label -> text) and it does not allow to make binding towards any field.

    It is still not possible to make a custom binding. That is to say, to implement a standard .NET interface as:

    https://docs.microsoft.com/es-es/dotnet/api/system.componentmodel.inotifypropertychanged

    Which is the basis of WPF.

    I don't like to have to attach my data model to Unity API (SerializeObject, SerializeField...) and only bind fields and not properties. Think that in a big project the data model can come from a precompiled dll shared between a backend (for example: ASP .NET Core) and a frontend (Unity).

    Nor do we have concepts like converters which is something basic in a binding system.

    https://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/

    Extensibility

    I'm just going to comment a use case of a feature we have in our organization's code base. I explain the case with uGUI.

    In our projects 99.9% of the cases are multilanguage applications. Therefore, we have developed a multilanguage system through *.json files in StreamingAssets and runtime scripts and editor. The system works as follows:

    - We have a script that is placed next to a GameObject with a component type "Text" or "TextMeshProUGUI" or similar.
    - That script has a custom inspector where reading through the *.json appear a dropdown to choose text keys and assign a specific one.
    - In execution that script makes use of the multilanguage service to which it requests the text through the key and also listens when there is a language change in the execution of the application to update the text to the current language.
    - Besides that in that script you can control simpler things like a prefix, a postfix and the text case (upper, lower, capitalize...).

    I've been analyzing how we could migrate all this to UIElements but I'm not able to see how.

    I see that through "UxmlFactory" and "UxmlTraits" it is possible to read values of VisualElements attributes but the first thing I have realized is that it is not possible to "inject" an attribute to an existing control but you have to inherit one or create your own control.

    In WPF it is solved through Dependency Properties:

    https://www.tutorialspoint.com/wpf/wpf_dependency_properties.htm

    Also interesting is the concept of Behaviours:

    https://www.wpftutorial.net/Behaviors.html

    So I don't see an easy way to do this.

    On top of that, we use IOC containers at the scene. I do not see now the way if it is not on the scene to make reference to the IOC container. Is there any way to pass "metadata" or similar to a visual element? I wouldn't want to have to solve it through methods or static properties.

    We would need the IOC container in order to interpret this custom attribute in the UIElements element and assign it, in the case of a "Label", its corresponding text in the current language.

    Customization

    Here's a simple example, if you want a toggle not to look like a toggle (with its tick) and look otherwise how you would do it? Examples:

    https://www.cssscript.com/demo/csscss3-custom-checkboxes-radios-switches-el-checkbox/

    http://www.bootstraptoggle.com/#demos

    I haven't seen examples of anything like it.

    Another use case. If you have a button that doesn't look like a button, how would you do it? Example

    https://bootsnipp.com/snippets/GqBjl
     
    RKar, Creta_Park and jGate99 like this.
  7. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    @bdovaz Thanks for your feedback!

    Totally agreed. The current version of the bindings system is really simple and was the bare minimum that was needed for simple editor usage. We are actively working on improving it for runtime and more complex use cases.

    Localization is a common use-case and we aim to simplify these workflows in the future.
    A few ideas here:
    For the time being, most of this will require you to create subclasses of existing controls and provide UXMLFactory/Traits to handle edition in the UI Builder. For instance, here I created a generic wrapper around elements that let's you add your own attributes.

    Code (CSharp):
    1.  
    2. public class UXMLWrapperElement<TVisualElementType> : VisualElement where TVisualElementType : VisualElement, new()
    3. {
    4.     //A wrapper class for the original class traits
    5.     public new class UxmlTraits : UnityEngine.UIElements.UxmlTraits
    6.     {
    7.         UnityEngine.UIElements.UxmlTraits m_WrappedTraits;
    8.  
    9.         UnityEngine.UIElements.UxmlTraits CreateBaseClassTraits(Type t)
    10.         {
    11.             foreach (var innerType in t.GetNestedTypes())
    12.             {
    13.                 if (typeof(UnityEngine.UIElements.UxmlTraits).IsAssignableFrom(innerType))
    14.                 {
    15.                     return Activator.CreateInstance(innerType) as UnityEngine.UIElements.UxmlTraits;
    16.                 }
    17.             }
    18.  
    19.             return t == typeof(VisualElement) ? null : CreateBaseClassTraits(t.BaseType);
    20.         }
    21.  
    22.         public UxmlTraits()
    23.         {
    24.             m_WrappedTraits = CreateBaseClassTraits(typeof(TVisualElementType));
    25.         }
    26.  
    27.         public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
    28.         {
    29.             get
    30.             {
    31.                 return m_WrappedTraits.uxmlChildElementsDescription;
    32.             }
    33.         }
    34.  
    35.         public override IEnumerable<UxmlAttributeDescription> uxmlAttributesDescription
    36.         {
    37.             get
    38.             {
    39.                 foreach (var attr in m_WrappedTraits.uxmlAttributesDescription)
    40.                 {
    41.                     yield return attr;
    42.                 }
    43.  
    44.                 foreach (var attr in base.uxmlAttributesDescription)
    45.                 {
    46.                     yield return attr;
    47.                 }
    48.             }
    49.         }
    50.  
    51.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    52.         {
    53.             var wrapped = (UXMLWrapperElement<TVisualElementType>) (ve);
    54.             base.Init(ve, bag, cc);
    55.             m_WrappedTraits.Init(wrapped.target, bag, cc);
    56.         }
    57.     }
    58.  
    59.     //this way doing .Add() will add to the target instead
    60.     public override VisualElement contentContainer => target;
    61.  
    62.     public TVisualElementType target { get; private set; }
    63.  
    64.     protected UXMLWrapperElement()
    65.     {
    66.         target = new TVisualElementType();
    67.  
    68.         //we want to physically add to this element
    69.         hierarchy.Add(target);
    70.     }
    71. }
    72.  
    73. public class LocalizedElement<TVisualElementType> : UXMLWrapperElement<TVisualElementType> where TVisualElementType : VisualElement, new()
    74. {
    75.     public new class UxmlTraits : UXMLWrapperElement<TVisualElementType>.UxmlTraits
    76.     {
    77.         readonly UxmlStringAttributeDescription m_LocalizedKey = new UxmlStringAttributeDescription {name = "localization-key"};
    78.  
    79.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    80.         {
    81.             var localized = (LocalizedElement<TVisualElementType>) (ve);
    82.             localized.localizationKey = m_LocalizedKey.GetValueFromBag(bag, cc);
    83.             base.Init(ve, bag, cc);
    84.         }
    85.     }
    86.  
    87.     public string localizationKey { get; set; }
    88. }
    89.  
    90. public class LocalizedLabel : LocalizedElement<Label>
    91. {
    92.     public new class UxmlFactory : UxmlFactory<LocalizedLabel, UxmlTraits>{}
    93. }
    94.  
    95. public class LocalizedButton : LocalizedElement<Button>
    96. {
    97.     public new class UxmlFactory : UxmlFactory<LocalizedButton, UxmlTraits>{}
    98. }
    99.  
    100. public class LocalizedToggle : LocalizedElement<Toggle>
    101. {
    102.     public new class UxmlFactory : UxmlFactory<LocalizedToggle, UxmlTraits>{}
    103. }
    104.  
    A subclass of TextElement (or label) could add prefix/postfix members that would end up modifying the base class text value. Or you could do it with a composite VisualElement with 3 sub labels for prefix/text/suffix. This way alignment layout could be more easily controlled.

    We're actively working on improving the bindings and uxml support for runtime and this is the type of use cases we want to address.



    These are called Manipulators in UIElements. A simple example is the clickable manipulator. These however are not properly fleshed out yet and the api design needs some improvements. Because of this, most built-in Manipulators used by GraphView (resize/zoom/pan, etc) are still internal.
    We have some stuff in the pipeline regarding this in the 2020.x cycle.

    You can store a custom object in the userData property of your VisualElements. If you need more than one property, a workaround is to store a List or Dictionary of values in there.

    One way to achieve this is to inspect the generated hierarchy in the UIElementsDebugger and figure out the original style classes for Button and Toggle (hint: it's "unity-button" and "unity-toggle") and provide a stylesheet that overrides the default styling values.

    The hardcore way to do it, would be to completely remove the styling from your button instances and provide your own. In the builder you can remove the classes from the you can remove classes from the Inspector > StyleSheet section. The Button will display like a default VisualElement, you can then add any class to provide your custom styling. If you'd like to code this once you can to create a derived class from Button that setups the class names and expose it to uxml by providing UxmlFactory/UxmlTraits. Then, designers can then reuse this custom-styled button in the ui builder.
     
    Last edited: Oct 1, 2019
    RKar likes this.
  8. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    @uMathieu thanks for your response. I will try that.

    What about extensibility? Did you read my comments on my specific problem and what I want to achieve that was possible with components+custom inspectors?
     
  9. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    @bdovaz I edited my original answer
     
  10. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    Ok thank you. For the extensibility part I expected that it would be possible to add attributes to existing controls without subclassing them... Would be that possible at some point? It's really weird to have to subclassing them only for this...

    I thought you would apply wpf's dependency property pattern or something similar to that.

    And for my localizable label use case would be possible in the UI Builder when you are exposing a custom attribute to draw a property drawer or similar so I can convert that textbox in a drop-down and fill it with my localizable text keys? That was possible with property drawers or custom inspector with the monobehaviour approach.
     
    Last edited: Oct 1, 2019
  11. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Right now, the only way to use attributes is from within an element's implementation. That is, the consumption logic of your custom attributes has to live in a derived (sub-class). That said, in theory, with our current implementation of UXML, it should be possible to read any attribute on any element that was created from UXML. Extra unused attributes still exist somewhere in the UXML asset. But this is not a high priority feature at this time.

    This request is also possible with the current architecture of our attributes system, but again, would take some time for us to implement.

    All in all, your feature requests make a lot of sense and I see them being very useful, but for now you'll have to make do with sub-classing. However, you shouldn't have to sub-class each control you want to add your custom logic to (like your localization system). You can create a wrapper element (sub-class just
    VisualElement
    or
    BindableElement
    ), and have it handle the custom attributes by forwarding the strings to the child control element it is currently wrapping. So you'd have uxml something like this:
    Code (CSharp):
    1. <Wrapper localization-string="test-string">
    2.     <Label name="some-label />
    3. </Wrapper>
     
    RKar likes this.
  12. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    @uDamian Thank you for your answers. They have been very useful to me and I hope it will be for more people with doubts similar to mine.

    I also hope that everything I have said will be taken into consideration and applied sooner or later.
     
  13. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    @uDamian could you update on all this questions? How is it going?

    Thanks.
     
    RKar likes this.
  14. RKar

    RKar

    Joined:
    Mar 6, 2019
    Posts:
    22
    I have the same issue. There are some changes maybe?