-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow multiple root UI Node #1211
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. I think before merging we'd want to have an answer to the z-layer problem. We can't use "entity iteration order" to determine layering here because that is undefined (it starts as being entity insertion order, but as soon as archetypes change or entities are removed this breaks down).
It should also be possible to change z-ordering at runtime.
I agree that a z ordering is needed, that's the reason I changed the PR back to a draft. Do we want to specify z order for any node or only root nodes? If we can specify z order for any node, do we want to use offsets or layers? (relative or absolute z)
Should the offset be a new field of
My current opinion is to only have an offset for root nodes. The new commit implements that with a |
We need to ensure that layers never "intersect" with each other. Ex: if layer1 has a "stacked z-size" of 10, layer2 should start at 11 (10 and 11 use arbitrary units ... in practice it would be a floating point value)
I think it probably makes sense to make it relative. Maybe we can do something similar to CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/z-index.
I pretty strongly feel that it should be a part of Style (defaulting to a None or Auto value). I don't like requiring a separate component for roots because that is "redundant information". The fact that something is a root is already encoded by the hierarchy itself. Additionally, it allows the "root component" to be added to non-root nodes (or even non-root non-node entities), which is a bit weird imo. Additionally, it requires additional manual action from the user to define a root when constructing the hierarchy. |
Good idea!
I agree with you now.
In my WIP rewrite, I store a WindowId in it, so it does have extra information. We would need to store that information somewhere anyway to support multiple windows, and it makes sense to me to have it there.
I automatically add that component when necessary (with the primary window's id), so users only need to add or modify it when they deal with several windows.
Allowing "UI root" entities that are not a global root is IMO an attractive feature. I would like for instance to embed UI trees under the root entity of a scene, and automatically load/store them. I'm not completely sure about that, but I'd like to test it first, even if you want me to remove it later. I expect to push something by tomorrow for feedback, but I suspect it will need extensive testing. Do you have any suggestion of things to test in addition to the ui examples? |
Hmm yeah the "where should we put WindowId" discussion should also be had. That is root-specific information. We could consider just putting it in Style for convenience (even though it would only ever apply to root nodes). Or we could consider adding a new NodeWindowId component (probably optional and defaulting to the primary window when it isn't defined)
I want to avoid automatically adding components on the user's behalf whenever possible:
Some cases are basically unavoidable (such as Parent/Child components), but in this case I think just defaulting to the primary window is the right call.
I think I agree with that, but I still think that should be "behavior implicit to the hierarchy". If we want to support non-global roots, I think it should work by nature of a Node being added beneath some non-Node root. Adding the component is still redundant information / boilerplate.
We we regrettably don't have any built in examples/tests more complex than the |
Most steps of the layout system are now incremental. There are some other new features:
Additional thoughts:
|
Not sure if this is a case we want to support, but a node might want to assert that it is a root node regardless of its position in the hierarchy. For example, a "popup button" component might be getting spawned under a UI node, but it might spawn a popup as a root node. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I'm loving these changes. Much more flexible and much easier to read.
crates/bevy_ui/src/flex/mod.rs
Outdated
// update children | ||
for (entity, children) in children_query.iter() { | ||
flex_surface.update_children(entity, &children); | ||
fn update_one_transform_z( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does a ton of redunant work in terms of allocations, traversals, sorting, etc (especially on startup). I think it makes sense to add some tracking here to cut down on the combinatorics.
Ex: On startup every single node:
- walks up the tree to (worst case) the root O(logn)
- Allocates/populates a stacking context with every node (worst case the whole tree, which is O(n))
- Sorts the stacking context O(nlogn)
- Sets the transform z value of each node in the stacking context (which is O(n))
By my calculations thats worst case O(n^2logn) compute AND allocations, which is not fantastic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ex: On startup every single node:
- walks up the tree to (worst case) the root O(logn)
This is a real concern but I'd like to point out that it only does that work for their stacking context, which by default and in most cases (ZIndex::None
or ZIndex::Some(i)
) means their children. The worst case happens if the user explicitly selects ZIndex::Auto
in every single node. In practice I only expect that feature to be used inside the small hierarchy of a widget for instance.
- Sorts the stacking context O(nlogn)
In most cases (ZIndex::None
and ZIndex::Auto
), the stacking context should already be sorted at creation (I need to test that). The sorting only has to move nodes with ZIndex::Some(i)
.
Here is a list of potential improvements:
- use a smallvec so that most cases without
ZIndex::Auto
do not allocate - skip sorting if there is no
ZIndex::Some(i)
- cache the stacking context (maybe only when
ZIndex::Auto
is involved?) - use an iterator to add all children at once instead of pushing one by one
- as discussed, making ZIndex a separate component so that changes to other Style properties do not trigger this whole process. (Alternatively, ZIndex could have its own mutation flag?)
The part that I'm unsatisfied with is that the process is duplicated when several non leaf entities from the same stacking context are added/updated at once. However I'm being cautious because it would be unfortunate if an optimisation for that adds overhead for the default/common cases. I'll think about what kind of tracking can be done there.
Besides, I think there is a bug in this implementation for negative z-index, I'll have to check.
Last thought: is the ZIndex::Auto
feature worth adding all that complexity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new commit only allocates stacking contexts once, and makes sure they are updated at most once every frame, when required.
However the z transforms are now all updated if any stacking context changed, since using the transforms's scale for incremental update was causing issues with f32 precision.
@@ -1,20 +1,23 @@ | |||
mod convert; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This impl breaks scale factor support. We'll need to fix that before merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention it, sorry.
I'm not familiar with this topic, is it required to round the translations at all?
If it is we would need to cache the scale factor for every node.
If it's not we could just disable the final rounding in stretch and do all layout calculations in logical pixels, which is very simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think ideally we would use logical pixels to keep the code simple, but I do think stretch rounds on our behalf, which would break things when we try to scale back up via the scale factor. We could either try to fix that in stretch or revert to the "physical pixel" approach used on "bevy master".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to fix it in stretch, but it seems the maintainers are not responsive. How would you like to handle that?
Yeah thats a good idea. Lets hold off for now and when the "UI to Texture" feature is built we can re-visit. No sense in building a feature when we aren't sure its needed yet.
Hmm thats worth considering, but I think I'd prefer to break it out if/when theres a demand for it. Taking on the "interoperable ui" problem introduces a whole new set of problems that I don't think we've discussed enough to understand the scope of.
Yeah lets revisit this after the hierarchy rework.
I think this is probably ok, but we should probably make sure it doesn't produce precision issues for deeply nested ui. |
I just realized that the current z-index implementation don't actually solve the layering of root nodes, which is what I wanted from it in the first place. I need to put them in their own stacking context.
The minimum precision between 0..1 is about 5.96e-8 near 1. If we take the simple case of each node having one child, we would hit that limit in -ln(5.96e-8)/ln(2) = 24 nested scopes. That's not as deep as I expected, but makes sense in hindsight since that's the size of the mantissa of f32, and each scope will be half the size of its parent. If that's not good enough, we would have to switch back to updating all components at every change. It will be worth checking the performance difference between the two in large applications when they exist. |
Yeah if 32 is the limit, I have a feeling someone will hit that eventually. We should support "near infinite" nesting (in theory, if not in practice, for perf reasons). |
After testing it, I ran into that issue even earlier than that. |
Here is the reimplementation of the z-index property without using the transforms' scale. The z-indices for window nodes and negative z indices are fixed, and I added a lot of tracking to avoid duplicating work. I added a Not<> QueryFilter for that, but it's a bit out of scope of this PR, tell me if you want me to split it out. The window nodes' layering is undefined if their z-index is not set to Some(_). They will be on different layers, but which one is in front can change. That should probably be documented somewhere.
Since NodeWindowId is already specific to window nodes, it now allows users to explicitly mark window nodes (in the previous commit that component was just ignored on normal nodes, which people could find surprising). I had to add a new stage for |
All the UI entities with a Node that have no Parent component will stretch on the whole window instead of sharing it, allowing them to act as layers.
Mutliple UI roots is especially useful for 3rd party plugin providing UI functionalities like https://github.com/nicopap/bevy-debug-text-overlay/ . I guess the user could specify a node to which to parent the plugin's UI, but it will mess with the user's UI, which we do not want. |
Agreed, my original use case was also a debug overlay using bevy_ui. |
@Davier @alice-i-cecile What's the status on this. Is there something actively blocking it? I imagine it may be out of sync with latest Bevy now, but is it worth updating or should a different solution be adopted? I was thinking about this problem yesterday, before finding this PR, and had an idea for a flexible but not too complex solution. I'm wondering if I should try implementing it and make a different PR instead. The idea is to allow setting either "local" and "global" z-index via an enum component, where local affects the render order of siblings and global affects render order of all UI nodes (e.g. to allow absolute nodes that are deep in the tree to render on top or under other things).
|
This needs to be adopted; we have consensus that this work needs to be done. I'm not immediately sure on whether or not this specific approach is optimal. |
Closing in favor of #5892. |
All the UI entities with Node that are at the root of their hierarchy (i.e. have no Parent component) will stretch on the whole window instead of sharing it, allowing them to act as layers.