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

UI Node Border Radius and Shadows #8973

Closed
wants to merge 76 commits into from
Closed
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
e772dfe
`bevy_ui` changes
ickshonpe Jun 27, 2023
7a49efb
Added invert corners mode to `ui.wgsl`
ickshonpe Jun 27, 2023
b86bd5a
Corruption resolved, the rect field for the inner border rect should …
ickshonpe Jun 27, 2023
728b866
Check for border radius should have greater than not greater or equal…
ickshonpe Jun 27, 2023
b8f784e
Implemented inner radius for partial borders
ickshonpe Jun 27, 2023
3b6fa71
Cleaned up `ui.wgsl` a bit
ickshonpe Jun 27, 2023
8cf677a
cleaned up fragnment shader
ickshonpe Jun 27, 2023
f8f035d
`ui.wgsl`
ickshonpe Jun 27, 2023
8c7b7e4
`extract_uinode_borders`
ickshonpe Jun 27, 2023
0a2e8b5
cargo fmt --all
ickshonpe Jun 27, 2023
9c2fbc6
Merge branch 'main' into rounded-nodes
ickshonpe Jun 27, 2023
87e1429
Clean up.
ickshonpe Jun 27, 2023
661eb8a
Adds support for non-uniform corner rounding.
ickshonpe Jun 28, 2023
8dc2f88
Most bugs seem to be fixed, remaining case is with two adjacent roun…
ickshonpe Jun 28, 2023
c0cf89e
Clean up, cargo fmt
ickshonpe Jun 28, 2023
26d8451
Removed unused import.
ickshonpe Jun 28, 2023
0350886
`UiCornerRadius`:
ickshonpe Jun 29, 2023
c900671
cargo fmt
ickshonpe Jun 29, 2023
b9cc579
`bevy_ui::node_bundles`
ickshonpe Jun 30, 2023
49618a4
`many_buttons`
ickshonpe Jun 30, 2023
e855a8b
Use the `as_rainbow` function for border colors too.
ickshonpe Jun 30, 2023
a7cbf3a
cargo fmt --all
ickshonpe Jun 30, 2023
588c0ae
Use percentages for border values
ickshonpe Jun 30, 2023
eaf714d
Merge branch 'many-buttons-bordered' into rounded-nodes
ickshonpe Jun 30, 2023
667d69e
Removed `FromReflect` derive from `UiBorderRadius`.
ickshonpe Jun 30, 2023
67630fe
Fixed textures.
ickshonpe Jun 30, 2023
d9e98eb
fixes for ci lints
ickshonpe Jun 30, 2023
ae2b5c3
Merge branch 'many-buttons-bordered' into rounded-nodes
ickshonpe Jun 30, 2023
c95b5b4
Added more doc comments.
ickshonpe Jun 30, 2023
e7d86a8
Update crates/bevy_ui/src/ui_node.rs
ickshonpe Jun 30, 2023
c575717
add missing `UiBorderRadius` import to doc comment example
ickshonpe Jun 30, 2023
2513156
added border and round corners to game menu example
ickshonpe Jun 30, 2023
b49bdcd
Added missing default to `Style` declaration in the `border_radius` d…
ickshonpe Jun 30, 2023
8330dc7
better AA
ickshonpe Jul 1, 2023
1543c9b
`UiNodeShadow`
ickshonpe Jul 3, 2023
8a7d9e0
Added an index buffer to UI renderer.
ickshonpe Jul 3, 2023
9cb44bb
Almost there, but 0 size borders rendered
ickshonpe Jul 3, 2023
9db2de6
Fixed corner and border values to map correctly from bevy types to sh…
ickshonpe Jul 3, 2023
c95317b
chopped
ickshonpe Jul 4, 2023
adb8ace
Added `with_left`, `with_right`, `with_top`, `with_bottom` `UiRect` …
ickshonpe Jul 4, 2023
d099ae4
Merge branch 'rounded-nodes' into rounded-nodes-index-buffer-two-colors
ickshonpe Jul 4, 2023
86f851a
`bevy_ui::geometry::UiRect`
ickshonpe Jul 4, 2023
fbdd5a9
Merge branch 'ui-rect-builder-funcs' into rounded-nodes-index-buffer-…
ickshonpe Jul 4, 2023
b6b0ca2
All unrounded cases seem to be working now.
ickshonpe Jul 4, 2023
2560785
Update examples/stress_tests/many_buttons.rs
ickshonpe Jul 4, 2023
29139ef
rounded rects displaying correctly
ickshonpe Jul 4, 2023
91e0795
Clean up ui shader dead code
ickshonpe Jul 4, 2023
f342980
`bevy_ui::render`
ickshonpe Jul 4, 2023
bdda002
cargo fmt
ickshonpe Jul 4, 2023
49ce0c4
formatting
ickshonpe Jul 4, 2023
77f625c
`ui.wgsl`
ickshonpe Jul 5, 2023
ceda428
`bevy_ui::render`
ickshonpe Jul 5, 2023
afe327e
`ui.wgsl`
ickshonpe Jul 5, 2023
1d6b976
Merge branch 'rounded-nodes-box-shadow' into border-radius-box-shadow
ickshonpe Jul 5, 2023
7d908b1
fix merge
ickshonpe Jul 5, 2023
0663393
`UiNodeShadow`
ickshonpe Jul 5, 2023
87ee2c7
Clean up + cargo fmt
ickshonpe Jul 5, 2023
7963803
`bevy_ui::render` module
ickshonpe Jul 5, 2023
8d0af9d
cargo fmt
ickshonpe Jul 5, 2023
b2cd4ae
Merge branch 'rounded-nodes' of https://github.com/ickshonpe/bevy int…
ickshonpe Jul 5, 2023
0ceaba9
Merge branch 'border-radius-box-shadow' into rounded-nodes
ickshonpe Jul 5, 2023
a32ef6b
comments
ickshonpe Jul 5, 2023
19da4b1
Reordered UiBorderRadius methods.
ickshonpe Jul 5, 2023
dfebacf
fix whitespace
ickshonpe Jul 5, 2023
4294fd9
formatting
ickshonpe Jul 6, 2023
3d2c438
Adjusted shadow defaults.
ickshonpe Jul 6, 2023
d84c61e
`UiShadow`
ickshonpe Jul 7, 2023
65c92be
`ui.wgsl` changes:
ickshonpe Jul 7, 2023
737ea7f
`bevy_ui::render`
ickshonpe Jul 7, 2023
6018ab6
Added an alternative sdf function that sets the inner radius to zero …
ickshonpe Jul 7, 2023
2adc733
Swapped the default inner radius behaviour back to how it was. Slight…
ickshonpe Jul 7, 2023
e4eca30
`ui.wgsl`: `sd_rounded_box` refactor
ickshonpe Jul 7, 2023
b2896bd
`bevy_ui::render`
ickshonpe Jul 10, 2023
5e9fab2
`ExtractedUiNode`
ickshonpe Jul 11, 2023
63b1967
`ui.wgsl`
ickshonpe Jul 13, 2023
c04e484
`prepare_uinodes`
ickshonpe Jul 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/bevy_ui/src/layout/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ mod tests {
],
grid_column: GridPlacement::start(4),
grid_row: GridPlacement::span(3),
border_radius: Default::default(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
border_radius: Default::default(),
border_radius: UiBorderRadius::default(),

};
let viewport_values = LayoutContext::new(1.0, bevy_math::Vec2::new(800., 600.));
let taffy_style = from_style(&viewport_values, &bevy_style);
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ impl Plugin for UiPlugin {
.register_type::<UiRect>()
.register_type::<Val>()
.register_type::<BorderColor>()
.register_type::<UiBorderRadius>()
.register_type::<widget::Button>()
.register_type::<widget::Label>()
.register_type::<ZIndex>()
Expand Down
178 changes: 118 additions & 60 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
pub use render_pass::*;

use crate::UiTextureAtlasImage;
use crate::{
prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, Node, UiImage, UiStack,
};
use crate::{ContentSize, Style, Val};
use crate::{UiBorderRadius, UiScale, UiTextureAtlasImage};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, Rect, UVec4, Vec2, Vec3, Vec4Swizzles};
use bevy_math::{Mat4, Rect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles};
use bevy_reflect::TypeUuid;
use bevy_render::texture::DEFAULT_IMAGE_HANDLE;
use bevy_render::{
Expand Down Expand Up @@ -159,6 +159,9 @@ pub struct ExtractedUiNode {
pub clip: Option<Rect>,
pub flip_x: bool,
pub flip_y: bool,
pub border_radius: [f32; 4],
pub border: [f32; 4],
pub invert: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does invert mean here? I just don't understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit confusing because I tried a lot of different approaches and some things are leftover from different branches I haven't cleaned up yet. This is from an implementation that constructs borders from out of five quads. It uses this flag to mark the middle section of the border which would be drawn "inverted" so only the outside of the curved corners would be coloured. It has much better performance as long as most of the rects the UI draws aren't bordered (which is true almost all of the time) but its a bit overcomplicated and I couldn't get it to work nicely with anti-aliasing.

}

#[derive(Resource, Default)]
Expand All @@ -168,9 +171,10 @@ pub struct ExtractedUiNodes {

pub fn extract_atlas_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
images: Extract<Res<Assets<Image>>>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,

ui_scale: Extract<Res<UiScale>>,
ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<
Expand All @@ -182,14 +186,28 @@ pub fn extract_atlas_uinodes(
Option<&CalculatedClip>,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
&Style,
),
Without<UiImage>,
>,
>,
) {
let viewport_size = windows
.get_single()
.map(|window| Vec2::new(window.resolution.width(), window.resolution.height()))
.unwrap_or(Vec2::ZERO);

for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, transform, color, visibility, clip, texture_atlas_handle, atlas_image)) =
uinode_query.get(*entity)
if let Ok((
uinode,
transform,
color,
visibility,
clip,
texture_atlas_handle,
atlas_image,
style,
)) = uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
if !visibility.is_visible() || color.0.a() == 0.0 {
Expand Down Expand Up @@ -238,11 +256,20 @@ pub fn extract_atlas_uinodes(
atlas_size: Some(atlas_size),
flip_x: atlas_image.flip_x,
flip_y: atlas_image.flip_y,
border_radius: resolve_border_radius(
&style.border_radius,
uinode.calculated_size,
viewport_size,
ui_scale.scale,
),
invert: false,
border: [0.; 4],
});
}
}
}

#[inline]
fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
match value {
Val::Auto => 0.,
Expand All @@ -255,9 +282,35 @@ fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2)
}
}

#[inline]
fn resolve_border_radius(
&values: &UiBorderRadius,
node_size: Vec2,
viewport_size: Vec2,
ui_scale: f64,
) -> [f32; 4] {
<[Val; 4]>::from(values).map(|value| {
let px_val = match value {
Val::Auto => 0.,
Val::Px(px) => ui_scale as f32 * px.max(0.),
Val::Percent(percent) => (node_size.min_element() * percent / 100.).max(0.),
Val::Vw(percent) => (viewport_size.x * percent / 100.).max(0.),
Val::Vh(percent) => (viewport_size.y * percent / 100.).max(0.),
Val::VMin(percent) => (viewport_size.min_element() * percent / 100.).max(0.),
Val::VMax(percent) => (viewport_size.max_element() * percent / 100.).max(0.),
};
if px_val <= 0. {
0.
} else {
px_val.min(0.5 * node_size.min_element()) * ui_scale as f32
}
})
}

pub fn extract_uinode_borders(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
ui_scale: Extract<Res<UiScale>>,
ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<
Expand All @@ -280,7 +333,8 @@ pub fn extract_uinode_borders(
let viewport_size = windows
.get_single()
.map(|window| Vec2::new(window.resolution.width(), window.resolution.height()))
.unwrap_or(Vec2::ZERO);
.unwrap_or(Vec2::ZERO)
* ui_scale.scale as f32;

for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((node, global_transform, style, border_color, parent, visibility, clip)) =
Expand All @@ -306,64 +360,40 @@ pub fn extract_uinode_borders(
let top = resolve_border_thickness(style.border.top, parent_width, viewport_size);
let bottom = resolve_border_thickness(style.border.bottom, parent_width, viewport_size);

// Calculate the border rects, ensuring no overlap.
// The border occupies the space between the node's bounding rect and the node's bounding rect inset in each direction by the node's corresponding border value.
let max = 0.5 * node.size();
let min = -max;
let inner_min = min + Vec2::new(left, top);
let inner_max = (max - Vec2::new(right, bottom)).max(inner_min);
let border_rects = [
// Left border
Rect {
min,
max: Vec2::new(inner_min.x, max.y),
},
// Right border
Rect {
min: Vec2::new(inner_max.x, min.y),
max,
},
// Top border
Rect {
min: Vec2::new(inner_min.x, min.y),
max: Vec2::new(inner_max.x, inner_min.y),
},
// Bottom border
Rect {
min: Vec2::new(inner_min.x, inner_max.y),
max: Vec2::new(inner_max.x, max.y),
},
];

let transform = global_transform.compute_matrix();

for edge in border_rects {
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
extracted_uinodes.uinodes.push(ExtractedUiNode {
stack_index,
// This translates the uinode's transform to the center of the current border rectangle
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
color: border_color.0,
rect: Rect {
max: edge.size(),
..Default::default()
},
image: image.clone_weak(),
atlas_size: None,
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,
});
}
}
extracted_uinodes.uinodes.push(ExtractedUiNode {
stack_index,
// This translates the uinode's transform to the center of the current border rectangle
transform,
color: border_color.0,
rect: Rect {
min: Vec2::ZERO,
max: node.size(),
},
image: image.clone_weak(),
atlas_size: None,
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,
border_radius: resolve_border_radius(
&style.border_radius,
node.calculated_size,
viewport_size,
ui_scale.scale,
),
invert: true,
border: [left, top, right, bottom],
});
}
}
}

pub fn extract_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
images: Extract<Res<Assets<Image>>>,
ui_stack: Extract<Res<UiStack>>,
ui_scale: Extract<Res<UiScale>>,
uinode_query: Extract<
Query<
(
Expand All @@ -373,15 +403,22 @@ pub fn extract_uinodes(
Option<&UiImage>,
&ComputedVisibility,
Option<&CalculatedClip>,
&Style,
),
Without<UiTextureAtlasImage>,
>,
>,
) {
extracted_uinodes.uinodes.clear();

let viewport_size = windows
.get_single()
.map(|window| Vec2::new(window.resolution.width(), window.resolution.height()))
.unwrap_or(Vec2::ZERO)
* ui_scale.scale as f32;

for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, transform, color, maybe_image, visibility, clip)) =
if let Ok((uinode, transform, color, maybe_image, visibility, clip, style)) =
uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
Expand Down Expand Up @@ -412,6 +449,14 @@ pub fn extract_uinodes(
atlas_size: None,
flip_x,
flip_y,
border_radius: resolve_border_radius(
&style.border_radius,
uinode.calculated_size,
viewport_size,
ui_scale.scale,
),
invert: false,
border: [0.; 4],
});
};
}
Expand Down Expand Up @@ -540,6 +585,9 @@ pub fn extract_text_uinodes(
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,
border_radius: [0.; 4],
invert: false,
border: [0.; 4],
});
}
}
Expand All @@ -553,6 +601,9 @@ struct UiVertex {
pub uv: [f32; 2],
pub color: [f32; 4],
pub mode: u32,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use an enum or a bitflags for mode here? I don't understand what it is for.

pub radius: [f32; 4],
pub border: [f32; 4],
pub size: [f32; 2],
}

#[derive(Resource)]
Expand Down Expand Up @@ -689,9 +740,10 @@ pub fn prepare_uinodes(
continue;
}
}
let uvs = if mode == UNTEXTURED_QUAD {
[Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
} else {
let uvs = {
// if mode == UNTEXTURED_QUAD {
// [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
// } else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth extracting the code from the block expression and doing a let uvs = […] after the if extracted_uinode.flip_y…

Also commented code worth removing.

let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max);
if extracted_uinode.flip_x {
std::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x);
Expand Down Expand Up @@ -734,7 +786,13 @@ pub fn prepare_uinodes(
position: positions_clipped[i].into(),
uv: uvs[i].into(),
color,
mode,
mode: if extracted_uinode.invert { 2 } else { mode },
radius: extracted_uinode.border_radius,
border: extracted_uinode.border,
size: extracted_uinode
.atlas_size
.unwrap_or_else(|| transformed_rect_size.xy())
.into(),
});
}

Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_ui/src/render/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ impl SpecializedRenderPipeline for UiPipeline {
VertexFormat::Float32x4,
// mode
VertexFormat::Uint32,
// radius
VertexFormat::Float32x4,
// thickness
VertexFormat::Float32x4,
// size
VertexFormat::Float32x2,
],
);
let shader_defs = Vec::new();
Expand Down