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

Feedback Opinion: TemplateContainers should have no impact on the layout.

Discussion in 'UI Toolkit' started by Spectragate, Feb 19, 2022.

  1. Spectragate

    Spectragate

    Joined:
    Jan 23, 2022
    Posts:
    37
    When using a template uxml file and adding it to the layout, by default it will be added inside a TemplateContainer. The reason for having TemplateContainers seems totally valid, however I find it strange that they will by default add their own "grow 0" to the layout unless you manually override it every time you call Instantiate, causing unexpected results in the layout:

    Code (CSharp):
    1.  
    2.         var clone = m_VisualTreeAsset.Instantiate();
    3.  
    4.         //required every time
    5.         clone.style.flexGrow = 1;
    See Flex not growing vertically in EditorWindows.

    In my opinion, if templates are required to use TemplateContainers, the layout by default should treat them as completely invisible and they shouldn't have any effect on the layout at all (basically acting like an empty div).

    Now it's nothing major to reset the flexGrow after every Instantiate() call, but it can get trickier when you start adding more styles to your root template item, only to forget to manually duplicate those in code (eg, absolute positioning a modal popup, which you then also have to remember to apply to the TemplateContainer in code after you've created it too).

    As a side note, you apparently can use .CloneTree instead of Instantiate to only load the xml below the TemplateContainer, but from what I've read CloneTree is being deprecated and is not recommended to be used (and in my experience it still adds the TemplateContainer anyway, but I'm not sure if that's because I'm using it wrong or the functionality changed since that post). That being said, I don't mind TemplateContainer appearing in the DOM if it makes dealing with child nodes easier, I just don't want to have to add inline styles to keep fixing the parent container.

    That's my 2c. Agree? Disagree?
     
    Last edited: Feb 20, 2022
    Faimen and mmcveigh33 like this.
  2. Spectragate

    Spectragate

    Joined:
    Jan 23, 2022
    Posts:
    37
    It seems that you can sort of get around the template containers by using

    Code (CSharp):
    1. ve.Instantiate().Children().FirstOrDefault();
    However, this is only useful in scenarios where you don't want the TemplateContainer in the DOM (and sometimes it is useful to have for things like scrollviews, plus I assume it only works when your UXML template is itself inside a single container). Even if you set this in code though, it still creates a very strange editor experience. Here I have a list of images I want to put into a flexbox (each avatar being a UXML template):



    The avatars have a fixed width of 80px by 80px which 'flex' nicely into the box with wrapping (you can see the original size by the orange box around the avatar). But once I turn them into UXML templates and drag them back into the scene, the dang TemplateContainer appears in the DOM, overriding the fixed width/height that I had and blowing out to the max height.

    Am I missing something? Is there a way around this?

    Edit - It turns out the TemplateContainer can't be skipped since it also brings in any stylesheets the template needs, so the workaround above causes issues.
     
    Last edited: Feb 20, 2022
  3. vejab

    vejab

    Joined:
    Dec 21, 2021
    Posts:
    85
    Code (CSharp):
    1. public static VisualElement CloneOne(this VisualTreeAsset vta)
    2.         {
    3.             var e = vta.CloneTree();
    4.             return e.Children().First();
    5.         }
    I was annoyed by TemplateContainer as well so I made this function which is pretty much what you did. Of course it only works if the uxml file has a "root" visual element. I'm not using stylesheets yet so I didn't know it may cause trouble later :s

    My post is pretty useless actually but yes, I agree, it would prefer if it didn't have an impact on the layout.
     
  4. Spectragate

    Spectragate

    Joined:
    Jan 23, 2022
    Posts:
    37
    Any thoughts on this from the Unity Devs? If there's a workaround for this I'd be happy to try it.

    Right now I'm fighting with this system a lot - today for example I'm adding a modal popup to the screen. I have the top level container in the template set to absolute position with a width and height of 100% (which acts as a centre justified container for the modal window) - but that then ends up in a TemplateContainer which I also have to set to absolute position with a width and height of 100% in the C# code. Now I have a full screen container with styles being set in USS inside another full screen container with duplicate styles being set in C#.

    It creates a very confusing workflow.

    If the TemplateContainers can't be ignored by the layout engine, even just having the option of making them viewable in the UI builder would be nice (so in the hierarchy view I could drop in a TemplateContainer type) - then I could attach my USS classes to them in advance.

    Right now my workaround is to use a modified version of what vejab posted above:

    Code (CSharp):
    1.  var classes = windowTemplateContainer.Children().First().GetClasses();
    2.  
    3.                 foreach(var @class in classes)
    4.                     windowTemplateContainer.AddToClassList(@class);
    Which works, but I just know I'm going to run into weird edge cases at some point where I've got something like a 50% width TemplateContainer with a 50% width interior element and end up with a messed up layout.

    Thoughts?
     
    Last edited: Feb 27, 2022
  5. ChGuidi

    ChGuidi

    Joined:
    Dec 28, 2021
    Posts:
    105
    I also encountered issues with this a couple of times. I think it would help if the TemplateContainer copies the flex-shrink/grow properties of its child by default.
     
  6. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    384
    There are multiple options here. When instantiating from c#, VisualTreeAsset.Intantiate() will create a TemplateContainer for the elements coming from a UXML. If you don't want that container, you can use VisualTreeAsset.CloneTree(myParentElement) which will add elements from the uxml directly into the parent.

    When reffering to templates from uxml, that TemplateContainer will always get created. There are 2 ways to control the style applied to that container:
    1. From the parent uxml, as inline styles. In the UI Builder, select the container element and set with/height directly on it. Adding a custom class to the container and adding a css rule from uss will also work here.

    2. From the instantiated template itself, from uss. For this to work, you must use a uss style sheet in your child uxml. In the ui builder, you can add it, or create a new one. From uss, add your custom styles to that container by using the :root selector. In UIToolkit, the root selector will match not the root element of the window, but the element on which the stylesheet was applied.
     
  7. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    384
    Here is a small sample which will get you something like this:
    upload_2022-2-28_9-41-58.png

    The a template container a red 80px box element with a yellow border, instantiated 4 times.

    The first template has inline styles with with a green bg and a height of 100px. The 3 remaining templates are styled from their own uss using the :root selector (90px height and a magenta bg)
     

    Attached Files:

  8. Spectragate

    Spectragate

    Joined:
    Jan 23, 2022
    Posts:
    37
    I've read that CloneTree is being deprecated so I've been avoiding using it. I've also found if I chop off the TemplateContainer, any attached style sheets I have don't get imported - they seem to be part of the TemplateContainer. Here is a screenshot with my USS import missing:



    Note that I couldn't use "VisualTreeAsset.CloneTree" as you suggested since it doesn't seem to be in the API anymore. I used "myVisualElement.CloneTree()", but that still included the TemplateContainer, so I did "myVisualElement.CloneTree().Children().First()".

    Ah that's really neat, albeit a bit of a strange quirk of that selector. I had seen that selector in passing, but didn't realize it was scoped to only the current document. That certainly helps.

    If this is the recommended way of handling this type of layout setup for the foreseeable future, I have a feeling this is going to catch a lot of people off guard. If I had to throw my suggestion into the ring, I think the ":root" selector and how you use it in your templates (ie, always) should be a first class citizen in the UI builder. How or where I'm not sure :D Something to make it more obvious to new users that you can't just call "Instantiate()" without a :root selector in your USS without expecting strange side effects.

    Thanks for the advice uMathieu!
     
    uMathieu likes this.
  9. JLifeDev

    JLifeDev

    Joined:
    Feb 8, 2016
    Posts:
    9
  10. Deive_Ex

    Deive_Ex

    Joined:
    Dec 30, 2014
    Posts:
    23
    This is really confusing and I just spent half an hour trying to figure out why a TemplateContainer was being created out of nowhere.
    From the documentation, it seems only the
    CloneTree()
    and
    CloneTree(string bindingPath)
    are being deprecated, but
    CloneTree(UIElements.VisualElement target)
    is not.
    I understand why the TemplateContainer exists, but wouldn't it be better if we just had a
    Instantiate(UIElements.VisualElement target)
    and ditched CloneTree() althogether?
    I mean, I've read somewhere that the "Instantiate" exists to mimick the gameObject workflow, and for game objects we DO have an overload that accepts a parent object.
     
    ledshok likes this.
  11. snw

    snw

    Joined:
    Mar 13, 2014
    Posts:
    42
    I also just had the pleasure of realizing that TemplateElements were the cause of layouts not working as designed.

    Note that TemplateElement instances also have pickingMode set to "position" by default, which may block pointer events unintentially. As this property can not be set via USS, you have to set it via C#.

    Having a strong background in web-based programming,TemplateElements should not be generated automatically at all, in my opinion.

    If I use Instantiate() on a VisualElement, I expect the result to be exaclty that: an instance of that VisualElement, without any dynamically added elements.

    Otherwise, it will only result in broken layouts, confusion and extra code being written just to bypass/fix issues introduced by TemplateElement.
     
    Last edited: Jan 18, 2023
  12. niuage

    niuage

    Joined:
    Nov 17, 2019
    Posts:
    88
    I'm glad this post is exactly describing all the issues and annoyances I've been having, I feel like at least it's not just me not liking the current system.

    I sadly don't see Unity going back on that decision, might be hard for them to change this behaviour without breaking existing games, but hopefully they find a way to at least make it an option or something like that. Having template containers be added without us being able to do anything about it when dragging UXML into the UI builder is quite annoying and makes building layouts harder.
     
    tjumma likes this.
  13. tjumma

    tjumma

    Joined:
    Sep 30, 2019
    Posts:
    12
    I'm happy to have found this post too. Can someone pls expain what the possible benefits of having this TemplateContainer would be? I don't have a lot of web experience.
     
  14. santelelle

    santelelle

    Joined:
    Oct 31, 2022
    Posts:
    4
    I found an alternative way to get a similar result. In my case this works better because of the Position.Absolute and allows me to instantiate multiple UXMLs at the same time that covers different parts of the screen.
    Code (CSharp):
    1.  
    2.    public VisualTreeAsset document;
    3.  
    4.    TemplateContainer templateContainer = document.Instantiate();
    5.    templateContainer.style.display = DisplayStyle.Flex;
    6.    templateContainer.style.position = Position.Absolute;
    7.    templateContainer.style.left = 0;
    8.    templateContainer.style.top = 0;
    9.    templateContainer.style.right = 0;
    10.    templateContainer.style.bottom = 0;
    11.    Root.Add(templateContainer);
    12.  
     
  15. jjfawkes

    jjfawkes

    Joined:
    May 28, 2013
    Posts:
    12
    And this is one of the many reasons why UI Toolkit is not being widely adapted by the community.
    I am too far in my current game to switch back to the old UI system, but I can tell you, that I am ditching UI Toolkit for my next game because it is unpredictable and it keeps fighting me, even though I have plenty of experience in web development.

    It's not okay, that UI looks different in play mode, because it is adding dynamic elements, what's the point of UI Builder then? If you need those parent containers, then find a way to add them already in UI Builder, so we know what to expect in the design phase.
     
    Klug76 and tjumma like this.
  16. Faimen

    Faimen

    Joined:
    Feb 12, 2020
    Posts:
    1
    You need to add to the documentation the use of the
     :root 
    property, for templates
    I spent several hours solving this problem :(