mirror of
https://github.com/bevyengine/bevy
synced 2024-12-27 13:33:08 +00:00
4b5a33d970
# Objective Add consistent UI rendering and interaction where deep nodes inside two different hierarchies will never render on top of one-another by default and offer an escape hatch (z-index) for nodes to change their depth. ## The problem with current implementation The current implementation of UI rendering is broken in that regard, mainly because [it sets the Z value of the `Transform` component based on a "global Z" space](https://github.com/bevyengine/bevy/blob/main/crates/bevy_ui/src/update.rs#L43) shared by all nodes in the UI. This doesn't account for the fact that each node's final `GlobalTransform` value will be relative to its parent. This effectively makes the depth unpredictable when two deep trees are rendered on top of one-another. At the moment, it's also up to each part of the UI code to sort all of the UI nodes. The solution that's offered here does the full sorting of UI node entities once and offers the result through a resource so that all systems can use it. ## Solution ### New ZIndex component This adds a new optional `ZIndex` enum component for nodes which offers two mechanism: - `ZIndex::Local(i32)`: Overrides the depth of the node relative to its siblings. - `ZIndex::Global(i32)`: Overrides the depth of the node relative to the UI root. This basically allows any node in the tree to "escape" the parent and be ordered relative to the entire UI. Note that in the current implementation, omitting `ZIndex` on a node has the same result as adding `ZIndex::Local(0)`. Additionally, the "global" stacking context is essentially a way to add your node to the root stacking context, so using `ZIndex::Local(n)` on a root node (one without parent) will share that space with all nodes using `Index::Global(n)`. ### New UiStack resource This adds a new `UiStack` resource which is calculated from both hierarchy and `ZIndex` during UI update and contains a vector of all node entities in the UI, ordered by depth (from farthest from camera to closest). This is exposed publicly by the bevy_ui crate with the hope that it can be used for consistent ordering and to reduce the amount of sorting that needs to be done by UI systems (i.e. instead of sorting everything by `global_transform.z` in every system, this array can be iterated over). ### New z_index example This also adds a new z_index example that showcases the new `ZIndex` component. It's also a good general demo of the new UI stack system, because making this kind of UI was very broken with the old system (e.g. nodes would render on top of each other, not respecting hierarchy or insert order at all). ![image](https://user-images.githubusercontent.com/1060971/189015985-8ea8f989-0e9d-4601-a7e0-4a27a43a53f9.png) --- ## Changelog - Added the `ZIndex` component to bevy_ui. - Added the `UiStack` resource to bevy_ui, and added implementation in a new `stack.rs` module. - Removed the previous Z updating system from bevy_ui, because it was replaced with the above. - Changed bevy_ui rendering to use UiStack instead of z ordering. - Changed bevy_ui focus/interaction system to use UiStack instead of z ordering. - Added a new z_index example. ## ZIndex demo Here's a demo I wrote to test these features https://user-images.githubusercontent.com/1060971/188329295-d7beebd6-9aee-43ab-821e-d437df5dbe8a.mp4 Co-authored-by: Carter Anderson <mcanders1@gmail.com>
72 lines
2.2 KiB
Rust
72 lines
2.2 KiB
Rust
//! This module contains systems that update the UI when something changes
|
|
|
|
use crate::{CalculatedClip, Overflow, Style};
|
|
|
|
use super::Node;
|
|
use bevy_ecs::{
|
|
entity::Entity,
|
|
query::{With, Without},
|
|
system::{Commands, Query},
|
|
};
|
|
use bevy_hierarchy::{Children, Parent};
|
|
use bevy_math::Rect;
|
|
use bevy_transform::components::GlobalTransform;
|
|
|
|
/// Updates clipping for all nodes
|
|
pub fn update_clipping_system(
|
|
mut commands: Commands,
|
|
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
|
mut node_query: Query<(&Node, &GlobalTransform, &Style, Option<&mut CalculatedClip>)>,
|
|
children_query: Query<&Children>,
|
|
) {
|
|
for root_node in &root_node_query {
|
|
update_clipping(
|
|
&mut commands,
|
|
&children_query,
|
|
&mut node_query,
|
|
root_node,
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn update_clipping(
|
|
commands: &mut Commands,
|
|
children_query: &Query<&Children>,
|
|
node_query: &mut Query<(&Node, &GlobalTransform, &Style, Option<&mut CalculatedClip>)>,
|
|
entity: Entity,
|
|
clip: Option<Rect>,
|
|
) {
|
|
let (node, global_transform, style, calculated_clip) = node_query.get_mut(entity).unwrap();
|
|
// Update this node's CalculatedClip component
|
|
match (clip, calculated_clip) {
|
|
(None, None) => {}
|
|
(None, Some(_)) => {
|
|
commands.entity(entity).remove::<CalculatedClip>();
|
|
}
|
|
(Some(clip), None) => {
|
|
commands.entity(entity).insert(CalculatedClip { clip });
|
|
}
|
|
(Some(clip), Some(mut old_clip)) => {
|
|
if old_clip.clip != clip {
|
|
*old_clip = CalculatedClip { clip };
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate new clip for its children
|
|
let children_clip = match style.overflow {
|
|
Overflow::Visible => clip,
|
|
Overflow::Hidden => {
|
|
let node_center = global_transform.translation().truncate();
|
|
let node_rect = Rect::from_center_size(node_center, node.calculated_size);
|
|
Some(clip.map_or(node_rect, |c| c.intersect(node_rect)))
|
|
}
|
|
};
|
|
|
|
if let Ok(children) = children_query.get(entity) {
|
|
for child in children.iter().cloned() {
|
|
update_clipping(commands, children_query, node_query, child, children_clip);
|
|
}
|
|
}
|
|
}
|