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
					
				| @ -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_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; | ||||||
| use bevy_ecs::entity::{EntityHashMap, EntityHashSet}; | use bevy_ecs::entity::{EntityHashMap, EntityHashSet}; | ||||||
| use bevy_ecs::prelude::*; | use bevy_ecs::prelude::*; | ||||||
|  | use bevy_hierarchy::Parent; | ||||||
| use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; | use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; | ||||||
| use bevy_render::render_phase::ViewSortedRenderPhases; | use bevy_render::render_phase::ViewSortedRenderPhases; | ||||||
| use bevy_render::sync_world::MainEntity; | use bevy_render::sync_world::MainEntity; | ||||||
| @ -404,8 +405,10 @@ pub fn extract_uinode_borders( | |||||||
|             Option<&CalculatedClip>, |             Option<&CalculatedClip>, | ||||||
|             Option<&TargetCamera>, |             Option<&TargetCamera>, | ||||||
|             AnyOf<(&BorderColor, &Outline)>, |             AnyOf<(&BorderColor, &Outline)>, | ||||||
|  |             Option<&Parent>, | ||||||
|         )>, |         )>, | ||||||
|     >, |     >, | ||||||
|  |     parent_clip_query: Extract<Query<&CalculatedClip>>, | ||||||
|     mapping: Extract<Query<RenderEntity>>, |     mapping: Extract<Query<RenderEntity>>, | ||||||
| ) { | ) { | ||||||
|     let image = AssetId::<Image>::default(); |     let image = AssetId::<Image>::default(); | ||||||
| @ -418,6 +421,7 @@ pub fn extract_uinode_borders( | |||||||
|         maybe_clip, |         maybe_clip, | ||||||
|         maybe_camera, |         maybe_camera, | ||||||
|         (maybe_border_color, maybe_outline), |         (maybe_border_color, maybe_outline), | ||||||
|  |         maybe_parent, | ||||||
|     ) in &uinode_query |     ) in &uinode_query | ||||||
|     { |     { | ||||||
|         let Some(camera_entity) = maybe_camera |         let Some(camera_entity) = maybe_camera | ||||||
| @ -471,6 +475,9 @@ pub fn extract_uinode_borders( | |||||||
| 
 | 
 | ||||||
|         if let Some(outline) = maybe_outline { |         if let Some(outline) = maybe_outline { | ||||||
|             let outline_size = uinode.outlined_node_size(); |             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( |             extracted_uinodes.uinodes.insert( | ||||||
|                 commands.spawn(TemporaryRenderEntity).id(), |                 commands.spawn(TemporaryRenderEntity).id(), | ||||||
|                 ExtractedUiNode { |                 ExtractedUiNode { | ||||||
| @ -481,7 +488,7 @@ pub fn extract_uinode_borders( | |||||||
|                         ..Default::default() |                         ..Default::default() | ||||||
|                     }, |                     }, | ||||||
|                     image, |                     image, | ||||||
|                     clip: maybe_clip.map(|clip| clip.clip), |                     clip: parent_clip.map(|clip| clip.clip), | ||||||
|                     camera_entity: render_camera_entity, |                     camera_entity: render_camera_entity, | ||||||
|                     item: ExtractedUiItem::Node { |                     item: ExtractedUiItem::Node { | ||||||
|                         transform: global_transform.compute_matrix(), |                         transform: global_transform.compute_matrix(), | ||||||
| @ -768,6 +775,8 @@ struct UiVertex { | |||||||
|     pub border: [f32; 4], |     pub border: [f32; 4], | ||||||
|     /// Size of the UI node.
 |     /// Size of the UI node.
 | ||||||
|     pub size: [f32; 2], |     pub size: [f32; 2], | ||||||
|  |     /// Position relative to the center of the UI node.
 | ||||||
|  |     pub point: [f32; 2], | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Resource)] | #[derive(Resource)] | ||||||
| @ -998,6 +1007,7 @@ pub fn prepare_uinodes( | |||||||
|                             // Specify the corners of the node
 |                             // Specify the corners of the node
 | ||||||
|                             let positions = QUAD_VERTEX_POSITIONS |                             let positions = QUAD_VERTEX_POSITIONS | ||||||
|                                 .map(|pos| (*transform * (pos * rect_size).extend(1.)).xyz()); |                                 .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
 |                             // 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)
 |                             // 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.), |                                 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); |                             let transformed_rect_size = transform.transform_vector3(rect_size); | ||||||
| 
 | 
 | ||||||
|                             // Don't try to cull nodes that have a rotation
 |                             // 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], |                                     border: [border.left, border.top, border.right, border.bottom], | ||||||
|                                     size: rect_size.xy().into(), |                                     size: rect_size.xy().into(), | ||||||
|  |                                     point: points[i].into(), | ||||||
|                                 }); |                                 }); | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
| @ -1215,6 +1233,7 @@ pub fn prepare_uinodes( | |||||||
|                                         radius: [0.0; 4], |                                         radius: [0.0; 4], | ||||||
|                                         border: [0.0; 4], |                                         border: [0.0; 4], | ||||||
|                                         size: size.into(), |                                         size: size.into(), | ||||||
|  |                                         point: [0.0; 2], | ||||||
|                                     }); |                                     }); | ||||||
|                                 } |                                 } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -72,6 +72,8 @@ impl SpecializedRenderPipeline for UiPipeline { | |||||||
|                 VertexFormat::Float32x4, |                 VertexFormat::Float32x4, | ||||||
|                 // border size
 |                 // border size
 | ||||||
|                 VertexFormat::Float32x2, |                 VertexFormat::Float32x2, | ||||||
|  |                 // position relative to the center
 | ||||||
|  |                 VertexFormat::Float32x2, | ||||||
|             ], |             ], | ||||||
|         ); |         ); | ||||||
|         let shader_defs = if key.anti_alias { |         let shader_defs = if key.anti_alias { | ||||||
|  | |||||||
| @ -38,6 +38,7 @@ fn vertex( | |||||||
|     // x: left, y: top, z: right, w: bottom. |     // x: left, y: top, z: right, w: bottom. | ||||||
|     @location(5) border: vec4<f32>, |     @location(5) border: vec4<f32>, | ||||||
|     @location(6) size: vec2<f32>, |     @location(6) size: vec2<f32>, | ||||||
|  |     @location(7) point: vec2<f32>, | ||||||
| ) -> VertexOutput { | ) -> VertexOutput { | ||||||
|     var out: VertexOutput; |     var out: VertexOutput; | ||||||
|     out.uv = vertex_uv; |     out.uv = vertex_uv; | ||||||
| @ -47,13 +48,6 @@ fn vertex( | |||||||
|     out.radius = radius; |     out.radius = radius; | ||||||
|     out.size = size; |     out.size = size; | ||||||
|     out.border = border; |     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; |     out.point = point; | ||||||
| 
 | 
 | ||||||
|     return out; |     return out; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 ickshonpe
						ickshonpe