1use 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#[derive(Debug, Resource, Default)]
16pub struct UiStack {
17 pub partition: Vec<Range<usize>>,
19 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
38pub 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 #[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")), (Label("3")), (Label("1-2")), (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")), (Label("1-0")),
257 (Label("1-0-2")), (Label("1-0-0")),
259 (Label("1-0-1")),
260 (Label("1-1")),
261 (Label("1-3")),
262 (Label("0")), ];
264 assert_eq!(actual_result, expected_result);
265
266 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")), (Label("1-0")),
279 (Label("1-0-2")), (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}