Skip to content

Commit

Permalink
UI borders and outlines clipping fix (bevyengine#16044)
Browse files Browse the repository at this point in the history
# 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 <alice.i.cecile@gmail.com>
  • Loading branch information
ickshonpe and alice-i-cecile authored Oct 21, 2024
1 parent d0af199 commit 9930df8
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 8 deletions.
21 changes: 20 additions & 1 deletion crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
});
}

Expand Down Expand Up @@ -1215,6 +1233,7 @@ pub fn prepare_uinodes(
radius: [0.0; 4],
border: [0.0; 4],
size: size.into(),
point: [0.0; 2],
});
}

Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ui/src/render/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 1 addition & 7 deletions crates/bevy_ui/src/render/ui.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit 9930df8

Please sign in to comment.