bevy_ui_debug_overlay/
lib.rs1use bevy_app::Plugin;
2use bevy_asset::AssetId;
3use bevy_color::Hsla;
4use bevy_ecs::entity::Entity;
5#[cfg(feature = "bevy_reflect")]
6use bevy_ecs::reflect::ReflectResource;
7use bevy_ecs::system::Commands;
8use bevy_ecs::system::Query;
9use bevy_ecs::system::Res;
10use bevy_ecs::system::ResMut;
11use bevy_ecs::system::Resource;
12use bevy_math::Rect;
13use bevy_math::Vec2;
14use bevy_render::sync_world::RenderEntity;
15use bevy_render::sync_world::TemporaryRenderEntity;
16use bevy_render::view::ViewVisibility;
17use bevy_render::Extract;
18use bevy_render::ExtractSchedule;
19use bevy_render::RenderApp;
20use bevy_sprite::BorderRect;
21use bevy_transform::components::GlobalTransform;
22use bevy_ui::CalculatedClip;
23use bevy_ui::ComputedNode;
24use bevy_ui::DefaultUiCamera;
25
26use bevy_ui::ExtractedUiItem;
27use bevy_ui::ExtractedUiNode;
28use bevy_ui::ExtractedUiNodes;
29use bevy_ui::NodeType;
30use bevy_ui::TargetCamera;
31
32#[derive(Resource)]
34#[cfg_attr(
35 feature = "bevy_reflect",
36 derive(bevy_reflect::Reflect),
37 reflect(Resource)
38)]
39pub struct UiDebugOverlay {
40 pub enabled: bool,
42 pub line_width: f32,
44 pub show_hidden: bool,
46 pub show_clipped: bool,
48}
49
50impl UiDebugOverlay {
51 pub fn toggle(&mut self) {
52 self.enabled = !self.enabled;
53 }
54}
55
56impl Default for UiDebugOverlay {
57 fn default() -> Self {
58 Self {
59 enabled: false,
60 line_width: 1.,
61 show_hidden: false,
62 show_clipped: false,
63 }
64 }
65}
66
67#[allow(clippy::too_many_arguments, clippy::type_complexity)]
68pub fn extract_debug_overlay(
69 mut commands: Commands,
70 debug_overlay: Extract<Res<UiDebugOverlay>>,
71 mut extracted_uinodes: ResMut<ExtractedUiNodes>,
72 default_ui_camera: Extract<DefaultUiCamera>,
73 uinode_query: Extract<
74 Query<(
75 Entity,
76 &ComputedNode,
77 &ViewVisibility,
78 Option<&CalculatedClip>,
79 &GlobalTransform,
80 Option<&TargetCamera>,
81 )>,
82 >,
83 mapping: Extract<Query<RenderEntity>>,
84) {
85 if !debug_overlay.enabled {
86 return;
87 }
88
89 for (entity, uinode, visibility, maybe_clip, transform, camera) in &uinode_query {
90 if !debug_overlay.show_hidden && !visibility.get() {
91 continue;
92 }
93
94 let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
95 else {
96 continue;
97 };
98
99 let Ok(render_camera_entity) = mapping.get(camera_entity) else {
100 continue;
101 };
102
103 extracted_uinodes.uinodes.insert(
105 commands.spawn(TemporaryRenderEntity).id(),
106 ExtractedUiNode {
107 stack_index: uinode.stack_index() + u32::MAX / 2,
109 color: Hsla::sequential_dispersed(entity.index()).into(),
110 rect: Rect {
111 min: Vec2::ZERO,
112 max: uinode.size(),
113 },
114 clip: maybe_clip
115 .filter(|_| !debug_overlay.show_clipped)
116 .map(|clip| clip.clip),
117 image: AssetId::default(),
118 camera_entity: render_camera_entity,
119 item: ExtractedUiItem::Node {
120 atlas_scaling: None,
121 transform: transform.compute_matrix(),
122 flip_x: false,
123 flip_y: false,
124 border: BorderRect::square(
125 debug_overlay.line_width / uinode.inverse_scale_factor(),
126 ),
127 border_radius: uinode.border_radius(),
128 node_type: NodeType::Border,
129 },
130 main_entity: entity.into(),
131 },
132 );
133 }
134}
135
136#[derive(Default)]
137pub struct UiDebugOverlayPlugin(pub UiDebugOverlay);
138
139impl UiDebugOverlayPlugin {
140 pub fn start_disabled() -> Self {
142 Default::default()
143 }
144
145 pub fn start_enabled() -> Self {
147 Self(UiDebugOverlay {
148 enabled: true,
149 ..Default::default()
150 })
151 }
152
153 pub fn with_line_width(mut self, line_width: f32) -> Self {
155 self.0.line_width = line_width;
156 self
157 }
158}
159
160impl Plugin for UiDebugOverlayPlugin {
161 fn build(&self, app: &mut bevy_app::App) {
162 #[cfg(feature = "bevy_reflect")]
163 app.register_type::<UiDebugOverlay>();
164 app.insert_resource(UiDebugOverlay { ..self.0 });
165
166 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
167 render_app.add_systems(ExtractSchedule, extract_debug_overlay);
168 }
169 }
170}