1use crate::{
4 experimental::{UiChildren, UiRootNodes},
5 ui_transform::UiGlobalTransform,
6 CalculatedClip, ComputedUiRenderTargetInfo, ComputedUiTargetCamera, DefaultUiCamera, Display,
7 Node, OverflowAxis, OverrideClip, UiScale, UiTargetCamera,
8};
9
10use super::ComputedNode;
11use bevy_app::Propagate;
12use bevy_camera::Camera;
13use bevy_ecs::{
14 entity::Entity,
15 query::Has,
16 system::{Commands, Query, Res},
17};
18use bevy_math::{Rect, UVec2};
19use bevy_sprite::BorderRect;
20
21pub 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
45fn 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 has_override_clip {
66 maybe_inherited_clip = None;
67 }
68
69 if node.display == Display::None {
71 maybe_inherited_clip = Some(Rect::default());
72 }
73
74 if let Some(mut calculated_clip) = maybe_calculated_clip {
76 if let Some(inherited_clip) = maybe_inherited_clip {
77 if calculated_clip.clip != inherited_clip {
79 *calculated_clip = CalculatedClip {
80 clip: inherited_clip,
81 };
82 }
83 } else {
84 commands.entity(entity).remove::<CalculatedClip>();
86 }
87 } else if let Some(inherited_clip) = maybe_inherited_clip {
88 commands.entity(entity).try_insert(CalculatedClip {
90 clip: inherited_clip,
91 });
92 }
93
94 let children_clip = if node.overflow.is_visible() {
96 maybe_inherited_clip
98 } else {
99 let mut clip_rect = Rect::from_center_size(transform.translation, computed_node.size());
101
102 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
134pub 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)]
177mod 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 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 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}