diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index b30062818d..07829f3c2e 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -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 diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index b668c2b951..a056cf48c3 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -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 ) +//! This UI is laid out with the Flexbox layout model (see ) mod flex; mod focus; mod geometry; diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 47cfac0d10..4f8f1457d0 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -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, } @@ -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, root_node_query: Query, Without)>, zindex_query: Query<&ZIndex, With>, 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>, 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, 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); }