Improve UI stack docs and other small tweaks (#8094)
This commit is contained in:
parent
5144a2ac25
commit
803b9ef601
@ -171,10 +171,10 @@ pub fn ui_focus_system(
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
|
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
|
||||||
.filter_map(|(camera, _)| {
|
.filter_map(|(camera, _)| {
|
||||||
if let Some(NormalizedRenderTarget::Window(window_id)) =
|
if let Some(NormalizedRenderTarget::Window(window_ref)) =
|
||||||
camera.target.normalize(primary_window)
|
camera.target.normalize(primary_window)
|
||||||
{
|
{
|
||||||
Some(window_id)
|
Some(window_ref)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ pub fn ui_focus_system(
|
|||||||
// prepare an iterator that contains all the nodes that have the cursor in their rect,
|
// prepare an iterator that contains all the nodes that have the cursor in their rect,
|
||||||
// from the top node to the bottom one. this will also reset the interaction to `None`
|
// from the top node to the bottom one. this will also reset the interaction to `None`
|
||||||
// for all nodes encountered that are no longer hovered.
|
// for all nodes encountered that are no longer hovered.
|
||||||
let mut moused_over_nodes = ui_stack
|
let mut hovered_nodes = ui_stack
|
||||||
.uinodes
|
.uinodes
|
||||||
.iter()
|
.iter()
|
||||||
// reverse the iterator to traverse the tree from closest nodes to furthest
|
// reverse the iterator to traverse the tree from closest nodes to furthest
|
||||||
@ -263,7 +263,7 @@ pub fn ui_focus_system(
|
|||||||
|
|
||||||
// set Clicked or Hovered on top nodes. as soon as a node with a `Block` focus policy is detected,
|
// set Clicked or Hovered on top nodes. as soon as a node with a `Block` focus policy is detected,
|
||||||
// the iteration will stop on it because it "captures" the interaction.
|
// the iteration will stop on it because it "captures" the interaction.
|
||||||
let mut iter = node_query.iter_many_mut(moused_over_nodes.by_ref());
|
let mut iter = node_query.iter_many_mut(hovered_nodes.by_ref());
|
||||||
while let Some(node) = iter.fetch_next() {
|
while let Some(node) = iter.fetch_next() {
|
||||||
if let Some(mut interaction) = node.interaction {
|
if let Some(mut interaction) = node.interaction {
|
||||||
if mouse_clicked {
|
if mouse_clicked {
|
||||||
@ -290,7 +290,7 @@ pub fn ui_focus_system(
|
|||||||
}
|
}
|
||||||
// reset `Interaction` for the remaining lower nodes to `None`. those are the nodes that remain in
|
// reset `Interaction` for the remaining lower nodes to `None`. those are the nodes that remain in
|
||||||
// `moused_over_nodes` after the previous loop is exited.
|
// `moused_over_nodes` after the previous loop is exited.
|
||||||
let mut iter = node_query.iter_many_mut(moused_over_nodes);
|
let mut iter = node_query.iter_many_mut(hovered_nodes);
|
||||||
while let Some(node) = iter.fetch_next() {
|
while let Some(node) = iter.fetch_next() {
|
||||||
if let Some(mut interaction) = node.interaction {
|
if let Some(mut interaction) = node.interaction {
|
||||||
// don't reset clicked nodes because they're handled separately
|
// don't reset clicked nodes because they're handled separately
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
|
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
|
||||||
//! # Basic usage
|
//! # Basic usage
|
||||||
//! Spawn UI elements with [`node_bundles::ButtonBundle`], [`node_bundles::ImageBundle`], [`node_bundles::TextBundle`] and [`node_bundles::NodeBundle`]
|
//! Spawn UI elements with [`node_bundles::ButtonBundle`], [`node_bundles::ImageBundle`], [`node_bundles::TextBundle`] and [`node_bundles::NodeBundle`]
|
||||||
//! This UI is laid out with the Flexbox paradigm (see <https://cssreference.io/flexbox/>)
|
//! This UI is laid out with the Flexbox layout model (see <https://cssreference.io/flexbox/>)
|
||||||
mod flex;
|
mod flex;
|
||||||
mod focus;
|
mod focus;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
|
@ -5,12 +5,13 @@ use bevy_hierarchy::prelude::*;
|
|||||||
|
|
||||||
use crate::{Node, ZIndex};
|
use crate::{Node, ZIndex};
|
||||||
|
|
||||||
/// The current UI stack, which contains all UI nodes ordered by their depth.
|
/// The current UI stack, which contains all UI nodes ordered by their depth (back-to-front).
|
||||||
///
|
///
|
||||||
/// The first entry is the furthest node from the camera and is the first one to get rendered
|
/// The first entry is the furthest node from the camera and is the first one to get rendered
|
||||||
/// while the last entry is the first node to receive interactions.
|
/// while the last entry is the first node to receive interactions.
|
||||||
#[derive(Debug, Resource, Default)]
|
#[derive(Debug, Resource, Default)]
|
||||||
pub struct UiStack {
|
pub struct UiStack {
|
||||||
|
/// List of UI nodes ordered from back-to-front
|
||||||
pub uinodes: Vec<Entity>,
|
pub uinodes: Vec<Entity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,15 +27,19 @@ struct StackingContextEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
|
/// Then flatten that tree into back-to-front ordered `UiStack`.
|
||||||
pub fn ui_stack_system(
|
pub fn ui_stack_system(
|
||||||
mut ui_stack: ResMut<UiStack>,
|
mut ui_stack: ResMut<UiStack>,
|
||||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||||
zindex_query: Query<&ZIndex, With<Node>>,
|
zindex_query: Query<&ZIndex, With<Node>>,
|
||||||
children_query: Query<&Children>,
|
children_query: Query<&Children>,
|
||||||
) {
|
) {
|
||||||
|
// Generate `StackingContext` tree
|
||||||
let mut global_context = StackingContext::default();
|
let mut global_context = StackingContext::default();
|
||||||
|
|
||||||
let mut total_entry_count: usize = 0;
|
let mut total_entry_count: usize = 0;
|
||||||
|
|
||||||
for entity in &root_node_query {
|
for entity in &root_node_query {
|
||||||
insert_context_hierarchy(
|
insert_context_hierarchy(
|
||||||
&zindex_query,
|
&zindex_query,
|
||||||
@ -46,11 +51,13 @@ pub fn ui_stack_system(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flatten `StackingContext` into `UiStack`
|
||||||
ui_stack.uinodes.clear();
|
ui_stack.uinodes.clear();
|
||||||
ui_stack.uinodes.reserve(total_entry_count);
|
ui_stack.uinodes.reserve(total_entry_count);
|
||||||
fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context);
|
fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate z-index based UI node tree
|
||||||
fn insert_context_hierarchy(
|
fn insert_context_hierarchy(
|
||||||
zindex_query: &Query<&ZIndex, With<Node>>,
|
zindex_query: &Query<&ZIndex, With<Node>>,
|
||||||
children_query: &Query<&Children>,
|
children_query: &Query<&Children>,
|
||||||
@ -60,8 +67,10 @@ fn insert_context_hierarchy(
|
|||||||
total_entry_count: &mut usize,
|
total_entry_count: &mut usize,
|
||||||
) {
|
) {
|
||||||
let mut new_context = StackingContext::default();
|
let mut new_context = StackingContext::default();
|
||||||
|
|
||||||
if let Ok(children) = children_query.get(entity) {
|
if let Ok(children) = children_query.get(entity) {
|
||||||
// reserve space for all children. in practice, some may not get pushed.
|
// Reserve space for all children. In practice, some may not get pushed since
|
||||||
|
// nodes with `ZIndex::Global` are pushed to the global (root) context.
|
||||||
new_context.entries.reserve_exact(children.len());
|
new_context.entries.reserve_exact(children.len());
|
||||||
|
|
||||||
for entity in children {
|
for entity in children {
|
||||||
@ -76,6 +85,7 @@ fn insert_context_hierarchy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 z_index = zindex_query.get(entity).unwrap_or(&ZIndex::Local(0));
|
||||||
let (entity_context, z_index) = match z_index {
|
let (entity_context, z_index) = match z_index {
|
||||||
ZIndex::Local(value) => (parent_context.unwrap_or(global_context), *value),
|
ZIndex::Local(value) => (parent_context.unwrap_or(global_context), *value),
|
||||||
@ -90,12 +100,15 @@ fn insert_context_hierarchy(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flatten `StackingContext` (z-index based UI node tree) into back-to-front entities list
|
||||||
fn fill_stack_recursively(result: &mut Vec<Entity>, stack: &mut StackingContext) {
|
fn fill_stack_recursively(result: &mut Vec<Entity>, stack: &mut StackingContext) {
|
||||||
// sort entries by ascending z_index, while ensuring that siblings
|
// Sort entries by ascending z_index, while ensuring that siblings
|
||||||
// with the same local z_index will keep their ordering.
|
// 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);
|
stack.entries.sort_by_key(|e| e.z_index);
|
||||||
|
|
||||||
for entry in &mut stack.entries {
|
for entry in &mut stack.entries {
|
||||||
|
// Parent node renders before/behind childs nodes
|
||||||
result.push(entry.entity);
|
result.push(entry.entity);
|
||||||
fill_stack_recursively(result, &mut entry.stack);
|
fill_stack_recursively(result, &mut entry.stack);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user