From f86ee325768d8fb69e1a178be482a672945f5119 Mon Sep 17 00:00:00 2001 From: Viktor Gustavsson Date: Wed, 2 Oct 2024 02:24:28 +0200 Subject: [PATCH] Add UI `GhostNode` (#15341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Fixes #14826 - For context, see #15238 ## Solution Add a `GhostNode` component to `bevy_ui` and update all the relevant systems to use it to traverse for UI children. - [x] `ghost_hierarchy` module - [x] Add `GhostNode` - [x] Add `UiRootNodes` system param for iterating (ghost-aware) UI root nodes - [x] Add `UiChildren` system param for iterating (ghost-aware) UI children - [x] Update `layout::ui_layout_system` - [x] Use ghost-aware root nodes for camera updates - [x] Update and remove children in taffy - [x] Initial spawn - [x] Detect changes on nested UI children - [x] Use ghost-aware children traversal in `update_uinode_geometry_recursive` - [x] Update the rest of the UI systems to use the ghost hierarchy - [x] `stack::ui_stack_system` - [x] `update::` - [x] `update_clipping_system` - [x] `update_target_camera_system` - [x] `accessibility::calc_name` ## Testing - [x] Added a new example `ghost_nodes` that can be used as a testbed. - [x] Added unit tests for _some_ of the traversal utilities in `ghost_hierarchy` - [x] Ensure this fulfills the needs for currently known use cases - [x] Reactivity libraries (test with `bevy_reactor`) - [ ] Text spans (mentioned by koe [on discord](https://discord.com/channels/691052431525675048/1285371432460881991/1285377442998915246)) --- ## Performance [See comment below](https://github.com/bevyengine/bevy/pull/15341#issuecomment-2385456820) ## Migration guide Any code that previously relied on `Parent`/`Children` to iterate UI children may now want to use `bevy_ui::UiChildren` to ensure ghost nodes are skipped, and their first descendant Nodes included. UI root nodes may now be children of ghost nodes, which means `Without` might not query all root nodes. Use `bevy_ui::UiRootNodes` where needed to iterate root nodes instead. ## Potential future work - Benchmarking/optimizations of hierarchies containing lots of ghost nodes - Further exploration of UI hierarchies and markers for root nodes/leaf nodes to create better ergonomics for things like `UiLayer` (world-space ui) --------- Co-authored-by: Alice Cecile Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com> --- Cargo.toml | 11 ++ crates/bevy_ui/src/accessibility.rs | 24 ++- crates/bevy_ui/src/ghost_hierarchy.rs | 205 ++++++++++++++++++++++++ crates/bevy_ui/src/layout/mod.rs | 113 ++++++------- crates/bevy_ui/src/layout/ui_surface.rs | 19 ++- crates/bevy_ui/src/lib.rs | 2 + crates/bevy_ui/src/stack.rs | 77 ++++----- crates/bevy_ui/src/update.rs | 55 +++---- examples/README.md | 1 + examples/ui/ghost_nodes.rs | 122 ++++++++++++++ 10 files changed, 488 insertions(+), 141 deletions(-) create mode 100644 crates/bevy_ui/src/ghost_hierarchy.rs create mode 100644 examples/ui/ghost_nodes.rs diff --git a/Cargo.toml b/Cargo.toml index 28fb16ffcc..1775b2dd7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2972,6 +2972,17 @@ description = "Demonstrates text wrapping" category = "UI (User Interface)" wasm = true +[[example]] +name = "ghost_nodes" +path = "examples/ui/ghost_nodes.rs" +doc-scrape-examples = true + +[package.metadata.example.ghost_nodes] +name = "Ghost Nodes" +description = "Demonstrates the use of Ghost Nodes to skip entities in the UI layout hierarchy" +category = "UI (User Interface)" +wasm = true + [[example]] name = "grid" path = "examples/ui/grid.rs" diff --git a/crates/bevy_ui/src/accessibility.rs b/crates/bevy_ui/src/accessibility.rs index 53cfb272b2..fb17415fe2 100644 --- a/crates/bevy_ui/src/accessibility.rs +++ b/crates/bevy_ui/src/accessibility.rs @@ -1,6 +1,6 @@ use crate::{ prelude::{Button, Label}, - Node, UiImage, + Node, UiChildren, UiImage, }; use bevy_a11y::{ accesskit::{NodeBuilder, Rect, Role}, @@ -14,15 +14,14 @@ use bevy_ecs::{ system::{Commands, Query}, world::Ref, }; -use bevy_hierarchy::Children; use bevy_render::{camera::CameraUpdateSystem, prelude::Camera}; use bevy_text::Text; use bevy_transform::prelude::GlobalTransform; -fn calc_name(texts: &Query<&Text>, children: &Children) -> Option> { +fn calc_name(texts: &Query<&Text>, children: impl Iterator) -> Option> { let mut name = None; for child in children { - if let Ok(text) = texts.get(*child) { + if let Ok(text) = texts.get(child) { let values = text .sections .iter() @@ -59,11 +58,12 @@ fn calc_bounds( fn button_changed( mut commands: Commands, - mut query: Query<(Entity, &Children, Option<&mut AccessibilityNode>), Changed