Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Border and CornerRadius components to bevy_ui #3991

Closed
Closed
16 changes: 15 additions & 1 deletion crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::{
widget::{Button, ImageMode},
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI,
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, BorderRadius, Border,
};
use bevy_ecs::bundle::Bundle;
use bevy_render::{
Expand Down Expand Up @@ -31,6 +31,10 @@ pub struct NodeBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Describes the border radius of the node
pub border_radius: BorderRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

/// A UI node that is an image
Expand All @@ -56,6 +60,10 @@ pub struct ImageBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Describes the border radius of the node
pub border_radius: BorderRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

/// A UI node that is text
Expand Down Expand Up @@ -117,6 +125,10 @@ pub struct ButtonBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Describes the border radius of the node
pub border_radius: BorderRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

impl Default for ButtonBundle {
Expand All @@ -132,6 +144,8 @@ impl Default for ButtonBundle {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
border_radius: Default::default(),
border: Default::default(),
}
}
}
Expand Down
46 changes: 37 additions & 9 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use bevy_window::Windows;

use bytemuck::{Pod, Zeroable};

use crate::{CalculatedClip, Node, UiColor, UiImage};
use crate::{Border, BorderRadius, CalculatedClip, Node, UiColor, UiImage};

pub mod node {
pub const UI_PASS_DRIVER: &str = "ui_pass_driver";
Expand Down Expand Up @@ -126,6 +126,9 @@ pub struct ExtractedUiNode {
pub image: Handle<Image>,
pub atlas_size: Option<Vec2>,
pub clip: Option<Rect>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub border_radius: Option<[f32; 4]>,
}

#[derive(Default)]
Expand All @@ -143,11 +146,15 @@ pub fn extract_uinodes(
&UiImage,
&Visibility,
Option<&CalculatedClip>,
Option<&BorderRadius>,
Option<&Border>,
)>,
) {
let mut extracted_uinodes = render_world.get_resource_mut::<ExtractedUiNodes>().unwrap();
extracted_uinodes.uinodes.clear();
for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
for (uinode, transform, color, image, visibility, clip, border_radius, border) in
uinode_query.iter()
{
if !visibility.is_visible {
continue;
}
Expand All @@ -166,6 +173,9 @@ pub fn extract_uinodes(
image,
atlas_size: None,
clip: clip.map(|clip| clip.clip),
border_color: border.map(|border| border.color),
border_width: border.map(|border| border.width),
border_radius: border_radius.map(|border_radius| border_radius.to_array()),
});
}
}
Expand Down Expand Up @@ -228,6 +238,9 @@ pub fn extract_text_uinodes(
image: texture,
atlas_size,
clip: clip.map(|clip| clip.clip),
border_color: None,
border_width: None,
border_radius: None,
});
}
}
Expand All @@ -240,6 +253,13 @@ struct UiVertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub color: u32,
pub uv_min: Vec2,
pub uv_max: Vec2,
pub size: Vec2,
pub border_color: u32,
pub border_width: f32,
/// Radius for each corner in this order: top-left, bottom-left, top-right, bottom-right
pub border_radius: [f32; 4],
oceantume marked this conversation as resolved.
Show resolved Hide resolved
}

pub struct UiMeta {
Expand Down Expand Up @@ -371,18 +391,26 @@ pub fn prepare_uinodes(
]
.map(|pos| pos / atlas_extent);

let color = extracted_uinode.color.as_linear_rgba_f32();
// encode color as a single u32 to save space
let color = (color[0] * 255.0) as u32
| ((color[1] * 255.0) as u32) << 8
| ((color[2] * 255.0) as u32) << 16
| ((color[3] * 255.0) as u32) << 24;
fn encode_color_as_u32(color: Color) -> u32 {
let color = color.as_linear_rgba_f32();
// encode color as a single u32 to save space
(color[0] * 255.0) as u32
| ((color[1] * 255.0) as u32) << 8
| ((color[2] * 255.0) as u32) << 16
| ((color[3] * 255.0) as u32) << 24
}

for i in QUAD_INDICES {
ui_meta.vertices.push(UiVertex {
position: positions_clipped[i].into(),
uv: uvs[i].into(),
color,
color: encode_color_as_u32(extracted_uinode.color),
size: Vec2::new(rect_size.x, rect_size.y),
uv_min: uinode_rect.min / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max),
uv_max: uinode_rect.max / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max),
border_color: extracted_uinode.border_color.map_or(0, encode_color_as_u32),
border_width: extracted_uinode.border_width.unwrap_or(0.0),
border_radius: extracted_uinode.border_radius.unwrap_or([0.0; 4]),
});
}

Expand Down
39 changes: 38 additions & 1 deletion crates/bevy_ui/src/render/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl SpecializedPipeline for UiPipeline {
/// FIXME: there are no specialization for now, should this be removed?
fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor {
let vertex_buffer_layout = VertexBufferLayout {
array_stride: 24,
array_stride: 72,
step_mode: VertexStepMode::Vertex,
attributes: vec![
// Position
Expand All @@ -82,11 +82,48 @@ impl SpecializedPipeline for UiPipeline {
offset: 12,
shader_location: 1,
},
// Color
VertexAttribute {
format: VertexFormat::Uint32,
offset: 20,
shader_location: 2,
},
// UV Min
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 24,
shader_location: 3,
},
// UV Max
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 32,
shader_location: 4,
},
// Size
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 40,
shader_location: 5,
},
// Border Color
VertexAttribute {
format: VertexFormat::Uint32,
offset: 48,
shader_location: 6,
},
// Border Width
VertexAttribute {
format: VertexFormat::Float32,
offset: 52,
shader_location: 7,
},
// Border Radius
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 56,
shader_location: 8,
},
],
};
let shader_defs = Vec::new();
Expand Down
48 changes: 45 additions & 3 deletions crates/bevy_ui/src/render/ui.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ var<uniform> view: View;
struct VertexOutput {
[[location(0)]] uv: vec2<f32>;
[[location(1)]] color: vec4<f32>;
[[location(2)]] center: vec2<f32>;
[[location(3)]] uv_min: vec2<f32>;
[[location(4)]] uv_max: vec2<f32>;
[[location(5)]] size: vec2<f32>;
[[location(6)]] border_color: vec4<f32>;
[[location(7)]] border_width: f32;
[[location(8)]] border_radius: vec4<f32>;
[[builtin(position)]] position: vec4<f32>;
};

Expand All @@ -16,22 +23,57 @@ fn vertex(
[[location(0)]] vertex_position: vec3<f32>,
[[location(1)]] vertex_uv: vec2<f32>,
[[location(2)]] vertex_color: u32,
[[location(3)]] uv_min: vec2<f32>,
[[location(4)]] uv_max: vec2<f32>,
[[location(5)]] size: vec2<f32>,
[[location(6)]] border_color: u32,
[[location(7)]] border_width: f32,
[[location(8)]] border_radius: vec4<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.uv = vertex_uv;
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
out.color = vec4<f32>((vec4<u32>(vertex_color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
out.size = size;
out.uv_min = uv_min;
out.uv_max = uv_max;
out.border_width = border_width;
out.border_color = vec4<f32>((vec4<u32>(border_color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
out.border_radius = border_radius;
return out;
}
}

[[group(1), binding(0)]]
var sprite_texture: texture_2d<f32>;
[[group(1), binding(1)]]
var sprite_sampler: sampler;

fn distance_round_border(point: vec2<f32>, size: vec2<f32>, radius_by_corner: vec4<f32>) -> f32 {
var corner_index = select(0, 1, point.y > 0.0) + select(0, 2, point.x > 0.0);
var radius = radius_by_corner[corner_index];
oceantume marked this conversation as resolved.
Show resolved Hide resolved
var q = abs(point) - size + radius;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2<f32>(0.0))) - radius;
}

[[stage(fragment)]]
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
var color = textureSample(sprite_texture, sprite_sampler, in.uv);
var color = textureSample(sprite_texture, sprite_sampler, in.uv);
color = in.color * color;

// this makes rounded borders look softer, but it's affecting colors so I'm excluding it for now
var edge_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 2.0);
var border_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 1.0);

// clamp radius between (0.0) and (shortest side / 2.0)
oceantume marked this conversation as resolved.
Show resolved Hide resolved
var radius = clamp(in.border_radius, vec4<f32>(0.0), vec4<f32>(min(in.size.x, in.size.y) / 2.0));

// get a normalized point based on uv, uv_max and uv_min
var point = ((in.uv - in.uv_min) / (in.uv_max - in.uv_min) - vec2<f32>(0.5)) * in.size;
var distance = distance_round_border(point, in.size * 0.5, radius);

var inner_alpha = 1.0 - smoothStep(0.0, edge_softness, distance + edge_softness);
var border_alpha = 1.0 - smoothStep(in.border_width - border_softness, in.border_width, abs(distance));
color = mix(vec4<f32>(0.0), mix(color, in.border_color, border_alpha), inner_alpha);

return color;
}
}
45 changes: 45 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,48 @@ pub struct CalculatedClip {
/// The rect of the clip
pub clip: bevy_sprite::Rect,
}

/// The border radius of the node
///
/// This doesn't require a [`Border`] component
oceantume marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct BorderRadius {
pub top_left: f32,
pub bottom_left: f32,
pub top_right: f32,
pub bottom_right: f32,
}

impl BorderRadius {
pub fn all(border_radius: f32) -> Self {
Self {
top_left: border_radius,
bottom_left: border_radius,
top_right: border_radius,
bottom_right: border_radius,
}
}

pub fn to_array(&self) -> [f32; 4] {
[
self.top_left,
self.bottom_left,
self.top_right,
self.bottom_right,
]
}
}

/// The visual properties of the node's border
oceantume marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct Border {
/// The width of the border
///
/// This is different from [`Style`] border and it will not cause any displacement inside the node.
pub width: f32,
oceantume marked this conversation as resolved.
Show resolved Hide resolved

/// The color of the border
pub color: Color,
}
5 changes: 5 additions & 0 deletions examples/ui/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..Default::default()
},
color: NORMAL_BUTTON.into(),
border_radius: BorderRadius::all(5.0),
border: Border {
color: Color::rgb(0.05, 0.05, 0.05),
width: 5.0,
},
..Default::default()
})
.with_children(|parent| {
Expand Down
Loading