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.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
136pub 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#[cfg(test)]
181pub(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)]
208mod 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 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 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}