Draw the UI debug overlay using the UI renderer (#16693)

# Objective

Draw the UI debug overlay using the UI renderer.

Significantly simpler and easier to use than
`bevy_dev_tools::ui_debug_overlay` which uses `bevy_gizmos`.
* Supports multiple windows and UI rendered to texture.
* Draws rounded debug rects for rounded UI nodes. 

Fixes #16666

## Solution

Removed the `ui_debug_overlay` module from `bevy_dev_tools`.

Added a `bevy_ui_debug` feature gate.

Draw the UI debug overlay using the UI renderer.
Adds a new module `bevy_ui::render::debug_overlay`. 

The debug overlay extraction function queries for the existing UI layout
and then adds a border around each UI node with `u32::MAX / 2` added to
each stack index so it's drawn on top.

There is a `UiDebugOptions` resource that can be used to enable or
disable the debug overlay and set the line width.

## Testing

The `testbed_ui` example has been changed to use the new debug overlay:

```
cargo run --example testbed_ui --features bevy_ui_debug
```

Press Space to toggle the debug overlay on and off.

---

## Showcase

<img width="961" alt="testbed-ui-new-debug"
src="https://github.com/user-attachments/assets/e9523d18-39ae-46a8-adbe-7d3f3ab8e951">

## Migration Guide

The `ui_debug_overlay` module has been removed from `bevy_dev_tools`.
There is a new debug overlay implemented using the `bevy_ui` renderer.
To use it, enable the `bevy_ui_debug` feature and set the `enable` field
of the `UiDebugOptions` resource to `true`.
This commit is contained in:
ickshonpe 2024-12-11 00:49:47 +00:00 committed by GitHub
parent c4a24d5b51
commit 9098973fb9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 142 additions and 497 deletions

View file

@ -156,6 +156,9 @@ bevy_ui_picking_backend = [
"bevy_internal/bevy_ui_picking_backend",
]
# Provides a debug overlay for bevy UI
bevy_ui_debug = ["bevy_internal/bevy_ui_debug"]
# Force dynamic linking, which improves iterative compile times
dynamic_linking = ["dep:bevy_dylib", "bevy_internal/dynamic_linking"]

View file

@ -9,29 +9,19 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[features]
default = ["bevy_ui_debug"]
bevy_ci_testing = ["serde", "ron"]
bevy_ui_debug = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.15.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.15.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.15.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", version = "0.15.0-dev", features = [
"bevy_render",
] }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.15.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.15.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.15.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.15.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.15.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.15.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" }

View file

@ -15,9 +15,6 @@ pub mod ci_testing;
pub mod fps_overlay;
#[cfg(feature = "bevy_ui_debug")]
pub mod ui_debug_overlay;
pub mod states;
/// Enables developer tools in an [`App`]. This plugin is added automatically with `bevy_dev_tools`

View file

@ -1,192 +0,0 @@
use bevy_color::Color;
use bevy_gizmos::{config::GizmoConfigGroup, prelude::Gizmos};
use bevy_math::{Vec2, Vec2Swizzles};
use bevy_reflect::Reflect;
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::HashMap;
use super::{CameraQuery, LayoutRect};
// Function used here so we don't need to redraw lines that are fairly close to each other.
fn approx_eq(compared: f32, other: f32) -> bool {
(compared - other).abs() < 0.001
}
fn rect_border_axis(rect: LayoutRect) -> (f32, f32, f32, f32) {
let pos = rect.pos;
let size = rect.size;
let offset = pos + size;
(pos.x, offset.x, pos.y, offset.y)
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
enum Dir {
Start,
End,
}
impl Dir {
const fn increments(self) -> i64 {
match self {
Dir::Start => 1,
Dir::End => -1,
}
}
}
impl From<i64> for Dir {
fn from(value: i64) -> Self {
if value.is_positive() {
Dir::Start
} else {
Dir::End
}
}
}
/// Collection of axis aligned "lines" (actually just their coordinate on
/// a given axis).
#[derive(Debug, Clone)]
struct DrawnLines {
lines: HashMap<i64, Dir>,
width: f32,
}
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
impl DrawnLines {
fn new(width: f32) -> Self {
DrawnLines {
lines: HashMap::default(),
width,
}
}
/// Return `value` offset by as many `increment`s as necessary to make it
/// not overlap with already drawn lines.
fn inset(&self, value: f32) -> f32 {
let scaled = value / self.width;
let fract = scaled.fract();
let mut on_grid = scaled.floor() as i64;
for _ in 0..10 {
let Some(dir) = self.lines.get(&on_grid) else {
break;
};
// TODO(clean): This fixes a panic, but I'm not sure how valid this is
let Some(added) = on_grid.checked_add(dir.increments()) else {
break;
};
on_grid = added;
}
((on_grid as f32) + fract) * self.width
}
/// Remove a line from the collection of drawn lines.
///
/// Typically, we only care for pre-existing lines when drawing the children
/// of a container, nothing more. So we remove it after we are done with
/// the children.
fn remove(&mut self, value: f32, increment: i64) {
let mut on_grid = (value / self.width).floor() as i64;
loop {
// TODO(clean): This fixes a panic, but I'm not sure how valid this is
let Some(next_cell) = on_grid.checked_add(increment) else {
return;
};
if !self.lines.contains_key(&next_cell) {
self.lines.remove(&on_grid);
return;
}
on_grid = next_cell;
}
}
/// Add a line from the collection of drawn lines.
fn add(&mut self, value: f32, increment: i64) {
let mut on_grid = (value / self.width).floor() as i64;
loop {
let old_value = self.lines.insert(on_grid, increment.into());
if old_value.is_none() {
return;
}
// TODO(clean): This fixes a panic, but I'm not sure how valid this is
let Some(added) = on_grid.checked_add(increment) else {
return;
};
on_grid = added;
}
}
}
#[derive(GizmoConfigGroup, Reflect, Default)]
pub struct UiGizmosDebug;
pub(super) struct InsetGizmo<'w, 's> {
draw: Gizmos<'w, 's, UiGizmosDebug>,
cam: CameraQuery<'w, 's>,
known_y: DrawnLines,
known_x: DrawnLines,
}
impl<'w, 's> InsetGizmo<'w, 's> {
pub(super) fn new(
draw: Gizmos<'w, 's, UiGizmosDebug>,
cam: CameraQuery<'w, 's>,
line_width: f32,
) -> Self {
InsetGizmo {
draw,
cam,
known_y: DrawnLines::new(line_width),
known_x: DrawnLines::new(line_width),
}
}
fn relative(&self, mut position: Vec2) -> Vec2 {
let zero = GlobalTransform::IDENTITY;
let Ok(cam) = self.cam.get_single() else {
return Vec2::ZERO;
};
if let Ok(new_position) = cam.world_to_viewport(&zero, position.extend(0.)) {
position = new_position;
};
position.xy()
}
fn line_2d(&mut self, mut start: Vec2, mut end: Vec2, color: Color) {
if approx_eq(start.x, end.x) {
start.x = self.known_x.inset(start.x);
end.x = start.x;
} else if approx_eq(start.y, end.y) {
start.y = self.known_y.inset(start.y);
end.y = start.y;
}
let (start, end) = (self.relative(start), self.relative(end));
self.draw.line_2d(start, end, color);
}
pub(super) fn set_scope(&mut self, rect: LayoutRect) {
let (left, right, top, bottom) = rect_border_axis(rect);
self.known_x.add(left, 1);
self.known_x.add(right, -1);
self.known_y.add(top, 1);
self.known_y.add(bottom, -1);
}
pub(super) fn clear_scope(&mut self, rect: LayoutRect) {
let (left, right, top, bottom) = rect_border_axis(rect);
self.known_x.remove(left, 1);
self.known_x.remove(right, -1);
self.known_y.remove(top, 1);
self.known_y.remove(bottom, -1);
}
pub(super) fn rect_2d(&mut self, rect: LayoutRect, color: Color) {
let (left, right, top, bottom) = rect_border_axis(rect);
if approx_eq(left, right) {
self.line_2d(Vec2::new(left, top), Vec2::new(left, bottom), color);
} else if approx_eq(top, bottom) {
self.line_2d(Vec2::new(left, top), Vec2::new(right, top), color);
} else {
let inset_x = |v| self.known_x.inset(v);
let inset_y = |v| self.known_y.inset(v);
let (left, right) = (inset_x(left), inset_x(right));
let (top, bottom) = (inset_y(top), inset_y(bottom));
let strip = [
Vec2::new(left, top),
Vec2::new(left, bottom),
Vec2::new(right, bottom),
Vec2::new(right, top),
Vec2::new(left, top),
]
.map(|v| self.relative(v));
self.draw.linestrip_2d(strip, color);
}
}
}

View file

@ -1,278 +0,0 @@
//! A visual representation of UI node sizes.
use core::any::{Any, TypeId};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_color::Hsla;
use bevy_core::Name;
use bevy_core_pipeline::core_2d::Camera2d;
use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_gizmos::{config::GizmoConfigStore, prelude::Gizmos, AppGizmoBuilder};
use bevy_hierarchy::{Children, Parent};
use bevy_math::{Vec2, Vec3Swizzles};
use bevy_render::{
camera::RenderTarget,
prelude::*,
view::{RenderLayers, VisibilitySystems},
};
use bevy_transform::{prelude::GlobalTransform, TransformSystem};
use bevy_ui::{ComputedNode, DefaultUiCamera, Display, Node, TargetCamera, UiScale};
use bevy_utils::{default, warn_once};
use bevy_window::{PrimaryWindow, Window, WindowRef};
use inset::InsetGizmo;
use self::inset::UiGizmosDebug;
mod inset;
/// The [`Camera::order`] index used by the layout debug camera.
pub const LAYOUT_DEBUG_CAMERA_ORDER: isize = 255;
/// The [`RenderLayers`] used by the debug gizmos and the debug camera.
pub const LAYOUT_DEBUG_LAYERS: RenderLayers = RenderLayers::layer(16);
#[derive(Clone, Copy)]
struct LayoutRect {
pos: Vec2,
size: Vec2,
}
impl LayoutRect {
fn new(trans: &GlobalTransform, node: &ComputedNode, scale: f32) -> Self {
let mut this = Self {
pos: trans.translation().xy() * scale,
size: node.size() * scale,
};
this.pos -= this.size / 2.;
this
}
}
#[derive(Component, Debug, Clone, Default)]
struct DebugOverlayCamera;
/// The debug overlay options.
#[derive(Resource, Clone, Default)]
pub struct UiDebugOptions {
/// Whether the overlay is enabled.
pub enabled: bool,
layout_gizmos_camera: Option<Entity>,
}
impl UiDebugOptions {
/// This will toggle the enabled field, setting it to false if true and true if false.
pub fn toggle(&mut self) {
self.enabled = !self.enabled;
}
}
/// The system responsible to change the [`Camera`] config based on changes in [`UiDebugOptions`] and [`GizmoConfig`](bevy_gizmos::prelude::GizmoConfig).
fn update_debug_camera(
mut gizmo_config: ResMut<GizmoConfigStore>,
mut options: ResMut<UiDebugOptions>,
mut cmds: Commands,
mut debug_cams: Query<&mut Camera, With<DebugOverlayCamera>>,
) {
if !options.is_changed() && !gizmo_config.is_changed() {
return;
}
if !options.enabled {
let Some(cam) = options.layout_gizmos_camera else {
return;
};
let Ok(mut cam) = debug_cams.get_mut(cam) else {
return;
};
cam.is_active = false;
if let Some((config, _)) = gizmo_config.get_config_mut_dyn(&TypeId::of::<UiGizmosDebug>()) {
config.enabled = false;
}
} else {
let spawn_cam = || {
cmds.spawn((
Camera2d,
OrthographicProjection {
far: 1000.0,
viewport_origin: Vec2::new(0.0, 0.0),
..OrthographicProjection::default_3d()
},
Camera {
order: LAYOUT_DEBUG_CAMERA_ORDER,
clear_color: ClearColorConfig::None,
..default()
},
LAYOUT_DEBUG_LAYERS.clone(),
DebugOverlayCamera,
Name::new("Layout Debug Camera"),
))
.id()
};
if let Some((config, _)) = gizmo_config.get_config_mut_dyn(&TypeId::of::<UiGizmosDebug>()) {
config.enabled = true;
config.render_layers = LAYOUT_DEBUG_LAYERS.clone();
}
let cam = *options.layout_gizmos_camera.get_or_insert_with(spawn_cam);
let Ok(mut cam) = debug_cams.get_mut(cam) else {
return;
};
cam.is_active = true;
}
}
/// The function that goes over every children of given [`Entity`], skipping the not visible ones and drawing the gizmos outlines.
fn outline_nodes(outline: &OutlineParam, draw: &mut InsetGizmo, this_entity: Entity, scale: f32) {
let Ok(to_iter) = outline.children.get(this_entity) else {
return;
};
for (entity, trans, node, computed_node, children) in outline.nodes.iter_many(to_iter) {
if matches!(node.display, Display::None) {
continue;
}
if let Ok(view_visibility) = outline.view_visibility.get(entity) {
if !view_visibility.get() {
continue;
}
}
let rect = LayoutRect::new(trans, computed_node, scale);
outline_node(entity, rect, draw);
if children.is_some() {
outline_nodes(outline, draw, entity, scale);
}
draw.clear_scope(rect);
}
}
type NodesQuery = (
Entity,
&'static GlobalTransform,
&'static Node,
&'static ComputedNode,
Option<&'static Children>,
);
#[derive(SystemParam)]
struct OutlineParam<'w, 's> {
gizmo_config: Res<'w, GizmoConfigStore>,
children: Query<'w, 's, &'static Children>,
nodes: Query<'w, 's, NodesQuery>,
view_visibility: Query<'w, 's, &'static ViewVisibility>,
ui_scale: Res<'w, UiScale>,
}
type CameraQuery<'w, 's> = Query<'w, 's, &'static Camera, With<DebugOverlayCamera>>;
#[derive(SystemParam)]
struct CameraParam<'w, 's> {
debug_camera: Query<'w, 's, &'static Camera, With<DebugOverlayCamera>>,
cameras: Query<'w, 's, &'static Camera, Without<DebugOverlayCamera>>,
primary_window: Query<'w, 's, &'static Window, With<PrimaryWindow>>,
default_ui_camera: DefaultUiCamera<'w, 's>,
}
/// system responsible for drawing the gizmos lines around all the node roots, iterating recursively through all visible children.
fn outline_roots(
outline: OutlineParam,
draw: Gizmos<UiGizmosDebug>,
cam: CameraParam,
roots: Query<
(
Entity,
&GlobalTransform,
&ComputedNode,
Option<&ViewVisibility>,
Option<&TargetCamera>,
),
Without<Parent>,
>,
window: Query<&Window, With<PrimaryWindow>>,
nonprimary_windows: Query<&Window, Without<PrimaryWindow>>,
options: Res<UiDebugOptions>,
) {
if !options.enabled {
return;
}
if !nonprimary_windows.is_empty() {
warn_once!(
"The layout debug view only uses the primary window scale, \
you might notice gaps between container lines"
);
}
let window_scale = window.get_single().map_or(1., Window::scale_factor);
let scale_factor = outline.ui_scale.0;
// We let the line be defined by the window scale alone
let line_width = outline
.gizmo_config
.get_config_dyn(&UiGizmosDebug.type_id())
.map_or(2., |(config, _)| config.line.width)
/ window_scale;
let mut draw = InsetGizmo::new(draw, cam.debug_camera, line_width);
for (entity, trans, node, view_visibility, maybe_target_camera) in &roots {
if let Some(view_visibility) = view_visibility {
// If the entity isn't visible, we will not draw any lines.
if !view_visibility.get() {
continue;
}
}
// We skip ui in other windows that are not the primary one
if let Some(camera_entity) = maybe_target_camera
.map(|target| target.0)
.or(cam.default_ui_camera.get())
{
let Ok(camera) = cam.cameras.get(camera_entity) else {
// The camera wasn't found. Either the Camera don't exist or the Camera is the debug Camera, that we want to skip and warn
warn_once!("Camera {:?} wasn't found for debug overlay", camera_entity);
continue;
};
match camera.target {
RenderTarget::Window(window_ref) => {
if let WindowRef::Entity(window_entity) = window_ref {
if cam.primary_window.get(window_entity).is_err() {
// This window isn't the primary, so we skip this root.
continue;
}
}
}
// Hard to know the results of this, better skip this target.
_ => continue,
}
}
let rect = LayoutRect::new(trans, node, scale_factor);
outline_node(entity, rect, &mut draw);
outline_nodes(&outline, &mut draw, entity, scale_factor);
}
}
/// Function responsible for drawing the gizmos lines around the given Entity
fn outline_node(entity: Entity, rect: LayoutRect, draw: &mut InsetGizmo) {
let color = Hsla::sequential_dispersed(entity.index());
draw.rect_2d(rect, color.into());
draw.set_scope(rect);
}
/// The debug overlay plugin.
///
/// This spawns a new camera with a low order, and draws gizmo.
///
/// Note that due to limitation with [`bevy_gizmos`], multiple windows with this feature
/// enabled isn't supported and the lines are only drawn in the [`PrimaryWindow`]
pub struct DebugUiPlugin;
impl Plugin for DebugUiPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<UiDebugOptions>()
.init_gizmo_group::<UiGizmosDebug>()
.add_systems(
PostUpdate,
(
update_debug_camera,
outline_roots
.after(TransformSystem::TransformPropagate)
// This needs to run before VisibilityPropagate so it can relies on ViewVisibility
.before(VisibilitySystems::VisibilityPropagate),
)
.chain(),
);
}
}

View file

@ -235,6 +235,9 @@ bevy_sprite_picking_backend = [
# Provides a UI picking backend
bevy_ui_picking_backend = ["bevy_picking", "bevy_ui/bevy_ui_picking_backend"]
# Provides a UI debug overlay
bevy_ui_debug = ["bevy_ui?/bevy_ui_debug"]
# Enable support for the ios_simulator by downgrading some rendering capabilities
ios_simulator = ["bevy_pbr?/ios_simulator", "bevy_render?/ios_simulator"]

View file

@ -43,8 +43,10 @@ smallvec = "1.11"
accesskit = "0.17"
[features]
default = []
serialize = ["serde", "smallvec/serde", "bevy_math/serialize"]
bevy_ui_picking_backend = ["bevy_picking"]
bevy_ui_debug = []
# Experimental features
ghost_nodes = []

View file

@ -40,12 +40,16 @@ pub use measurement::*;
pub use render::*;
pub use ui_material::*;
pub use ui_node::*;
use widget::{ImageNode, ImageNodeSize};
/// The UI prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
#[cfg(feature = "bevy_ui_debug")]
pub use crate::render::UiDebugOptions;
#[allow(deprecated)]
#[doc(hidden)]
pub use crate::widget::TextBundle;
@ -230,6 +234,9 @@ impl Plugin for UiPlugin {
return;
}
#[cfg(feature = "bevy_ui_debug")]
app.init_resource::<UiDebugOptions>();
build_ui_render(app);
}

View file

@ -0,0 +1,109 @@
use bevy_asset::AssetId;
use bevy_color::Hsla;
use bevy_ecs::entity::Entity;
use bevy_ecs::system::Commands;
use bevy_ecs::system::Query;
use bevy_ecs::system::Res;
use bevy_ecs::system::ResMut;
use bevy_ecs::system::Resource;
use bevy_math::Rect;
use bevy_math::Vec2;
use bevy_render::sync_world::RenderEntity;
use bevy_render::sync_world::TemporaryRenderEntity;
use bevy_render::Extract;
use bevy_sprite::BorderRect;
use bevy_transform::components::GlobalTransform;
use crate::ComputedNode;
use crate::DefaultUiCamera;
use crate::TargetCamera;
use super::ExtractedUiItem;
use super::ExtractedUiNode;
use super::ExtractedUiNodes;
use super::NodeType;
/// Configuration for the UI debug overlay
#[derive(Resource)]
pub struct UiDebugOptions {
/// Set to true to enable the UI debug overlay
pub enabled: bool,
/// Width of the overlay's lines in logical pixels
pub line_width: f32,
}
impl UiDebugOptions {
pub fn toggle(&mut self) {
self.enabled = !self.enabled;
}
}
impl Default for UiDebugOptions {
fn default() -> Self {
Self {
enabled: false,
line_width: 1.,
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn extract_debug_overlay(
mut commands: Commands,
debug_options: Extract<Res<UiDebugOptions>>,
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
default_ui_camera: Extract<DefaultUiCamera>,
uinode_query: Extract<
Query<(
Entity,
&ComputedNode,
&GlobalTransform,
Option<&TargetCamera>,
)>,
>,
mapping: Extract<Query<RenderEntity>>,
) {
if !debug_options.enabled {
return;
}
for (entity, uinode, transform, camera) in &uinode_query {
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
else {
continue;
};
let Ok(render_camera_entity) = mapping.get(camera_entity) else {
continue;
};
// Extract a border box to display an outline for every UI Node in the layout
extracted_uinodes.uinodes.insert(
commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiNode {
// Add a large number to the UI node's stack index so that the overlay is always drawn on top
stack_index: uinode.stack_index + u32::MAX / 2,
color: Hsla::sequential_dispersed(entity.index()).into(),
rect: Rect {
min: Vec2::ZERO,
max: uinode.size,
},
clip: None,
image: AssetId::default(),
camera_entity: render_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: BorderRect::square(
debug_options.line_width / uinode.inverse_scale_factor(),
),
border_radius: uinode.border_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
},
);
}
}

View file

@ -4,6 +4,9 @@ mod render_pass;
mod ui_material_pipeline;
pub mod ui_texture_slice_pipeline;
#[cfg(feature = "bevy_ui_debug")]
mod debug_overlay;
use crate::widget::ImageNode;
use crate::{
experimental::UiChildren, BackgroundColor, BorderColor, BoxShadowSamples, CalculatedClip,
@ -41,6 +44,8 @@ use bevy_render::{
};
use bevy_sprite::TextureAtlasLayout;
use bevy_sprite::{BorderRect, SpriteAssetEvents};
#[cfg(feature = "bevy_ui_debug")]
pub use debug_overlay::UiDebugOptions;
use crate::{Display, Node};
use bevy_text::{ComputedTextBlock, PositionedGlyph, TextColor, TextLayoutInfo};
@ -98,6 +103,7 @@ pub enum RenderUiSystem {
ExtractTextureSlice,
ExtractBorders,
ExtractText,
ExtractDebug,
}
pub fn build_ui_render(app: &mut App) {
@ -125,6 +131,7 @@ pub fn build_ui_render(app: &mut App) {
RenderUiSystem::ExtractTextureSlice,
RenderUiSystem::ExtractBorders,
RenderUiSystem::ExtractText,
RenderUiSystem::ExtractDebug,
)
.chain(),
)
@ -136,6 +143,8 @@ pub fn build_ui_render(app: &mut App) {
extract_uinode_images.in_set(RenderUiSystem::ExtractImages),
extract_uinode_borders.in_set(RenderUiSystem::ExtractBorders),
extract_text_sections.in_set(RenderUiSystem::ExtractText),
#[cfg(feature = "bevy_ui_debug")]
debug_overlay::extract_debug_overlay.in_set(RenderUiSystem::ExtractDebug),
),
)
.add_systems(

View file

@ -62,6 +62,7 @@ The default feature set enables most of the expected features of a game engine,
|bevy_debug_stepping|Enable stepping-based debugging of Bevy systems|
|bevy_dev_tools|Provides a collection of developer tools|
|bevy_remote|Enable the Bevy Remote Protocol|
|bevy_ui_debug|Provides a debug overlay for bevy UI|
|bmp|BMP image format support|
|dds|DDS compressed texture support|
|debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam|

View file

@ -18,11 +18,8 @@ fn main() {
.add_systems(Startup, setup)
.add_systems(Update, update_scroll_position);
#[cfg(feature = "bevy_dev_tools")]
{
app.add_plugins(bevy::dev_tools::ui_debug_overlay::DebugUiPlugin)
.add_systems(Update, toggle_overlay);
}
#[cfg(feature = "bevy_ui_debug")]
app.add_systems(Update, toggle_debug_overlay);
app.run();
}
@ -79,10 +76,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Label,
));
#[cfg(feature = "bevy_dev_tools")]
#[cfg(feature = "bevy_ui_debug")]
// Debug overlay text
parent.spawn((
Text::new("Press Space to enable debug outlines."),
Text::new("Press Space to toggle debug outlines."),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default()
@ -90,9 +87,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Label,
));
#[cfg(not(feature = "bevy_dev_tools"))]
#[cfg(not(feature = "bevy_ui_debug"))]
parent.spawn((
Text::new("Try enabling feature \"bevy_dev_tools\"."),
Text::new("Try enabling feature \"bevy_ui_debug\"."),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default()
@ -347,12 +344,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
}
#[cfg(feature = "bevy_dev_tools")]
#[cfg(feature = "bevy_ui_debug")]
// The system that will enable/disable the debug outlines around the nodes
fn toggle_overlay(
input: Res<ButtonInput<KeyCode>>,
mut options: ResMut<bevy::dev_tools::ui_debug_overlay::UiDebugOptions>,
) {
fn toggle_debug_overlay(input: Res<ButtonInput<KeyCode>>, mut options: ResMut<UiDebugOptions>) {
info_once!("The debug outlines are enabled, press Space to turn them on/off");
if input.just_pressed(KeyCode::Space) {
// The toggle method will enable the debug_overlay if disabled and disable if enabled