Skip to content

Commit 9930df8

Browse files
UI borders and outlines clipping fix (bevyengine#16044)
# Objective fixes bevyengine#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 <[email protected]>
1 parent d0af199 commit 9930df8

File tree

3 files changed

+23
-8
lines changed

3 files changed

+23
-8
lines changed

crates/bevy_ui/src/render/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
1616
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
1717
use bevy_ecs::entity::{EntityHashMap, EntityHashSet};
1818
use bevy_ecs::prelude::*;
19+
use bevy_hierarchy::Parent;
1920
use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles};
2021
use bevy_render::render_phase::ViewSortedRenderPhases;
2122
use bevy_render::sync_world::MainEntity;
@@ -404,8 +405,10 @@ pub fn extract_uinode_borders(
404405
Option<&CalculatedClip>,
405406
Option<&TargetCamera>,
406407
AnyOf<(&BorderColor, &Outline)>,
408+
Option<&Parent>,
407409
)>,
408410
>,
411+
parent_clip_query: Extract<Query<&CalculatedClip>>,
409412
mapping: Extract<Query<RenderEntity>>,
410413
) {
411414
let image = AssetId::<Image>::default();
@@ -418,6 +421,7 @@ pub fn extract_uinode_borders(
418421
maybe_clip,
419422
maybe_camera,
420423
(maybe_border_color, maybe_outline),
424+
maybe_parent,
421425
) in &uinode_query
422426
{
423427
let Some(camera_entity) = maybe_camera
@@ -471,6 +475,9 @@ pub fn extract_uinode_borders(
471475

472476
if let Some(outline) = maybe_outline {
473477
let outline_size = uinode.outlined_node_size();
478+
let parent_clip =
479+
maybe_parent.and_then(|parent| parent_clip_query.get(parent.get()).ok());
480+
474481
extracted_uinodes.uinodes.insert(
475482
commands.spawn(TemporaryRenderEntity).id(),
476483
ExtractedUiNode {
@@ -481,7 +488,7 @@ pub fn extract_uinode_borders(
481488
..Default::default()
482489
},
483490
image,
484-
clip: maybe_clip.map(|clip| clip.clip),
491+
clip: parent_clip.map(|clip| clip.clip),
485492
camera_entity: render_camera_entity,
486493
item: ExtractedUiItem::Node {
487494
transform: global_transform.compute_matrix(),
@@ -768,6 +775,8 @@ struct UiVertex {
768775
pub border: [f32; 4],
769776
/// Size of the UI node.
770777
pub size: [f32; 2],
778+
/// Position relative to the center of the UI node.
779+
pub point: [f32; 2],
771780
}
772781

773782
#[derive(Resource)]
@@ -998,6 +1007,7 @@ pub fn prepare_uinodes(
9981007
// Specify the corners of the node
9991008
let positions = QUAD_VERTEX_POSITIONS
10001009
.map(|pos| (*transform * (pos * rect_size).extend(1.)).xyz());
1010+
let points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy());
10011011

10021012
// Calculate the effect of clipping
10031013
// 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(
10311041
positions[3] + positions_diff[3].extend(0.),
10321042
];
10331043

1044+
let points = [
1045+
points[0] + positions_diff[0],
1046+
points[1] + positions_diff[1],
1047+
points[2] + positions_diff[2],
1048+
points[3] + positions_diff[3],
1049+
];
1050+
10341051
let transformed_rect_size = transform.transform_vector3(rect_size);
10351052

10361053
// Don't try to cull nodes that have a rotation
@@ -1113,6 +1130,7 @@ pub fn prepare_uinodes(
11131130
],
11141131
border: [border.left, border.top, border.right, border.bottom],
11151132
size: rect_size.xy().into(),
1133+
point: points[i].into(),
11161134
});
11171135
}
11181136

@@ -1215,6 +1233,7 @@ pub fn prepare_uinodes(
12151233
radius: [0.0; 4],
12161234
border: [0.0; 4],
12171235
size: size.into(),
1236+
point: [0.0; 2],
12181237
});
12191238
}
12201239

crates/bevy_ui/src/render/pipeline.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ impl SpecializedRenderPipeline for UiPipeline {
7272
VertexFormat::Float32x4,
7373
// border size
7474
VertexFormat::Float32x2,
75+
// position relative to the center
76+
VertexFormat::Float32x2,
7577
],
7678
);
7779
let shader_defs = if key.anti_alias {

crates/bevy_ui/src/render/ui.wgsl

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ fn vertex(
3838
// x: left, y: top, z: right, w: bottom.
3939
@location(5) border: vec4<f32>,
4040
@location(6) size: vec2<f32>,
41+
@location(7) point: vec2<f32>,
4142
) -> VertexOutput {
4243
var out: VertexOutput;
4344
out.uv = vertex_uv;
@@ -47,13 +48,6 @@ fn vertex(
4748
out.radius = radius;
4849
out.size = size;
4950
out.border = border;
50-
var point = 0.49999 * size;
51-
if (flags & RIGHT_VERTEX) == 0u {
52-
point.x *= -1.;
53-
}
54-
if (flags & BOTTOM_VERTEX) == 0u {
55-
point.y *= -1.;
56-
}
5751
out.point = point;
5852

5953
return out;

0 commit comments

Comments
 (0)