bevy_ui/
stack.rs

1//! This module contains the systems that update the stored UI nodes stack
2
3use bevy_ecs::prelude::*;
4use bevy_platform::collections::HashSet;
5
6use crate::{
7    experimental::{UiChildren, UiRootNodes},
8    ComputedNode, GlobalZIndex, ZIndex,
9};
10
11/// The current UI stack, which contains all UI nodes ordered by their depth (back-to-front).
12///
13/// The first entry is the furthest node from the camera and is the first one to get rendered
14/// while the last entry is the first node to receive interactions.
15#[derive(Debug, Resource, Default)]
16pub struct UiStack {
17    /// List of UI nodes ordered from back-to-front
18    pub uinodes: Vec<Entity>,
19}
20
21#[derive(Default)]
22pub(crate) struct ChildBufferCache {
23    pub inner: Vec<Vec<(Entity, i32)>>,
24}
25
26impl ChildBufferCache {
27    fn pop(&mut self) -> Vec<(Entity, i32)> {
28        self.inner.pop().unwrap_or_default()
29    }
30
31    fn push(&mut self, vec: Vec<(Entity, i32)>) {
32        self.inner.push(vec);
33    }
34}
35
36/// Generates the render stack for UI nodes.
37///
38/// Create a list of root nodes from parentless entities and entities with a `GlobalZIndex` component.
39/// Then build the `UiStack` from a walk of the existing layout trees starting from each root node,
40/// filtering branches by `Without<GlobalZIndex>`so that we don't revisit nodes.
41pub fn ui_stack_system(
42    mut cache: Local<ChildBufferCache>,
43    mut root_nodes: Local<Vec<(Entity, (i32, i32))>>,
44    mut visited_root_nodes: Local<HashSet<Entity>>,
45    mut ui_stack: ResMut<UiStack>,
46    ui_root_nodes: UiRootNodes,
47    root_node_query: Query<(Entity, Option<&GlobalZIndex>, Option<&ZIndex>)>,
48    zindex_global_node_query: Query<(Entity, &GlobalZIndex, Option<&ZIndex>), With<ComputedNode>>,
49    ui_children: UiChildren,
50    zindex_query: Query<Option<&ZIndex>, (With<ComputedNode>, Without<GlobalZIndex>)>,
51    mut update_query: Query<&mut ComputedNode>,
52) {
53    ui_stack.uinodes.clear();
54    visited_root_nodes.clear();
55
56    for (id, maybe_global_zindex, maybe_zindex) in root_node_query.iter_many(ui_root_nodes.iter()) {
57        root_nodes.push((
58            id,
59            (
60                maybe_global_zindex.map(|zindex| zindex.0).unwrap_or(0),
61                maybe_zindex.map(|zindex| zindex.0).unwrap_or(0),
62            ),
63        ));
64        visited_root_nodes.insert(id);
65    }
66
67    for (id, global_zindex, maybe_zindex) in zindex_global_node_query.iter() {
68        if visited_root_nodes.contains(&id) {
69            continue;
70        }
71
72        root_nodes.push((
73            id,
74            (
75                global_zindex.0,
76                maybe_zindex.map(|zindex| zindex.0).unwrap_or(0),
77            ),
78        ));
79    }
80
81    root_nodes.sort_by_key(|(_, z)| *z);
82
83    for (root_entity, _) in root_nodes.drain(..) {
84        update_uistack_recursive(
85            &mut cache,
86            root_entity,
87            &ui_children,
88            &zindex_query,
89            &mut ui_stack.uinodes,
90        );
91    }
92
93    for (i, entity) in ui_stack.uinodes.iter().enumerate() {
94        if let Ok(mut node) = update_query.get_mut(*entity) {
95            node.bypass_change_detection().stack_index = i as u32;
96        }
97    }
98}
99
100fn update_uistack_recursive(
101    cache: &mut ChildBufferCache,
102    node_entity: Entity,
103    ui_children: &UiChildren,
104    zindex_query: &Query<Option<&ZIndex>, (With<ComputedNode>, Without<GlobalZIndex>)>,
105    ui_stack: &mut Vec<Entity>,
106) {
107    ui_stack.push(node_entity);
108
109    let mut child_buffer = cache.pop();
110    child_buffer.extend(
111        ui_children
112            .iter_ui_children(node_entity)
113            .filter_map(|child_entity| {
114                zindex_query
115                    .get(child_entity)
116                    .ok()
117                    .map(|zindex| (child_entity, zindex.map(|zindex| zindex.0).unwrap_or(0)))
118            }),
119    );
120    child_buffer.sort_by_key(|k| k.1);
121    for (child_entity, _) in child_buffer.drain(..) {
122        update_uistack_recursive(cache, child_entity, ui_children, zindex_query, ui_stack);
123    }
124    cache.push(child_buffer);
125}
126
127#[cfg(test)]
128mod tests {
129    use bevy_ecs::{
130        component::Component,
131        schedule::Schedule,
132        system::Commands,
133        world::{CommandQueue, World},
134    };
135
136    use crate::{GlobalZIndex, Node, UiStack, ZIndex};
137
138    use super::ui_stack_system;
139
140    #[derive(Component, PartialEq, Debug, Clone)]
141    struct Label(&'static str);
142
143    fn node_with_global_and_local_zindex(
144        name: &'static str,
145        global_zindex: i32,
146        local_zindex: i32,
147    ) -> (Label, Node, GlobalZIndex, ZIndex) {
148        (
149            Label(name),
150            Node::default(),
151            GlobalZIndex(global_zindex),
152            ZIndex(local_zindex),
153        )
154    }
155
156    fn node_with_global_zindex(
157        name: &'static str,
158        global_zindex: i32,
159    ) -> (Label, Node, GlobalZIndex) {
160        (Label(name), Node::default(), GlobalZIndex(global_zindex))
161    }
162
163    fn node_with_zindex(name: &'static str, zindex: i32) -> (Label, Node, ZIndex) {
164        (Label(name), Node::default(), ZIndex(zindex))
165    }
166
167    fn node_without_zindex(name: &'static str) -> (Label, Node) {
168        (Label(name), Node::default())
169    }
170
171    /// Tests the UI Stack system.
172    ///
173    /// This tests for siblings default ordering according to their insertion order, but it
174    /// can't test the same thing for UI roots. UI roots having no parents, they do not have
175    /// a stable ordering that we can test against. If we test it, it may pass now and start
176    /// failing randomly in the future because of some unrelated `bevy_ecs` change.
177    #[test]
178    fn test_ui_stack_system() {
179        let mut world = World::default();
180        world.init_resource::<UiStack>();
181
182        let mut queue = CommandQueue::default();
183        let mut commands = Commands::new(&mut queue, &world);
184        commands.spawn(node_with_global_zindex("0", 2));
185
186        commands
187            .spawn(node_with_zindex("1", 1))
188            .with_children(|parent| {
189                parent
190                    .spawn(node_without_zindex("1-0"))
191                    .with_children(|parent| {
192                        parent.spawn(node_without_zindex("1-0-0"));
193                        parent.spawn(node_without_zindex("1-0-1"));
194                        parent.spawn(node_with_zindex("1-0-2", -1));
195                    });
196                parent.spawn(node_without_zindex("1-1"));
197                parent
198                    .spawn(node_with_global_zindex("1-2", -1))
199                    .with_children(|parent| {
200                        parent.spawn(node_without_zindex("1-2-0"));
201                        parent.spawn(node_with_global_zindex("1-2-1", -3));
202                        parent
203                            .spawn(node_without_zindex("1-2-2"))
204                            .with_children(|_| ());
205                        parent.spawn(node_without_zindex("1-2-3"));
206                    });
207                parent.spawn(node_without_zindex("1-3"));
208            });
209
210        commands
211            .spawn(node_without_zindex("2"))
212            .with_children(|parent| {
213                parent
214                    .spawn(node_without_zindex("2-0"))
215                    .with_children(|_parent| ());
216                parent
217                    .spawn(node_without_zindex("2-1"))
218                    .with_children(|parent| {
219                        parent.spawn(node_without_zindex("2-1-0"));
220                    });
221            });
222
223        commands.spawn(node_with_global_zindex("3", -2));
224
225        queue.apply(&mut world);
226
227        let mut schedule = Schedule::default();
228        schedule.add_systems(ui_stack_system);
229        schedule.run(&mut world);
230
231        let mut query = world.query::<&Label>();
232        let ui_stack = world.resource::<UiStack>();
233        let actual_result = ui_stack
234            .uinodes
235            .iter()
236            .map(|entity| query.get(&world, *entity).unwrap().clone())
237            .collect::<Vec<_>>();
238        let expected_result = vec![
239            (Label("1-2-1")), // GlobalZIndex(-3)
240            (Label("3")),     // GlobalZIndex(-2)
241            (Label("1-2")),   // GlobalZIndex(-1)
242            (Label("1-2-0")),
243            (Label("1-2-2")),
244            (Label("1-2-3")),
245            (Label("2")),
246            (Label("2-0")),
247            (Label("2-1")),
248            (Label("2-1-0")),
249            (Label("1")), // ZIndex(1)
250            (Label("1-0")),
251            (Label("1-0-2")), // ZIndex(-1)
252            (Label("1-0-0")),
253            (Label("1-0-1")),
254            (Label("1-1")),
255            (Label("1-3")),
256            (Label("0")), // GlobalZIndex(2)
257        ];
258        assert_eq!(actual_result, expected_result);
259    }
260
261    #[test]
262    fn test_with_equal_global_zindex_zindex_decides_order() {
263        let mut world = World::default();
264        world.init_resource::<UiStack>();
265
266        let mut queue = CommandQueue::default();
267        let mut commands = Commands::new(&mut queue, &world);
268        commands.spawn(node_with_global_and_local_zindex("0", -1, 1));
269        commands.spawn(node_with_global_and_local_zindex("1", -1, 2));
270        commands.spawn(node_with_global_and_local_zindex("2", 1, 3));
271        commands.spawn(node_with_global_and_local_zindex("3", 1, -3));
272        commands
273            .spawn(node_without_zindex("4"))
274            .with_children(|builder| {
275                builder.spawn(node_with_global_and_local_zindex("5", 0, -1));
276                builder.spawn(node_with_global_and_local_zindex("6", 0, 1));
277                builder.spawn(node_with_global_and_local_zindex("7", -1, -1));
278                builder.spawn(node_with_global_zindex("8", 1));
279            });
280
281        queue.apply(&mut world);
282
283        let mut schedule = Schedule::default();
284        schedule.add_systems(ui_stack_system);
285        schedule.run(&mut world);
286
287        let mut query = world.query::<&Label>();
288        let ui_stack = world.resource::<UiStack>();
289        let actual_result = ui_stack
290            .uinodes
291            .iter()
292            .map(|entity| query.get(&world, *entity).unwrap().clone())
293            .collect::<Vec<_>>();
294
295        let expected_result = vec![
296            (Label("7")),
297            (Label("0")),
298            (Label("1")),
299            (Label("5")),
300            (Label("4")),
301            (Label("6")),
302            (Label("3")),
303            (Label("8")),
304            (Label("2")),
305        ];
306
307        assert_eq!(actual_result, expected_result);
308    }
309}