Path: blob/main/crates/bevy_ui_render/src/debug_overlay.rs
9356 views
use super::ExtractedUiItem;1use super::ExtractedUiNode;2use super::ExtractedUiNodes;3use super::NodeType;4use super::UiCameraMap;5use crate::shader_flags;6use bevy_asset::AssetId;7use bevy_camera::visibility::InheritedVisibility;8use bevy_color::Hsla;9use bevy_color::LinearRgba;10use bevy_ecs::entity::Entity;11use bevy_ecs::prelude::Component;12use bevy_ecs::prelude::ReflectComponent;13use bevy_ecs::prelude::ReflectResource;14use bevy_ecs::resource::Resource;15use bevy_ecs::system::Commands;16use bevy_ecs::system::Query;17use bevy_ecs::system::Res;18use bevy_ecs::system::ResMut;19use bevy_math::Affine2;20use bevy_math::Rect;21use bevy_math::Vec2;22use bevy_reflect::Reflect;23use bevy_render::sync_world::TemporaryRenderEntity;24use bevy_render::Extract;25use bevy_sprite::BorderRect;26use bevy_ui::ui_transform::UiGlobalTransform;27use bevy_ui::CalculatedClip;28use bevy_ui::ComputedNode;29use bevy_ui::ComputedUiTargetCamera;30use bevy_ui::ResolvedBorderRadius;31use bevy_ui::UiStack;3233/// Configuration for the UI debug overlay34///35/// Can be added as both a global `Resource` and locally as a `Component` to individual UI node entities.36/// The local component options override the global resource.37#[derive(Component, Resource, Reflect)]38#[reflect(Component, Resource)]39pub struct UiDebugOptions {40/// Set to true to enable the UI debug overlay41pub enabled: bool,42/// Show outlines for the border boxes of UI nodes43pub outline_border_box: bool,44/// Show outlines for the padding boxes of UI nodes45pub outline_padding_box: bool,46/// Show outlines for the content boxes of UI nodes47pub outline_content_box: bool,48/// Show outlines for the scrollbar regions of UI nodes49pub outline_scrollbars: bool,50/// Width of the overlay's lines in logical pixels51pub line_width: f32,52/// Override Color for the overlay's lines53pub line_color_override: Option<LinearRgba>,54/// Show outlines for non-visible UI nodes55pub show_hidden: bool,56/// Show outlines for clipped sections of UI nodes57pub show_clipped: bool,58/// Draw outlines with sharp corners even if the UI nodes have border radii59pub ignore_border_radius: bool,60}6162impl UiDebugOptions {63pub fn toggle(&mut self) {64self.enabled = !self.enabled;65}66}6768impl Default for UiDebugOptions {69fn default() -> Self {70Self {71enabled: false,72line_width: 1.,73line_color_override: None,74show_hidden: false,75show_clipped: false,76ignore_border_radius: false,77outline_border_box: true,78outline_padding_box: false,79outline_content_box: false,80outline_scrollbars: false,81}82}83}8485pub fn extract_debug_overlay(86mut commands: Commands,87debug_options: Extract<Res<UiDebugOptions>>,88mut extracted_uinodes: ResMut<ExtractedUiNodes>,89uinode_query: Extract<90Query<(91Entity,92&ComputedNode,93&UiGlobalTransform,94&InheritedVisibility,95Option<&CalculatedClip>,96&ComputedUiTargetCamera,97Option<&UiDebugOptions>,98)>,99>,100ui_stack: Extract<Res<UiStack>>,101camera_map: Extract<UiCameraMap>,102) {103let mut camera_mapper = camera_map.get_mapper();104105for (entity, uinode, transform, visibility, maybe_clip, computed_target, debug) in &uinode_query106{107let debug_options = debug.unwrap_or(&debug_options);108if !debug_options.enabled {109continue;110}111if !debug_options.show_hidden && !visibility.get() {112continue;113}114115let Some(extracted_camera_entity) = camera_mapper.map(computed_target) else {116continue;117};118119let color = debug_options120.line_color_override121.unwrap_or_else(|| Hsla::sequential_dispersed(entity.index_u32()).into());122let z_order = (ui_stack.uinodes.len() as u32 + uinode.stack_index()) as f32;123let border = BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor());124let transform = transform.affine();125126let mut push_outline = |rect: Rect, radius: ResolvedBorderRadius| {127if rect.is_empty() {128return;129}130131extracted_uinodes.uinodes.push(ExtractedUiNode {132render_entity: commands.spawn(TemporaryRenderEntity).id(),133// Keep all overlays above UI, and nudge each type slightly in Z so ordering is stable.134z_order,135clip: maybe_clip136.filter(|_| !debug_options.show_clipped)137.map(|clip| clip.clip),138image: AssetId::default(),139extracted_camera_entity,140transform: transform * Affine2::from_translation(rect.center()),141item: ExtractedUiItem::Node {142color,143rect: Rect {144min: Vec2::ZERO,145max: rect.size(),146},147atlas_scaling: None,148flip_x: false,149flip_y: false,150border,151border_radius: radius,152node_type: NodeType::Border(shader_flags::BORDER_ALL),153},154main_entity: entity.into(),155});156};157158let border_box = Rect::from_center_size(Vec2::ZERO, uinode.size);159160if debug_options.outline_border_box {161push_outline(border_box, uinode.border_radius());162}163164if debug_options.outline_padding_box {165let mut padding_box = border_box;166padding_box.min += uinode.border.min_inset;167padding_box.max -= uinode.border.max_inset;168push_outline(padding_box, uinode.inner_radius());169}170171if debug_options.outline_content_box {172let mut content_box = border_box;173let content_inset = uinode.content_inset();174content_box.min += content_inset.min_inset;175content_box.max -= content_inset.max_inset;176push_outline(content_box, ResolvedBorderRadius::ZERO);177}178179if debug_options.outline_scrollbars {180if let Some((gutter, [thumb_min, thumb_max])) = uinode.horizontal_scrollbar() {181push_outline(gutter, ResolvedBorderRadius::ZERO);182push_outline(183Rect {184min: Vec2::new(thumb_min, gutter.min.y),185max: Vec2::new(thumb_max, gutter.max.y),186},187ResolvedBorderRadius::ZERO,188);189}190if let Some((gutter, [thumb_min, thumb_max])) = uinode.vertical_scrollbar() {191push_outline(gutter, ResolvedBorderRadius::ZERO);192push_outline(193Rect {194min: Vec2::new(gutter.min.x, thumb_min),195max: Vec2::new(gutter.max.x, thumb_max),196},197ResolvedBorderRadius::ZERO,198);199}200}201}202}203204205