Ignore clicks on uinodes outside of rounded corners (#14957)
Fixes #14941 1. Add a `resolved_border_radius` field to `Node` to hold the resolved border radius values. 2. Remove the border radius calculations from the UI's extraction functions. 4. Compute the border radius during UI relayouts in `ui_layout_system` and store them in `Node`. 5. New `pick_rounded_rect` function based on the border radius SDF from `ui.wgsl`. 6. Use `pick_rounded_rect` in `focus` and `picking_backend` to check if the pointer is hovering UI nodes with rounded corners. --- ``` cargo run --example button ``` https://github.com/user-attachments/assets/ea951a64-17ef-455e-b5c9-a2e6f6360648 Modified button example with buttons with different corner radius: ``` use bevy::{color::palettes::basic::*, prelude::*, winit::WinitSettings}; fn main() { App::new() .add_plugins(DefaultPlugins) // Only run the app when there is user input. This will significantly reduce CPU/GPU use. .insert_resource(WinitSettings::desktop_app()) .add_systems(Startup, setup) .add_systems(Update, button_system) .run(); } const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); fn button_system( mut interaction_query: Query< ( &Interaction, &mut BackgroundColor, &mut BorderColor, &Children, ), (Changed<Interaction>, With<Button>), >, mut text_query: Query<&mut Text>, ) { for (interaction, mut color, mut border_color, children) in &mut interaction_query { let mut text = text_query.get_mut(children[0]).unwrap(); match *interaction { Interaction::Pressed => { text.sections[0].value = "Press".to_string(); *color = PRESSED_BUTTON.into(); border_color.0 = RED.into(); } Interaction::Hovered => { text.sections[0].value = "Hover".to_string(); *color = HOVERED_BUTTON.into(); border_color.0 = Color::WHITE; } Interaction::None => { text.sections[0].value = "Button".to_string(); *color = NORMAL_BUTTON.into(); border_color.0 = Color::BLACK; } } } } fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { // ui camera commands.spawn(Camera2dBundle::default()); commands .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, row_gap: Val::Px(10.), ..default() }, ..default() }) .with_children(|parent| { for border_radius in [ BorderRadius { top_left: Val::ZERO, ..BorderRadius::MAX }, BorderRadius { top_right: Val::ZERO, ..BorderRadius::MAX }, BorderRadius { bottom_right: Val::ZERO, ..BorderRadius::MAX }, BorderRadius { bottom_left: Val::ZERO, ..BorderRadius::MAX }, ] { parent .spawn(ButtonBundle { style: Style { width: Val::Px(150.0), height: Val::Px(65.0), border: UiRect::all(Val::Px(5.0)), // horizontally center child text justify_content: JustifyContent::Center, // vertically center child text align_items: AlignItems::Center, ..default() }, border_color: BorderColor(Color::BLACK), border_radius, background_color: NORMAL_BUTTON.into(), ..default() }) .with_child(TextBundle::from_section( "Button", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 40.0, color: Color::srgb(0.9, 0.9, 0.9), }, )); } }); } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Matty <weatherleymatthew@gmail.com>
This commit is contained in:
parent
9afe666f6d
commit
6231ed02dd
@ -1,4 +1,6 @@
|
|||||||
use crate::{CalculatedClip, DefaultUiCamera, Node, TargetCamera, UiScale, UiStack};
|
use crate::{
|
||||||
|
CalculatedClip, DefaultUiCamera, Node, ResolvedBorderRadius, TargetCamera, UiScale, UiStack,
|
||||||
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChangesMut,
|
change_detection::DetectChangesMut,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
@ -249,13 +251,12 @@ pub fn ui_focus_system(
|
|||||||
.map(|clip| node_rect.intersect(clip.clip))
|
.map(|clip| node_rect.intersect(clip.clip))
|
||||||
.unwrap_or(node_rect);
|
.unwrap_or(node_rect);
|
||||||
|
|
||||||
|
let cursor_position = camera_cursor_positions.get(&camera_entity);
|
||||||
|
|
||||||
// The mouse position relative to the node
|
// The mouse position relative to the node
|
||||||
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
|
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
|
||||||
// Coordinates are relative to the entire node, not just the visible region.
|
// Coordinates are relative to the entire node, not just the visible region.
|
||||||
let relative_cursor_position =
|
let relative_cursor_position = cursor_position.and_then(|cursor_position| {
|
||||||
camera_cursor_positions
|
|
||||||
.get(&camera_entity)
|
|
||||||
.and_then(|cursor_position| {
|
|
||||||
// ensure node size is non-zero in all dimensions, otherwise relative position will be
|
// ensure node size is non-zero in all dimensions, otherwise relative position will be
|
||||||
// +/-inf. if the node is hidden, the visible rect min/max will also be -inf leading to
|
// +/-inf. if the node is hidden, the visible rect min/max will also be -inf leading to
|
||||||
// false positives for mouse_over (#12395)
|
// false positives for mouse_over (#12395)
|
||||||
@ -270,7 +271,16 @@ pub fn ui_focus_system(
|
|||||||
normalized: relative_cursor_position,
|
normalized: relative_cursor_position,
|
||||||
};
|
};
|
||||||
|
|
||||||
let contains_cursor = relative_cursor_position_component.mouse_over();
|
let contains_cursor = relative_cursor_position_component.mouse_over()
|
||||||
|
&& cursor_position
|
||||||
|
.map(|point| {
|
||||||
|
pick_rounded_rect(
|
||||||
|
*point - node_rect.center(),
|
||||||
|
node_rect.size(),
|
||||||
|
node.node.border_radius,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
// Save the relative cursor position to the correct component
|
// Save the relative cursor position to the correct component
|
||||||
if let Some(mut node_relative_cursor_position_component) = node.relative_cursor_position
|
if let Some(mut node_relative_cursor_position_component) = node.relative_cursor_position
|
||||||
@ -332,3 +342,18 @@ pub fn ui_focus_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pick_rounded_rect(point: Vec2, size: Vec2, border_radius: ResolvedBorderRadius) -> bool {
|
||||||
|
let s = point.signum();
|
||||||
|
let r = (border_radius.top_left * (1. - s.x) * (1. - s.y)
|
||||||
|
+ border_radius.top_right * (1. + s.x) * (1. - s.y)
|
||||||
|
+ border_radius.bottom_right * (1. + s.x) * (1. + s.y)
|
||||||
|
+ border_radius.bottom_left * (1. - s.x) * (1. + s.y))
|
||||||
|
/ 4.;
|
||||||
|
|
||||||
|
let corner_to_point = point.abs() - 0.5 * size;
|
||||||
|
let q = corner_to_point + r;
|
||||||
|
let l = q.max(Vec2::ZERO).length();
|
||||||
|
let m = q.max_element().min(0.);
|
||||||
|
l + m - r < 0.
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale};
|
use crate::{
|
||||||
|
BorderRadius, ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale,
|
||||||
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::{DetectChanges, DetectChangesMut},
|
change_detection::{DetectChanges, DetectChangesMut},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
@ -93,7 +95,12 @@ pub fn ui_layout_system(
|
|||||||
children_query: Query<(Entity, Ref<Children>), With<Node>>,
|
children_query: Query<(Entity, Ref<Children>), With<Node>>,
|
||||||
just_children_query: Query<&Children>,
|
just_children_query: Query<&Children>,
|
||||||
mut removed_components: UiLayoutSystemRemovedComponentParam,
|
mut removed_components: UiLayoutSystemRemovedComponentParam,
|
||||||
mut node_transform_query: Query<(&mut Node, &mut Transform, Option<&Outline>)>,
|
mut node_transform_query: Query<(
|
||||||
|
&mut Node,
|
||||||
|
&mut Transform,
|
||||||
|
Option<&BorderRadius>,
|
||||||
|
Option<&Outline>,
|
||||||
|
)>,
|
||||||
) {
|
) {
|
||||||
struct CameraLayoutInfo {
|
struct CameraLayoutInfo {
|
||||||
size: UVec2,
|
size: UVec2,
|
||||||
@ -246,13 +253,20 @@ pub fn ui_layout_system(
|
|||||||
entity: Entity,
|
entity: Entity,
|
||||||
ui_surface: &UiSurface,
|
ui_surface: &UiSurface,
|
||||||
root_size: Option<Vec2>,
|
root_size: Option<Vec2>,
|
||||||
node_transform_query: &mut Query<(&mut Node, &mut Transform, Option<&Outline>)>,
|
node_transform_query: &mut Query<(
|
||||||
|
&mut Node,
|
||||||
|
&mut Transform,
|
||||||
|
Option<&BorderRadius>,
|
||||||
|
Option<&Outline>,
|
||||||
|
)>,
|
||||||
children_query: &Query<&Children>,
|
children_query: &Query<&Children>,
|
||||||
inverse_target_scale_factor: f32,
|
inverse_target_scale_factor: f32,
|
||||||
parent_size: Vec2,
|
parent_size: Vec2,
|
||||||
mut absolute_location: Vec2,
|
mut absolute_location: Vec2,
|
||||||
) {
|
) {
|
||||||
if let Ok((mut node, mut transform, outline)) = node_transform_query.get_mut(entity) {
|
if let Ok((mut node, mut transform, maybe_border_radius, maybe_outline)) =
|
||||||
|
node_transform_query.get_mut(entity)
|
||||||
|
{
|
||||||
let Ok(layout) = ui_surface.get_layout(entity) else {
|
let Ok(layout) = ui_surface.get_layout(entity) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -277,7 +291,13 @@ pub fn ui_layout_system(
|
|||||||
|
|
||||||
let viewport_size = root_size.unwrap_or(node.calculated_size);
|
let viewport_size = root_size.unwrap_or(node.calculated_size);
|
||||||
|
|
||||||
if let Some(outline) = outline {
|
if let Some(border_radius) = maybe_border_radius {
|
||||||
|
// We don't trigger change detection for changes to border radius
|
||||||
|
node.bypass_change_detection().border_radius =
|
||||||
|
border_radius.resolve(node.calculated_size, viewport_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(outline) = maybe_outline {
|
||||||
// don't trigger change detection when only outlines are changed
|
// don't trigger change detection when only outlines are changed
|
||||||
let node = node.bypass_change_detection();
|
let node = node.bypass_change_detection();
|
||||||
node.outline_width = outline
|
node.outline_width = outline
|
||||||
|
@ -23,8 +23,8 @@ use ui_texture_slice_pipeline::UiTextureSlicerPlugin;
|
|||||||
|
|
||||||
use crate::graph::{NodeUi, SubGraphUi};
|
use crate::graph::{NodeUi, SubGraphUi};
|
||||||
use crate::{
|
use crate::{
|
||||||
BackgroundColor, BorderColor, BorderRadius, CalculatedClip, ContentSize, DefaultUiCamera, Node,
|
BackgroundColor, BorderColor, CalculatedClip, ContentSize, DefaultUiCamera, Node, Outline,
|
||||||
Outline, Style, TargetCamera, UiImage, UiScale, Val,
|
Style, TargetCamera, UiImage, UiScale, Val,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
@ -201,7 +201,6 @@ pub fn extract_uinode_background_colors(
|
|||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&BackgroundColor,
|
&BackgroundColor,
|
||||||
Option<&BorderRadius>,
|
|
||||||
&Style,
|
&Style,
|
||||||
Option<&Parent>,
|
Option<&Parent>,
|
||||||
)>,
|
)>,
|
||||||
@ -216,7 +215,6 @@ pub fn extract_uinode_background_colors(
|
|||||||
clip,
|
clip,
|
||||||
camera,
|
camera,
|
||||||
background_color,
|
background_color,
|
||||||
border_radius,
|
|
||||||
style,
|
style,
|
||||||
parent,
|
parent,
|
||||||
) in &uinode_query
|
) in &uinode_query
|
||||||
@ -257,16 +255,13 @@ pub fn extract_uinode_background_colors(
|
|||||||
|
|
||||||
let border = [left, top, right, bottom];
|
let border = [left, top, right, bottom];
|
||||||
|
|
||||||
let border_radius = if let Some(border_radius) = border_radius {
|
let border_radius = [
|
||||||
resolve_border_radius(
|
uinode.border_radius.top_left,
|
||||||
border_radius,
|
uinode.border_radius.top_right,
|
||||||
uinode.size(),
|
uinode.border_radius.bottom_right,
|
||||||
ui_logical_viewport_size,
|
uinode.border_radius.bottom_left,
|
||||||
ui_scale.0,
|
]
|
||||||
)
|
.map(|r| r * ui_scale.0);
|
||||||
} else {
|
|
||||||
[0.; 4]
|
|
||||||
};
|
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
entity,
|
entity,
|
||||||
@ -310,7 +305,6 @@ pub fn extract_uinode_images(
|
|||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&UiImage,
|
&UiImage,
|
||||||
Option<&TextureAtlas>,
|
Option<&TextureAtlas>,
|
||||||
Option<&BorderRadius>,
|
|
||||||
Option<&Parent>,
|
Option<&Parent>,
|
||||||
&Style,
|
&Style,
|
||||||
),
|
),
|
||||||
@ -319,18 +313,8 @@ pub fn extract_uinode_images(
|
|||||||
>,
|
>,
|
||||||
node_query: Extract<Query<&Node>>,
|
node_query: Extract<Query<&Node>>,
|
||||||
) {
|
) {
|
||||||
for (
|
for (uinode, transform, view_visibility, clip, camera, image, atlas, parent, style) in
|
||||||
uinode,
|
&uinode_query
|
||||||
transform,
|
|
||||||
view_visibility,
|
|
||||||
clip,
|
|
||||||
camera,
|
|
||||||
image,
|
|
||||||
atlas,
|
|
||||||
border_radius,
|
|
||||||
parent,
|
|
||||||
style,
|
|
||||||
) in &uinode_query
|
|
||||||
{
|
{
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||||
else {
|
else {
|
||||||
@ -391,16 +375,13 @@ pub fn extract_uinode_images(
|
|||||||
|
|
||||||
let border = [left, top, right, bottom];
|
let border = [left, top, right, bottom];
|
||||||
|
|
||||||
let border_radius = if let Some(border_radius) = border_radius {
|
let border_radius = [
|
||||||
resolve_border_radius(
|
uinode.border_radius.top_left,
|
||||||
border_radius,
|
uinode.border_radius.top_right,
|
||||||
uinode.size(),
|
uinode.border_radius.bottom_right,
|
||||||
ui_logical_viewport_size,
|
uinode.border_radius.bottom_left,
|
||||||
ui_scale.0,
|
]
|
||||||
)
|
.map(|r| r * ui_scale.0);
|
||||||
} else {
|
|
||||||
[0.; 4]
|
|
||||||
};
|
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
@ -435,33 +416,6 @@ pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_border_radius(
|
|
||||||
&values: &BorderRadius,
|
|
||||||
node_size: Vec2,
|
|
||||||
viewport_size: Vec2,
|
|
||||||
ui_scale: f32,
|
|
||||||
) -> [f32; 4] {
|
|
||||||
let max_radius = 0.5 * node_size.min_element() * ui_scale;
|
|
||||||
[
|
|
||||||
values.top_left,
|
|
||||||
values.top_right,
|
|
||||||
values.bottom_right,
|
|
||||||
values.bottom_left,
|
|
||||||
]
|
|
||||||
.map(|value| {
|
|
||||||
match value {
|
|
||||||
Val::Auto => 0.,
|
|
||||||
Val::Px(px) => ui_scale * px,
|
|
||||||
Val::Percent(percent) => node_size.min_element() * percent / 100.,
|
|
||||||
Val::Vw(percent) => viewport_size.x * percent / 100.,
|
|
||||||
Val::Vh(percent) => viewport_size.y * percent / 100.,
|
|
||||||
Val::VMin(percent) => viewport_size.min_element() * percent / 100.,
|
|
||||||
Val::VMax(percent) => viewport_size.max_element() * percent / 100.,
|
|
||||||
}
|
|
||||||
.clamp(0., max_radius)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {
|
fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {
|
||||||
let s = 0.5 * size + offset;
|
let s = 0.5 * size + offset;
|
||||||
@ -501,7 +455,6 @@ pub fn extract_uinode_borders(
|
|||||||
Option<&Parent>,
|
Option<&Parent>,
|
||||||
&Style,
|
&Style,
|
||||||
&BorderColor,
|
&BorderColor,
|
||||||
&BorderRadius,
|
|
||||||
),
|
),
|
||||||
Without<ContentSize>,
|
Without<ContentSize>,
|
||||||
>,
|
>,
|
||||||
@ -510,17 +463,8 @@ pub fn extract_uinode_borders(
|
|||||||
) {
|
) {
|
||||||
let image = AssetId::<Image>::default();
|
let image = AssetId::<Image>::default();
|
||||||
|
|
||||||
for (
|
for (uinode, global_transform, view_visibility, clip, camera, parent, style, border_color) in
|
||||||
node,
|
&uinode_query
|
||||||
global_transform,
|
|
||||||
view_visibility,
|
|
||||||
clip,
|
|
||||||
camera,
|
|
||||||
parent,
|
|
||||||
style,
|
|
||||||
border_color,
|
|
||||||
border_radius,
|
|
||||||
) in &uinode_query
|
|
||||||
{
|
{
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||||
else {
|
else {
|
||||||
@ -530,8 +474,8 @@ pub fn extract_uinode_borders(
|
|||||||
// Skip invisible borders
|
// Skip invisible borders
|
||||||
if !view_visibility.get()
|
if !view_visibility.get()
|
||||||
|| border_color.0.is_fully_transparent()
|
|| border_color.0.is_fully_transparent()
|
||||||
|| node.size().x <= 0.
|
|| uinode.size().x <= 0.
|
||||||
|| node.size().y <= 0.
|
|| uinode.size().y <= 0.
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -567,25 +511,27 @@ pub fn extract_uinode_borders(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let border_radius = resolve_border_radius(
|
let border_radius = [
|
||||||
border_radius,
|
uinode.border_radius.top_left,
|
||||||
node.size(),
|
uinode.border_radius.top_right,
|
||||||
ui_logical_viewport_size,
|
uinode.border_radius.bottom_right,
|
||||||
ui_scale.0,
|
uinode.border_radius.bottom_left,
|
||||||
);
|
]
|
||||||
|
.map(|r| r * ui_scale.0);
|
||||||
|
|
||||||
|
let border_radius = clamp_radius(border_radius, uinode.size(), border.into());
|
||||||
|
|
||||||
let border_radius = clamp_radius(border_radius, node.size(), border.into());
|
|
||||||
let transform = global_transform.compute_matrix();
|
let transform = global_transform.compute_matrix();
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
stack_index: node.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
// This translates the uinode's transform to the center of the current border rectangle
|
// This translates the uinode's transform to the center of the current border rectangle
|
||||||
transform,
|
transform,
|
||||||
color: border_color.0.into(),
|
color: border_color.0.into(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
max: node.size(),
|
max: uinode.size(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
image,
|
image,
|
||||||
|
@ -48,6 +48,11 @@ pub struct Node {
|
|||||||
///
|
///
|
||||||
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
pub(crate) unrounded_size: Vec2,
|
pub(crate) unrounded_size: Vec2,
|
||||||
|
/// Resolved border radius values in logical pixels.
|
||||||
|
/// Border radius updates bypass change detection.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
pub(crate) border_radius: ResolvedBorderRadius,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@ -122,6 +127,7 @@ impl Node {
|
|||||||
outline_width: 0.,
|
outline_width: 0.,
|
||||||
outline_offset: 0.,
|
outline_offset: 0.,
|
||||||
unrounded_size: Vec2::ZERO,
|
unrounded_size: Vec2::ZERO,
|
||||||
|
border_radius: ResolvedBorderRadius::ZERO,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2211,6 +2217,49 @@ impl BorderRadius {
|
|||||||
self.bottom_right = radius;
|
self.bottom_right = radius;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute the logical border radius for a single corner from the given values
|
||||||
|
pub fn resolve_single_corner(radius: Val, node_size: Vec2, viewport_size: Vec2) -> f32 {
|
||||||
|
match radius {
|
||||||
|
Val::Auto => 0.,
|
||||||
|
Val::Px(px) => px,
|
||||||
|
Val::Percent(percent) => node_size.min_element() * percent / 100.,
|
||||||
|
Val::Vw(percent) => viewport_size.x * percent / 100.,
|
||||||
|
Val::Vh(percent) => viewport_size.y * percent / 100.,
|
||||||
|
Val::VMin(percent) => viewport_size.min_element() * percent / 100.,
|
||||||
|
Val::VMax(percent) => viewport_size.max_element() * percent / 100.,
|
||||||
|
}
|
||||||
|
.clamp(0., 0.5 * node_size.min_element())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(&self, node_size: Vec2, viewport_size: Vec2) -> ResolvedBorderRadius {
|
||||||
|
ResolvedBorderRadius {
|
||||||
|
top_left: Self::resolve_single_corner(self.top_left, node_size, viewport_size),
|
||||||
|
top_right: Self::resolve_single_corner(self.top_right, node_size, viewport_size),
|
||||||
|
bottom_left: Self::resolve_single_corner(self.bottom_left, node_size, viewport_size),
|
||||||
|
bottom_right: Self::resolve_single_corner(self.bottom_right, node_size, viewport_size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the resolved border radius values for a UI node.
|
||||||
|
///
|
||||||
|
/// The values are in logical pixels.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
|
||||||
|
pub struct ResolvedBorderRadius {
|
||||||
|
pub top_left: f32,
|
||||||
|
pub top_right: f32,
|
||||||
|
pub bottom_left: f32,
|
||||||
|
pub bottom_right: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolvedBorderRadius {
|
||||||
|
pub const ZERO: Self = Self {
|
||||||
|
top_left: 0.,
|
||||||
|
top_right: 0.,
|
||||||
|
bottom_left: 0.,
|
||||||
|
bottom_right: 0.,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
Loading…
Reference in New Issue
Block a user