Search Unity

How to modify the internal style on UIElements?

Discussion in 'UI Toolkit' started by 5argon, May 5, 2019.

  1. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Previously I could do something like draw a label, but use EditorStyles from HelpBox, for example.

    Now supposed I am drawing `new ObjectField`, how could I go in and modify the style it use to draw the built-in component? In `.style`, there are mostly layout related properties except for something like backgroundColor.
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    I could try to explain in boring text but if you have some time (and are coming from IMGUI), it's worth watching the Unite LA 2018 intro session for UIElements. It details the main equivalences between IMGUI and UIElements, including styles.


    But as a quick answer, use the UIElements Debugger (Window > Analysis > UIElements Debugger) to understand the hierarchy under ObjectField, and then use either
    myObjectField.Q<Label>(className: "some-class-name")
    query system in C# to change styles directly on the child elements, or better, use a StyleSheet (.uss) asset.
     
    Mauri and 5argon like this.
  3. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Thank you, will try that.

    One more related question to manipulate the internal style, I have to measure the "calculated width" of some internal elements, but all elements came with a flex box settings without a definite width/height. I need to specify the width explicitly, because I want to layout horizontally, one after another without distributing equal width to all members, within a new VisualElement with flex direction as Row. If left as is, the 0 width will not be arranged from left to right correctly.

    Screenshot 2019-05-06 23.07.08.png

    For example the ObjectField. It apparently contains Image and Label for the icon and the text, and ObjectFieldSelector as that dot on the back. I want to put one ObjectField after another, so each one need the width of the icon and the label added up.

    I want to estimate the width if they are, for example, like display:inline-block in the front end dev. (I notice Unity provided just Flex and None)

    For the icon, I could go with asking its maxWidth that is preventing the icon from getting too large as its width. But for the Label, currently I turned to IMGUI's `EditorStyles.objectField.CalcSize(new GUIContent(labelText)).x` to estimate the width required, which works, but I found it weird that I need help from equivalent style (EditorStyles.objectField) in IMGUI to properly use UIElements (ObjectField). Is there any methods that help me "simulate the layout engine" and get a minimum required width?

    On the Label element, I found MeasureTextSize but I had trouble understanding it to make it return something. So it returns Vector2, but also require me to input both width and height? Is this how it should be used?

    Code (CSharp):
    1. label.MeasureTextSize(labelText, 0, MeasureMode.Undefined, EditorGUIUtility.singleLineHeight, MeasureMode.Exactly).x;
    (This returns NaN for labelText that is not empty)
     
    Last edited: May 6, 2019
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    One of the main reasons to use UIElements in the first place is to *not* have to calculate any sizes or layouts manually and let the "system" do it for you. If you just want 2 ObjectFields on the same row, I'm not sure why
    Code (csharp):
    1. flex-direction: row
    is not working for you. It should put them both beside each other.

    You can additionally give them min-widths to make sure they are a certain size, or just give them widths to hard-code specific sizes. But these properties would still be on the ObjectFields themselves, not their child elements.

    Can you clarify exactly what you're trying to achieve? Maybe create something simple in IMGUI and screenshot?
     
  5. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Sure. I have 2 ObjectField layout with simple .Add to the root VisualElement like this. It take the entire row. So I thought, I want to bring them to the same line and I want to know the width of red rectangles. If I don't specify this width, when I add both of them to a VisualElement with Row flex direction they all disappeared, I thought explicit width is required.

    Screenshot 2019-05-06 17.57.21 copy.png

    So, I finished trying to use the debugger and strip off some classes so that the back dot disappear along with the dropwell box. When I was putting width = 100 manually, it works but less than ideal.

    Screenshot 2019-05-06 19.12.24.png

    After using text measuring from IMGUI, I could achieve what I want. Also I have one more element in the middle, which thanks to explicit width it is able to move the 3rd element (which also has an explicit width) back accordingly.

    Screenshot 2019-05-06 19.55.38 copy.png

    So I just wish there is more official UIElements way to get the text width measurement to do something like this.
     
  6. MattM_Unity

    MattM_Unity

    Unity Technologies

    Joined:
    Aug 18, 2017
    Posts:
    45
    Hey 5argon!

    I'm going to investigate your problem, I'll get back to you asap!
     
  7. arichards

    arichards

    Unity Technologies

    Joined:
    Mar 28, 2019
    Posts:
    1
    @uDamian suggests `flex-direction: row` on the parent container, but I found we also needed `white-space: normal` on the children.

    Actually here are my styles in case any of these myriad properties is needed:

    Code (CSharp):
    1. .container {
    2.     display: flex;
    3.     flex-wrap: wrap;
    4.     flex-direction: row;
    5.     justify-content: flex-start;
    6.     align-content: flex-start;
    7. }
    8.  
    9. .container Label {
    10.     align-self: flex-start;
    11.     white-space: normal;
    12. }
     
    etliaojing likes this.
  8. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Is there a reason why the IStyle var in VisualElement is implemented as an 'internal' (i.e private) class that we're not allowed to use? I can force access to it via reflection, and it's probably worth it to avoid re-writing 1,000 lines of code that I have no reason to write, but maybe there's a replacement class you intended us to use for this? (can't find it by googling or by reading forums - this post was the nearest I found).

    It was useful enough for someone in Unity to make an explicit "InlineStyleAccess" class, which seems an obvious one almost everyone needs to use in their own code - but that's 1200 lines (!) of code that are held 'secret'.

    (I ran into this when trying to create a simple inline style and apply it to more than one element - there may be some other way, e.g. a dedicated API method for doing this, but the most obvious approach, of simply instantiating the same IStyle instance that UIToolkit uses itself everywhere by default ... is blocked because of the above)
     
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    .style is .inlineStyleAccess:

    Code (csharp):
    1.  
    2. internal InlineStyleAccess inlineStyleAccess;
    3. /// <summary>
    4. /// Reference to the style object of this element.
    5. /// </summary>
    6. /// <remarks>
    7. /// Contains data computed from USS files or inline styles written to this object in C#.
    8. /// </remarks>
    9. public IStyle style
    10. {
    11.     get
    12.     {
    13.         if (inlineStyleAccess == null)
    14.             inlineStyleAccess = new InlineStyleAccess(this);
    15.  
    16.         return inlineStyleAccess;
    17.     }
    18. }
    19.  
     
  10. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Yes. And you cannot instantiate that class. Hence you cannot use it.

    I guess ... you could create a VisualElement, read the ref to the style, then destroy the VisualElement, and hope that there was no internal code that processes on the VE's destruction that affects the style.

    But it seems a lot of hacky, potentially dangerous (for ongoing maintenance) code to workaround Unity's decision to make this common class private.

    If there's a reason it has to be private ... then ... I'd like to know what that is! It probably means that e.g. you CANNOT share style instances between VEs (which would be very important to know).
     
  11. jonathanma_unity

    jonathanma_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    229
    Hi,

    There are many reasons why InlineStyleAccess is not public. Basically it's not meant to be used on its own, it's only used to handle inline style data and inline styles are not shared across VisualElement. Also keeping this private allows us to refactor the way the data is stored without introducing breaking api changes.

    Internally the actual style data is stored in the ComputedStyle class. The difference between InlineStyleAccess and ComputedStyle is that InlineStyleAccess only contains data that come from C# (aka inline styles). ComputedStyle however contains both data from USS and C#.

    UIToolkit will automatically shared ComputedStyle that match the same USS rules. However, once a VisualElement has an InlineStyleAccess it will have its own private ComputedStyle and no sharing will be done. This is something that will be improved over time...

    I'm not sure I understand what you're trying to achieve, but it's not possible to share VisualElement style data when the element has inline style. The way to shared style data is to use USS.
     
    a436t4ataf likes this.
  12. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    1. Create 40 VisualElements (eg visually-complex rows in a table)
    2. Which have 4 different background colors: col1, col2, col3, col4, col1, col2 .... etc
    3. So there's only 4 different styles, ideally want to define each one only once, and re-use each one 10 times, and without copy/pasting the code 36 times

    Right now the way I'm doing this (because it's 10x-100x quicker than re-implementing the entirety of the IStyle interface by hand and then having to maintain that)

    1. Write 4x methods "fixTheStyle1", "fixTheStyle2", "fixTheStyle3", and "fixTheStyle4"
    2. Create the 40 VisualElements ("for( int i=0; i<40; i++) ... var ve = new VisualElement() ... etc"
    3. After each one, invoke "{ ... switch( i ) { case 0: fixTheStyle1( ve ); break; case1: fixTheStyle2( ve ); break; ..."
    4. In "fixTheStyle1" overwrite the supplied VisualElement's style with the correct values we wanted to share across all the elements of that type

    In an OOP language this is ridiculously bad (lots of extra code and complexity to do something that should be automatic/trivial in OOP), but I wasn't confident in what/how/where I could duplicate or share a style directly.

    NB: Your comments above suggest I was correct to assume that "sharing a style directly" would be a bad idea and probably not work :).
     
  13. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    If there's a simple way to use USS in code that could be the right approach here (and for some of the 'internal style' cases (*)).

    But we don't use USS. On most of the projects where we have UIToolkit right now the UI is built in code. It's much faster to implement and maintain (because everything's procedural. I believe USS doesn't have any way to embed C# in it?). Especially for multi-device UIs: its been many times easier to develop and maintain - most multi-resolution stuff has some nasty code if done purely in CSS stylesheets (even with HTML5/CSS3), and some of it is impossible if you don't have full CSS-functions support. Doing those procedurally makes it short, simple, clear, and very easy to deal with device size and orientation changes without having to maintain hundreds of stylesheets - all of which have to be changed by hand every time a UI designer changes a small part of the design.

    (* - Last time I checked, USS didn't support all of CSS's functions (maybe that's implemented now? I think it was a missing feature of Yoga). So we were(?) limited to relatively basic, static, UIs. Which are better than nothing, but a big step down compared to procedural ones)
     
  14. jonathanma_unity

    jonathanma_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    229
    This is where USS is useful, you just need to create 4 rules for each columns and set them accordingly on the elements.

    You can have the UI built in code and still use USS and then for the stuff with nasty code you can use C# to override the styles set from USS.
    We have built many complex UIs with UI Toolkit (UI Builder, ShaderGraph etc) so I wouldn't say that it's limited to relatively basic static UIs.

    The intended way to use UI Toolkit is to rely on USS, I highly suggest you try to leverage it where possible.
     
  15. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    Since .uss files are close to standard css files, you can probably experiment with sass/less/ preprocessors to generate repetitive styles.
     
  16. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    There's a bunch of situations where it'd be really nice to generate the style from C# and apply it to VisualElements instead of having it come from a css file. One of the primary uses is compactness - it's very nice to be able to share something useful as one file instead of a folder with two files.

    It's also less cumbersome to modify a window/editor if everything is in one file, so we don't have to tab between different files in order to edit a single file.


    If we were able to generate StyleSheet objects and apply them via C#, that'd help a lot. There's no reason we shouldn't be able to do that (it's for sure easier to implement than to implement a css parser), but I haven't found a way to do it yet. I mean, we could CreateInstance<StyleSheet> and then use reflection to build around everything being internal, but that's clunky as hell.

    Being able to generate StyleSheets from code would for sure fix @a436t4ataf's problem - they could generate the StyleSheet, define rules for 4 classes, and be off to the races.
     
    a436t4ataf likes this.
  17. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    Setting styles via assets does allows you to iterate faster though, without having to go through a domain reload to see changes you make.

    That being said, I totally see the value of setting styles through code. Unfortunately, before making an api public we must really make sure it's the right one, because once it's out, we can't really change it. It's a call we're not quite ready to make at this point.

    If you want to play with fire, you can look at how the uibuilder does it and open things up using reflection.
     
  18. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Yes, @Baste explained it better than me, thanks!

    @uMathieu 95% of the time we have to do a domain load anyway because you dont edit a stylesheet without also editing code. If you sit next to someone developing Editor UI this is immediately apparent. Even when developing game UI its not rare (depends on your game, team, etc - and how much you use practices like TDD - but its pretty normal). So, in reality, There is often no particular time advantage from using assets, and theres considerable time loss (because multiple files = more cognitive load = slower human programmers + more bugs + bugs take longer to fix). For the cases where USS is the right answer - great! Its there as an option! Its another tool in our toolbox - but reaction here and lot of the responses I’ve had recently point to Unity considering USS as the only option, even though on its own its obviously not as powerful, not as effective, (its good but not perfect) as a code-based solution, or a code+USS solution, a lot of the time.

    It seems like the things we need from Unity, for code solutions to work “great”, as fully supported usecases ... are usually mostly a subset of what youve already had to write to implement USS, so the foundations for the code already exist (with some additional effort needed - obviously: making public versions of that code with stable APIs is not free)

    I 100% agree with the sentiment that a public API needs careful planning - eg look at the places in UIToolkit where insufficiently developed API designs already caused large amounts of wasted time because of API designs that seemed a good idea at the time but didnt really work well (and ugly code in our codebases to workaround the API) - the “completely ignores the existing API pattern used for all other callbacks” solution for mouseclick handlers for Buttons I’m looking at you! Designing good APIs is a skill and requires work and time.

    ...hence my opening question: I wasnt sure if there was a reason for this missing API feature (because it seems to be core for people doing serious UI work in games!), or if it was an oversight, or if the feature does exost but Im looking in the wrong place, etc etc.

    NB: I’m not saying its impossible to write good, complex, rich, UI without API features like this, but I’m saying that any experienced team of game engineers (people working on live games) is likely (not all - but most should already know how to build procedural UI extremely fast *in CSS* (ie mixing code + styles) and/or in native ui libs; this stuff is standard practice, and in many cases *works much better* than using USS files alone) to do it faster and more effectively this way ... so I would expect that such features exist in the API and work out of the box.

    But we’re getting very close to the previously anniunced official launch date for UIT, and it seems theres quite a lot of these holes still unplugged, with only USS properly supported as a use case. I would really like to see them plugged (happy to give feedback, test API ideas, etc if it helps) before it goes gold. Or to get official answers on which features have been left out of UIT and why, so we can decide how much to use it on future projects, versuseg building/maintaining more proprietary solutions on top of UnityUI where these limitations dont exist.
     
  19. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I mean, a public API that you stuff into the .Experimental namespace like what you've done with the UI animation stuff would be a good middle ground. If we use that, we accept that we'll have to rewrite our code later if you change your mind about the API design.
     
    Windwalk_Rosco and a436t4ataf like this.
  20. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    82
    We don't have too many option regarding inline style (C#). While uss/uxml is cool and all, more often than not it forces us to deal with bloats of uss/uxml files