use super::ExtractedUiItem;
use super::ExtractedUiNode;
use super::ExtractedUiNodes;
use super::NodeType;
use super::UiCameraMap;
use crate::shader_flags;
use bevy_asset::AssetId;
use bevy_camera::visibility::InheritedVisibility;
use bevy_color::Hsla;
use bevy_color::LinearRgba;
use bevy_ecs::entity::Entity;
use bevy_ecs::prelude::Component;
use bevy_ecs::prelude::ReflectComponent;
use bevy_ecs::prelude::ReflectResource;
use bevy_ecs::resource::Resource;
use bevy_ecs::system::Commands;
use bevy_ecs::system::Query;
use bevy_ecs::system::Res;
use bevy_ecs::system::ResMut;
use bevy_math::Affine2;
use bevy_math::Rect;
use bevy_math::Vec2;
use bevy_reflect::Reflect;
use bevy_render::sync_world::TemporaryRenderEntity;
use bevy_render::Extract;
use bevy_sprite::BorderRect;
use bevy_ui::ui_transform::UiGlobalTransform;
use bevy_ui::CalculatedClip;
use bevy_ui::ComputedNode;
use bevy_ui::ComputedStackIndex;
use bevy_ui::ComputedUiTargetCamera;
use bevy_ui::ResolvedBorderRadius;
use bevy_ui::UiStack;
#[derive(Component, Reflect, Copy, Clone)]
#[reflect(Component)]
pub struct UiDebugOptions {
pub enabled: bool,
pub outline_border_box: bool,
pub outline_padding_box: bool,
pub outline_content_box: bool,
pub outline_scrollbars: bool,
pub line_width: f32,
pub line_color_override: Option<LinearRgba>,
pub show_hidden: bool,
pub show_clipped: bool,
pub ignore_border_radius: bool,
}
impl UiDebugOptions {
pub fn toggle(&mut self) {
self.enabled = !self.enabled;
}
}
impl Default for UiDebugOptions {
fn default() -> Self {
Self {
enabled: false,
line_width: 1.,
line_color_override: None,
show_hidden: false,
show_clipped: false,
ignore_border_radius: false,
outline_border_box: true,
outline_padding_box: false,
outline_content_box: false,
outline_scrollbars: false,
}
}
}
impl From<GlobalUiDebugOptions> for UiDebugOptions {
fn from(other: GlobalUiDebugOptions) -> Self {
Self {
enabled: other.enabled,
outline_border_box: other.outline_border_box,
outline_padding_box: other.outline_padding_box,
outline_content_box: other.outline_content_box,
outline_scrollbars: other.outline_scrollbars,
line_width: other.line_width,
line_color_override: other.line_color_override,
show_hidden: other.show_hidden,
show_clipped: other.show_clipped,
ignore_border_radius: other.ignore_border_radius,
}
}
}
#[derive(Resource, Reflect, Copy, Clone)]
#[reflect(Resource)]
pub struct GlobalUiDebugOptions {
pub enabled: bool,
pub outline_border_box: bool,
pub outline_padding_box: bool,
pub outline_content_box: bool,
pub outline_scrollbars: bool,
pub line_width: f32,
pub line_color_override: Option<LinearRgba>,
pub show_hidden: bool,
pub show_clipped: bool,
pub ignore_border_radius: bool,
}
impl GlobalUiDebugOptions {
pub fn toggle(&mut self) {
self.enabled = !self.enabled;
}
}
impl Default for GlobalUiDebugOptions {
fn default() -> Self {
Self {
enabled: false,
line_width: 1.,
line_color_override: None,
show_hidden: false,
show_clipped: false,
ignore_border_radius: false,
outline_border_box: true,
outline_padding_box: false,
outline_content_box: false,
outline_scrollbars: false,
}
}
}
impl From<UiDebugOptions> for GlobalUiDebugOptions {
fn from(other: UiDebugOptions) -> Self {
Self {
enabled: other.enabled,
outline_border_box: other.outline_border_box,
outline_padding_box: other.outline_padding_box,
outline_content_box: other.outline_content_box,
outline_scrollbars: other.outline_scrollbars,
line_width: other.line_width,
line_color_override: other.line_color_override,
show_hidden: other.show_hidden,
show_clipped: other.show_clipped,
ignore_border_radius: other.ignore_border_radius,
}
}
}
pub fn extract_debug_overlay(
mut commands: Commands,
debug_options: Extract<Res<GlobalUiDebugOptions>>,
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
uinode_query: Extract<
Query<(
Entity,
&ComputedNode,
&ComputedStackIndex,
&UiGlobalTransform,
&InheritedVisibility,
Option<&CalculatedClip>,
&ComputedUiTargetCamera,
Option<&UiDebugOptions>,
)>,
>,
ui_stack: Extract<Res<UiStack>>,
camera_map: Extract<UiCameraMap>,
) {
let mut camera_mapper = camera_map.get_mapper();
for (entity, uinode, stack_index, transform, visibility, maybe_clip, computed_target, debug) in
&uinode_query
{
let debug_options = debug.copied().unwrap_or((*debug_options.as_ref()).into());
if !debug_options.enabled {
continue;
}
if !debug_options.show_hidden && !visibility.get() {
continue;
}
let Some(extracted_camera_entity) = camera_mapper.map(computed_target) else {
continue;
};
let color = debug_options
.line_color_override
.unwrap_or_else(|| Hsla::sequential_dispersed(entity.index_u32()).into());
let z_order = (ui_stack.uinodes.len() as u32 + stack_index.0) as f32;
let border = BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor());
let transform = transform.affine();
let mut push_outline = |rect: Rect, radius: ResolvedBorderRadius| {
if rect.is_empty() {
return;
}
extracted_uinodes.uinodes.push(ExtractedUiNode {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
z_order,
clip: maybe_clip
.filter(|_| !debug_options.show_clipped)
.map(|clip| clip.clip),
image: AssetId::default(),
extracted_camera_entity,
transform: transform * Affine2::from_translation(rect.center()),
item: ExtractedUiItem::Node {
color,
rect: Rect {
min: Vec2::ZERO,
max: rect.size(),
},
atlas_scaling: None,
flip_x: false,
flip_y: false,
border,
border_radius: radius,
node_type: NodeType::Border(shader_flags::BORDER_ALL),
},
main_entity: entity.into(),
});
};
let border_box = Rect::from_center_size(Vec2::ZERO, uinode.size);
if debug_options.outline_border_box {
push_outline(border_box, uinode.border_radius());
}
if debug_options.outline_padding_box {
let mut padding_box = border_box;
padding_box.min += uinode.border.min_inset;
padding_box.max -= uinode.border.max_inset;
push_outline(padding_box, uinode.inner_radius());
}
if debug_options.outline_content_box {
let mut content_box = border_box;
let content_inset = uinode.content_inset();
content_box.min += content_inset.min_inset;
content_box.max -= content_inset.max_inset;
push_outline(content_box, ResolvedBorderRadius::ZERO);
}
if debug_options.outline_scrollbars {
if let Some((gutter, [thumb_min, thumb_max])) = uinode.horizontal_scrollbar() {
push_outline(gutter, ResolvedBorderRadius::ZERO);
push_outline(
Rect {
min: Vec2::new(thumb_min, gutter.min.y),
max: Vec2::new(thumb_max, gutter.max.y),
},
ResolvedBorderRadius::ZERO,
);
}
if let Some((gutter, [thumb_min, thumb_max])) = uinode.vertical_scrollbar() {
push_outline(gutter, ResolvedBorderRadius::ZERO);
push_outline(
Rect {
min: Vec2::new(gutter.min.x, thumb_min),
max: Vec2::new(gutter.max.x, thumb_max),
},
ResolvedBorderRadius::ZERO,
);
}
}
}
}