diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b71404c3a27eb..be0b0172182a3 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -199,8 +199,11 @@ pub fn extract_uinode_background_colors( Option<&TargetCamera>, &BackgroundColor, Option<&BorderRadius>, + &Style, + Option<&Parent>, )>, >, + node_query: Extract>, ) { for ( entity, @@ -211,6 +214,8 @@ pub fn extract_uinode_background_colors( camera, background_color, border_radius, + style, + parent, ) in &uinode_query { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) @@ -232,6 +237,23 @@ pub fn extract_uinode_background_colors( // so we have to divide by `UiScale` to get the size of the UI viewport. / ui_scale.0; + // Both vertical and horizontal percentage border values are calculated based on the width of the parent node + // + let parent_width = parent + .and_then(|parent| node_query.get(parent.get()).ok()) + .map(|parent_node| parent_node.size().x) + .unwrap_or(ui_logical_viewport_size.x); + let left = + resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size); + let right = + resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size); + let top = + resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size); + let bottom = + resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size); + + let border = [left, top, right, bottom]; + let border_radius = if let Some(border_radius) = border_radius { resolve_border_radius( border_radius, @@ -259,7 +281,7 @@ pub fn extract_uinode_background_colors( flip_x: false, flip_y: false, camera_entity, - border: [0.; 4], + border, border_radius, node_type: NodeType::Rect, }, @@ -267,6 +289,7 @@ pub fn extract_uinode_background_colors( } } +#[allow(clippy::too_many_arguments)] pub fn extract_uinode_images( mut commands: Commands, mut extracted_uinodes: ResMut, @@ -285,11 +308,25 @@ pub fn extract_uinode_images( Option<&TextureAtlas>, Option<&ComputedTextureSlices>, Option<&BorderRadius>, + Option<&Parent>, + &Style, )>, >, + node_query: Extract>, ) { - for (uinode, transform, view_visibility, clip, camera, image, atlas, slices, border_radius) in - &uinode_query + for ( + uinode, + transform, + view_visibility, + clip, + camera, + image, + atlas, + slices, + border_radius, + parent, + style, + ) in &uinode_query { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) else { @@ -342,6 +379,23 @@ pub fn extract_uinode_images( // so we have to divide by `UiScale` to get the size of the UI viewport. / ui_scale.0; + // Both vertical and horizontal percentage border values are calculated based on the width of the parent node + // + let parent_width = parent + .and_then(|parent| node_query.get(parent.get()).ok()) + .map(|parent_node| parent_node.size().x) + .unwrap_or(ui_logical_viewport_size.x); + let left = + resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size); + let right = + resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size); + let top = + resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size); + let bottom = + resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size); + + let border = [left, top, right, bottom]; + let border_radius = if let Some(border_radius) = border_radius { resolve_border_radius( border_radius, @@ -366,7 +420,7 @@ pub fn extract_uinode_images( flip_x: image.flip_x, flip_y: image.flip_y, camera_entity, - border: [0.; 4], + border, border_radius, node_type: NodeType::Rect, }, @@ -513,6 +567,11 @@ pub fn extract_uinode_borders( let border = [left, top, right, bottom]; + // don't extract border if no border + if left == 0.0 && top == 0.0 && right == 0.0 && bottom == 0.0 { + continue; + } + let border_radius = resolve_border_radius( border_radius, node.size(), diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index b23d89bdb6d12..b22c8615ff126 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -79,7 +79,7 @@ fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> // If 0.0 < y then select bottom left (w) and bottom right corner radius (z). // Else select top left (x) and top right corner radius (y). let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y); - // w and z are swapped so that both pairs are in left to right order, otherwise this second + // w and z are swapped above so that both pairs are in left to right order, otherwise this second // select statement would return the incorrect value for the bottom pair. let radius = select(rs.x, rs.y, 0.0 < point.x); // Vector from the corner closest to the point, to the point. @@ -120,6 +120,12 @@ fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, in return sd_rounded_box(inner_point, inner_size, r); } +// get alpha for antialiasing for sdf +fn antialias(distance: f32) -> f32 { + // Using the fwidth(distance) was causing artifacts, so just use the distance. + return clamp(0.0, 1.0, 0.5 - distance); +} + fn draw(in: VertexOutput) -> vec4 { let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv); @@ -145,32 +151,31 @@ fn draw(in: VertexOutput) -> vec4 { // outside the outside edge, or inside the inner edge have positive signed distance. let border_distance = max(external_distance, -internal_distance); - // The `fwidth` function returns an approximation of the rate of change of the signed distance - // value that is used to ensure that the smooth alpha transition created by smoothstep occurs - // over a range of distance values that is proportional to how quickly the distance is changing. - let fborder = fwidth(border_distance); - let fexternal = fwidth(external_distance); - - if enabled(in.flags, BORDER) { - // The item is a border + // At external edges with no border, `border_distance` is equal to zero. + // This select statement ensures we only perform anti-aliasing where a non-zero width border + // is present, otherwise an outline about the external boundary would be drawn even without + // a border. + let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance); - // At external edges with no border, `border_distance` is equal to zero. - // This select statement ensures we only perform anti-aliasing where a non-zero width border - // is present, otherwise an outline about the external boundary would be drawn even without - // a border. - let t = 1. - select(step(0.0, border_distance), smoothstep(0.0, fborder, border_distance), external_distance < internal_distance); - - // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. - return vec4(color.rgb, color.a * t); - } + // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. + return vec4(color.rgb, color.a * t); +} - // The item is a rectangle, draw normally with anti-aliasing at the edges. - let t = 1. - smoothstep(0.0, fexternal, external_distance); +fn draw_background(in: VertexOutput) -> vec4 { + let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv); + let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED)); + // When drawing the background only draw the internal area and not the border. + let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border); + let t = antialias(internal_distance); return vec4(color.rgb, color.a * t); } @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - return draw(in); + if enabled(in.flags, BORDER) { + return draw(in); + } else { + return draw_background(in); + } }