Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Vertical Spacer

Discussion in 'UI Toolkit' started by Andrew-Carvalho, Jun 25, 2020.

  1. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    39
    Hi! I am just starting to work with UI Toolkit as one of my projects is on 2019.4 and requires a custom UI (custom editor) and I figured I would follow best practices and implement both IMGUI and UI Toolkit.

    I am building out the VisualElement entirely in code as I find it faster/more familiar. As such I am simply adding elements to a root element in code to get the job done as the layout is fairly straightforward.

    I would like to add some vertical space between elements. To do this, I would much rather start building up a library of small, reusable UI Toolkit fields so I created a SpacerField script, as follows:

    Code (CSharp):
    1. using UnityEngine.UIElements;
    2.  
    3. public class SpacerField : VisualElement
    4. {
    5.     public new class UxmlFactory : UxmlFactory<SpacerField, SpacerField.UxmlTraits> { }
    6.  
    7.     public new class UxmlTraits : VisualElement.UxmlTraits
    8.     {
    9.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    10.         {
    11.             base.Init(ve, bag, cc);
    12.             ve.style.height = 12;
    13.         }
    14.     }
    15.  
    16.     public override bool canGrabFocus => false;
    17. }
    I then iterate over the serialized elements in an array (serializedProperty) and draw each with a spacer afterward.

    Code (CSharp):
    1.  
    2. ...
    3.             SerializedProperty serializedRegions = serializedObject.FindProperty(MEMBER_REGIONS);
    4.             for (int arrayIndex = 0; arrayIndex < serializedRegions.arraySize; ++arrayIndex)
    5.             {
    6.                 SerializedProperty elementProp = serializedRegions.GetArrayElementAtIndex(arrayIndex);
    7.                 indentedContainer.Add(new PropertyField(elementProp));
    8.                 indentedContainer.Add(new SpacerField());
    9.             }
    10. ...
    11.  
    This does not add any visual space and, using the debugger, the SpacerField has a height of 0.

    upload_2020-6-25_10-24-49.png

    What is confusing is, Using the UI Builder, if I drag in a SpacerField, it works as intended

    upload_2020-6-25_10-26-14.png

    I am pretty certain this is simply a case of me not understanding styles/how to override them for an individual element, but the inconsistency is definitely confusing and some guidance would be appreciated.
     
  2. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    384
    By setting the style in the UxmlTraits Init() method, the inlined style will only be applied when instantiated from uxml. Set the height value from SpacerField() constructor instead.
     
  3. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    39
    Thanks! That explains the difference between inspector and UI Builder!

    One last question: if moving this logic to the constructor, it looks like I can drop the UxmlTraits entirely. Is that a correct assumption or will there be cases when using uxml where it will still be necessary?
     
  4. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    384
    UxmlTraits are only necessary if you want to expose custom attributes to uxml. In your case, it's not needed.

    I'd recommend to the styling via uss instead of inlining it:
    Code (CSharp):
    1. SpacerField{
    2.   height: 12px;
    3. }
    This way, if you ever have have another spacer where it size needs to be different, you still override it by uss or by code.
    Code (CSharp):
    1. #my-very-large-spacer {
    2.      height: 45px;
    3. }
     
  5. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    39
    That's a solid suggestion! I already allow a custom height to be passed in via code int eh constructor but wanted to also make sure other use cases (a non-technical dev or building a larger editor with uxml and uss) would still be a viable approach and provide flexibility needed.

    If I leave the inlining in the constructor, would that still play nicely with uss? I'd like to avoid needing to include uss for every editor as I am far more comfortable doing everything in code and find iterating and debugging quicker.

    Thank you!

    Update

    The inline style will always override the uss value, which I guess makes sense. Is there any other way to be able to set the height via code only and avoid creating an additional uss file, but still allow it to be overridden later if uss is added? I have added a class name to my visual element via
    AddToClassList("spacer-field")
    .

    I'm currently looking at the styleSheets property and wondering if there is some way to get the style sheet currently used to render the UI and read the value from there and, if there is no uss for the "spacer-field" class, default to whatever is passed in code.
     
    Last edited: Jun 25, 2020
  6. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    This is not possible.

    Looking at the styleSheets property is not enough because the style can come from a parent element's styleSheets. You'd have to look at all parents. This can also change at any time if a user adds more stylesheets to the tree. Inline styles are meant to overwrite USS in all cases. I would just have 2 constructors, one that doesn't set it and relies on a USS class with a default value, and another constructor that takes a value and inlines the style.

    Worth also adding that you should not have to create a USS file per-custom-element. You can have one or 2 global USS files you assign to the root of your UI (or Editor in your case).
     
  7. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    39
    Yes, that makes sense. I've already implemented a two constructor version where the height can be overridden, but it's good to know that is an approach shared by others.

    I've done this for now and it works fine for my current use case. In general, I rely heavily on having many small, specific packages I share between projects. Since I try to keep them focus and modular, I'll eventually need uss files for each since many do not have dependencies on each other. I don't mind creating them, I just prefer having fewer files to get a job done.

    Related to the above: is there any way to change the order in which uss are applied, specifically with respect to the buily in uss files? I have been trying to create a simple indent that shrinks the label but leaves the input field the same size, like a foldout, but there are many cases where the container overrides the build in uss which results in some ugly layouts and a lot of rewriting functionality of the built in uss.
     
  8. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    All user USS will overwrite the built-in styles in the Editor. And the order of USS being applied is the order in which you add them to an element. Last USS wins. But the order really only comes in if you have duplicate selectors. Worth adding some specifics here around which exact elements and styles were giving you trouble.

    Friendly reminder to use the UI Toolkit Debugger to see what elements are inside the built-in controls and how the build-in styles are assigned. When overwriting a specific style property, see which selector is used to set it in the first place and use the same full selector to overwrite it in your own USS.
     
  9. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    39
    I wrote up the lengthy post below and near the end something I in my own uss made me think to try a slightly different approach which made the specific issue below solvable within my own uss without the need to copy attributes from the base uss by using the > symbol to specify direct child. Without any background in web development I have a fair bit to learn about what is possible with uss.

    I'm leaving the issue below in case this issue is possible in some other setup that I can't think of right now.

    --------------------

    Yes, I've been using the debugger extensively (and how I realized I was overriding some settings which was causing
    some ugly styling).

    I was trying to create an indented set of properties. I'll explain the specifics of what I am trying to accomplish.

    I have started using SerializeReference in order to serialize standard C# classes that implement a specific interface. My use case is currently an interface for damage volumes but that doesn't have any bearing on the issue.

    My current approach is to use an attribute to above the interface to create a popup field that will be populated with all implementations of interface. Selecting one will update the object and draw the selected interface implementation beneath. Since this is a nested object, I wanted to have it indented, much like the built in foldout. I applied a margin of 15px, same as the foldout, but noticed the label and input field scaled proportionally as opposed to keeping the input fields lined up and only indenting the labels.

    upload_2020-7-11_20-2-52.png

    Yes, I realize this is a very minor issue but I was curious as to how to solve it. I went back to the debugger to find the foldout settings for the main container and nested labels and copied them into my own uss.

    Inside that indented container I manually draw all the serialized properties of the object instance. All seemed to work until I went to render a vector field and it looked like this:

    upload_2020-7-11_20-4-10.png

    The Vector property field was scaling incorrectly making the input fields inaccessible. I had overrode the
    .unity-composite-field__field .unity-base-field__label
    rule from the default uss. I then had to recreate the rule in my own uss because I couldn't change the order of the stylesheets.
     
  10. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    Even if you could override the order, this wouldn't solve your issue. You are overriding styles from the monolithic Default.uss, so the only way you could "fix" any bad overrides would be to re-apply Default.uss on top (or, as you did, re-create the specific broken styles from Default.uss).

    Yes, that's what I mean by using the Debugger to know the full selector to use to override a specific default element:
    upload_2020-7-12_17-57-55.png
    So if you want to override padding-left of the first label inside a Composite Field, you'd use exactly this: ".unity-composite-field__field--first > .unity-base-field__label" selector in your own USS.

    There's a lot more magic that the Foldout does to make the input elements of each field line up even though the labels of each field is indented. For your use case, I would try to re-use an actual Foldout instead of trying to replicate its internal styles. Just put down a Foldout for your custom class's inspector and simply hide the Foldout's header so you can't see it or collapse it. Hiding just the Foldout header will still need some Debugger use to find the correct selector but it will be much simpler than trying to make the indents work.