use crate::{
experimental::{UiChildren, UiRootNodes},
ui_transform::UiGlobalTransform,
CalculatedClip, ComputedUiRenderTargetInfo, ComputedUiTargetCamera, DefaultUiCamera, Display,
Node, OverflowAxis, OverrideClip, UiScale, UiTargetCamera,
};
use super::ComputedNode;
use bevy_app::Propagate;
use bevy_camera::Camera;
use bevy_ecs::{
entity::Entity,
query::Has,
system::{Commands, Query, Res},
};
use bevy_math::{Rect, UVec2};
use bevy_sprite::BorderRect;
pub fn update_clipping_system(
mut commands: Commands,
root_nodes: UiRootNodes,
mut node_query: Query<(
&Node,
&ComputedNode,
&UiGlobalTransform,
Option<&mut CalculatedClip>,
Has<OverrideClip>,
)>,
ui_children: UiChildren,
) {
for root_node in root_nodes.iter() {
update_clipping(
&mut commands,
&ui_children,
&mut node_query,
root_node,
None,
);
}
}
fn update_clipping(
commands: &mut Commands,
ui_children: &UiChildren,
node_query: &mut Query<(
&Node,
&ComputedNode,
&UiGlobalTransform,
Option<&mut CalculatedClip>,
Has<OverrideClip>,
)>,
entity: Entity,
mut maybe_inherited_clip: Option<Rect>,
) {
let Ok((node, computed_node, transform, maybe_calculated_clip, has_override_clip)) =
node_query.get_mut(entity)
else {
return;
};
if has_override_clip {
maybe_inherited_clip = None;
}
if node.display == Display::None {
maybe_inherited_clip = Some(Rect::default());
}
if let Some(mut calculated_clip) = maybe_calculated_clip {
if let Some(inherited_clip) = maybe_inherited_clip {
if calculated_clip.clip != inherited_clip {
*calculated_clip = CalculatedClip {
clip: inherited_clip,
};
}
} else {
commands.entity(entity).remove::<CalculatedClip>();
}
} else if let Some(inherited_clip) = maybe_inherited_clip {
commands.entity(entity).try_insert(CalculatedClip {
clip: inherited_clip,
});
}
let children_clip = if node.overflow.is_visible() {
maybe_inherited_clip
} else {
let mut clip_rect = Rect::from_center_size(transform.translation, computed_node.size());
let clip_inset = match node.overflow_clip_margin.visual_box {
crate::OverflowClipBox::BorderBox => BorderRect::ZERO,
crate::OverflowClipBox::ContentBox => computed_node.content_inset(),
crate::OverflowClipBox::PaddingBox => computed_node.border(),
};
clip_rect.min += clip_inset.min_inset;
clip_rect.max -= clip_inset.max_inset;
clip_rect = clip_rect
.inflate(node.overflow_clip_margin.margin.max(0.) / computed_node.inverse_scale_factor);
if node.overflow.x == OverflowAxis::Visible {
clip_rect.min.x = -f32::INFINITY;
clip_rect.max.x = f32::INFINITY;
}
if node.overflow.y == OverflowAxis::Visible {
clip_rect.min.y = -f32::INFINITY;
clip_rect.max.y = f32::INFINITY;
}
Some(maybe_inherited_clip.map_or(clip_rect, |c| c.intersect(clip_rect)))
};
for child in ui_children.iter_ui_children(entity) {
update_clipping(commands, ui_children, node_query, child, children_clip);
}
}
pub fn propagate_ui_target_cameras(
mut commands: Commands,
default_ui_camera: DefaultUiCamera,
ui_scale: Res<UiScale>,
camera_query: Query<&Camera>,
target_camera_query: Query<&UiTargetCamera>,
ui_root_nodes: UiRootNodes,
) {
let default_camera_entity = default_ui_camera.get();
for root_entity in ui_root_nodes.iter() {
let camera = target_camera_query
.get(root_entity)
.ok()
.map(UiTargetCamera::entity)
.or(default_camera_entity)
.unwrap_or(Entity::PLACEHOLDER);
commands
.entity(root_entity)
.try_insert(Propagate(ComputedUiTargetCamera { camera }));
let (scale_factor, physical_size) = camera_query
.get(camera)
.ok()
.map(|camera| {
(
camera.target_scaling_factor().unwrap_or(1.) * ui_scale.0,
camera.physical_viewport_size().unwrap_or(UVec2::ZERO),
)
})
.unwrap_or((1., UVec2::ZERO));
commands
.entity(root_entity)
.try_insert(Propagate(ComputedUiRenderTargetInfo {
scale_factor,
physical_size,
}));
}
}
#[cfg(test)]
mod tests {
use crate::update::propagate_ui_target_cameras;
use crate::ComputedUiRenderTargetInfo;
use crate::ComputedUiTargetCamera;
use crate::IsDefaultUiCamera;
use crate::Node;
use crate::UiScale;
use crate::UiTargetCamera;
use bevy_app::App;
use bevy_app::HierarchyPropagatePlugin;
use bevy_app::PostUpdate;
use bevy_app::PropagateSet;
use bevy_camera::Camera;
use bevy_camera::Camera2d;
use bevy_camera::ComputedCameraValues;
use bevy_camera::RenderTargetInfo;
use bevy_ecs::hierarchy::ChildOf;
use bevy_math::UVec2;
use bevy_utils::default;
fn setup_test_app() -> App {
let mut app = App::new();
app.init_resource::<UiScale>();
app.add_plugins(HierarchyPropagatePlugin::<ComputedUiTargetCamera>::new(
PostUpdate,
));
app.configure_sets(
PostUpdate,
PropagateSet::<ComputedUiTargetCamera>::default(),
);
app.add_plugins(HierarchyPropagatePlugin::<ComputedUiRenderTargetInfo>::new(
PostUpdate,
));
app.configure_sets(
PostUpdate,
PropagateSet::<ComputedUiRenderTargetInfo>::default(),
);
app.add_systems(bevy_app::Update, propagate_ui_target_cameras);
app
}
#[test]
fn update_context_for_single_ui_root() {
let mut app = setup_test_app();
let world = app.world_mut();
let scale_factor = 10.;
let physical_size = UVec2::new(1000, 500);
let camera = world
.spawn((
Camera2d,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size,
scale_factor,
}),
..Default::default()
},
..Default::default()
},
))
.id();
let uinode = world.spawn(Node::default()).id();
app.update();
let world = app.world_mut();
assert_eq!(
*world.get::<ComputedUiTargetCamera>(uinode).unwrap(),
ComputedUiTargetCamera { camera }
);
assert_eq!(
*world.get::<ComputedUiRenderTargetInfo>(uinode).unwrap(),
ComputedUiRenderTargetInfo {
physical_size,
scale_factor,
}
);
}
#[test]
fn update_multiple_context_for_multiple_ui_roots() {
let mut app = setup_test_app();
let world = app.world_mut();
let scale1 = 1.;
let size1 = UVec2::new(100, 100);
let scale2 = 2.;
let size2 = UVec2::new(200, 200);
let camera1 = world
.spawn((
Camera2d,
IsDefaultUiCamera,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size1,
scale_factor: scale1,
}),
..Default::default()
},
..Default::default()
},
))
.id();
let camera2 = world
.spawn((
Camera2d,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size2,
scale_factor: scale2,
}),
..Default::default()
},
..default()
},
))
.id();
let uinode1a = world.spawn(Node::default()).id();
let uinode2a = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
let uinode2b = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
let uinode2c = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
let uinode1b = world.spawn(Node::default()).id();
app.update();
let world = app.world_mut();
for (uinode, camera, scale_factor, physical_size) in [
(uinode1a, camera1, scale1, size1),
(uinode1b, camera1, scale1, size1),
(uinode2a, camera2, scale2, size2),
(uinode2b, camera2, scale2, size2),
(uinode2c, camera2, scale2, size2),
] {
assert_eq!(
*world.get::<ComputedUiTargetCamera>(uinode).unwrap(),
ComputedUiTargetCamera { camera }
);
assert_eq!(
*world.get::<ComputedUiRenderTargetInfo>(uinode).unwrap(),
ComputedUiRenderTargetInfo {
physical_size,
scale_factor,
}
);
}
}
#[test]
fn update_context_on_changed_camera() {
let mut app = setup_test_app();
let world = app.world_mut();
let scale1 = 1.;
let size1 = UVec2::new(100, 100);
let scale2 = 2.;
let size2 = UVec2::new(200, 200);
let camera1 = world
.spawn((
Camera2d,
IsDefaultUiCamera,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size1,
scale_factor: scale1,
}),
..Default::default()
},
..Default::default()
},
))
.id();
let camera2 = world
.spawn((
Camera2d,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size2,
scale_factor: scale2,
}),
..Default::default()
},
..default()
},
))
.id();
let uinode = world.spawn(Node::default()).id();
app.update();
let world = app.world_mut();
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.scale_factor,
scale1
);
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.physical_size,
size1
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode)
.unwrap()
.get()
.unwrap(),
camera1
);
world.entity_mut(uinode).insert(UiTargetCamera(camera2));
app.update();
let world = app.world_mut();
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.scale_factor,
scale2
);
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.physical_size,
size2
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode)
.unwrap()
.get()
.unwrap(),
camera2
);
}
#[test]
fn update_context_after_parent_removed() {
let mut app = setup_test_app();
let world = app.world_mut();
let scale1 = 1.;
let size1 = UVec2::new(100, 100);
let scale2 = 2.;
let size2 = UVec2::new(200, 200);
let camera1 = world
.spawn((
Camera2d,
IsDefaultUiCamera,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size1,
scale_factor: scale1,
}),
..Default::default()
},
..Default::default()
},
))
.id();
let camera2 = world
.spawn((
Camera2d,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size2,
scale_factor: scale2,
}),
..Default::default()
},
..default()
},
))
.id();
let uinode1 = world.spawn((Node::default(), UiTargetCamera(camera2))).id();
let uinode2 = world.spawn(Node::default()).add_child(uinode1).id();
app.update();
let world = app.world_mut();
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode1)
.unwrap()
.scale_factor(),
scale1
);
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode1)
.unwrap()
.physical_size(),
size1
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode1)
.unwrap()
.get()
.unwrap(),
camera1
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode2)
.unwrap()
.get()
.unwrap(),
camera1
);
world.entity_mut(uinode1).remove::<ChildOf>();
app.update();
let world = app.world_mut();
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode1)
.unwrap()
.scale_factor(),
scale2
);
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode1)
.unwrap()
.physical_size(),
size2
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode1)
.unwrap()
.get()
.unwrap(),
camera2
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode2)
.unwrap()
.get()
.unwrap(),
camera1
);
}
#[test]
fn update_great_grandchild() {
let mut app = setup_test_app();
let world = app.world_mut();
let scale = 1.;
let size = UVec2::new(100, 100);
let camera = world
.spawn((
Camera2d,
Camera {
computed: ComputedCameraValues {
target_info: Some(RenderTargetInfo {
physical_size: size,
scale_factor: scale,
}),
..Default::default()
},
..Default::default()
},
))
.id();
let uinode = world.spawn(Node::default()).id();
world.spawn(Node::default()).with_children(|builder| {
builder.spawn(Node::default()).with_children(|builder| {
builder.spawn(Node::default()).add_child(uinode);
});
});
app.update();
let world = app.world_mut();
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.scale_factor,
scale
);
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.physical_size,
size
);
assert_eq!(
world
.get::<ComputedUiTargetCamera>(uinode)
.unwrap()
.get()
.unwrap(),
camera
);
world.resource_mut::<UiScale>().0 = 2.;
app.update();
let world = app.world_mut();
assert_eq!(
world
.get::<ComputedUiRenderTargetInfo>(uinode)
.unwrap()
.scale_factor(),
2.
);
}
}