Improve UI stack docs and other small tweaks (#8094)

This commit is contained in:
Asier Illarramendi 2023-03-16 13:54:52 +01:00 committed by François
parent 5144a2ac25
commit 803b9ef601
3 changed files with 24 additions and 11 deletions

View File

@ -171,10 +171,10 @@ pub fn ui_focus_system(
.iter()
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
.filter_map(|(camera, _)| {
if let Some(NormalizedRenderTarget::Window(window_id)) =
if let Some(NormalizedRenderTarget::Window(window_ref)) =
camera.target.normalize(primary_window)
{
Some(window_id)
Some(window_ref)
} else {
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,
// 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.
let mut moused_over_nodes = ui_stack
let mut hovered_nodes = ui_stack
.uinodes
.iter()
// 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,
// 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() {
if let Some(mut interaction) = node.interaction {
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
// `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() {
if let Some(mut interaction) = node.interaction {
// don't reset clicked nodes because they're handled separately

View File

@ -1,7 +1,7 @@
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
//! # Basic usage
//! 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 focus;
mod geometry;

View File

@ -5,12 +5,13 @@ use bevy_hierarchy::prelude::*;
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
/// while the last entry is the first node to receive interactions.
#[derive(Debug, Resource, Default)]
pub struct UiStack {
/// List of UI nodes ordered from back-to-front
pub uinodes: Vec<Entity>,
}
@ -26,15 +27,19 @@ struct StackingContextEntry {
}
/// 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(
mut ui_stack: ResMut<UiStack>,
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
zindex_query: Query<&ZIndex, With<Node>>,
children_query: Query<&Children>,
) {
// Generate `StackingContext` tree
let mut global_context = StackingContext::default();
let mut total_entry_count: usize = 0;
for entity in &root_node_query {
insert_context_hierarchy(
&zindex_query,
@ -46,11 +51,13 @@ pub fn ui_stack_system(
);
}
// Flatten `StackingContext` into `UiStack`
ui_stack.uinodes.clear();
ui_stack.uinodes.reserve(total_entry_count);
fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context);
}
/// Generate z-index based UI node tree
fn insert_context_hierarchy(
zindex_query: &Query<&ZIndex, With<Node>>,
children_query: &Query<&Children>,
@ -60,8 +67,10 @@ fn insert_context_hierarchy(
total_entry_count: &mut usize,
) {
let mut new_context = StackingContext::default();
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());
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 (entity_context, z_index) = match z_index {
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) {
// sort entries by ascending z_index, while ensuring that siblings
// with the same local z_index will keep their ordering.
// 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 entry in &mut stack.entries {
// Parent node renders before/behind childs nodes
result.push(entry.entity);
fill_stack_recursively(result, &mut entry.stack);
}