mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
UI borders and outlines clipping fix (#16044)
# Objective fixes #15502 Clipped borders and outlines aren't drawn correctly. ### Borders aren't clipped Spawn two nodes with the same dimensions and border thickness, but clip on of the nodes so that only its top left quarter is visible: <img width="194" alt="clip" src="https://github.com/user-attachments/assets/2d3f6d28-aa20-44df-967a-677725828294"> You can see that instead of clipping the border, instead the border is scaled to fit inside of the unclipped section. ```rust use bevy::color::palettes::css::BLUE; use bevy::prelude::*; use bevy::winit::WinitSettings; fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(WinitSettings::desktop_app()) .add_systems(Startup, setup) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2d); commands .spawn(Node { width: Val::Percent(100.), height: Val::Percent(100.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..Default::default() }) .with_children(|commands| { commands .spawn(Node { column_gap: Val::Px(10.), ..Default::default() }) .with_children(|commands| { commands .spawn(Node { width: Val::Px(100.), height: Val::Px(100.), overflow: Overflow::clip(), ..Default::default() }) .with_child(( Node { position_type: PositionType::Absolute, width: Val::Px(100.), height: Val::Px(100.), border: UiRect::all(Val::Px(10.)), ..Default::default() }, BackgroundColor(Color::WHITE), BorderColor(BLUE.into()), )); commands .spawn(Node { width: Val::Px(50.), height: Val::Px(50.), overflow: Overflow::clip(), ..Default::default() }) .with_child(( Node { position_type: PositionType::Absolute, width: Val::Px(100.), height: Val::Px(100.), border: UiRect::all(Val::Px(10.)), ..Default::default() }, BackgroundColor(Color::WHITE), BorderColor(BLUE.into()), )); }); }); } ``` You can also see this problem in the `overflow` example. If you hover over any of the clipped nodes you'll see that the outline only wraps the visible section of the node ### Outlines are clipped incorrectly A UI nodes Outline's are drawn outside of its bounds, so applying the local clipping rect to the outline doesn't make any sense. Instead an `Outline` should be clipped using its parent's clipping rect. ## Solution * Pass the `point` value into the vertex shader instead of calculating it in the shader. * In `extract_uinode_borders` use the parents clipping rect when clipping outlines. The extra parameter isn't a great solution I think, but I wanted to fix borders for the 0.15 release and this is the most minimal approach I could think of without replacing the whole shader and prepare function. ## Showcase <img width="149" alt="clipp" src="https://github.com/user-attachments/assets/19fbd3cc-e7cd-42e1-a5e0-fd92aad04dcd"> --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
d0af199249
commit
9930df83ed
3 changed files with 23 additions and 8 deletions
|
@ -16,6 +16,7 @@ use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
|
|||
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||
use bevy_ecs::entity::{EntityHashMap, EntityHashSet};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::Parent;
|
||||
use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles};
|
||||
use bevy_render::render_phase::ViewSortedRenderPhases;
|
||||
use bevy_render::sync_world::MainEntity;
|
||||
|
@ -404,8 +405,10 @@ pub fn extract_uinode_borders(
|
|||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
AnyOf<(&BorderColor, &Outline)>,
|
||||
Option<&Parent>,
|
||||
)>,
|
||||
>,
|
||||
parent_clip_query: Extract<Query<&CalculatedClip>>,
|
||||
mapping: Extract<Query<RenderEntity>>,
|
||||
) {
|
||||
let image = AssetId::<Image>::default();
|
||||
|
@ -418,6 +421,7 @@ pub fn extract_uinode_borders(
|
|||
maybe_clip,
|
||||
maybe_camera,
|
||||
(maybe_border_color, maybe_outline),
|
||||
maybe_parent,
|
||||
) in &uinode_query
|
||||
{
|
||||
let Some(camera_entity) = maybe_camera
|
||||
|
@ -471,6 +475,9 @@ pub fn extract_uinode_borders(
|
|||
|
||||
if let Some(outline) = maybe_outline {
|
||||
let outline_size = uinode.outlined_node_size();
|
||||
let parent_clip =
|
||||
maybe_parent.and_then(|parent| parent_clip_query.get(parent.get()).ok());
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
|
@ -481,7 +488,7 @@ pub fn extract_uinode_borders(
|
|||
..Default::default()
|
||||
},
|
||||
image,
|
||||
clip: maybe_clip.map(|clip| clip.clip),
|
||||
clip: parent_clip.map(|clip| clip.clip),
|
||||
camera_entity: render_camera_entity,
|
||||
item: ExtractedUiItem::Node {
|
||||
transform: global_transform.compute_matrix(),
|
||||
|
@ -768,6 +775,8 @@ struct UiVertex {
|
|||
pub border: [f32; 4],
|
||||
/// Size of the UI node.
|
||||
pub size: [f32; 2],
|
||||
/// Position relative to the center of the UI node.
|
||||
pub point: [f32; 2],
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
|
@ -998,6 +1007,7 @@ pub fn prepare_uinodes(
|
|||
// Specify the corners of the node
|
||||
let positions = QUAD_VERTEX_POSITIONS
|
||||
.map(|pos| (*transform * (pos * rect_size).extend(1.)).xyz());
|
||||
let points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy());
|
||||
|
||||
// Calculate the effect of clipping
|
||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||
|
@ -1031,6 +1041,13 @@ pub fn prepare_uinodes(
|
|||
positions[3] + positions_diff[3].extend(0.),
|
||||
];
|
||||
|
||||
let points = [
|
||||
points[0] + positions_diff[0],
|
||||
points[1] + positions_diff[1],
|
||||
points[2] + positions_diff[2],
|
||||
points[3] + positions_diff[3],
|
||||
];
|
||||
|
||||
let transformed_rect_size = transform.transform_vector3(rect_size);
|
||||
|
||||
// Don't try to cull nodes that have a rotation
|
||||
|
@ -1113,6 +1130,7 @@ pub fn prepare_uinodes(
|
|||
],
|
||||
border: [border.left, border.top, border.right, border.bottom],
|
||||
size: rect_size.xy().into(),
|
||||
point: points[i].into(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1215,6 +1233,7 @@ pub fn prepare_uinodes(
|
|||
radius: [0.0; 4],
|
||||
border: [0.0; 4],
|
||||
size: size.into(),
|
||||
point: [0.0; 2],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,8 @@ impl SpecializedRenderPipeline for UiPipeline {
|
|||
VertexFormat::Float32x4,
|
||||
// border size
|
||||
VertexFormat::Float32x2,
|
||||
// position relative to the center
|
||||
VertexFormat::Float32x2,
|
||||
],
|
||||
);
|
||||
let shader_defs = if key.anti_alias {
|
||||
|
|
|
@ -38,6 +38,7 @@ fn vertex(
|
|||
// x: left, y: top, z: right, w: bottom.
|
||||
@location(5) border: vec4<f32>,
|
||||
@location(6) size: vec2<f32>,
|
||||
@location(7) point: vec2<f32>,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.uv = vertex_uv;
|
||||
|
@ -47,13 +48,6 @@ fn vertex(
|
|||
out.radius = radius;
|
||||
out.size = size;
|
||||
out.border = border;
|
||||
var point = 0.49999 * size;
|
||||
if (flags & RIGHT_VERTEX) == 0u {
|
||||
point.x *= -1.;
|
||||
}
|
||||
if (flags & BOTTOM_VERTEX) == 0u {
|
||||
point.y *= -1.;
|
||||
}
|
||||
out.point = point;
|
||||
|
||||
return out;
|
||||
|
|
Loading…
Reference in a new issue