-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more tools for UI tree traversal #15923
base: main
Are you sure you want to change the base?
Conversation
This should be ready for reviews now. I ended up skipping For some reason git doesn't diff the rename of ghost_hierarchy.rs properly, to see the actual changes: If anyone has any tips on how make it diff properly, I'm all ears! |
To aid reviewing, here's the actual diff (against main at fac0b34): diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ui_tree.rs
index 42832c048..06f325834 100644
--- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs
+++ b/crates/bevy_ui/src/experimental/ui_tree.rs
@@ -48,7 +48,7 @@ pub struct UiRootNodes<'w, 's> {
root_node_query: Query<'w, 's, Entity, (With<Node>, Without<Parent>)>,
root_ghost_node_query: Query<'w, 's, Entity, (With<GhostNode>, Without<Parent>)>,
all_nodes_query: Query<'w, 's, Entity, With<Node>>,
- ui_children: UiChildren<'w, 's>,
+ ui_tree: UiTree<'w, 's>,
}
impl<'w, 's> UiRootNodes<'w, 's> {
@@ -57,14 +57,14 @@ impl<'w, 's> UiRootNodes<'w, 's> {
.iter()
.chain(self.root_ghost_node_query.iter().flat_map(|root_ghost| {
self.all_nodes_query
- .iter_many(self.ui_children.iter_ui_children(root_ghost))
+ .iter_many(self.ui_tree.iter_children(root_ghost))
}))
}
}
-/// System param that gives access to UI children utilities, skipping over [`GhostNode`].
+/// System param that gives access to UI tree utilities, skipping over [`GhostNode`] and stopping traversal at non-UI entities.
#[derive(SystemParam)]
-pub struct UiChildren<'w, 's> {
+pub struct UiTree<'w, 's> {
ui_children_query: Query<
'w,
's,
@@ -72,20 +72,21 @@ pub struct UiChildren<'w, 's> {
Or<(With<Node>, With<GhostNode>)>,
>,
changed_children_query: Query<'w, 's, Entity, Changed<Children>>,
- children_query: Query<'w, 's, &'static Children>,
+ nodes_query: Query<'w, 's, Entity, With<Node>>,
ghost_nodes_query: Query<'w, 's, Entity, With<GhostNode>>,
- parents_query: Query<'w, 's, &'static Parent>,
+ children_query: Query<'w, 's, &'static Children, Or<(With<Node>, With<GhostNode>)>>,
+ parents_query: Query<'w, 's, &'static Parent, Or<(With<Node>, With<GhostNode>)>>,
}
-impl<'w, 's> UiChildren<'w, 's> {
- /// Iterates the children of `entity`, skipping over [`GhostNode`].
+impl<'w, 's> UiTree<'w, 's> {
+ /// Iterates the [`Node`] children of `entity`, skipping over [`GhostNode`].
///
/// Traverses the hierarchy depth-first to ensure child order.
///
/// # Performance
///
/// This iterator allocates if the `entity` node has more than 8 children (including ghost nodes).
- pub fn iter_ui_children(&'s self, entity: Entity) -> UiChildrenIter<'w, 's> {
+ pub fn iter_children(&'s self, entity: Entity) -> UiChildrenIter<'w, 's> {
UiChildrenIter {
stack: self
.ui_children_query
@@ -97,11 +98,88 @@ impl<'w, 's> UiChildren<'w, 's> {
}
}
- /// Returns the UI parent of the provided entity, skipping over [`GhostNode`].
- pub fn get_parent(&'s self, entity: Entity) -> Option<Entity> {
+ /// Returns the [`Node`] parent of the provided entity, skipping over [`GhostNode`].
+ pub fn parent(&'s self, entity: Entity) -> Option<Entity> {
self.parents_query
.iter_ancestors(entity)
- .find(|entity| !self.ghost_nodes_query.contains(*entity))
+ .find(|entity| self.nodes_query.contains(*entity))
+ }
+
+ /// Returns the topmost [`Node`] ancestor of the given `entity`.
+ ///
+ /// This may be the entity itself if it has no parent or if it isn't part of a UI tree.
+ pub fn root_ancestor(&'s self, entity: Entity) -> Entity {
+ // Recursively search up the tree until we're out of parents
+ match self.parent(entity) {
+ Some(parent) => self.root_ancestor(parent),
+ None => entity,
+ }
+ }
+
+ /// Returns an [`Iterator`] of [`Node`] entities over all `entity`s ancestors within the current UI tree.
+ ///
+ /// Does not include the entity itself.
+ pub fn iter_ancestors(&'s self, entity: Entity) -> impl Iterator<Item = Entity> + 's {
+ self.parents_query
+ .iter_ancestors(entity)
+ .filter(|entity| self.nodes_query.contains(*entity))
+ }
+
+ /// Returns an [`Iterator`] of [`Node`] entities over all of `entity`s descendants within the current UI tree.
+ ///
+ /// Traverses the hierarchy breadth-first and does not include the entity itself.
+ pub fn iter_descendants(&'s self, entity: Entity) -> impl Iterator<Item = Entity> + 's {
+ self.children_query
+ .iter_descendants(entity)
+ .filter(|entity| self.nodes_query.contains(*entity))
+ }
+
+ /// Returns an [`Iterator`] of [`Node`] entities over all of `entity`s descendants within the current UI tree.
+ ///
+ /// Traverses the hierarchy depth-first and does not include the entity itself.
+ pub fn iter_descendants_depth_first(
+ &'s self,
+ entity: Entity,
+ ) -> impl Iterator<Item = Entity> + 's {
+ self.children_query
+ .iter_descendants_depth_first(entity)
+ .filter(|entity| self.nodes_query.contains(*entity))
+ }
+
+ /// Returns an [`Iterator`] of [`Node`] entities over the leaves of the UI tree underneath this `entity`.
+ ///
+ /// Only entities which have no [`Node`] descendants are considered leaves.
+ /// This will not include the entity itself, and will not include any entities which are not descendants of the entity,
+ /// even if they are leaves in the same hierarchical tree.
+ ///
+ /// Traverses the hierarchy depth-first.
+ pub fn iter_leaves(&'s self, entity: Entity) -> impl Iterator<Item = Entity> + 's {
+ UiLeavesIter {
+ stack: self
+ .ui_children_query
+ .get(entity)
+ .map_or(SmallVec::new(), |(children, _)| {
+ children.into_iter().flatten().rev().copied().collect()
+ }),
+ query: &self.ui_children_query,
+ potential_leaf: None,
+ }
+ }
+
+ /// Returns an [`Iterator`] of [`Node`] entities over the `entity`s immediate siblings, who share the same first [`Node`] ancestor within the UI tree.
+ ///
+ /// The entity itself is not included in the iterator.
+ pub fn iter_siblings(&'s self, entity: Entity) -> impl Iterator<Item = Entity> + 's {
+ self.parent(entity).into_iter().flat_map(move |parent| {
+ self.iter_children(parent)
+ .filter(move |child| *child != entity)
+ })
+ }
+
+ /// Returns the index of a [`Node`] among its siblings, or [`None`] if it does not have a parent.
+ pub fn child_index(&'s self, entity: Entity) -> Option<usize> {
+ self.parent(entity)
+ .and_then(|parent| self.iter_children(parent).position(|child| child == entity))
}
/// Iterates the [`GhostNode`]s between this entity and its UI children.
@@ -120,8 +198,8 @@ impl<'w, 's> UiChildren<'w, 's> {
)
}
- /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed.
- pub fn is_changed(&'s self, entity: Entity) -> bool {
+ /// Given an entity in the UI tree, check if its children has changed, e.g if children has been added/removed or if the order has changed.
+ pub fn children_is_changed(&'s self, entity: Entity) -> bool {
self.changed_children_query.contains(entity)
|| self
.iter_ghost_nodes(entity)
@@ -129,9 +207,37 @@ impl<'w, 's> UiChildren<'w, 's> {
}
/// Returns `true` if the given entity is either a [`Node`] or a [`GhostNode`].
- pub fn is_ui_node(&'s self, entity: Entity) -> bool {
+ pub fn is_ui_entity(&'s self, entity: Entity) -> bool {
self.ui_children_query.contains(entity)
}
+
+ /// Returns `true` if the given entity is a root in the UI tree.
+ ///
+ /// A [`Node`] is a root if it has no parent, or if all ancestors are ghost nodes.
+ ///
+ /// A [`GhostNode`] is a root if it has no parent.
+ pub fn is_root(&'s self, entity: Entity) -> bool {
+ self.parent(entity).is_none()
+ }
+
+ /// Returns `true` if the given entity is a leaf in the UI tree.
+ ///
+ /// A [`Node`] is a leaf if it has no [`Node`] descendants.
+ ///
+ /// A [`GhostNode`] is a leaf if it has no [`Node`] or [`GhostNode`] children.
+ pub fn is_leaf(&'s self, entity: Entity) -> bool {
+ if self.ghost_nodes_query.contains(entity) {
+ if let Ok(children) = self.children_query.get(entity) {
+ !children.iter().any(|child| {
+ self.ghost_nodes_query.contains(*child) || self.nodes_query.contains(*child)
+ })
+ } else {
+ true
+ }
+ } else {
+ self.iter_descendants(entity).next().is_none()
+ }
+ }
}
pub struct UiChildrenIter<'w, 's> {
@@ -161,6 +267,43 @@ impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
}
}
+pub struct UiLeavesIter<'w, 's> {
+ stack: SmallVec<[Entity; 8]>,
+ query: &'s Query<
+ 'w,
+ 's,
+ (Option<&'static Children>, Has<GhostNode>),
+ Or<(With<Node>, With<GhostNode>)>,
+ >,
+ potential_leaf: Option<(usize, Entity)>,
+}
+
+impl<'w, 's> Iterator for UiLeavesIter<'w, 's> {
+ type Item = Entity;
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if let Some((stack_length, node_entity)) = self.potential_leaf {
+ if stack_length == self.stack.len() {
+ // Confirmed leaf Node since no entities below it were Nodes
+ self.potential_leaf = None;
+ return Some(node_entity);
+ }
+ }
+
+ let entity = self.stack.pop()?;
+ if let Ok((children, has_ghost_node)) = self.query.get(entity) {
+ if !has_ghost_node {
+ // This is a Node, store as potential leaf and continue traversing.
+ self.potential_leaf = Some((self.stack.len(), entity));
+ }
+ if let Some(children) = children {
+ self.stack.extend(children.iter().rev().copied());
+ }
+ }
+ }
+ }
+}
+
#[cfg(all(test, feature = "ghost_nodes"))]
mod tests {
use bevy_ecs::{
@@ -170,7 +313,7 @@ mod tests {
};
use bevy_hierarchy::{BuildChildren, ChildBuild};
- use super::{GhostNode, Node, UiChildren, UiRootNodes};
+ use super::{GhostNode, Node, UiRootNodes, UiTree};
#[derive(Component, PartialEq, Debug)]
struct A(usize);
@@ -223,6 +366,7 @@ mod tests {
let n8 = world.spawn((A(8), Node::default())).id();
let n9 = world.spawn((A(9), GhostNode::new())).id();
let n10 = world.spawn((A(10), Node::default())).id();
+ let n11 = world.spawn((A(11), Node::default())).id();
let no_ui = world.spawn_empty().id();
@@ -232,14 +376,148 @@ mod tests {
world.entity_mut(n6).add_children(&[n7, no_ui, n9]);
world.entity_mut(n7).add_children(&[n8]);
world.entity_mut(n9).add_children(&[n10]);
+ world.entity_mut(n10).add_children(&[n11]);
+
+ let mut system_state = SystemState::<(UiTree, Query<&A>)>::new(world);
+ let (ui_tree, a_query) = system_state.get(world);
+
+ let result: Vec<_> = a_query.iter_many(ui_tree.iter_children(n1)).collect();
+ assert_eq!([&A(5), &A(4), &A(8), &A(10)], result.as_slice());
- let mut system_state = SystemState::<(UiChildren, Query<&A>)>::new(world);
- let (ui_children, a_query) = system_state.get(world);
+ assert_eq!(None, ui_tree.child_index(n1));
+ assert_eq!(Some(0), ui_tree.child_index(n5));
+ assert_eq!(Some(1), ui_tree.child_index(n4));
+ assert_eq!(Some(2), ui_tree.child_index(n8));
+ assert_eq!(Some(3), ui_tree.child_index(n10));
+
+ let result: Vec<_> = a_query.iter_many(ui_tree.iter_descendants(n1)).collect();
+ assert_eq!([&A(4), &A(5), &A(8), &A(10), &A(11)], result.as_slice());
let result: Vec<_> = a_query
- .iter_many(ui_children.iter_ui_children(n1))
+ .iter_many(ui_tree.iter_descendants_depth_first(n1))
.collect();
+ assert_eq!([&A(5), &A(4), &A(8), &A(10), &A(11)], result.as_slice());
+ }
- assert_eq!([&A(5), &A(4), &A(8), &A(10)], result.as_slice());
+ #[test]
+ fn ancestors() {
+ let world = &mut World::new();
+
+ let n1 = world.spawn((A(1), Node::default())).id();
+ let n2 = world.spawn((A(2), GhostNode::new())).id();
+ let n3 = world.spawn((A(3), GhostNode::new())).id();
+ let n4 = world.spawn((A(4), Node::default())).id();
+ let n5 = world.spawn((A(5), Node::default())).id();
+
+ let n6 = world.spawn((A(6), GhostNode::new())).id();
+ let n7 = world.spawn((A(7), GhostNode::new())).id();
+ let n8 = world.spawn((A(8), Node::default())).id();
+
+ world.entity_mut(n1).add_children(&[n2, n3]);
+ world.entity_mut(n3).add_children(&[n4]);
+ world.entity_mut(n4).add_children(&[n5]);
+
+ world.entity_mut(n6).add_children(&[n7]);
+ world.entity_mut(n7).add_children(&[n8]);
+
+ let mut system_state = SystemState::<(UiTree, Query<&A>)>::new(world);
+ let (ui_tree, a_query) = system_state.get(world);
+
+ assert_eq!(&A(1), a_query.get(ui_tree.root_ancestor(n1)).unwrap());
+ assert_eq!(&A(1), a_query.get(ui_tree.root_ancestor(n2)).unwrap());
+ assert_eq!(&A(1), a_query.get(ui_tree.root_ancestor(n4)).unwrap());
+ assert_eq!(&A(1), a_query.get(ui_tree.root_ancestor(n5)).unwrap());
+ assert_eq!(&A(8), a_query.get(ui_tree.root_ancestor(n8)).unwrap());
+
+ assert_eq!(
+ [&A(1)],
+ a_query
+ .iter_many(ui_tree.iter_ancestors(n4))
+ .collect::<Vec<_>>()
+ .as_slice()
+ );
+
+ assert_eq!(
+ [&A(4), &A(1)],
+ a_query
+ .iter_many(ui_tree.iter_ancestors(n5))
+ .collect::<Vec<_>>()
+ .as_slice()
+ );
+
+ assert!(ui_tree.iter_ancestors(n8).next().is_none());
+ }
+
+ #[test]
+ fn iter_leaves() {
+ let world = &mut World::new();
+
+ let n1 = world.spawn((A(1), Node::default())).id();
+ let n2 = world.spawn((A(2), GhostNode::new())).id();
+ let n3 = world.spawn((A(3), Node::default())).id();
+ let n4 = world.spawn((A(4), Node::default())).id();
+ let n5 = world.spawn((A(5), Node::default())).id();
+ let n6 = world.spawn((A(6), GhostNode::default())).id();
+
+ world.entity_mut(n1).add_children(&[n2, n3]);
+ world.entity_mut(n2).add_children(&[n4]);
+ world.entity_mut(n3).add_children(&[n5]);
+ world.entity_mut(n5).add_children(&[n6]);
+
+ let mut system_state = SystemState::<(UiTree, Query<&A>)>::new(world);
+ let (ui_tree, a_query) = system_state.get(world);
+
+ let result: Vec<_> = a_query.iter_many(ui_tree.iter_leaves(n1)).collect();
+ assert_eq!([&A(4), &A(5)], result.as_slice());
+ }
+
+ #[test]
+ fn iter_siblings() {
+ let world = &mut World::new();
+
+ let n1 = world.spawn((A(1), Node::default())).id();
+ let n2 = world.spawn((A(2), GhostNode::new())).id();
+ let n3 = world.spawn((A(3), Node::default())).id();
+ let n4 = world.spawn((A(4), Node::default())).id();
+ let n5 = world.spawn((A(5), Node::default())).id();
+
+ world.entity_mut(n1).add_children(&[n2, n3]);
+ world.entity_mut(n2).add_children(&[n4, n5]);
+
+ let mut system_state = SystemState::<(UiTree, Query<&A>)>::new(world);
+ let (ui_tree, a_query) = system_state.get(world);
+
+ let result: Vec<_> = a_query.iter_many(ui_tree.iter_siblings(n5)).collect();
+ assert_eq!([&A(4), &A(3)], result.as_slice());
+ }
+
+ #[test]
+ fn is_root_or_leaf() {
+ let world = &mut World::new();
+
+ let n1 = world.spawn((A(1), Node::default())).id();
+ let n2 = world.spawn((A(2), GhostNode::new())).id();
+ let n3 = world.spawn((A(3), Node::default())).id();
+
+ let n4 = world.spawn((A(4), GhostNode::new())).id();
+ let n5 = world.spawn((A(5), Node::default())).id();
+
+ world.entity_mut(n1).add_children(&[n2, n3]);
+ world.entity_mut(n4).add_children(&[n5]);
+
+ let mut system_state = SystemState::<UiTree>::new(world);
+ let ui_tree = system_state.get(world);
+
+ assert!(ui_tree.is_root(n1));
+ assert!(!ui_tree.is_root(n2));
+ assert!(!ui_tree.is_root(n3));
+ assert!(ui_tree.is_root(n4));
+ assert!(ui_tree.is_root(n5));
+
+ assert!(!ui_tree.is_leaf(n1));
+ assert!(ui_tree.is_leaf(n2));
+ assert!(ui_tree.is_leaf(n3));
+ assert!(!ui_tree.is_leaf(n4));
+ assert!(ui_tree.is_leaf(n5));
}
}
|
Objective
HierarchyQueryExt
.Solution
ghost_hierarchy
module toui_tree
UiChildren
->UiTree
HierarchyQueryExt
get_parent
->parent
iter_ui_children
->iter_children
children
because it an iterator, rather than a slice like inHierarchyQueryExt
.is_ui_node
->is_ui_entity
Node
.is_changed
->children_is_changed
root_ancestor
iter_ancestors
iter_descendants
iter_descendants_depth_first
iter_leaves
iter_siblings
is_root
is_leaf
child_index
Testing
Migration Guide
Only applies if merged after 0.15 release
experimental::UiChildren
=>experimental::UiTree
UiChildren::get_parent
=>UiTree::parent
UiChildren::iter_ui_children
=>UiTree::iter_children
UiChildren::is_ui_node
=>UiTree::is_ui_entity
UiChildren::is_changed
=>UiTree::children_is_changed