bevy_ui_render/
debug_overlay.rs

1use super::ExtractedUiItem;
2use super::ExtractedUiNode;
3use super::ExtractedUiNodes;
4use super::NodeType;
5use super::UiCameraMap;
6use crate::shader_flags;
7use bevy_asset::AssetId;
8use bevy_camera::visibility::InheritedVisibility;
9use bevy_color::Hsla;
10use bevy_color::LinearRgba;
11use bevy_ecs::entity::Entity;
12use bevy_ecs::prelude::ReflectResource;
13use bevy_ecs::resource::Resource;
14use bevy_ecs::system::Commands;
15use bevy_ecs::system::Query;
16use bevy_ecs::system::Res;
17use bevy_ecs::system::ResMut;
18use bevy_math::Rect;
19use bevy_math::Vec2;
20use bevy_reflect::Reflect;
21use bevy_render::sync_world::TemporaryRenderEntity;
22use bevy_render::Extract;
23use bevy_sprite::BorderRect;
24use bevy_ui::ui_transform::UiGlobalTransform;
25use bevy_ui::CalculatedClip;
26use bevy_ui::ComputedNode;
27use bevy_ui::ComputedUiTargetCamera;
28use bevy_ui::UiStack;
29
30/// Configuration for the UI debug overlay
31#[derive(Resource, Reflect)]
32#[reflect(Resource)]
33pub struct UiDebugOptions {
34    /// Set to true to enable the UI debug overlay
35    pub enabled: bool,
36    /// Width of the overlay's lines in logical pixels
37    pub line_width: f32,
38    /// Override Color for the overlay's lines
39    pub line_color_override: Option<LinearRgba>,
40    /// Show outlines for non-visible UI nodes
41    pub show_hidden: bool,
42    /// Show outlines for clipped sections of UI nodes
43    pub show_clipped: bool,
44}
45
46impl UiDebugOptions {
47    pub fn toggle(&mut self) {
48        self.enabled = !self.enabled;
49    }
50}
51
52impl Default for UiDebugOptions {
53    fn default() -> Self {
54        Self {
55            enabled: false,
56            line_width: 1.,
57            line_color_override: None,
58            show_hidden: false,
59            show_clipped: false,
60        }
61    }
62}
63
64pub fn extract_debug_overlay(
65    mut commands: Commands,
66    debug_options: Extract<Res<UiDebugOptions>>,
67    mut extracted_uinodes: ResMut<ExtractedUiNodes>,
68    uinode_query: Extract<
69        Query<(
70            Entity,
71            &ComputedNode,
72            &UiGlobalTransform,
73            &InheritedVisibility,
74            Option<&CalculatedClip>,
75            &ComputedUiTargetCamera,
76        )>,
77    >,
78    ui_stack: Extract<Res<UiStack>>,
79    camera_map: Extract<UiCameraMap>,
80) {
81    if !debug_options.enabled {
82        return;
83    }
84
85    let mut camera_mapper = camera_map.get_mapper();
86
87    for (entity, uinode, transform, visibility, maybe_clip, computed_target) in &uinode_query {
88        if !debug_options.show_hidden && !visibility.get() {
89            continue;
90        }
91
92        let Some(extracted_camera_entity) = camera_mapper.map(computed_target) else {
93            continue;
94        };
95
96        // Extract a border box to display an outline for every UI Node in the layout
97        extracted_uinodes.uinodes.push(ExtractedUiNode {
98            render_entity: commands.spawn(TemporaryRenderEntity).id(),
99            // Add a large number to the UI node's stack index so that the overlay is always drawn on top
100            z_order: (ui_stack.uinodes.len() as u32 + uinode.stack_index()) as f32,
101            clip: maybe_clip
102                .filter(|_| !debug_options.show_clipped)
103                .map(|clip| clip.clip),
104            image: AssetId::default(),
105            extracted_camera_entity,
106            transform: transform.into(),
107            item: ExtractedUiItem::Node {
108                color: debug_options
109                    .line_color_override
110                    .unwrap_or_else(|| Hsla::sequential_dispersed(entity.index_u32()).into()),
111                rect: Rect {
112                    min: Vec2::ZERO,
113                    max: uinode.size,
114                },
115                atlas_scaling: None,
116                flip_x: false,
117                flip_y: false,
118                border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
119                border_radius: uinode.border_radius(),
120                node_type: NodeType::Border(shader_flags::BORDER_ALL),
121            },
122            main_entity: entity.into(),
123        });
124    }
125}