Skip to main content

bevy_ui/
stack.rs

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