Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/stack.rs
9351 views
1
//! This module contains the systems that update the stored UI nodes stack
2
3
use crate::{
4
experimental::{UiChildren, UiRootNodes},
5
ComputedNode, GlobalZIndex, ZIndex,
6
};
7
use bevy_ecs::prelude::*;
8
use bevy_platform::collections::HashSet;
9
use 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)]
16
pub 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)]
24
pub(crate) struct ChildBufferCache {
25
pub inner: Vec<Vec<(Entity, i32)>>,
26
}
27
28
impl 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.
43
pub 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
106
fn 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)]
134
mod 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
}
342
343