bevy/crates/bevy_ui/src/render/debug_overlay.rs
ickshonpe a949867a1c
UI z-ordering fix (#19691)
# Objective

During the migration to required components a lot of things were changed
around and somehow the draw order for some UI elements ended up
depending on the system ordering in `RenderSystems::Queue`, which can
sometimes result in the elements being drawn in the wrong order.

Fixes #19674

## Solution

* Added some more `stack_z_offsets` constants and used them to enforce
an explicit ordering.
* Removed the `stack_index: u32` field from `ExtractedUiNodes` and
replaced it with a `z_order: f32` field.

These changes should fix all the ordering problems. 

## Testing

I added a nine-patched bordered node with a navy background color to the
slice section of the `testbed_ui` example.
The border should always be drawn above the background color.
2025-07-01 19:20:07 +00:00

117 lines
3.5 KiB
Rust

use super::ExtractedUiItem;
use super::ExtractedUiNode;
use super::ExtractedUiNodes;
use super::NodeType;
use super::UiCameraMap;
use crate::shader_flags;
use crate::ui_node::ComputedNodeTarget;
use crate::ui_transform::UiGlobalTransform;
use crate::CalculatedClip;
use crate::ComputedNode;
use crate::UiStack;
use bevy_asset::AssetId;
use bevy_color::Hsla;
use bevy_ecs::entity::Entity;
use bevy_ecs::resource::Resource;
use bevy_ecs::system::Commands;
use bevy_ecs::system::Query;
use bevy_ecs::system::Res;
use bevy_ecs::system::ResMut;
use bevy_math::Rect;
use bevy_math::Vec2;
use bevy_render::sync_world::TemporaryRenderEntity;
use bevy_render::view::InheritedVisibility;
use bevy_render::Extract;
use bevy_sprite::BorderRect;
/// 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,
/// Show outlines for non-visible UI nodes
pub show_hidden: bool,
/// Show outlines for clipped sections of UI nodes
pub show_clipped: bool,
}
impl UiDebugOptions {
pub fn toggle(&mut self) {
self.enabled = !self.enabled;
}
}
impl Default for UiDebugOptions {
fn default() -> Self {
Self {
enabled: false,
line_width: 1.,
show_hidden: false,
show_clipped: false,
}
}
}
pub fn extract_debug_overlay(
mut commands: Commands,
debug_options: Extract<Res<UiDebugOptions>>,
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
uinode_query: Extract<
Query<(
Entity,
&ComputedNode,
&UiGlobalTransform,
&InheritedVisibility,
Option<&CalculatedClip>,
&ComputedNodeTarget,
)>,
>,
ui_stack: Extract<Res<UiStack>>,
camera_map: Extract<UiCameraMap>,
) {
if !debug_options.enabled {
return;
}
let mut camera_mapper = camera_map.get_mapper();
for (entity, uinode, transform, visibility, maybe_clip, computed_target) in &uinode_query {
if !debug_options.show_hidden && !visibility.get() {
continue;
}
let Some(extracted_camera_entity) = camera_mapper.map(computed_target) else {
continue;
};
// Extract a border box to display an outline for every UI Node in the layout
extracted_uinodes.uinodes.push(ExtractedUiNode {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
// Add a large number to the UI node's stack index so that the overlay is always drawn on top
z_order: (ui_stack.uinodes.len() as u32 + uinode.stack_index()) as f32,
color: Hsla::sequential_dispersed(entity.index()).into(),
rect: Rect {
min: Vec2::ZERO,
max: uinode.size,
},
clip: maybe_clip
.filter(|_| !debug_options.show_clipped)
.map(|clip| clip.clip),
image: AssetId::default(),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.into(),
flip_x: false,
flip_y: false,
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
border_radius: uinode.border_radius(),
node_type: NodeType::Border(shader_flags::BORDER_ALL),
},
main_entity: entity.into(),
});
}
}