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.
This commit is contained in:
parent
5e8aa7986b
commit
a949867a1c
@ -1,8 +1,14 @@
|
|||||||
|
use super::ExtractedUiItem;
|
||||||
|
use super::ExtractedUiNode;
|
||||||
|
use super::ExtractedUiNodes;
|
||||||
|
use super::NodeType;
|
||||||
|
use super::UiCameraMap;
|
||||||
use crate::shader_flags;
|
use crate::shader_flags;
|
||||||
use crate::ui_node::ComputedNodeTarget;
|
use crate::ui_node::ComputedNodeTarget;
|
||||||
use crate::ui_transform::UiGlobalTransform;
|
use crate::ui_transform::UiGlobalTransform;
|
||||||
use crate::CalculatedClip;
|
use crate::CalculatedClip;
|
||||||
use crate::ComputedNode;
|
use crate::ComputedNode;
|
||||||
|
use crate::UiStack;
|
||||||
use bevy_asset::AssetId;
|
use bevy_asset::AssetId;
|
||||||
use bevy_color::Hsla;
|
use bevy_color::Hsla;
|
||||||
use bevy_ecs::entity::Entity;
|
use bevy_ecs::entity::Entity;
|
||||||
@ -18,12 +24,6 @@ use bevy_render::view::InheritedVisibility;
|
|||||||
use bevy_render::Extract;
|
use bevy_render::Extract;
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
|
|
||||||
use super::ExtractedUiItem;
|
|
||||||
use super::ExtractedUiNode;
|
|
||||||
use super::ExtractedUiNodes;
|
|
||||||
use super::NodeType;
|
|
||||||
use super::UiCameraMap;
|
|
||||||
|
|
||||||
/// Configuration for the UI debug overlay
|
/// Configuration for the UI debug overlay
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct UiDebugOptions {
|
pub struct UiDebugOptions {
|
||||||
@ -68,6 +68,7 @@ pub fn extract_debug_overlay(
|
|||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
|
ui_stack: Extract<Res<UiStack>>,
|
||||||
camera_map: Extract<UiCameraMap>,
|
camera_map: Extract<UiCameraMap>,
|
||||||
) {
|
) {
|
||||||
if !debug_options.enabled {
|
if !debug_options.enabled {
|
||||||
@ -89,7 +90,7 @@ pub fn extract_debug_overlay(
|
|||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
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
|
// 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,
|
z_order: (ui_stack.uinodes.len() as u32 + uinode.stack_index()) as f32,
|
||||||
color: Hsla::sequential_dispersed(entity.index()).into(),
|
color: Hsla::sequential_dispersed(entity.index()).into(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
|
@ -408,7 +408,11 @@ pub fn extract_gradients(
|
|||||||
if let Some(color) = gradient.get_single() {
|
if let Some(color) = gradient.get_single() {
|
||||||
// With a single color stop there's no gradient, fill the node with the color
|
// With a single color stop there's no gradient, fill the node with the color
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
stack_index: uinode.stack_index,
|
z_order: uinode.stack_index as f32
|
||||||
|
+ match node_type {
|
||||||
|
NodeType::Rect => stack_z_offsets::GRADIENT,
|
||||||
|
NodeType::Border(_) => stack_z_offsets::BORDER_GRADIENT,
|
||||||
|
},
|
||||||
color: color.into(),
|
color: color.into(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -629,7 +633,13 @@ pub fn queue_gradient(
|
|||||||
draw_function,
|
draw_function,
|
||||||
pipeline,
|
pipeline,
|
||||||
entity: (gradient.render_entity, gradient.main_entity),
|
entity: (gradient.render_entity, gradient.main_entity),
|
||||||
sort_key: FloatOrd(gradient.stack_index as f32 + stack_z_offsets::GRADIENT),
|
sort_key: FloatOrd(
|
||||||
|
gradient.stack_index as f32
|
||||||
|
+ match gradient.node_type {
|
||||||
|
NodeType::Rect => stack_z_offsets::GRADIENT,
|
||||||
|
NodeType::Border(_) => stack_z_offsets::BORDER_GRADIENT,
|
||||||
|
},
|
||||||
|
),
|
||||||
batch_range: 0..0,
|
batch_range: 0..0,
|
||||||
extra_index: PhaseItemExtraIndex::None,
|
extra_index: PhaseItemExtraIndex::None,
|
||||||
index,
|
index,
|
||||||
|
@ -81,7 +81,7 @@ pub mod graph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Z offsets of "extracted nodes" for a given entity. These exist to allow rendering multiple "extracted nodes"
|
/// Local Z offsets of "extracted nodes" for a given entity. These exist to allow rendering multiple "extracted nodes"
|
||||||
/// for a given source entity (ex: render both a background color _and_ a custom material for a given node).
|
/// for a given source entity (ex: render both a background color _and_ a custom material for a given node).
|
||||||
///
|
///
|
||||||
/// When possible these offsets should be defined in _this_ module to ensure z-index coordination across contexts.
|
/// When possible these offsets should be defined in _this_ module to ensure z-index coordination across contexts.
|
||||||
@ -97,10 +97,13 @@ pub mod graph {
|
|||||||
/// a positive offset on a node below.
|
/// a positive offset on a node below.
|
||||||
pub mod stack_z_offsets {
|
pub mod stack_z_offsets {
|
||||||
pub const BOX_SHADOW: f32 = -0.1;
|
pub const BOX_SHADOW: f32 = -0.1;
|
||||||
pub const TEXTURE_SLICE: f32 = 0.0;
|
pub const BACKGROUND_COLOR: f32 = 0.0;
|
||||||
pub const NODE: f32 = 0.0;
|
pub const BORDER: f32 = 0.01;
|
||||||
pub const GRADIENT: f32 = 0.1;
|
pub const GRADIENT: f32 = 0.02;
|
||||||
pub const MATERIAL: f32 = 0.18267;
|
pub const BORDER_GRADIENT: f32 = 0.03;
|
||||||
|
pub const IMAGE: f32 = 0.04;
|
||||||
|
pub const MATERIAL: f32 = 0.05;
|
||||||
|
pub const TEXT: f32 = 0.06;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
@ -213,7 +216,7 @@ fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractedUiNode {
|
pub struct ExtractedUiNode {
|
||||||
pub stack_index: u32,
|
pub z_order: f32,
|
||||||
pub color: LinearRgba,
|
pub color: LinearRgba,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub image: AssetId<Image>,
|
pub image: AssetId<Image>,
|
||||||
@ -374,7 +377,7 @@ pub fn extract_uinode_background_colors(
|
|||||||
|
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
z_order: uinode.stack_index as f32 + stack_z_offsets::BACKGROUND_COLOR,
|
||||||
color: background_color.0.into(),
|
color: background_color.0.into(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -460,8 +463,8 @@ pub fn extract_uinode_images(
|
|||||||
};
|
};
|
||||||
|
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
|
z_order: uinode.stack_index as f32 + stack_z_offsets::IMAGE,
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
|
||||||
color: image.color.into(),
|
color: image.color.into(),
|
||||||
rect,
|
rect,
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
@ -558,7 +561,7 @@ pub fn extract_uinode_borders(
|
|||||||
completed_flags |= border_flags;
|
completed_flags |= border_flags;
|
||||||
|
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
stack_index: computed_node.stack_index,
|
z_order: computed_node.stack_index as f32 + stack_z_offsets::BORDER,
|
||||||
color,
|
color,
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
max: computed_node.size(),
|
max: computed_node.size(),
|
||||||
@ -591,8 +594,8 @@ pub fn extract_uinode_borders(
|
|||||||
{
|
{
|
||||||
let outline_size = computed_node.outlined_node_size();
|
let outline_size = computed_node.outlined_node_size();
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
|
z_order: computed_node.stack_index as f32 + stack_z_offsets::BORDER,
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: computed_node.stack_index,
|
|
||||||
color: outline.color.into(),
|
color: outline.color.into(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
max: outline_size,
|
max: outline_size,
|
||||||
@ -782,8 +785,8 @@ pub fn extract_viewport_nodes(
|
|||||||
};
|
};
|
||||||
|
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
|
z_order: uinode.stack_index as f32 + stack_z_offsets::IMAGE,
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
|
||||||
color: LinearRgba::WHITE,
|
color: LinearRgba::WHITE,
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -885,8 +888,8 @@ pub fn extract_text_sections(
|
|||||||
.map(|text_color| LinearRgba::from(text_color.0))
|
.map(|text_color| LinearRgba::from(text_color.0))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
|
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
|
||||||
color,
|
color,
|
||||||
image: atlas_info.texture.id(),
|
image: atlas_info.texture.id(),
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
@ -966,8 +969,8 @@ pub fn extract_text_shadows(
|
|||||||
info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture
|
info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture
|
||||||
}) {
|
}) {
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
|
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
|
||||||
color: shadow.color.into(),
|
color: shadow.color.into(),
|
||||||
image: atlas_info.texture.id(),
|
image: atlas_info.texture.id(),
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
@ -1023,8 +1026,8 @@ pub fn extract_text_background_colors(
|
|||||||
};
|
};
|
||||||
|
|
||||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||||
|
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
|
||||||
color: text_background_color.0.to_linear(),
|
color: text_background_color.0.to_linear(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -1167,7 +1170,7 @@ pub fn queue_uinodes(
|
|||||||
draw_function,
|
draw_function,
|
||||||
pipeline,
|
pipeline,
|
||||||
entity: (extracted_uinode.render_entity, extracted_uinode.main_entity),
|
entity: (extracted_uinode.render_entity, extracted_uinode.main_entity),
|
||||||
sort_key: FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE),
|
sort_key: FloatOrd(extracted_uinode.z_order),
|
||||||
index,
|
index,
|
||||||
// batch_range will be calculated in prepare_uinodes
|
// batch_range will be calculated in prepare_uinodes
|
||||||
batch_range: 0..0,
|
batch_range: 0..0,
|
||||||
|
@ -366,9 +366,7 @@ pub fn queue_ui_slices(
|
|||||||
draw_function,
|
draw_function,
|
||||||
pipeline,
|
pipeline,
|
||||||
entity: (extracted_slicer.render_entity, extracted_slicer.main_entity),
|
entity: (extracted_slicer.render_entity, extracted_slicer.main_entity),
|
||||||
sort_key: FloatOrd(
|
sort_key: FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::IMAGE),
|
||||||
extracted_slicer.stack_index as f32 + stack_z_offsets::TEXTURE_SLICE,
|
|
||||||
),
|
|
||||||
batch_range: 0..0,
|
batch_range: 0..0,
|
||||||
extra_index: PhaseItemExtraIndex::None,
|
extra_index: PhaseItemExtraIndex::None,
|
||||||
index,
|
index,
|
||||||
|
@ -484,6 +484,26 @@ mod slice {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parent.spawn((
|
||||||
|
ImageNode {
|
||||||
|
image: asset_server
|
||||||
|
.load("textures/fantasy_ui_borders/panel-border-010.png"),
|
||||||
|
image_mode: NodeImageMode::Sliced(TextureSlicer {
|
||||||
|
border: BorderRect::all(22.0),
|
||||||
|
center_scale_mode: SliceScaleMode::Stretch,
|
||||||
|
sides_scale_mode: SliceScaleMode::Stretch,
|
||||||
|
max_corner_scale: 1.0,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Node {
|
||||||
|
width: Val::Px(100.),
|
||||||
|
height: Val::Px(100.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(bevy::color::palettes::css::NAVY.into()),
|
||||||
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
release-content/migration-guides/stack_z_offsets_changes.md
Normal file
34
release-content/migration-guides/stack_z_offsets_changes.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Fixed UI draw order and `stack_z_offsets` changes
|
||||||
|
pull_requests: [19691]
|
||||||
|
---
|
||||||
|
|
||||||
|
The draw order of some renderable UI elements relative to others wasn't fixed and depended on system ordering.
|
||||||
|
In particular the ordering of background colors and texture sliced images was sometimes swapped.
|
||||||
|
|
||||||
|
The UI draw order is now fixed.
|
||||||
|
The new order is (back-to-front):
|
||||||
|
|
||||||
|
1. Box shadows
|
||||||
|
|
||||||
|
2. Node background colors
|
||||||
|
|
||||||
|
3. Node borders
|
||||||
|
|
||||||
|
4. Gradients
|
||||||
|
|
||||||
|
5. Border Gradients
|
||||||
|
|
||||||
|
6. Images (including texture-sliced images)
|
||||||
|
|
||||||
|
7. Materials
|
||||||
|
|
||||||
|
8. Text (including text shadows)
|
||||||
|
|
||||||
|
The values of the `stack_z_offsets` constants have been updated to enforce the new ordering. Other changes:
|
||||||
|
|
||||||
|
* `NODE` is renamed to `BACKGROUND_COLOR`
|
||||||
|
|
||||||
|
* `TEXTURE_SLICE` is removed, use `IMAGE`.
|
||||||
|
|
||||||
|
* New `BORDER`, `BORDER_GRADIENT` and `TEXT` constants.
|
Loading…
Reference in New Issue
Block a user