1use bevy_ecs::prelude::*;
4use bevy_platform::collections::HashSet;
5
6use crate::{
7 experimental::{UiChildren, UiRootNodes},
8 ComputedNode, GlobalZIndex, ZIndex,
9};
10
11#[derive(Debug, Resource, Default)]
16pub struct UiStack {
17 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
36pub 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 #[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")), (Label("3")), (Label("1-2")), (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")), (Label("1-0")),
251 (Label("1-0-2")), (Label("1-0-0")),
253 (Label("1-0-1")),
254 (Label("1-1")),
255 (Label("1-3")),
256 (Label("0")), ];
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}