Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/update.rs
6598 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.x += clip_inset.left;
113
clip_rect.min.y += clip_inset.top;
114
clip_rect.max.x -= clip_inset.right + computed_node.scrollbar_size.x;
115
clip_rect.max.y -= clip_inset.bottom + computed_node.scrollbar_size.y;
116
117
clip_rect = clip_rect
118
.inflate(node.overflow_clip_margin.margin.max(0.) / computed_node.inverse_scale_factor);
119
120
if node.overflow.x == OverflowAxis::Visible {
121
clip_rect.min.x = -f32::INFINITY;
122
clip_rect.max.x = f32::INFINITY;
123
}
124
if node.overflow.y == OverflowAxis::Visible {
125
clip_rect.min.y = -f32::INFINITY;
126
clip_rect.max.y = f32::INFINITY;
127
}
128
Some(maybe_inherited_clip.map_or(clip_rect, |c| c.intersect(clip_rect)))
129
};
130
131
for child in ui_children.iter_ui_children(entity) {
132
update_clipping(commands, ui_children, node_query, child, children_clip);
133
}
134
}
135
136
pub fn propagate_ui_target_cameras(
137
mut commands: Commands,
138
default_ui_camera: DefaultUiCamera,
139
ui_scale: Res<UiScale>,
140
camera_query: Query<&Camera>,
141
target_camera_query: Query<&UiTargetCamera>,
142
ui_root_nodes: UiRootNodes,
143
) {
144
let default_camera_entity = default_ui_camera.get();
145
146
for root_entity in ui_root_nodes.iter() {
147
let camera = target_camera_query
148
.get(root_entity)
149
.ok()
150
.map(UiTargetCamera::entity)
151
.or(default_camera_entity)
152
.unwrap_or(Entity::PLACEHOLDER);
153
154
commands
155
.entity(root_entity)
156
.insert(Propagate(ComputedUiTargetCamera { camera }));
157
158
let (scale_factor, physical_size) = camera_query
159
.get(camera)
160
.ok()
161
.map(|camera| {
162
(
163
camera.target_scaling_factor().unwrap_or(1.) * ui_scale.0,
164
camera.physical_viewport_size().unwrap_or(UVec2::ZERO),
165
)
166
})
167
.unwrap_or((1., UVec2::ZERO));
168
169
commands
170
.entity(root_entity)
171
.insert(Propagate(ComputedUiRenderTargetInfo {
172
scale_factor,
173
physical_size,
174
}));
175
}
176
}
177
178
/// Update each `Camera`'s `RenderTargetInfo` from its associated `Window` render target.
179
/// Cameras with non-window render targets are ignored.
180
#[cfg(test)]
181
pub(crate) fn update_cameras_test_system(
182
primary_window: Query<Entity, bevy_ecs::query::With<bevy_window::PrimaryWindow>>,
183
window_query: Query<&bevy_window::Window>,
184
mut camera_query: Query<&mut Camera>,
185
) {
186
let primary_window = primary_window.single().ok();
187
for mut camera in camera_query.iter_mut() {
188
let Some(camera_target) = camera.target.normalize(primary_window) else {
189
continue;
190
};
191
let bevy_camera::NormalizedRenderTarget::Window(window_ref) = camera_target else {
192
continue;
193
};
194
let Ok(window) = window_query.get(bevy_ecs::entity::ContainsEntity::entity(&window_ref))
195
else {
196
continue;
197
};
198
199
let render_target_info = bevy_camera::RenderTargetInfo {
200
physical_size: window.physical_size(),
201
scale_factor: window.scale_factor(),
202
};
203
camera.computed.target_info = Some(render_target_info);
204
}
205
}
206
207
#[cfg(test)]
208
mod tests {
209
use crate::update::propagate_ui_target_cameras;
210
use crate::ComputedUiRenderTargetInfo;
211
use crate::ComputedUiTargetCamera;
212
use crate::IsDefaultUiCamera;
213
use crate::Node;
214
use crate::UiScale;
215
use crate::UiTargetCamera;
216
use bevy_app::App;
217
use bevy_app::HierarchyPropagatePlugin;
218
use bevy_app::PostUpdate;
219
use bevy_app::PropagateSet;
220
use bevy_camera::Camera;
221
use bevy_camera::Camera2d;
222
use bevy_camera::RenderTarget;
223
use bevy_ecs::hierarchy::ChildOf;
224
use bevy_ecs::schedule::IntoScheduleConfigs;
225
use bevy_math::UVec2;
226
use bevy_utils::default;
227
use bevy_window::PrimaryWindow;
228
use bevy_window::Window;
229
use bevy_window::WindowRef;
230
use bevy_window::WindowResolution;
231
232
fn setup_test_app() -> App {
233
let mut app = App::new();
234
235
app.init_resource::<UiScale>();
236
237
app.add_plugins(HierarchyPropagatePlugin::<ComputedUiTargetCamera>::new(
238
PostUpdate,
239
));
240
app.configure_sets(
241
PostUpdate,
242
PropagateSet::<ComputedUiTargetCamera>::default(),
243
);
244
245
app.add_plugins(HierarchyPropagatePlugin::<ComputedUiRenderTargetInfo>::new(
246
PostUpdate,
247
));
248
app.configure_sets(
249
PostUpdate,
250
PropagateSet::<ComputedUiRenderTargetInfo>::default(),
251
);
252
253
app.add_systems(
254
bevy_app::Update,
255
(
256
super::update_cameras_test_system,
257
propagate_ui_target_cameras,
258
)
259
.chain(),
260
);
261
262
app
263
}
264
265
#[test]
266
fn update_context_for_single_ui_root() {
267
let mut app = setup_test_app();
268
let world = app.world_mut();
269
270
let scale_factor = 10.;
271
let physical_size = UVec2::new(1000, 500);
272
273
world.spawn((
274
Window {
275
resolution: WindowResolution::from(physical_size).with_scale_factor_override(10.),
276
..Default::default()
277
},
278
PrimaryWindow,
279
));
280
281
let camera = world.spawn(Camera2d).id();
282
283
let uinode = world.spawn(Node::default()).id();
284
285
app.update();
286
let world = app.world_mut();
287
288
assert_eq!(
289
*world.get::<ComputedUiTargetCamera>(uinode).unwrap(),
290
ComputedUiTargetCamera { camera }
291
);
292
293
assert_eq!(
294
*world.get::<ComputedUiRenderTargetInfo>(uinode).unwrap(),
295
ComputedUiRenderTargetInfo {
296
physical_size,
297
scale_factor,
298
}
299
);
300
}
301
302
#[test]
303
fn update_multiple_context_for_multiple_ui_roots() {
304
let mut app = setup_test_app();
305
let world = app.world_mut();
306
307
let scale1 = 1.;
308
let size1 = UVec2::new(100, 100);
309
let scale2 = 2.;
310
let size2 = UVec2::new(200, 200);
311
312
world.spawn((
313
Window {
314
resolution: WindowResolution::from(size1).with_scale_factor_override(scale1),
315
..Default::default()
316
},
317
PrimaryWindow,
318
));
319
320
let window_2 = world
321
.spawn((Window {
322
resolution: WindowResolution::from(size2).with_scale_factor_override(scale2),
323
..Default::default()
324
},))
325
.id();
326
327
let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id();
328
let camera2 = world
329
.spawn((
330
Camera2d,
331
Camera {
332
target: RenderTarget::Window(WindowRef::Entity(window_2)),
333
..default()
334
},
335
))
336
.id();
337
338
let uinode1a = world.spawn(Node::default()).id();
339
let uinode2a = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
340
let uinode2b = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
341
let uinode2c = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
342
let uinode1b = world.spawn(Node::default()).id();
343
344
app.update();
345
let world = app.world_mut();
346
347
for (uinode, camera, scale_factor, physical_size) in [
348
(uinode1a, camera1, scale1, size1),
349
(uinode1b, camera1, scale1, size1),
350
(uinode2a, camera2, scale2, size2),
351
(uinode2b, camera2, scale2, size2),
352
(uinode2c, camera2, scale2, size2),
353
] {
354
assert_eq!(
355
*world.get::<ComputedUiTargetCamera>(uinode).unwrap(),
356
ComputedUiTargetCamera { camera }
357
);
358
359
assert_eq!(
360
*world.get::<ComputedUiRenderTargetInfo>(uinode).unwrap(),
361
ComputedUiRenderTargetInfo {
362
physical_size,
363
scale_factor,
364
}
365
);
366
}
367
}
368
369
#[test]
370
fn update_context_on_changed_camera() {
371
let mut app = setup_test_app();
372
let world = app.world_mut();
373
374
let scale1 = 1.;
375
let size1 = UVec2::new(100, 100);
376
let scale2 = 2.;
377
let size2 = UVec2::new(200, 200);
378
379
world.spawn((
380
Window {
381
resolution: WindowResolution::from(size1).with_scale_factor_override(scale1),
382
..Default::default()
383
},
384
PrimaryWindow,
385
));
386
387
let window_2 = world
388
.spawn((Window {
389
resolution: WindowResolution::from(size2).with_scale_factor_override(scale2),
390
..Default::default()
391
},))
392
.id();
393
394
let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id();
395
let camera2 = world
396
.spawn((
397
Camera2d,
398
Camera {
399
target: RenderTarget::Window(WindowRef::Entity(window_2)),
400
..default()
401
},
402
))
403
.id();
404
405
let uinode = world.spawn(Node::default()).id();
406
407
app.update();
408
let world = app.world_mut();
409
410
assert_eq!(
411
world
412
.get::<ComputedUiRenderTargetInfo>(uinode)
413
.unwrap()
414
.scale_factor,
415
scale1
416
);
417
418
assert_eq!(
419
world
420
.get::<ComputedUiRenderTargetInfo>(uinode)
421
.unwrap()
422
.physical_size,
423
size1
424
);
425
426
assert_eq!(
427
world
428
.get::<ComputedUiTargetCamera>(uinode)
429
.unwrap()
430
.get()
431
.unwrap(),
432
camera1
433
);
434
435
world.entity_mut(uinode).insert(UiTargetCamera(camera2));
436
437
app.update();
438
let world = app.world_mut();
439
440
assert_eq!(
441
world
442
.get::<ComputedUiRenderTargetInfo>(uinode)
443
.unwrap()
444
.scale_factor,
445
scale2
446
);
447
448
assert_eq!(
449
world
450
.get::<ComputedUiRenderTargetInfo>(uinode)
451
.unwrap()
452
.physical_size,
453
size2
454
);
455
456
assert_eq!(
457
world
458
.get::<ComputedUiTargetCamera>(uinode)
459
.unwrap()
460
.get()
461
.unwrap(),
462
camera2
463
);
464
}
465
466
#[test]
467
fn update_context_after_parent_removed() {
468
let mut app = setup_test_app();
469
let world = app.world_mut();
470
471
let scale1 = 1.;
472
let size1 = UVec2::new(100, 100);
473
let scale2 = 2.;
474
let size2 = UVec2::new(200, 200);
475
476
world.spawn((
477
Window {
478
resolution: WindowResolution::from(size1).with_scale_factor_override(scale1),
479
..Default::default()
480
},
481
PrimaryWindow,
482
));
483
484
let window_2 = world
485
.spawn((Window {
486
resolution: WindowResolution::from(size2).with_scale_factor_override(scale2),
487
..Default::default()
488
},))
489
.id();
490
491
let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id();
492
let camera2 = world
493
.spawn((
494
Camera2d,
495
Camera {
496
target: RenderTarget::Window(WindowRef::Entity(window_2)),
497
..default()
498
},
499
))
500
.id();
501
502
// `UiTargetCamera` is ignored on non-root UI nodes
503
let uinode1 = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
504
let uinode2 = world.spawn(Node::default()).add_child(uinode1).id();
505
506
app.update();
507
let world = app.world_mut();
508
509
assert_eq!(
510
world
511
.get::<ComputedUiRenderTargetInfo>(uinode1)
512
.unwrap()
513
.scale_factor(),
514
scale1
515
);
516
517
assert_eq!(
518
world
519
.get::<ComputedUiRenderTargetInfo>(uinode1)
520
.unwrap()
521
.physical_size(),
522
size1
523
);
524
525
assert_eq!(
526
world
527
.get::<ComputedUiTargetCamera>(uinode1)
528
.unwrap()
529
.get()
530
.unwrap(),
531
camera1
532
);
533
534
assert_eq!(
535
world
536
.get::<ComputedUiTargetCamera>(uinode2)
537
.unwrap()
538
.get()
539
.unwrap(),
540
camera1
541
);
542
543
// Now `uinode1` is a root UI node its `UiTargetCamera` component will be used and its camera target set to `camera2`.
544
world.entity_mut(uinode1).remove::<ChildOf>();
545
546
app.update();
547
let world = app.world_mut();
548
549
assert_eq!(
550
world
551
.get::<ComputedUiRenderTargetInfo>(uinode1)
552
.unwrap()
553
.scale_factor(),
554
scale2
555
);
556
557
assert_eq!(
558
world
559
.get::<ComputedUiRenderTargetInfo>(uinode1)
560
.unwrap()
561
.physical_size(),
562
size2
563
);
564
565
assert_eq!(
566
world
567
.get::<ComputedUiTargetCamera>(uinode1)
568
.unwrap()
569
.get()
570
.unwrap(),
571
camera2
572
);
573
574
assert_eq!(
575
world
576
.get::<ComputedUiTargetCamera>(uinode2)
577
.unwrap()
578
.get()
579
.unwrap(),
580
camera1
581
);
582
}
583
584
#[test]
585
fn update_great_grandchild() {
586
let mut app = setup_test_app();
587
let world = app.world_mut();
588
589
let scale = 1.;
590
let size = UVec2::new(100, 100);
591
592
world.spawn((
593
Window {
594
resolution: WindowResolution::from(size).with_scale_factor_override(scale),
595
..Default::default()
596
},
597
PrimaryWindow,
598
));
599
600
let camera = world.spawn(Camera2d).id();
601
602
let uinode = world.spawn(Node::default()).id();
603
world.spawn(Node::default()).with_children(|builder| {
604
builder.spawn(Node::default()).with_children(|builder| {
605
builder.spawn(Node::default()).add_child(uinode);
606
});
607
});
608
609
app.update();
610
let world = app.world_mut();
611
612
assert_eq!(
613
world
614
.get::<ComputedUiRenderTargetInfo>(uinode)
615
.unwrap()
616
.scale_factor,
617
scale
618
);
619
620
assert_eq!(
621
world
622
.get::<ComputedUiRenderTargetInfo>(uinode)
623
.unwrap()
624
.physical_size,
625
size
626
);
627
628
assert_eq!(
629
world
630
.get::<ComputedUiTargetCamera>(uinode)
631
.unwrap()
632
.get()
633
.unwrap(),
634
camera
635
);
636
637
world.resource_mut::<UiScale>().0 = 2.;
638
639
app.update();
640
let world = app.world_mut();
641
642
assert_eq!(
643
world
644
.get::<ComputedUiRenderTargetInfo>(uinode)
645
.unwrap()
646
.scale_factor(),
647
2.
648
);
649
}
650
}
651
652