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.x += clip_inset.left;
clip_rect.min.y += clip_inset.top;
clip_rect.max.x -= clip_inset.right + computed_node.scrollbar_size.x;
clip_rect.max.y -= clip_inset.bottom + computed_node.scrollbar_size.y;
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)
.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)
.insert(Propagate(ComputedUiRenderTargetInfo {
scale_factor,
physical_size,
}));
}
}
#[cfg(test)]
pub(crate) fn update_cameras_test_system(
primary_window: Query<Entity, bevy_ecs::query::With<bevy_window::PrimaryWindow>>,
window_query: Query<&bevy_window::Window>,
mut camera_query: Query<&mut Camera>,
) {
let primary_window = primary_window.single().ok();
for mut camera in camera_query.iter_mut() {
let Some(camera_target) = camera.target.normalize(primary_window) else {
continue;
};
let bevy_camera::NormalizedRenderTarget::Window(window_ref) = camera_target else {
continue;
};
let Ok(window) = window_query.get(bevy_ecs::entity::ContainsEntity::entity(&window_ref))
else {
continue;
};
let render_target_info = bevy_camera::RenderTargetInfo {
physical_size: window.physical_size(),
scale_factor: window.scale_factor(),
};
camera.computed.target_info = Some(render_target_info);
}
}
#[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::RenderTarget;
use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_math::UVec2;
use bevy_utils::default;
use bevy_window::PrimaryWindow;
use bevy_window::Window;
use bevy_window::WindowRef;
use bevy_window::WindowResolution;
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,
(
super::update_cameras_test_system,
propagate_ui_target_cameras,
)
.chain(),
);
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);
world.spawn((
Window {
resolution: WindowResolution::from(physical_size).with_scale_factor_override(10.),
..Default::default()
},
PrimaryWindow,
));
let camera = world.spawn(Camera2d).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);
world.spawn((
Window {
resolution: WindowResolution::from(size1).with_scale_factor_override(scale1),
..Default::default()
},
PrimaryWindow,
));
let window_2 = world
.spawn((Window {
resolution: WindowResolution::from(size2).with_scale_factor_override(scale2),
..Default::default()
},))
.id();
let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id();
let camera2 = world
.spawn((
Camera2d,
Camera {
target: RenderTarget::Window(WindowRef::Entity(window_2)),
..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);
world.spawn((
Window {
resolution: WindowResolution::from(size1).with_scale_factor_override(scale1),
..Default::default()
},
PrimaryWindow,
));
let window_2 = world
.spawn((Window {
resolution: WindowResolution::from(size2).with_scale_factor_override(scale2),
..Default::default()
},))
.id();
let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id();
let camera2 = world
.spawn((
Camera2d,
Camera {
target: RenderTarget::Window(WindowRef::Entity(window_2)),
..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);
world.spawn((
Window {
resolution: WindowResolution::from(size1).with_scale_factor_override(scale1),
..Default::default()
},
PrimaryWindow,
));
let window_2 = world
.spawn((Window {
resolution: WindowResolution::from(size2).with_scale_factor_override(scale2),
..Default::default()
},))
.id();
let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id();
let camera2 = world
.spawn((
Camera2d,
Camera {
target: RenderTarget::Window(WindowRef::Entity(window_2)),
..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);
world.spawn((
Window {
resolution: WindowResolution::from(size).with_scale_factor_override(scale),
..Default::default()
},
PrimaryWindow,
));
let camera = world.spawn(Camera2d).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.
);
}
}