Simplified ui_stack_system
(#9889)
# Objective `ui_stack_system` generates a tree of `StackingContexts` which it then flattens to get the `UiStack`. But there's no need to construct a new tree. We can query for nodes with a global `ZIndex`, add those nodes to the root nodes list and then build the `UiStack` from a walk of the existing layout tree, ignoring any branches that have a global `Zindex`. Fixes #9877 ## Solution Split the `ZIndex` enum into two separate components, `ZIndex` and `GlobalZIndex` Query for nodes with a `GlobalZIndex`, add those nodes to the root nodes list and then build the `UiStack` from a walk of the existing layout tree, filtering branches by `Without<GlobalZIndex>` so we don't revisit nodes. ``` cargo run --profile stress-test --features trace_tracy --example many_buttons ``` <img width="672" alt="ui-stack-system-walk-split-enum" src="https://github.com/bevyengine/bevy/assets/27962798/11e357a5-477f-4804-8ada-c4527c009421"> (Yellow is this PR, red is main) --- ## Changelog `Zindex` * The `ZIndex` enum has been split into two separate components `ZIndex` (which replaces `ZIndex::Local`) and `GlobalZIndex` (which replaces `ZIndex::Global`). An entity can have both a `ZIndex` and `GlobalZIndex`, in comparisons `ZIndex` breaks ties if two `GlobalZIndex` values are equal. `ui_stack_system` * Instead of generating a tree of `StackingContexts`, query for nodes with a `GlobalZIndex`, add those nodes to the root nodes list and then build the `UiStack` from a walk of the existing layout tree, filtering branches by `Without<GlobalZIndex` so we don't revisit nodes. ## Migration Guide The `ZIndex` enum has been split into two separate components `ZIndex` (which replaces `ZIndex::Local`) and `GlobalZIndex` (which replaces `ZIndex::Global`). An entity can have both a `ZIndex` and `GlobalZIndex`, in comparisons `ZIndex` breaks ties if two `GlobalZindex` values are equal. --------- Co-authored-by: Gabriel Bourgeois <gabriel.bourgeoisv4si@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
This commit is contained in:
parent
93aa2a2cc4
commit
c5742ff43e
@ -16,11 +16,11 @@ use bevy_render::view::Visibility;
|
|||||||
use bevy_text::{Font, Text, TextSection, TextStyle};
|
use bevy_text::{Font, Text, TextSection, TextStyle};
|
||||||
use bevy_ui::{
|
use bevy_ui::{
|
||||||
node_bundles::{NodeBundle, TextBundle},
|
node_bundles::{NodeBundle, TextBundle},
|
||||||
PositionType, Style, ZIndex,
|
GlobalZIndex, PositionType, Style,
|
||||||
};
|
};
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
|
|
||||||
/// Global [`ZIndex`] used to render the fps overlay.
|
/// [`GlobalZIndex`] used to render the fps overlay.
|
||||||
///
|
///
|
||||||
/// We use a number slightly under `i32::MAX` so you can render on top of it if you really need to.
|
/// We use a number slightly under `i32::MAX` so you can render on top of it if you really need to.
|
||||||
pub const FPS_OVERLAY_ZINDEX: i32 = i32::MAX - 32;
|
pub const FPS_OVERLAY_ZINDEX: i32 = i32::MAX - 32;
|
||||||
@ -83,16 +83,18 @@ struct FpsText;
|
|||||||
|
|
||||||
fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
|
fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
|
||||||
commands
|
commands
|
||||||
.spawn(NodeBundle {
|
.spawn((
|
||||||
style: Style {
|
NodeBundle {
|
||||||
// We need to make sure the overlay doesn't affect the position of other UI nodes
|
style: Style {
|
||||||
position_type: PositionType::Absolute,
|
// We need to make sure the overlay doesn't affect the position of other UI nodes
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
// Render overlay on top of everything
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
// Render overlay on top of everything
|
GlobalZIndex(FPS_OVERLAY_ZINDEX),
|
||||||
z_index: ZIndex::Global(FPS_OVERLAY_ZINDEX),
|
))
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|c| {
|
.with_children(|c| {
|
||||||
c.spawn((
|
c.spawn((
|
||||||
TextBundle::from_sections([
|
TextBundle::from_sections([
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_hierarchy::prelude::*;
|
use bevy_hierarchy::prelude::*;
|
||||||
|
|
||||||
use crate::{Node, ZIndex};
|
use crate::{GlobalZIndex, Node, ZIndex};
|
||||||
|
|
||||||
/// The current UI stack, which contains all UI nodes ordered by their depth (back-to-front).
|
/// The current UI stack, which contains all UI nodes ordered by their depth (back-to-front).
|
||||||
///
|
///
|
||||||
@ -15,69 +15,75 @@ pub struct UiStack {
|
|||||||
pub uinodes: Vec<Entity>,
|
pub uinodes: Vec<Entity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caches stacking context buffers for use in [`ui_stack_system`].
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct StackingContextCache {
|
pub(crate) struct ChildBufferCache {
|
||||||
inner: Vec<StackingContext>,
|
pub inner: Vec<Vec<(Entity, i32)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackingContextCache {
|
impl ChildBufferCache {
|
||||||
fn pop(&mut self) -> StackingContext {
|
fn pop(&mut self) -> Vec<(Entity, i32)> {
|
||||||
self.inner.pop().unwrap_or_default()
|
self.inner.pop().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, mut context: StackingContext) {
|
fn push(&mut self, vec: Vec<(Entity, i32)>) {
|
||||||
for entry in context.entries.drain(..) {
|
self.inner.push(vec);
|
||||||
self.push(entry.stack);
|
|
||||||
}
|
|
||||||
self.inner.push(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct StackingContext {
|
|
||||||
entries: Vec<StackingContextEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StackingContextEntry {
|
|
||||||
z_index: i32,
|
|
||||||
entity: Entity,
|
|
||||||
stack: StackingContext,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates the render stack for UI nodes.
|
/// Generates the render stack for UI nodes.
|
||||||
///
|
///
|
||||||
/// First generate a UI node tree (`StackingContext`) based on z-index.
|
/// Create a list of root nodes from unparented entities and entities with a `GlobalZIndex` component.
|
||||||
/// Then flatten that tree into back-to-front ordered `UiStack`.
|
/// Then build the `UiStack` from a walk of the existing layout trees starting from each root node,
|
||||||
pub(crate) fn ui_stack_system(
|
/// filtering branches by `Without<GlobalZIndex>`so that we don't revisit nodes.
|
||||||
mut cache: Local<StackingContextCache>,
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn ui_stack_system(
|
||||||
|
mut cache: Local<ChildBufferCache>,
|
||||||
|
mut root_nodes: Local<Vec<(Entity, (i32, i32))>>,
|
||||||
mut ui_stack: ResMut<UiStack>,
|
mut ui_stack: ResMut<UiStack>,
|
||||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
root_node_query: Query<
|
||||||
zindex_query: Query<&ZIndex, With<Node>>,
|
(Entity, Option<&GlobalZIndex>, Option<&ZIndex>),
|
||||||
|
(With<Node>, Without<Parent>),
|
||||||
|
>,
|
||||||
|
zindex_global_node_query: Query<
|
||||||
|
(Entity, &GlobalZIndex, Option<&ZIndex>),
|
||||||
|
(With<Node>, With<Parent>),
|
||||||
|
>,
|
||||||
children_query: Query<&Children>,
|
children_query: Query<&Children>,
|
||||||
|
zindex_query: Query<Option<&ZIndex>, (With<Node>, Without<GlobalZIndex>)>,
|
||||||
mut update_query: Query<&mut Node>,
|
mut update_query: Query<&mut Node>,
|
||||||
) {
|
) {
|
||||||
// Generate `StackingContext` tree
|
ui_stack.uinodes.clear();
|
||||||
let mut global_context = cache.pop();
|
for (id, global_zindex, maybe_zindex) in zindex_global_node_query.iter() {
|
||||||
let mut total_entry_count: usize = 0;
|
root_nodes.push((
|
||||||
|
id,
|
||||||
for entity in &root_node_query {
|
(
|
||||||
insert_context_hierarchy(
|
global_zindex.0,
|
||||||
&mut cache,
|
maybe_zindex.map(|zindex| zindex.0).unwrap_or(0),
|
||||||
&zindex_query,
|
),
|
||||||
&children_query,
|
));
|
||||||
entity,
|
|
||||||
&mut global_context,
|
|
||||||
None,
|
|
||||||
&mut total_entry_count,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten `StackingContext` into `UiStack`
|
for (id, maybe_global_zindex, maybe_zindex) in root_node_query.iter() {
|
||||||
ui_stack.uinodes.clear();
|
root_nodes.push((
|
||||||
ui_stack.uinodes.reserve(total_entry_count);
|
id,
|
||||||
fill_stack_recursively(&mut cache, &mut ui_stack.uinodes, &mut global_context);
|
(
|
||||||
cache.push(global_context);
|
maybe_global_zindex.map(|zindex| zindex.0).unwrap_or(0),
|
||||||
|
maybe_zindex.map(|zindex| zindex.0).unwrap_or(0),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
root_nodes.sort_by_key(|(_, z)| *z);
|
||||||
|
|
||||||
|
for (root_entity, _) in root_nodes.drain(..) {
|
||||||
|
update_uistack_recursive(
|
||||||
|
&mut cache,
|
||||||
|
root_entity,
|
||||||
|
&children_query,
|
||||||
|
&zindex_query,
|
||||||
|
&mut ui_stack.uinodes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (i, entity) in ui_stack.uinodes.iter().enumerate() {
|
for (i, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||||
if let Ok(mut node) = update_query.get_mut(*entity) {
|
if let Ok(mut node) = update_query.get_mut(*entity) {
|
||||||
@ -86,67 +92,28 @@ pub(crate) fn ui_stack_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate z-index based UI node tree
|
fn update_uistack_recursive(
|
||||||
fn insert_context_hierarchy(
|
cache: &mut ChildBufferCache,
|
||||||
cache: &mut StackingContextCache,
|
node_entity: Entity,
|
||||||
zindex_query: &Query<&ZIndex, With<Node>>,
|
|
||||||
children_query: &Query<&Children>,
|
children_query: &Query<&Children>,
|
||||||
entity: Entity,
|
zindex_query: &Query<Option<&ZIndex>, (With<Node>, Without<GlobalZIndex>)>,
|
||||||
global_context: &mut StackingContext,
|
ui_stack: &mut Vec<Entity>,
|
||||||
parent_context: Option<&mut StackingContext>,
|
|
||||||
total_entry_count: &mut usize,
|
|
||||||
) {
|
) {
|
||||||
let mut new_context = cache.pop();
|
ui_stack.push(node_entity);
|
||||||
|
|
||||||
if let Ok(children) = children_query.get(entity) {
|
if let Ok(children) = children_query.get(node_entity) {
|
||||||
// Reserve space for all children. In practice, some may not get pushed since
|
let mut child_buffer = cache.pop();
|
||||||
// nodes with `ZIndex::Global` are pushed to the global (root) context.
|
child_buffer.extend(children.iter().filter_map(|child_entity| {
|
||||||
new_context.entries.reserve_exact(children.len());
|
zindex_query
|
||||||
|
.get(*child_entity)
|
||||||
for entity in children {
|
.ok()
|
||||||
insert_context_hierarchy(
|
.map(|zindex| (*child_entity, zindex.map(|zindex| zindex.0).unwrap_or(0)))
|
||||||
cache,
|
}));
|
||||||
zindex_query,
|
child_buffer.sort_by_key(|k| k.1);
|
||||||
children_query,
|
for (child_entity, _) in child_buffer.drain(..) {
|
||||||
*entity,
|
update_uistack_recursive(cache, child_entity, children_query, zindex_query, ui_stack);
|
||||||
global_context,
|
|
||||||
Some(&mut new_context),
|
|
||||||
total_entry_count,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
cache.push(child_buffer);
|
||||||
|
|
||||||
// The node will be added either to global/parent based on its z-index type: global/local.
|
|
||||||
let z_index = zindex_query.get(entity).unwrap_or(&ZIndex::Local(0));
|
|
||||||
let (entity_context, z_index) = match z_index {
|
|
||||||
ZIndex::Local(value) => (parent_context.unwrap_or(global_context), *value),
|
|
||||||
ZIndex::Global(value) => (global_context, *value),
|
|
||||||
};
|
|
||||||
|
|
||||||
*total_entry_count += 1;
|
|
||||||
entity_context.entries.push(StackingContextEntry {
|
|
||||||
z_index,
|
|
||||||
entity,
|
|
||||||
stack: new_context,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flatten `StackingContext` (z-index based UI node tree) into back-to-front entities list
|
|
||||||
fn fill_stack_recursively(
|
|
||||||
cache: &mut StackingContextCache,
|
|
||||||
result: &mut Vec<Entity>,
|
|
||||||
stack: &mut StackingContext,
|
|
||||||
) {
|
|
||||||
// Sort entries by ascending z_index, while ensuring that siblings
|
|
||||||
// with the same local z_index will keep their ordering. This results
|
|
||||||
// in `back-to-front` ordering, low z_index = back; high z_index = front.
|
|
||||||
stack.entries.sort_by_key(|e| e.z_index);
|
|
||||||
|
|
||||||
for mut entry in stack.entries.drain(..) {
|
|
||||||
// Parent node renders before/behind child nodes
|
|
||||||
result.push(entry.entity);
|
|
||||||
fill_stack_recursively(cache, result, &mut entry.stack);
|
|
||||||
cache.push(entry.stack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,15 +127,35 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use bevy_hierarchy::{BuildChildren, ChildBuild};
|
use bevy_hierarchy::{BuildChildren, ChildBuild};
|
||||||
|
|
||||||
use crate::{Node, UiStack, ZIndex};
|
use crate::{GlobalZIndex, Node, UiStack, ZIndex};
|
||||||
|
|
||||||
use super::ui_stack_system;
|
use super::ui_stack_system;
|
||||||
|
|
||||||
#[derive(Component, PartialEq, Debug, Clone)]
|
#[derive(Component, PartialEq, Debug, Clone)]
|
||||||
struct Label(&'static str);
|
struct Label(&'static str);
|
||||||
|
|
||||||
fn node_with_zindex(name: &'static str, z_index: ZIndex) -> (Label, Node, ZIndex) {
|
fn node_with_global_and_local_zindex(
|
||||||
(Label(name), Node::default(), z_index)
|
name: &'static str,
|
||||||
|
global_zindex: i32,
|
||||||
|
local_zindex: i32,
|
||||||
|
) -> (Label, Node, GlobalZIndex, ZIndex) {
|
||||||
|
(
|
||||||
|
Label(name),
|
||||||
|
Node::default(),
|
||||||
|
GlobalZIndex(global_zindex),
|
||||||
|
ZIndex(local_zindex),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_with_global_zindex(
|
||||||
|
name: &'static str,
|
||||||
|
global_zindex: i32,
|
||||||
|
) -> (Label, Node, GlobalZIndex) {
|
||||||
|
(Label(name), Node::default(), GlobalZIndex(global_zindex))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_with_zindex(name: &'static str, zindex: i32) -> (Label, Node, ZIndex) {
|
||||||
|
(Label(name), Node::default(), ZIndex(zindex))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_without_zindex(name: &'static str) -> (Label, Node) {
|
fn node_without_zindex(name: &'static str) -> (Label, Node) {
|
||||||
@ -188,24 +175,24 @@ mod tests {
|
|||||||
|
|
||||||
let mut queue = CommandQueue::default();
|
let mut queue = CommandQueue::default();
|
||||||
let mut commands = Commands::new(&mut queue, &world);
|
let mut commands = Commands::new(&mut queue, &world);
|
||||||
commands.spawn(node_with_zindex("0", ZIndex::Global(2)));
|
commands.spawn(node_with_global_zindex("0", 2));
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn(node_with_zindex("1", ZIndex::Local(1)))
|
.spawn(node_with_zindex("1", 1))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent
|
parent
|
||||||
.spawn(node_without_zindex("1-0"))
|
.spawn(node_without_zindex("1-0"))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(node_without_zindex("1-0-0"));
|
parent.spawn(node_without_zindex("1-0-0"));
|
||||||
parent.spawn(node_without_zindex("1-0-1"));
|
parent.spawn(node_without_zindex("1-0-1"));
|
||||||
parent.spawn(node_with_zindex("1-0-2", ZIndex::Local(-1)));
|
parent.spawn(node_with_zindex("1-0-2", -1));
|
||||||
});
|
});
|
||||||
parent.spawn(node_without_zindex("1-1"));
|
parent.spawn(node_without_zindex("1-1"));
|
||||||
parent
|
parent
|
||||||
.spawn(node_with_zindex("1-2", ZIndex::Global(-1)))
|
.spawn(node_with_global_zindex("1-2", -1))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(node_without_zindex("1-2-0"));
|
parent.spawn(node_without_zindex("1-2-0"));
|
||||||
parent.spawn(node_with_zindex("1-2-1", ZIndex::Global(-3)));
|
parent.spawn(node_with_global_zindex("1-2-1", -3));
|
||||||
parent
|
parent
|
||||||
.spawn(node_without_zindex("1-2-2"))
|
.spawn(node_without_zindex("1-2-2"))
|
||||||
.with_children(|_| ());
|
.with_children(|_| ());
|
||||||
@ -227,7 +214,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.spawn(node_with_zindex("3", ZIndex::Global(-2)));
|
commands.spawn(node_with_global_zindex("3", -2));
|
||||||
|
|
||||||
queue.apply(&mut world);
|
queue.apply(&mut world);
|
||||||
|
|
||||||
@ -243,25 +230,74 @@ mod tests {
|
|||||||
.map(|entity| query.get(&world, *entity).unwrap().clone())
|
.map(|entity| query.get(&world, *entity).unwrap().clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let expected_result = vec![
|
let expected_result = vec![
|
||||||
Label("1-2-1"), // ZIndex::Global(-3)
|
(Label("1-2-1")), // GlobalZIndex(-3)
|
||||||
Label("3"), // ZIndex::Global(-2)
|
(Label("3")), // GlobalZIndex(-2)
|
||||||
Label("1-2"), // ZIndex::Global(-1)
|
(Label("1-2")), // GlobalZIndex(-1)
|
||||||
Label("1-2-0"),
|
(Label("1-2-0")),
|
||||||
Label("1-2-2"),
|
(Label("1-2-2")),
|
||||||
Label("1-2-3"),
|
(Label("1-2-3")),
|
||||||
Label("2"),
|
(Label("2")),
|
||||||
Label("2-0"),
|
(Label("2-0")),
|
||||||
Label("2-1"),
|
(Label("2-1")),
|
||||||
Label("2-1-0"),
|
(Label("2-1-0")),
|
||||||
Label("1"), // ZIndex::Local(1)
|
(Label("1")), // ZIndex(1)
|
||||||
Label("1-0"),
|
(Label("1-0")),
|
||||||
Label("1-0-2"), // ZIndex::Local(-1)
|
(Label("1-0-2")), // ZIndex(-1)
|
||||||
Label("1-0-0"),
|
(Label("1-0-0")),
|
||||||
Label("1-0-1"),
|
(Label("1-0-1")),
|
||||||
Label("1-1"),
|
(Label("1-1")),
|
||||||
Label("1-3"),
|
(Label("1-3")),
|
||||||
Label("0"), // ZIndex::Global(2)
|
(Label("0")), // GlobalZIndex(2)
|
||||||
];
|
];
|
||||||
assert_eq!(actual_result, expected_result);
|
assert_eq!(actual_result, expected_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_equal_global_zindex_zindex_decides_order() {
|
||||||
|
let mut world = World::default();
|
||||||
|
world.init_resource::<UiStack>();
|
||||||
|
|
||||||
|
let mut queue = CommandQueue::default();
|
||||||
|
let mut commands = Commands::new(&mut queue, &world);
|
||||||
|
commands.spawn(node_with_global_and_local_zindex("0", -1, 1));
|
||||||
|
commands.spawn(node_with_global_and_local_zindex("1", -1, 2));
|
||||||
|
commands.spawn(node_with_global_and_local_zindex("2", 1, 3));
|
||||||
|
commands.spawn(node_with_global_and_local_zindex("3", 1, -3));
|
||||||
|
commands
|
||||||
|
.spawn(node_without_zindex("4"))
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn(node_with_global_and_local_zindex("5", 0, -1));
|
||||||
|
builder.spawn(node_with_global_and_local_zindex("6", 0, 1));
|
||||||
|
builder.spawn(node_with_global_and_local_zindex("7", -1, -1));
|
||||||
|
builder.spawn(node_with_global_zindex("8", 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
queue.apply(&mut world);
|
||||||
|
|
||||||
|
let mut schedule = Schedule::default();
|
||||||
|
schedule.add_systems(ui_stack_system);
|
||||||
|
schedule.run(&mut world);
|
||||||
|
|
||||||
|
let mut query = world.query::<&Label>();
|
||||||
|
let ui_stack = world.resource::<UiStack>();
|
||||||
|
let actual_result = ui_stack
|
||||||
|
.uinodes
|
||||||
|
.iter()
|
||||||
|
.map(|entity| query.get(&world, *entity).unwrap().clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected_result = vec![
|
||||||
|
(Label("7")),
|
||||||
|
(Label("0")),
|
||||||
|
(Label("1")),
|
||||||
|
(Label("5")),
|
||||||
|
(Label("4")),
|
||||||
|
(Label("6")),
|
||||||
|
(Label("3")),
|
||||||
|
(Label("8")),
|
||||||
|
(Label("2")),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(actual_result, expected_result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2052,26 +2052,21 @@ pub struct CalculatedClip {
|
|||||||
/// appear in the UI hierarchy. In such a case, the last node to be added to its parent
|
/// appear in the UI hierarchy. In such a case, the last node to be added to its parent
|
||||||
/// will appear in front of its siblings.
|
/// will appear in front of its siblings.
|
||||||
///
|
///
|
||||||
/// Internally, nodes with a global z-index share the stacking context of root UI nodes
|
|
||||||
/// (nodes that have no parent). Because of this, there is no difference between using
|
|
||||||
/// `ZIndex::Local(n)` and `ZIndex::Global(n)` for root nodes.
|
|
||||||
///
|
|
||||||
/// Nodes without this component will be treated as if they had a value of `ZIndex::Local(0)`.
|
|
||||||
#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Reflect)]
|
|
||||||
#[reflect(Component, Default, Debug, PartialEq)]
|
|
||||||
pub enum ZIndex {
|
|
||||||
/// Indicates the order in which this node should be rendered relative to its siblings.
|
|
||||||
Local(i32),
|
|
||||||
/// Indicates the order in which this node should be rendered relative to root nodes and
|
|
||||||
/// all other nodes that have a global z-index.
|
|
||||||
Global(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ZIndex {
|
/// Nodes without this component will be treated as if they had a value of [`ZIndex(0)`].
|
||||||
fn default() -> Self {
|
#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
|
||||||
Self::Local(0)
|
#[reflect(Component, Default, Debug, PartialEq)]
|
||||||
}
|
pub struct ZIndex(pub i32);
|
||||||
}
|
|
||||||
|
/// `GlobalZIndex` allows a [`Node`] entity anywhere in the UI hierarchy to escape the implicit draw ordering of the UI's layout tree and
|
||||||
|
/// be rendered above or below other UI nodes.
|
||||||
|
/// Nodes with a `GlobalZIndex` of greater than 0 will be drawn on top of nodes without a `GlobalZIndex` or nodes with a lower `GlobalZIndex`.
|
||||||
|
/// Nodes with a `GlobalZIndex` of less than 0 will be drawn below nodes without a `GlobalZIndex` or nodes with a greater `GlobalZIndex`.
|
||||||
|
///
|
||||||
|
/// If two Nodes have the same `GlobalZIndex`, the node with the greater [`ZIndex`] will be drawn on top.
|
||||||
|
#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug, PartialEq)]
|
||||||
|
pub struct GlobalZIndex(pub i32);
|
||||||
|
|
||||||
/// Used to add rounded corners to a UI node. You can set a UI node to have uniformly
|
/// Used to add rounded corners to a UI node. You can set a UI node to have uniformly
|
||||||
/// rounded corners or specify different radii for each corner. If a given radius exceeds half
|
/// rounded corners or specify different radii for each corner. If a given radius exceeds half
|
||||||
|
@ -116,16 +116,18 @@ fn setup(
|
|||||||
let style = TextStyle::default();
|
let style = TextStyle::default();
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn(NodeBundle {
|
.spawn((
|
||||||
style: Style {
|
NodeBundle {
|
||||||
position_type: PositionType::Absolute,
|
style: Style {
|
||||||
padding: UiRect::all(Val::Px(5.0)),
|
position_type: PositionType::Absolute,
|
||||||
|
padding: UiRect::all(Val::Px(5.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
background_color: Color::BLACK.with_alpha(0.75).into(),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
z_index: ZIndex::Global(i32::MAX),
|
GlobalZIndex(i32::MAX),
|
||||||
background_color: Color::BLACK.with_alpha(0.75).into(),
|
))
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|c| {
|
.with_children(|c| {
|
||||||
c.spawn(TextBundle::from_sections([
|
c.spawn(TextBundle::from_sections([
|
||||||
TextSection::new("Controls:\n", style.clone()),
|
TextSection::new("Controls:\n", style.clone()),
|
||||||
|
@ -270,16 +270,18 @@ fn setup(
|
|||||||
|
|
||||||
commands.spawn(Camera2dBundle::default());
|
commands.spawn(Camera2dBundle::default());
|
||||||
commands
|
commands
|
||||||
.spawn(NodeBundle {
|
.spawn((
|
||||||
style: Style {
|
NodeBundle {
|
||||||
position_type: PositionType::Absolute,
|
style: Style {
|
||||||
padding: UiRect::all(Val::Px(5.0)),
|
position_type: PositionType::Absolute,
|
||||||
|
padding: UiRect::all(Val::Px(5.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
background_color: Color::BLACK.with_alpha(0.75).into(),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
z_index: ZIndex::Global(i32::MAX),
|
GlobalZIndex(i32::MAX),
|
||||||
background_color: Color::BLACK.with_alpha(0.75).into(),
|
))
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|c| {
|
.with_children(|c| {
|
||||||
c.spawn((
|
c.spawn((
|
||||||
TextBundle::from_sections([
|
TextBundle::from_sections([
|
||||||
|
@ -62,7 +62,7 @@ fn setup(mut commands: Commands) {
|
|||||||
// spawn a node with a positive local z-index of 2.
|
// spawn a node with a positive local z-index of 2.
|
||||||
// it will show above other nodes in the gray container.
|
// it will show above other nodes in the gray container.
|
||||||
parent.spawn(NodeBundle {
|
parent.spawn(NodeBundle {
|
||||||
z_index: ZIndex::Local(2),
|
z_index: ZIndex(2),
|
||||||
background_color: BLUE.into(),
|
background_color: BLUE.into(),
|
||||||
style: Style {
|
style: Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
@ -78,7 +78,7 @@ fn setup(mut commands: Commands) {
|
|||||||
// spawn a node with a negative local z-index.
|
// spawn a node with a negative local z-index.
|
||||||
// it will show under other nodes in the gray container.
|
// it will show under other nodes in the gray container.
|
||||||
parent.spawn(NodeBundle {
|
parent.spawn(NodeBundle {
|
||||||
z_index: ZIndex::Local(-1),
|
z_index: ZIndex(-1),
|
||||||
background_color: LIME.into(),
|
background_color: LIME.into(),
|
||||||
style: Style {
|
style: Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
@ -94,36 +94,40 @@ fn setup(mut commands: Commands) {
|
|||||||
// spawn a node with a positive global z-index of 1.
|
// spawn a node with a positive global z-index of 1.
|
||||||
// it will show above all other nodes, because it's the highest global z-index in this example.
|
// it will show above all other nodes, because it's the highest global z-index in this example.
|
||||||
// by default, boxes all share the global z-index of 0 that the gray container is added to.
|
// by default, boxes all share the global z-index of 0 that the gray container is added to.
|
||||||
parent.spawn(NodeBundle {
|
parent.spawn((
|
||||||
z_index: ZIndex::Global(1),
|
NodeBundle {
|
||||||
background_color: PURPLE.into(),
|
background_color: PURPLE.into(),
|
||||||
style: Style {
|
style: Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
left: Val::Px(15.0),
|
left: Val::Px(15.0),
|
||||||
bottom: Val::Px(10.0),
|
bottom: Val::Px(10.0),
|
||||||
width: Val::Px(100.),
|
width: Val::Px(100.),
|
||||||
height: Val::Px(60.),
|
height: Val::Px(60.),
|
||||||
..default()
|
..default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
..default()
|
GlobalZIndex(1),
|
||||||
});
|
));
|
||||||
|
|
||||||
// spawn a node with a negative global z-index of -1.
|
// spawn a node with a negative global z-index of -1.
|
||||||
// this will show under all other nodes including its parent, because it's the lowest global z-index
|
// this will show under all other nodes including its parent, because it's the lowest global z-index
|
||||||
// in this example.
|
// in this example.
|
||||||
parent.spawn(NodeBundle {
|
parent.spawn((
|
||||||
z_index: ZIndex::Global(-1),
|
NodeBundle {
|
||||||
background_color: YELLOW.into(),
|
background_color: YELLOW.into(),
|
||||||
style: Style {
|
style: Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
left: Val::Px(-15.0),
|
left: Val::Px(-15.0),
|
||||||
bottom: Val::Px(-15.0),
|
bottom: Val::Px(-15.0),
|
||||||
width: Val::Px(100.),
|
width: Val::Px(100.),
|
||||||
height: Val::Px(125.),
|
height: Val::Px(125.),
|
||||||
..default()
|
..default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
..default()
|
GlobalZIndex(-1),
|
||||||
});
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user