Search Unity

Question Does UIDocument clear contents when disabled?

Discussion in 'UI Toolkit' started by Kazko, Apr 22, 2021.

  1. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    Hi, just found out that when I disable UIDocument component, the content added to it's rootVisualElement is lost. Is this by design? I am trying to create a poolable panel object that would have dynamic content within a specified template frame. However, whenever it's released to pool, the GO is set to inactive. So when it comes back, the UIDocument is reset, having lost the template frame. I want to avoid setting up the root on every enable.

    Thanks for any info!
     
  2. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    700
    Hello! Yes that's by design, especially because for hiding UI made in UI Toolkit there are alternatives to disabling the component/game object that are preferred. Simply by removing the UI Document from the PanelSettings for example stops showing it in the UI, or you can set the root's (or whatever visual element it makes sense to hide/show) style.display to DisplayStyle.None (and back to DisplayStyle.Flex to make it appear again).
     
  3. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    Thanks for the reply. However, what is the benefit of this? The hierarchy design implications are quite big, as such behavior of components is not usual. It's like if SpriteRenderer would lose assigned sprite on disable. Which would also make the usage of it very tricky.

    The problem is that I am not disabling the UIDocument component in effort to hide the panel. I hide it by other ways (as you mentioned). However I need to disable the object in hierarchy ...

    My use case is a prefab component that I can instantiate into my hierarchy, it will find a specified interface in parents and draw data from it to display into the panel. The prefab has a hierarchy of several components, there is a controller that governs the panel, there are tweeners that grab the RVE for show/hide tweening, etc. By disabling the whole object, everything keeps up and is ready for next usage, except the UIDocument. I fixed this particular issue by not having the object disabled when returned to the pool, which is kinda odd but it works. Still, this is a workaround because one component is behaving in a different way than others.

    Is it possible that this would become an option on the UIDocument component? To not lose the build tree on disable?
     
    Didux likes this.
  4. JoNax97

    JoNax97

    Joined:
    Feb 4, 2016
    Posts:
    611
    I agree with this. Please follow the principle of least atonishment; other components don't lose internal state when becoming disabled.

    This also.means that we would need to treat objecta with Panel Renderers differently, in situations where we may not even know there's one (like the pooling case above)
     
  5. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    700
    Thanks for the feedback. This behavior is related to the Live Reload feature, that allows the live update of UI assets edited in the UI Builder, and the call to use OnEnable for recreating the UI was mainly based on not forcing developers to jump through additional hoops to get their UIs to reload when necessary, but of course this is the side effect. UI Toolkit already doesn't work the same way as our well known game objects and components, and UIDocument is merely a connection from the UI Toolkit world and the game object world, though it's understandable to expect things continue to behave more or less the same. I'm taking this to the team for further discussions.
     
    Didux, JoNax97 and Kazko like this.
  6. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    This. It actually took me quite a while to discover this behaviour while trying to debug why the object doesn't work when returned from pool. Some component resetting its state was the last thing I'd suspect.

    Not sure I understand clearly, so in other words, this change to runtime behaviour is to allow some feature in design phase?
     
  7. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Short answer is: yes. This constraint was added to make it possible to have a consistent and reliable authoring experience.

    Longer answer: One of the major advantages of UITK is that it's asset-based and the natural assumption is that changing the asset should seamlessly update all uses of that asset. Clearing the UI before every OnEnable() is so far the best way to achieve this.

    Even longer answer (on why this is not likely to change anytime soon):

    This restriction makes it bit more obvious that UI Toolkit VisualElements are not Unity.Objects (like uGUI elements) and promotes the idea that these are fairly transient C# objects that should not own game state or be shared widely across many GameObjects. The intended pattern is that you have a MonoBehaviour that requires UIDocument and that itself exposes all required UI control to the rest of your game while keeping its VisualElement references private. The rest of your game should not need to know that VisualElements are a thing. If you do this, you can happily enable/disable/pool these UI "controller" GameObjects.

    To give a bit more context on why things are a bit different with UITK, the ideal UI Toolkit implementation in a game is one where there is a single global UIDocument GameObject/Component that serves as the bridge between the GameObject world and the VisualElement world. And the more you put in a single UIDocument, the more you can:
    1. test and design entirely in the UI Builder without going into playmode, and
    2. more easily create UI integration tests for

    When you inter-mix VisualElements with GameObjects a lot, you get access to less of what UITK can do for you. Obviously there are still many cases where this pattern is not super practical (like having individual health bars per player) which is why we do support multiple UIDocuments (and multiple Panels).

    From a performance standpoint, the current restriction gives Unity a lot more room for optimizations in the future, like the ability to automatically pool individual VisualElements to improve instantiation performance auto-magically.

    If I understand this correctly, you have multiple GameObjects that get a reference to a UIDocument and to individual elements inside and directly manipulate these elements from a MonoBehaviour land. Is that right? If that's right, I'm curious why these need to be GameObject-based tweeners and why not implement them as custom VisualElements.
     
    Kazko likes this.
  8. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    Thanks for all the variations of your answer. Starting to make sense (the reasoning). But increases my general confusion about how it should be used. I need to think about this for a while.

    It's not just tweeners. It is - perhaps a bit overcomplicated, yet very reusable - system for showing any kind of UI frames. There is FrameData base, FrameView base, and data binder - which is an implementation of the way those two are put together. Naturally this lives in the GO world. My UITK implementation is just another inheritor of the abstract FrameView, which creates VisualElement on UIDocument's RVE and provides this panel to the binder. This way I can display given data very easily in any way - UITK, uGUI, GO hierarchy with SpriteRenderers and TMPs, ...

    Likewise, the tweeners are modular. They tween properties of the FrameView, they don't care if it's UITK or something else. So if I replace the UIDocument FrameView with another type, the tweener still worksSo that's the reason for living in GO world.

    So for each instance of this frame prefab/hierarchy there is a UIDocument component. This is easy to work with in terms of knowing exactly what the instance is dealing with, even if it's several identical frames displayed on screen, but each is being fed different data. Cards, for example. Just as we are used to - for example - there is no question about which SpriteRenderer your prefab is supposed to be dealing with, other than the one packed and referenced within. Now I'm trying to wrap my head around this hypothetical thought experiment of having a global SpriteRenderer, which all pooled objects should statically reference and manage their "slots" to put sprites into. Can be done, of course, it's just something new and breaking the well known simplicity.

    So if I make one global reference to a single UIDocument, the FrameView will reference it, .Add it's own elements and provide these to the binder. Then, the coexistence of GO world with UITK world is even weirder, when some object A is pooled, looking at some non-pooled global thing B (UIDoc), to reference/manage/pool some other thing C (VisualElement).

    Since I think that the authoring experience is already great (apart from some bugs), I decided to play this game. But soon I hit a mindset roadblock. Now that I want to treat UIDocument as a global thing, it's Source Asset field stops making sense. It made sense in my use case where I used multiple UIDocuments and used source asset to conveniently set different enclosing containers for various frame types. But having only one UIDocument, there is no use for it, because it's basically the whole screen where all other VEs will live. This forces me to create an empty container asset to use as the Source Asset, and immediately I think - wait a minute, this surely isn't the way this was designed to work.

    Any guidance here? Thanks a lot!

    (btw UITK is awesome and despite these hardships I pushed on and was very rewarded, and while it's still under development, I'd love to align my mindset to yours so I don't hit other roadblocks down the road)
     
    Last edited: Apr 24, 2021
  9. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    This makes it sound like you shouldn't have any issues with the UIDocument resets. The main issue with UIDocument is if you hold references to VisualElements outside the prefab or group of related GameObjects. Can you maybe clarify what the original problem was that was caused by the UIDocument reset on OnEnable()?

    You should be able to just not have a Source Asset at all. It's not mandatory to assign a UXML asset there. :)

    Going back to my original point about having a SINGLE UIDocument component. This is just the "ideal" in terms of pure UITK workflow. You should not completely re-architect your setup just make this true. As long as your individual UIDocument component/gameobjects are fairly self-contained and knowledge of UITK does not spread to all GOs in your scene, you should be good. Bonus if you these "FrameView" each have their own UXML assets that can be authored individually or together in the UI Builder.

    Another thing I would add is around binding UI to data at runtime. Right now, UI Toolkit does not offer much out of the box. We do plan to focus on bindings a lot more in 2022, but that's still 2022. For now, I find it works better if you have VisualElements drive themselves more instead of having them driven by something in the GO world. For example, you can have a single "FrameViewManagerElement" custom VisualElement per FrameView type that exposes Object fields for everything in the scene that needs UI representation in this view. You then have your FrameView GO find this custom element in your local UIDocument and just assign it all the Objects it needs from the scene. Then, this FrameViewManagerElement takes over the actual manipulation of other VisualElements. The reasons I like this approach is that:
    1. you can "mock" this connection when working inside UI Builder and create a simulation of your FrameView UI without needing your entire scene open or being in playmode
    2. it aligns a bit better with our future plans for runtime bindings, where, it will be the VisualElement that is told how to update itself from an Object (a binding)

    I'm still quite unsure of your exact setup so my suggestions might not make any sense in your case. I think it will help a lot, again, to maybe add some clarity around the exact issue (like, the error or side effect) you ran into when the UIDocument cleared itself.
     
    Kazko likes this.
  10. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    Ah, ok, I somehow thought that was the RVE reference itself. It feels as if I should have read the manual, however, I rarely do, because most components are more intuitive! Thanks for clarifying.

    The problem is that the prefab has (had - I already made a global UIDoc and it's kind of liberating, thanks for the guidance). However just to clarify the issue which led me here - the prefab had it's UIDocument. The prefab's components created VEs from given VTAs and cached their references on this document during their initialize phase. Then they filled data to these VEs. Once the user closed the panel, the prefab was placed into a pool and .SetActive(false). The reasoning was that only the "data filling phase" would be needed from there on. But when the panel was needed somewhere, system pulled it out of the pool to fill another data, yet the UIDocument had lost the created VEs when it was disabled along with the prefab.

    So far I've had quite fun driving them from my GO components. I have to explore the path you're suggesting, but whenever I think of inheriting from VisualElement, I stop and think about how would I design that class? By design I mean the style. While I made some fairly complicated and great working UITK panels already, this is something I have not explored at all. Not asking you, this is definitely my time to open the docs. Thanks a lot for answers, really appreciate it!
     
    uDamian likes this.
  11. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    I know this is an old post, but I would just like some definitive clarity on this question:
    What is the "Unity way" to toggle the visibility of a UI Document?
    1. SetActive
      on the
      GameObject
      that has the
      UIDocument
      component?
    2. enable = true/false
      on the
      UIDocument
      component?
    3. Do something with the
      VisualTreeAsset
      of the
      UIDocument
      (if so, what?)
    4. Set the
      style.display
      property on the
      rootVisualElement
      of the
      UIDocument
      component to
      DisplayStyle.Flex
      or
      DisplayStyle.None
      ?
    5. Something else I haven't even consdered?
    According to this post, I suppose setting the
    enable
    property on the
    UIDocument
    component is not the solution. So what is the best way to show/hide menus made with the UI Toolkit?

    Sorry if this question was already answered, but UIToolkit is totally new to me and it's a bit daunting to sift through all of the answers/documentation to find the right answer...
     
  12. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    How can we test the design entirely in the UI Builder? I'm trying to create a "tabbed" menu but I suppose I need to control the visability of each
    VisualElement
    that represents the tab page in a C# script. But as far as I can see, there is no way to associate click events in the UI Builder and I also don't see a way to run a C# script in the UI builder... Or am I missing something?
     
  13. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    699
    If you set display to none on a rootVisualElement that has a dropdown field open, then the UIDocument will disappear but the dropdownfield menu will stay open. You can retrieve this dropdown menu like so:

    Code (csharp):
    1.  
    2.   var ubd = root.parent.Q<VisualElement>(null, "unity-base-dropdown");
    3.  
    I don't think that setting display none on this element is a good idea, but some kind of event to close it should do the trick. Probably sending an ESC key, I haven't tried this yet.
     
  14. Midiphony

    Midiphony

    Joined:
    Jan 25, 2019
    Posts:
    14
    If you'd like to test things from UI Builder, you'll need to write custom VisualElements

    https://docs.unity3d.com/Packages/c...anual/uib-structuring-ui-custom-elements.html
    https://docs.unity3d.com/Manual/UIE-create-custom-controls.html

    We've abandoned this route some time ago on our side however (writing custom VisualElements for navigation : we plug this kind of behaviour in MonoBehaviours for example)
     
  15. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    That doesn't sound like a good solution... I think I'll just stick to play mode in the Unity editor...
     
  16. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    Okay. Thanks for the headsup. I do have a drop-down in my UI but I haven't observed this issue yet.
     
  17. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    But my primary question is still not answered:

    What is the best way to show/hide the UIDocument?
     
  18. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    It seems that the drop-down menu captures the mouse. Clicking anywhere while the dropdown is open causes the dropwdown to be hidden again.

    I suppose if you hid the root element in code (without user interaction) this might be a problem, but I'm not doing that. Closing the menus always requires user interaction, so I suppose this isn't an issue form me..?
     
  19. TeorikDeli

    TeorikDeli

    Joined:
    Apr 6, 2014
    Posts:
    150
    I depends. If you need the references in the documents (buttons, lists, etc) all the time, then you should just set display none on the root element (unless you want a fresh copy, like if it is a modal/etc). Re-assigning the events, element references again and again is a overhead in my opinion. But if you don't need anything from that document, I think, disabling/enabling the UIDocument component is the best (and performant) way.
     
  20. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    699
    Unity_7T30JcBMz9.gif
     
  21. jpvanoosten

    jpvanoosten

    Joined:
    Nov 20, 2009
    Posts:
    50
    Disabling the UIDocument was the issue of the original post. I think I'll just set the display of the root element to None (since I also don't want to loose the previously selected tab on the menu when hiding/showing the menu).

    But I do hope this get's added to the documentation somewhere. Currently there doesn't seem to be any examples of doing this "the intended Unity way".
     
  22. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    Thanks for calling this out. I'll need to gather a few more opinions before writing a docs page about it but I feel comfortable saying that:

    - UIDocument responds to OnEnable()/OnDisable() by clearing its contents. This method is best suitable to get rid of a bit of UI that isn't likely to be reused soon. This cleans up associated resources (also if no UIDocument are registered with a PanelSettings it cleans itself up).
    - UIDocument.rootVisualElement toggling between None and Default (Flex) is recommended way to hide something that is likely to be reused soon, or needs to show up very quickly to avoid an initialization penalty.
     
    Last edited: Nov 18, 2022
  23. Chenturion

    Chenturion

    Joined:
    May 26, 2018
    Posts:
    1
    Good afternoon,
    I have a UIContrainer GameObject with 2 child gameobjects,
    1st holding the UI Document for mainMenu,
    2nd contains the pauseMenu

    when the 1st menu is shown, I can navigate with arrow keys (desired behaviour) and tabs (not needed, but ok)
    when I set the display of this rootElement to None, and the display of the pauseMenu root to Flex, I cannot use the arrow keys anymore for navigation, only TAB; why is this?
    (in both cases I can see the 'navigation' events in the console log)
    thanks