Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/stack.rs
6598 views
1
//! This module contains the systems that update the stored UI nodes stack
2
3
use bevy_ecs::prelude::*;
4
use bevy_platform::collections::HashSet;
5
6
use 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)]
16
pub struct UiStack {
17
/// List of UI nodes ordered from back-to-front
18
pub uinodes: Vec<Entity>,
19
}
20
21
#[derive(Default)]
22
pub(crate) struct ChildBufferCache {
23
pub inner: Vec<Vec<(Entity, i32)>>,
24
}
25
26
impl 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.
41
pub 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
100
fn 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)]
128
mod 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
}
310
311