Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/update.rs
9367 views
1
//! This module contains systems that update the UI when something changes
2
3
use crate::{
4
experimental::{UiChildren, UiRootNodes},
5
ui_transform::UiGlobalTransform,
6
CalculatedClip, ComputedUiRenderTargetInfo, ComputedUiTargetCamera, DefaultUiCamera, Display,
7
Node, OverflowAxis, OverrideClip, UiScale, UiTargetCamera,
8
};
9
10
use super::ComputedNode;
11
use bevy_app::Propagate;
12
use bevy_camera::Camera;
13
use bevy_ecs::{
14
entity::Entity,
15
query::Has,
16
system::{Commands, Query, Res},
17
};
18
use bevy_math::{Rect, UVec2};
19
use bevy_sprite::BorderRect;
20
21
/// Updates clipping for all nodes
22
pub fn update_clipping_system(
23
mut commands: Commands,
24
root_nodes: UiRootNodes,
25
mut node_query: Query<(
26
&Node,
27
&ComputedNode,
28
&UiGlobalTransform,
29
Option<&mut CalculatedClip>,
30
Has<OverrideClip>,
31
)>,
32
ui_children: UiChildren,
33
) {
34
for root_node in root_nodes.iter() {
35
update_clipping(
36
&mut commands,
37
&ui_children,
38
&mut node_query,
39
root_node,
40
None,
41
);
42
}
43
}
44
45
fn update_clipping(
46
commands: &mut Commands,
47
ui_children: &UiChildren,
48
node_query: &mut Query<(
49
&Node,
50
&ComputedNode,
51
&UiGlobalTransform,
52
Option<&mut CalculatedClip>,
53
Has<OverrideClip>,
54
)>,
55
entity: Entity,
56
mut maybe_inherited_clip: Option<Rect>,
57
) {
58
let Ok((node, computed_node, transform, maybe_calculated_clip, has_override_clip)) =
59
node_query.get_mut(entity)
60
else {
61
return;
62
};
63
64
// If the UI node entity has an `OverrideClip` component, discard any inherited clip rect
65
if has_override_clip {
66
maybe_inherited_clip = None;
67
}
68
69
// If `display` is None, clip the entire node and all its descendants by replacing the inherited clip with a default rect (which is empty)
70
if node.display == Display::None {
71
maybe_inherited_clip = Some(Rect::default());
72
}
73
74
// Update this node's CalculatedClip component
75
if let Some(mut calculated_clip) = maybe_calculated_clip {
76
if let Some(inherited_clip) = maybe_inherited_clip {
77
// Replace the previous calculated clip with the inherited clipping rect
78
if calculated_clip.clip != inherited_clip {
79
*calculated_clip = CalculatedClip {
80
clip: inherited_clip,
81
};
82
}
83
} else {
84
// No inherited clipping rect, remove the component
85
commands.entity(entity).remove::<CalculatedClip>();
86
}
87
} else if let Some(inherited_clip) = maybe_inherited_clip {
88
// No previous calculated clip, add a new CalculatedClip component with the inherited clipping rect
89
commands.entity(entity).try_insert(CalculatedClip {
90
clip: inherited_clip,
91
});
92
}
93
94
// Calculate new clip rectangle for children nodes
95
let children_clip = if node.overflow.is_visible() {
96
// The current node doesn't clip, propagate the optional inherited clipping rect to any children
97
maybe_inherited_clip
98
} else {
99
// Find the current node's clipping rect and intersect it with the inherited clipping rect, if one exists
100
let mut clip_rect = Rect::from_center_size(transform.translation, computed_node.size());
101
102
// Content isn't clipped at the edges of the node but at the edges of the region specified by [`Node::overflow_clip_margin`].
103
//
104
// `clip_inset` should always fit inside `node_rect`.
105
// Even if `clip_inset` were to overflow, we won't return a degenerate result as `Rect::intersect` will clamp the intersection, leaving it empty.
106
let clip_inset = match node.overflow_clip_margin.visual_box {
107
crate::OverflowClipBox::BorderBox => BorderRect::ZERO,
108
crate::OverflowClipBox::ContentBox => computed_node.content_inset(),
109
crate::OverflowClipBox::PaddingBox => computed_node.border(),
110
};
111
112
clip_rect.min += clip_inset.min_inset;
113
clip_rect.max -= clip_inset.max_inset;
114
115
clip_rect = clip_rect
116
.inflate(node.overflow_clip_margin.margin.max(0.) / computed_node.inverse_scale_factor);
117
118
if node.overflow.x == OverflowAxis::Visible {
119
clip_rect.min.x = -f32::INFINITY;
120
clip_rect.max.x = f32::INFINITY;
121
}
122
if node.overflow.y == OverflowAxis::Visible {
123
clip_rect.min.y = -f32::INFINITY;
124
clip_rect.max.y = f32::INFINITY;
125
}
126
Some(maybe_inherited_clip.map_or(clip_rect, |c| c.intersect(clip_rect)))
127
};
128
129
for child in ui_children.iter_ui_children(entity) {
130
update_clipping(commands, ui_children, node_query, child, children_clip);
131
}
132
}
133
134
pub fn propagate_ui_target_cameras(
135
mut commands: Commands,
136
default_ui_camera: DefaultUiCamera,
137
ui_scale: Res<UiScale>,
138
camera_query: Query<&Camera>,
139
target_camera_query: Query<&UiTargetCamera>,
140
ui_root_nodes: UiRootNodes,
141
) {
142
let default_camera_entity = default_ui_camera.get();
143
144
for root_entity in ui_root_nodes.iter() {
145
let camera = target_camera_query
146
.get(root_entity)
147
.ok()
148
.map(UiTargetCamera::entity)
149
.or(default_camera_entity)
150
.unwrap_or(Entity::PLACEHOLDER);
151
152
commands
153
.entity(root_entity)
154
.try_insert(Propagate(ComputedUiTargetCamera { camera }));
155
156
let (scale_factor, physical_size) = camera_query
157
.get(camera)
158
.ok()
159
.map(|camera| {
160
(
161
camera.target_scaling_factor().unwrap_or(1.) * ui_scale.0,
162
camera.physical_viewport_size().unwrap_or(UVec2::ZERO),
163
)
164
})
165
.unwrap_or((1., UVec2::ZERO));
166
167
commands
168
.entity(root_entity)
169
.try_insert(Propagate(ComputedUiRenderTargetInfo {
170
scale_factor,
171
physical_size,
172
}));
173
}
174
}
175
176
#[cfg(test)]
177
mod tests {
178
use crate::update::propagate_ui_target_cameras;
179
use crate::ComputedUiRenderTargetInfo;
180
use crate::ComputedUiTargetCamera;
181
use crate::IsDefaultUiCamera;
182
use crate::Node;
183
use crate::UiScale;
184
use crate::UiTargetCamera;
185
use bevy_app::App;
186
use bevy_app::HierarchyPropagatePlugin;
187
use bevy_app::PostUpdate;
188
use bevy_app::PropagateSet;
189
use bevy_camera::Camera;
190
use bevy_camera::Camera2d;
191
use bevy_camera::ComputedCameraValues;
192
use bevy_camera::RenderTargetInfo;
193
use bevy_ecs::hierarchy::ChildOf;
194
use bevy_math::UVec2;
195
use bevy_utils::default;
196
197
fn setup_test_app() -> App {
198
let mut app = App::new();
199
200
app.init_resource::<UiScale>();
201
202
app.add_plugins(HierarchyPropagatePlugin::<ComputedUiTargetCamera>::new(
203
PostUpdate,
204
));
205
app.configure_sets(
206
PostUpdate,
207
PropagateSet::<ComputedUiTargetCamera>::default(),
208
);
209
210
app.add_plugins(HierarchyPropagatePlugin::<ComputedUiRenderTargetInfo>::new(
211
PostUpdate,
212
));
213
app.configure_sets(
214
PostUpdate,
215
PropagateSet::<ComputedUiRenderTargetInfo>::default(),
216
);
217
218
app.add_systems(bevy_app::Update, propagate_ui_target_cameras);
219
220
app
221
}
222
223
#[test]
224
fn update_context_for_single_ui_root() {
225
let mut app = setup_test_app();
226
let world = app.world_mut();
227
228
let scale_factor = 10.;
229
let physical_size = UVec2::new(1000, 500);
230
231
let camera = world
232
.spawn((
233
Camera2d,
234
Camera {
235
computed: ComputedCameraValues {
236
target_info: Some(RenderTargetInfo {
237
physical_size,
238
scale_factor,
239
}),
240
..Default::default()
241
},
242
..Default::default()
243
},
244
))
245
.id();
246
247
let uinode = world.spawn(Node::default()).id();
248
249
app.update();
250
let world = app.world_mut();
251
252
assert_eq!(
253
*world.get::<ComputedUiTargetCamera>(uinode).unwrap(),
254
ComputedUiTargetCamera { camera }
255
);
256
257
assert_eq!(
258
*world.get::<ComputedUiRenderTargetInfo>(uinode).unwrap(),
259
ComputedUiRenderTargetInfo {
260
physical_size,
261
scale_factor,
262
}
263
);
264
}
265
266
#[test]
267
fn update_multiple_context_for_multiple_ui_roots() {
268
let mut app = setup_test_app();
269
let world = app.world_mut();
270
271
let scale1 = 1.;
272
let size1 = UVec2::new(100, 100);
273
let scale2 = 2.;
274
let size2 = UVec2::new(200, 200);
275
276
let camera1 = world
277
.spawn((
278
Camera2d,
279
IsDefaultUiCamera,
280
Camera {
281
computed: ComputedCameraValues {
282
target_info: Some(RenderTargetInfo {
283
physical_size: size1,
284
scale_factor: scale1,
285
}),
286
..Default::default()
287
},
288
..Default::default()
289
},
290
))
291
.id();
292
let camera2 = world
293
.spawn((
294
Camera2d,
295
Camera {
296
computed: ComputedCameraValues {
297
target_info: Some(RenderTargetInfo {
298
physical_size: size2,
299
scale_factor: scale2,
300
}),
301
..Default::default()
302
},
303
..default()
304
},
305
))
306
.id();
307
308
let uinode1a = world.spawn(Node::default()).id();
309
let uinode2a = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
310
let uinode2b = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
311
let uinode2c = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
312
let uinode1b = world.spawn(Node::default()).id();
313
314
app.update();
315
let world = app.world_mut();
316
317
for (uinode, camera, scale_factor, physical_size) in [
318
(uinode1a, camera1, scale1, size1),
319
(uinode1b, camera1, scale1, size1),
320
(uinode2a, camera2, scale2, size2),
321
(uinode2b, camera2, scale2, size2),
322
(uinode2c, camera2, scale2, size2),
323
] {
324
assert_eq!(
325
*world.get::<ComputedUiTargetCamera>(uinode).unwrap(),
326
ComputedUiTargetCamera { camera }
327
);
328
329
assert_eq!(
330
*world.get::<ComputedUiRenderTargetInfo>(uinode).unwrap(),
331
ComputedUiRenderTargetInfo {
332
physical_size,
333
scale_factor,
334
}
335
);
336
}
337
}
338
339
#[test]
340
fn update_context_on_changed_camera() {
341
let mut app = setup_test_app();
342
let world = app.world_mut();
343
344
let scale1 = 1.;
345
let size1 = UVec2::new(100, 100);
346
let scale2 = 2.;
347
let size2 = UVec2::new(200, 200);
348
349
let camera1 = world
350
.spawn((
351
Camera2d,
352
IsDefaultUiCamera,
353
Camera {
354
computed: ComputedCameraValues {
355
target_info: Some(RenderTargetInfo {
356
physical_size: size1,
357
scale_factor: scale1,
358
}),
359
..Default::default()
360
},
361
..Default::default()
362
},
363
))
364
.id();
365
let camera2 = world
366
.spawn((
367
Camera2d,
368
Camera {
369
computed: ComputedCameraValues {
370
target_info: Some(RenderTargetInfo {
371
physical_size: size2,
372
scale_factor: scale2,
373
}),
374
..Default::default()
375
},
376
..default()
377
},
378
))
379
.id();
380
381
let uinode = world.spawn(Node::default()).id();
382
383
app.update();
384
let world = app.world_mut();
385
386
assert_eq!(
387
world
388
.get::<ComputedUiRenderTargetInfo>(uinode)
389
.unwrap()
390
.scale_factor,
391
scale1
392
);
393
394
assert_eq!(
395
world
396
.get::<ComputedUiRenderTargetInfo>(uinode)
397
.unwrap()
398
.physical_size,
399
size1
400
);
401
402
assert_eq!(
403
world
404
.get::<ComputedUiTargetCamera>(uinode)
405
.unwrap()
406
.get()
407
.unwrap(),
408
camera1
409
);
410
411
world.entity_mut(uinode).insert(UiTargetCamera(camera2));
412
413
app.update();
414
let world = app.world_mut();
415
416
assert_eq!(
417
world
418
.get::<ComputedUiRenderTargetInfo>(uinode)
419
.unwrap()
420
.scale_factor,
421
scale2
422
);
423
424
assert_eq!(
425
world
426
.get::<ComputedUiRenderTargetInfo>(uinode)
427
.unwrap()
428
.physical_size,
429
size2
430
);
431
432
assert_eq!(
433
world
434
.get::<ComputedUiTargetCamera>(uinode)
435
.unwrap()
436
.get()
437
.unwrap(),
438
camera2
439
);
440
}
441
442
#[test]
443
fn update_context_after_parent_removed() {
444
let mut app = setup_test_app();
445
let world = app.world_mut();
446
447
let scale1 = 1.;
448
let size1 = UVec2::new(100, 100);
449
let scale2 = 2.;
450
let size2 = UVec2::new(200, 200);
451
452
let camera1 = world
453
.spawn((
454
Camera2d,
455
IsDefaultUiCamera,
456
Camera {
457
computed: ComputedCameraValues {
458
target_info: Some(RenderTargetInfo {
459
physical_size: size1,
460
scale_factor: scale1,
461
}),
462
..Default::default()
463
},
464
..Default::default()
465
},
466
))
467
.id();
468
let camera2 = world
469
.spawn((
470
Camera2d,
471
Camera {
472
computed: ComputedCameraValues {
473
target_info: Some(RenderTargetInfo {
474
physical_size: size2,
475
scale_factor: scale2,
476
}),
477
..Default::default()
478
},
479
..default()
480
},
481
))
482
.id();
483
484
// `UiTargetCamera` is ignored on non-root UI nodes
485
let uinode1 = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
486
let uinode2 = world.spawn(Node::default()).add_child(uinode1).id();
487
488
app.update();
489
let world = app.world_mut();
490
491
assert_eq!(
492
world
493
.get::<ComputedUiRenderTargetInfo>(uinode1)
494
.unwrap()
495
.scale_factor(),
496
scale1
497
);
498
499
assert_eq!(
500
world
501
.get::<ComputedUiRenderTargetInfo>(uinode1)
502
.unwrap()
503
.physical_size(),
504
size1
505
);
506
507
assert_eq!(
508
world
509
.get::<ComputedUiTargetCamera>(uinode1)
510
.unwrap()
511
.get()
512
.unwrap(),
513
camera1
514
);
515
516
assert_eq!(
517
world
518
.get::<ComputedUiTargetCamera>(uinode2)
519
.unwrap()
520
.get()
521
.unwrap(),
522
camera1
523
);
524
525
// Now `uinode1` is a root UI node its `UiTargetCamera` component will be used and its camera target set to `camera2`.
526
world.entity_mut(uinode1).remove::<ChildOf>();
527
528
app.update();
529
let world = app.world_mut();
530
531
assert_eq!(
532
world
533
.get::<ComputedUiRenderTargetInfo>(uinode1)
534
.unwrap()
535
.scale_factor(),
536
scale2
537
);
538
539
assert_eq!(
540
world
541
.get::<ComputedUiRenderTargetInfo>(uinode1)
542
.unwrap()
543
.physical_size(),
544
size2
545
);
546
547
assert_eq!(
548
world
549
.get::<ComputedUiTargetCamera>(uinode1)
550
.unwrap()
551
.get()
552
.unwrap(),
553
camera2
554
);
555
556
assert_eq!(
557
world
558
.get::<ComputedUiTargetCamera>(uinode2)
559
.unwrap()
560
.get()
561
.unwrap(),
562
camera1
563
);
564
}
565
566
#[test]
567
fn update_great_grandchild() {
568
let mut app = setup_test_app();
569
let world = app.world_mut();
570
571
let scale = 1.;
572
let size = UVec2::new(100, 100);
573
574
let camera = world
575
.spawn((
576
Camera2d,
577
Camera {
578
computed: ComputedCameraValues {
579
target_info: Some(RenderTargetInfo {
580
physical_size: size,
581
scale_factor: scale,
582
}),
583
..Default::default()
584
},
585
..Default::default()
586
},
587
))
588
.id();
589
590
let uinode = world.spawn(Node::default()).id();
591
world.spawn(Node::default()).with_children(|builder| {
592
builder.spawn(Node::default()).with_children(|builder| {
593
builder.spawn(Node::default()).add_child(uinode);
594
});
595
});
596
597
app.update();
598
let world = app.world_mut();
599
600
assert_eq!(
601
world
602
.get::<ComputedUiRenderTargetInfo>(uinode)
603
.unwrap()
604
.scale_factor,
605
scale
606
);
607
608
assert_eq!(
609
world
610
.get::<ComputedUiRenderTargetInfo>(uinode)
611
.unwrap()
612
.physical_size,
613
size
614
);
615
616
assert_eq!(
617
world
618
.get::<ComputedUiTargetCamera>(uinode)
619
.unwrap()
620
.get()
621
.unwrap(),
622
camera
623
);
624
625
world.resource_mut::<UiScale>().0 = 2.;
626
627
app.update();
628
let world = app.world_mut();
629
630
assert_eq!(
631
world
632
.get::<ComputedUiRenderTargetInfo>(uinode)
633
.unwrap()
634
.scale_factor(),
635
2.
636
);
637
}
638
}
639
640