-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Changes from 33 commits
e772dfe
7a49efb
b86bd5a
728b866
b8f784e
3b6fa71
8cf677a
f8f035d
8c7b7e4
0a2e8b5
9c2fbc6
87e1429
661eb8a
8dc2f88
c0cf89e
26d8451
0350886
c900671
b9cc579
49618a4
e855a8b
a7cbf3a
588c0ae
eaf714d
667d69e
67630fe
d9e98eb
ae2b5c3
c95b5b4
e7d86a8
c575717
2513156
b49bdcd
8330dc7
1543c9b
8a7d9e0
9cb44bb
9db2de6
c95317b
adb8ace
d099ae4
86f851a
fbdd5a9
b6b0ca2
2560785
29139ef
91e0795
f342980
bdda002
49ce0c4
77f625c
ceda428
afe327e
1d6b976
7d908b1
0663393
87ee2c7
7963803
8d0af9d
b2cd4ae
0ceaba9
a32ef6b
19da4b1
dfebacf
4294fd9
3d2c438
d84c61e
65c92be
737ea7f
6018ab6
2adc733
e4eca30
b2896bd
5e9fab2
63b1967
c04e484
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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::{ | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)] | ||
|
@@ -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< | ||
|
@@ -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 { | ||
|
@@ -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., | ||
|
@@ -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< | ||
|
@@ -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)) = | ||
|
@@ -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< | ||
( | ||
|
@@ -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 | ||
|
@@ -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], | ||
}); | ||
}; | ||
} | ||
|
@@ -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], | ||
}); | ||
} | ||
} | ||
|
@@ -553,6 +601,9 @@ struct UiVertex { | |
pub uv: [f32; 2], | ||
pub color: [f32; 4], | ||
pub mode: u32, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to use an |
||
pub radius: [f32; 4], | ||
pub border: [f32; 4], | ||
pub size: [f32; 2], | ||
} | ||
|
||
#[derive(Resource)] | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably worth extracting the code from the block expression and doing a 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); | ||
|
@@ -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(), | ||
}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.