mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
Add a gizmo-based overlay to show UI node outlines (Adopted) (#11237)
# Objective - This is an adopted version of #10420 - The objective is to help debugging the Ui layout tree with helpful outlines, that can be easily enabled/disabled ## Solution - Like #10420, the solution is using the bevy_gizmos in outlining the nodes --- ## Changelog ### Added - Added debug_overlay mod to `bevy_dev_tools` - Added bevy_ui_debug feature to `bevy_dev_tools` ## How to use - The user must use `bevy_dev_tools` feature in TOML - The user must use the plugin UiDebugPlugin, that can be found on `bevy::dev_tools::debug_overlay` - Finally, to enable the function, the user must set `UiDebugOptions::enabled` to true Someone can easily toggle the function with something like: ```rust fn toggle_overlay(input: Res<ButtonInput<KeyCode>>, options: ResMut<UiDebugOptions>) { if input.just_pressed(KeyCode::Space) { // The toggle method will enable if disabled and disable if enabled options.toggle(); } } ``` Note that this feature can be disabled from dev_tools, as its in fact behind a default feature there, being the feature bevy_ui_debug. # Limitations Currently, due to limitations with gizmos itself, it's not possible to support this feature to more the one window, so this tool is limited to the primary window only. # Showcase ![image](https://github.com/bevyengine/bevy/assets/126117294/ce9d70e6-0a57-4fa9-9753-ff5a9d82c009) Ui example with debug_overlay enabled ![image](https://github.com/bevyengine/bevy/assets/126117294/e945015c-5bab-4d7f-9273-472aabaf25a9) And disabled --------- Co-authored-by: Nicola Papale <nico@nicopap.ch> Co-authored-by: Pablo Reinhardt <pabloreinhardt@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
289a02cad6
commit
1af9bc853b
5 changed files with 543 additions and 11 deletions
|
@ -9,22 +9,31 @@ 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.14.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.14.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", version = "0.14.0-dev" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.14.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.14.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.14.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.14.0-dev" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.14.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.14.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.14.0-dev" }
|
||||
|
||||
# other
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
|
192
crates/bevy_dev_tools/src/debug_overlay/inset.rs
Normal file
192
crates/bevy_dev_tools/src/debug_overlay/inset.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
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::new(),
|
||||
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 Some(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),
|
||||
];
|
||||
self.draw
|
||||
.linestrip_2d(strip.map(|v| self.relative(v)), color);
|
||||
}
|
||||
}
|
||||
}
|
280
crates/bevy_dev_tools/src/debug_overlay/mod.rs
Normal file
280
crates/bevy_dev_tools/src/debug_overlay/mod.rs
Normal file
|
@ -0,0 +1,280 @@
|
|||
//! A visual representation of UI node sizes.
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_color::Hsla;
|
||||
use bevy_core::Name;
|
||||
use bevy_core_pipeline::core_2d::Camera2dBundle;
|
||||
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::{DefaultUiCamera, Display, Node, Style, 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::none().with(16);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct LayoutRect {
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
}
|
||||
|
||||
impl LayoutRect {
|
||||
fn new(trans: &GlobalTransform, node: &Node, 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((
|
||||
Camera2dBundle {
|
||||
projection: OrthographicProjection {
|
||||
far: 1000.0,
|
||||
viewport_origin: Vec2::new(0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
camera: Camera {
|
||||
order: LAYOUT_DEBUG_CAMERA_ORDER,
|
||||
clear_color: ClearColorConfig::None,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
LAYOUT_DEBUG_LAYERS,
|
||||
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;
|
||||
}
|
||||
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, style, children) in outline.nodes.iter_many(to_iter) {
|
||||
if style.is_none() || style.is_some_and(|s| matches!(s.display, Display::None)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(view_visibility) = outline.view_visibility.get(entity) {
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let rect = LayoutRect::new(trans, 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,
|
||||
Option<&'static Style>,
|
||||
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,
|
||||
&Node,
|
||||
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 = window_scale * 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(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,9 @@ use bevy_app::prelude::*;
|
|||
pub mod ci_testing;
|
||||
pub mod fps_overlay;
|
||||
|
||||
#[cfg(feature = "bevy_ui_debug")]
|
||||
pub mod debug_overlay;
|
||||
|
||||
/// Enables developer tools in an [`App`]. This plugin is added automatically with `bevy_dev_tools`
|
||||
/// feature.
|
||||
///
|
||||
|
|
|
@ -12,18 +12,25 @@ use bevy::{
|
|||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
let mut app = App::new();
|
||||
app.add_plugins(DefaultPlugins)
|
||||
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
|
||||
.insert_resource(WinitSettings::desktop_app())
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, mouse_scroll)
|
||||
.run();
|
||||
.add_systems(Update, mouse_scroll);
|
||||
|
||||
#[cfg(feature = "bevy_dev_tools")]
|
||||
{
|
||||
app.add_plugins(bevy::dev_tools::debug_overlay::DebugUiPlugin)
|
||||
.add_systems(Update, toggle_overlay);
|
||||
}
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
// Camera
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
commands.spawn((Camera2dBundle::default(), IsDefaultUiCamera));
|
||||
|
||||
// root node
|
||||
commands
|
||||
|
@ -54,6 +61,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.),
|
||||
flex_direction: FlexDirection::Column,
|
||||
..default()
|
||||
},
|
||||
background_color: Color::srgb(0.15, 0.15, 0.15).into(),
|
||||
|
@ -79,6 +87,33 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
// for accessibility to treat the text accordingly.
|
||||
Label,
|
||||
));
|
||||
|
||||
#[cfg(feature = "bevy_dev_tools")]
|
||||
// Debug overlay text
|
||||
parent.spawn((
|
||||
TextBundle::from_section(
|
||||
"Press Space to enable debug outlines.",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: 20.,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Label,
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "bevy_dev_tools"))]
|
||||
parent.spawn((
|
||||
TextBundle::from_section(
|
||||
"Try enabling feature \"bevy_dev_tools\".",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: 20.,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Label,
|
||||
));
|
||||
});
|
||||
});
|
||||
// right vertical fill
|
||||
|
@ -334,3 +369,16 @@ fn mouse_scroll(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_dev_tools")]
|
||||
// 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::debug_overlay::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
|
||||
options.toggle();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue