use precomputed border values (#15163)
# Objective Fixes #15142 ## Solution * Moved all the UI border geometry calculations that were scattered through the UI extraction functions into `ui_layout_system`. * Added a `border: BorderRect` field to `Node` to store the border size computed by `ui_layout_system`. * Use the border values returned from Taffy rather than calculate them ourselves during extraction. * Removed the `logical_rect` and `physical_rect` methods from `Node` the descriptions and namings are deceptive, it's better to create the rects manually instead. * Added a method `outline_radius` to `Node` that calculates the border radius of outlines. * For border values `ExtractedUiNode` takes `BorderRect` and `ResolvedBorderRadius` now instead of raw `[f32; 4]` values and converts them in `prepare_uinodes`. * Removed some unnecessary scaling and clamping of border values (#15142). * Added a `BorderRect::ZERO` constant. * Added an `outlined_node_size` method to `Node`. ## Testing Added some non-uniform borders to the border example. Everything seems to be in order: <img width="626" alt="nub" src="https://github.com/user-attachments/assets/258ed8b5-1a9e-4ac5-99c2-6bf25c0ef31c"> ## Migration Guide The `logical_rect` and `physical_rect` methods have been removed from `Node`. Use `Rect::from_center_size` with the translation and node size instead. The types of the fields border and border_radius of `ExtractedUiNode` have been changed to `BorderRect` and `ResolvedBorderRadius` respectively. --------- Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com> Co-authored-by: akimakinai <105044389+akimakinai@users.noreply.github.com>
This commit is contained in:
parent
35d10866b8
commit
0fe33c3bba
@ -14,6 +14,9 @@ pub struct BorderRect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BorderRect {
|
impl BorderRect {
|
||||||
|
/// An empty border with zero padding values in each direction
|
||||||
|
pub const ZERO: Self = Self::square(0.);
|
||||||
|
|
||||||
/// Creates a new border as a square, with identical pixel padding values on every direction
|
/// Creates a new border as a square, with identical pixel padding values on every direction
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -243,7 +243,10 @@ pub fn ui_focus_system(
|
|||||||
.map(TargetCamera::entity)
|
.map(TargetCamera::entity)
|
||||||
.or(default_ui_camera.get())?;
|
.or(default_ui_camera.get())?;
|
||||||
|
|
||||||
let node_rect = node.node.logical_rect(node.global_transform);
|
let node_rect = Rect::from_center_size(
|
||||||
|
node.global_transform.translation().truncate(),
|
||||||
|
node.node.size(),
|
||||||
|
);
|
||||||
|
|
||||||
// Intersect with the calculated clip rect to find the bounds of the visible region of the node
|
// Intersect with the calculated clip rect to find the bounds of the visible region of the node
|
||||||
let visible_rect = node
|
let visible_rect = node
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
BorderRadius, ContentSize, DefaultUiCamera, Node, Outline, OverflowAxis, ScrollPosition, Style,
|
BorderRadius, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis,
|
||||||
TargetCamera, UiScale,
|
ScrollPosition, Style, TargetCamera, UiScale,
|
||||||
};
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::{DetectChanges, DetectChangesMut},
|
change_detection::{DetectChanges, DetectChangesMut},
|
||||||
@ -14,6 +14,7 @@ use bevy_ecs::{
|
|||||||
use bevy_hierarchy::{Children, Parent};
|
use bevy_hierarchy::{Children, Parent};
|
||||||
use bevy_math::{UVec2, Vec2};
|
use bevy_math::{UVec2, Vec2};
|
||||||
use bevy_render::camera::{Camera, NormalizedRenderTarget};
|
use bevy_render::camera::{Camera, NormalizedRenderTarget};
|
||||||
|
use bevy_sprite::BorderRect;
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
use bevy_text::{CosmicBuffer, TextPipeline};
|
use bevy_text::{CosmicBuffer, TextPipeline};
|
||||||
use bevy_transform::components::Transform;
|
use bevy_transform::components::Transform;
|
||||||
@ -344,6 +345,13 @@ pub fn ui_layout_system(
|
|||||||
node.unrounded_size = layout_size;
|
node.unrounded_size = layout_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.bypass_change_detection().border = BorderRect {
|
||||||
|
left: layout.border.left * inverse_target_scale_factor,
|
||||||
|
right: layout.border.right * inverse_target_scale_factor,
|
||||||
|
top: layout.border.top * inverse_target_scale_factor,
|
||||||
|
bottom: layout.border.bottom * inverse_target_scale_factor,
|
||||||
|
};
|
||||||
|
|
||||||
let viewport_size = root_size.unwrap_or(node.calculated_size);
|
let viewport_size = root_size.unwrap_or(node.calculated_size);
|
||||||
|
|
||||||
if let Some(border_radius) = maybe_border_radius {
|
if let Some(border_radius) = maybe_border_radius {
|
||||||
@ -355,11 +363,15 @@ pub fn ui_layout_system(
|
|||||||
if let Some(outline) = maybe_outline {
|
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 = if style.display != Display::None {
|
||||||
.width
|
outline
|
||||||
.resolve(node.size().x, viewport_size)
|
.width
|
||||||
.unwrap_or(0.)
|
.resolve(node.size().x, viewport_size)
|
||||||
.max(0.);
|
.unwrap_or(0.)
|
||||||
|
.max(0.)
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
};
|
||||||
|
|
||||||
node.outline_offset = outline
|
node.outline_offset = outline
|
||||||
.offset
|
.offset
|
||||||
@ -834,7 +846,10 @@ mod tests {
|
|||||||
.fold(
|
.fold(
|
||||||
Option::<(Rect, bool)>::None,
|
Option::<(Rect, bool)>::None,
|
||||||
|option_rect, (entity, node, global_transform)| {
|
|option_rect, (entity, node, global_transform)| {
|
||||||
let current_rect = node.logical_rect(global_transform);
|
let current_rect = Rect::from_center_size(
|
||||||
|
global_transform.translation().truncate(),
|
||||||
|
node.size(),
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
current_rect.height().abs() + current_rect.width().abs() > 0.,
|
current_rect.height().abs() + current_rect.width().abs() > 0.,
|
||||||
"root ui node {entity:?} doesn't have a logical size"
|
"root ui node {entity:?} doesn't have a logical size"
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
use crate::{focus::pick_rounded_rect, prelude::*, UiStack};
|
use crate::{focus::pick_rounded_rect, prelude::*, UiStack};
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::{prelude::*, query::QueryData};
|
use bevy_ecs::{prelude::*, query::QueryData};
|
||||||
use bevy_math::Vec2;
|
use bevy_math::{Rect, Vec2};
|
||||||
use bevy_render::prelude::*;
|
use bevy_render::prelude::*;
|
||||||
use bevy_transform::prelude::*;
|
use bevy_transform::prelude::*;
|
||||||
use bevy_utils::hashbrown::HashMap;
|
use bevy_utils::hashbrown::HashMap;
|
||||||
@ -139,7 +139,10 @@ pub fn ui_picking(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let node_rect = node.node.logical_rect(node.global_transform);
|
let node_rect = Rect::from_center_size(
|
||||||
|
node.global_transform.translation().truncate(),
|
||||||
|
node.node.size(),
|
||||||
|
);
|
||||||
|
|
||||||
// Nodes with Display::None have a (0., 0.) logical rect and can be ignored
|
// Nodes with Display::None have a (0., 0.) logical rect and can be ignored
|
||||||
if node_rect.size() == Vec2::ZERO {
|
if node_rect.size() == Vec2::ZERO {
|
||||||
|
@ -3,43 +3,21 @@ mod render_pass;
|
|||||||
mod ui_material_pipeline;
|
mod ui_material_pipeline;
|
||||||
pub mod ui_texture_slice_pipeline;
|
pub mod ui_texture_slice_pipeline;
|
||||||
|
|
||||||
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
|
||||||
use bevy_core_pipeline::{
|
|
||||||
core_2d::{
|
|
||||||
graph::{Core2d, Node2d},
|
|
||||||
Camera2d,
|
|
||||||
},
|
|
||||||
core_3d::{
|
|
||||||
graph::{Core3d, Node3d},
|
|
||||||
Camera3d,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use bevy_hierarchy::Parent;
|
|
||||||
use bevy_render::{
|
|
||||||
render_phase::{PhaseItem, PhaseItemExtraIndex, ViewSortedRenderPhases},
|
|
||||||
texture::{GpuImage, TRANSPARENT_IMAGE_HANDLE},
|
|
||||||
view::ViewVisibility,
|
|
||||||
ExtractSchedule, Render,
|
|
||||||
};
|
|
||||||
use bevy_sprite::{ImageScaleMode, SpriteAssetEvents, TextureAtlas};
|
|
||||||
pub use pipeline::*;
|
|
||||||
pub use render_pass::*;
|
|
||||||
pub use ui_material_pipeline::*;
|
|
||||||
use ui_texture_slice_pipeline::UiTextureSlicerPlugin;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
graph::{NodeUi, SubGraphUi},
|
BackgroundColor, BorderColor, CalculatedClip, DefaultUiCamera, Node, Outline,
|
||||||
BackgroundColor, BorderColor, CalculatedClip, DefaultUiCamera, Display, Node, Outline, Style,
|
ResolvedBorderRadius, TargetCamera, UiAntiAlias, UiImage, UiScale,
|
||||||
TargetCamera, UiAntiAlias, UiImage, UiScale, Val,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
|
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
|
||||||
use bevy_ecs::{
|
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
||||||
entity::{EntityHashMap, EntityHashSet},
|
use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d};
|
||||||
prelude::*,
|
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
|
||||||
};
|
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||||
use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
use bevy_ecs::entity::{EntityHashMap, EntityHashSet};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles};
|
||||||
|
use bevy_render::render_phase::ViewSortedRenderPhases;
|
||||||
|
use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::Camera,
|
camera::Camera,
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
@ -51,13 +29,25 @@ use bevy_render::{
|
|||||||
view::{ExtractedView, ViewUniforms},
|
view::{ExtractedView, ViewUniforms},
|
||||||
Extract, RenderApp, RenderSet,
|
Extract, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
|
use bevy_render::{
|
||||||
|
render_phase::{PhaseItem, PhaseItemExtraIndex},
|
||||||
|
texture::GpuImage,
|
||||||
|
view::ViewVisibility,
|
||||||
|
ExtractSchedule, Render,
|
||||||
|
};
|
||||||
use bevy_sprite::TextureAtlasLayout;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
|
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas};
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use graph::{NodeUi, SubGraphUi};
|
||||||
|
pub use pipeline::*;
|
||||||
|
pub use render_pass::*;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
pub use ui_material_pipeline::*;
|
||||||
|
use ui_texture_slice_pipeline::UiTextureSlicerPlugin;
|
||||||
|
|
||||||
pub mod graph {
|
pub mod graph {
|
||||||
use bevy_render::render_graph::{RenderLabel, RenderSubGraph};
|
use bevy_render::render_graph::{RenderLabel, RenderSubGraph};
|
||||||
@ -183,11 +173,9 @@ pub struct ExtractedUiNode {
|
|||||||
// Nodes with ambiguous camera will be ignored.
|
// Nodes with ambiguous camera will be ignored.
|
||||||
pub camera_entity: Entity,
|
pub camera_entity: Entity,
|
||||||
/// Border radius of the UI node.
|
/// Border radius of the UI node.
|
||||||
/// Ordering: top left, top right, bottom right, bottom left.
|
pub border_radius: ResolvedBorderRadius,
|
||||||
pub border_radius: [f32; 4],
|
|
||||||
/// Border thickness of the UI node.
|
/// Border thickness of the UI node.
|
||||||
/// Ordering: left, top, right, bottom.
|
pub border: BorderRect,
|
||||||
pub border: [f32; 4],
|
|
||||||
pub node_type: NodeType,
|
pub node_type: NodeType,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,9 +186,7 @@ pub struct ExtractedUiNodes {
|
|||||||
|
|
||||||
pub fn extract_uinode_background_colors(
|
pub fn extract_uinode_background_colors(
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
|
||||||
default_ui_camera: Extract<DefaultUiCamera>,
|
default_ui_camera: Extract<DefaultUiCamera>,
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
@ -210,23 +196,11 @@ pub fn extract_uinode_background_colors(
|
|||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&BackgroundColor,
|
&BackgroundColor,
|
||||||
&Style,
|
|
||||||
Option<&Parent>,
|
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
node_query: Extract<Query<&Node>>,
|
|
||||||
) {
|
) {
|
||||||
for (
|
for (entity, uinode, transform, view_visibility, clip, camera, background_color) in
|
||||||
entity,
|
&uinode_query
|
||||||
uinode,
|
|
||||||
transform,
|
|
||||||
view_visibility,
|
|
||||||
clip,
|
|
||||||
camera,
|
|
||||||
background_color,
|
|
||||||
style,
|
|
||||||
parent,
|
|
||||||
) 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 {
|
||||||
@ -238,39 +212,6 @@ pub fn extract_uinode_background_colors(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ui_logical_viewport_size = camera_query
|
|
||||||
.get(camera_entity)
|
|
||||||
.ok()
|
|
||||||
.and_then(|(_, c)| c.logical_viewport_size())
|
|
||||||
.unwrap_or(Vec2::ZERO)
|
|
||||||
// The logical window resolution returned by `Window` only takes into account the window scale factor and not `UiScale`,
|
|
||||||
// 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
|
|
||||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
|
||||||
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 = [
|
|
||||||
uinode.border_radius.top_left,
|
|
||||||
uinode.border_radius.top_right,
|
|
||||||
uinode.border_radius.bottom_right,
|
|
||||||
uinode.border_radius.bottom_left,
|
|
||||||
];
|
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
entity,
|
entity,
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
@ -287,8 +228,8 @@ pub fn extract_uinode_background_colors(
|
|||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border,
|
border: uinode.border(),
|
||||||
border_radius,
|
border_radius: uinode.border_radius(),
|
||||||
node_type: NodeType::Rect,
|
node_type: NodeType::Rect,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -299,9 +240,7 @@ pub fn extract_uinode_background_colors(
|
|||||||
pub fn extract_uinode_images(
|
pub fn extract_uinode_images(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
|
||||||
default_ui_camera: Extract<DefaultUiCamera>,
|
default_ui_camera: Extract<DefaultUiCamera>,
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<
|
Query<
|
||||||
@ -313,17 +252,12 @@ pub fn extract_uinode_images(
|
|||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&UiImage,
|
&UiImage,
|
||||||
Option<&TextureAtlas>,
|
Option<&TextureAtlas>,
|
||||||
Option<&Parent>,
|
|
||||||
&Style,
|
|
||||||
),
|
),
|
||||||
Without<ImageScaleMode>,
|
Without<ImageScaleMode>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
node_query: Extract<Query<&Node>>,
|
|
||||||
) {
|
) {
|
||||||
for (uinode, transform, view_visibility, clip, camera, image, atlas, parent, style) in
|
for (uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query {
|
||||||
&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 {
|
||||||
continue;
|
continue;
|
||||||
@ -364,39 +298,6 @@ pub fn extract_uinode_images(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let ui_logical_viewport_size = camera_query
|
|
||||||
.get(camera_entity)
|
|
||||||
.ok()
|
|
||||||
.and_then(|(_, c)| c.logical_viewport_size())
|
|
||||||
.unwrap_or(Vec2::ZERO)
|
|
||||||
// The logical window resolution returned by `Window` only takes into account the window scale factor and not `UiScale`,
|
|
||||||
// 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
|
|
||||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
|
||||||
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 = [
|
|
||||||
uinode.border_radius.top_left,
|
|
||||||
uinode.border_radius.top_right,
|
|
||||||
uinode.border_radius.bottom_right,
|
|
||||||
uinode.border_radius.bottom_left,
|
|
||||||
];
|
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
@ -410,54 +311,18 @@ pub fn extract_uinode_images(
|
|||||||
flip_x: image.flip_x,
|
flip_x: image.flip_x,
|
||||||
flip_y: image.flip_y,
|
flip_y: image.flip_y,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border,
|
border: uinode.border,
|
||||||
border_radius,
|
border_radius: uinode.border_radius,
|
||||||
node_type: NodeType::Rect,
|
node_type: NodeType::Rect,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
|
|
||||||
match value {
|
|
||||||
Val::Auto => 0.,
|
|
||||||
Val::Px(px) => px.max(0.),
|
|
||||||
Val::Percent(percent) => (parent_width * 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.),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {
|
|
||||||
let s = 0.5 * size + offset;
|
|
||||||
let sm = s.x.min(s.y);
|
|
||||||
r.min(sm)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn clamp_radius(
|
|
||||||
[top_left, top_right, bottom_right, bottom_left]: [f32; 4],
|
|
||||||
size: Vec2,
|
|
||||||
border: Vec4,
|
|
||||||
) -> [f32; 4] {
|
|
||||||
let s = size - border.xy() - border.zw();
|
|
||||||
[
|
|
||||||
clamp_corner(top_left, s, border.xy()),
|
|
||||||
clamp_corner(top_right, s, border.zy()),
|
|
||||||
clamp_corner(bottom_right, s, border.zw()),
|
|
||||||
clamp_corner(bottom_left, s, border.xw()),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_uinode_borders(
|
pub fn extract_uinode_borders(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
|
||||||
default_ui_camera: Extract<DefaultUiCamera>,
|
default_ui_camera: Extract<DefaultUiCamera>,
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
&Node,
|
&Node,
|
||||||
@ -465,12 +330,9 @@ pub fn extract_uinode_borders(
|
|||||||
&ViewVisibility,
|
&ViewVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
Option<&Parent>,
|
|
||||||
&Style,
|
|
||||||
AnyOf<(&BorderColor, &Outline)>,
|
AnyOf<(&BorderColor, &Outline)>,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
node_query: Extract<Query<&Node>>,
|
|
||||||
) {
|
) {
|
||||||
let image = AssetId::<Image>::default();
|
let image = AssetId::<Image>::default();
|
||||||
|
|
||||||
@ -480,8 +342,6 @@ pub fn extract_uinode_borders(
|
|||||||
view_visibility,
|
view_visibility,
|
||||||
maybe_clip,
|
maybe_clip,
|
||||||
maybe_camera,
|
maybe_camera,
|
||||||
maybe_parent,
|
|
||||||
style,
|
|
||||||
(maybe_border_color, maybe_outline),
|
(maybe_border_color, maybe_outline),
|
||||||
) in &uinode_query
|
) in &uinode_query
|
||||||
{
|
{
|
||||||
@ -494,50 +354,14 @@ pub fn extract_uinode_borders(
|
|||||||
|
|
||||||
// Skip invisible borders
|
// Skip invisible borders
|
||||||
if !view_visibility.get()
|
if !view_visibility.get()
|
||||||
|| style.display == Display::None
|
|
||||||
|| maybe_border_color.is_some_and(|border_color| border_color.0.is_fully_transparent())
|
|| maybe_border_color.is_some_and(|border_color| border_color.0.is_fully_transparent())
|
||||||
&& maybe_outline.is_some_and(|outline| outline.color.is_fully_transparent())
|
&& maybe_outline.is_some_and(|outline| outline.color.is_fully_transparent())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ui_logical_viewport_size = camera_query
|
|
||||||
.get(camera_entity)
|
|
||||||
.ok()
|
|
||||||
.and_then(|(_, c)| c.logical_viewport_size())
|
|
||||||
.unwrap_or(Vec2::ZERO)
|
|
||||||
// The logical window resolution returned by `Window` only takes into account the window scale factor and not `UiScale`,
|
|
||||||
// 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
|
|
||||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
|
||||||
let parent_width = maybe_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 = [
|
|
||||||
uinode.border_radius.top_left,
|
|
||||||
uinode.border_radius.top_right,
|
|
||||||
uinode.border_radius.bottom_right,
|
|
||||||
uinode.border_radius.bottom_left,
|
|
||||||
];
|
|
||||||
|
|
||||||
let border_radius = clamp_radius(border_radius, uinode.size(), border.into());
|
|
||||||
|
|
||||||
// don't extract border if no border or the node is zero-sized (a zero sized node can still have an outline).
|
// don't extract border if no border or the node is zero-sized (a zero sized node can still have an outline).
|
||||||
if !uinode.is_empty() && border != [0.; 4] {
|
if !uinode.is_empty() && uinode.border() != BorderRect::ZERO {
|
||||||
if let Some(border_color) = maybe_border_color {
|
if let Some(border_color) = maybe_border_color {
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
@ -555,8 +379,8 @@ pub fn extract_uinode_borders(
|
|||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border_radius,
|
border_radius: uinode.border_radius(),
|
||||||
border,
|
border: uinode.border(),
|
||||||
node_type: NodeType::Border,
|
node_type: NodeType::Border,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -564,15 +388,7 @@ pub fn extract_uinode_borders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(outline) = maybe_outline {
|
if let Some(outline) = maybe_outline {
|
||||||
let outer_distance = uinode.outline_offset() + uinode.outline_width();
|
let outline_size = uinode.outlined_node_size();
|
||||||
let outline_radius = border_radius.map(|radius| {
|
|
||||||
if radius > 0. {
|
|
||||||
radius + outer_distance
|
|
||||||
} else {
|
|
||||||
0.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let outline_size = uinode.size() + 2. * outer_distance;
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
@ -589,8 +405,8 @@ pub fn extract_uinode_borders(
|
|||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border: [uinode.outline_width(); 4],
|
border: BorderRect::square(uinode.outline_width()),
|
||||||
border_radius: outline_radius,
|
border_radius: uinode.outline_radius(),
|
||||||
node_type: NodeType::Border,
|
node_type: NodeType::Border,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -775,8 +591,8 @@ pub fn extract_uinode_text(
|
|||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border: [0.; 4],
|
border: BorderRect::ZERO,
|
||||||
border_radius: [0.; 4],
|
border_radius: ResolvedBorderRadius::ZERO,
|
||||||
node_type: NodeType::Rect,
|
node_type: NodeType::Rect,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -1130,8 +946,18 @@ pub fn prepare_uinodes(
|
|||||||
uv: uvs[i].into(),
|
uv: uvs[i].into(),
|
||||||
color,
|
color,
|
||||||
flags: flags | shader_flags::CORNERS[i],
|
flags: flags | shader_flags::CORNERS[i],
|
||||||
radius: extracted_uinode.border_radius,
|
radius: [
|
||||||
border: extracted_uinode.border,
|
extracted_uinode.border_radius.top_left,
|
||||||
|
extracted_uinode.border_radius.top_right,
|
||||||
|
extracted_uinode.border_radius.bottom_right,
|
||||||
|
extracted_uinode.border_radius.bottom_left,
|
||||||
|
],
|
||||||
|
border: [
|
||||||
|
extracted_uinode.border.left,
|
||||||
|
extracted_uinode.border.top,
|
||||||
|
extracted_uinode.border.right,
|
||||||
|
extracted_uinode.border.bottom,
|
||||||
|
],
|
||||||
size: rect_size.xy().into(),
|
size: rect_size.xy().into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ use bevy_ecs::{
|
|||||||
*,
|
*,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_hierarchy::Parent;
|
|
||||||
use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles};
|
use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
extract_component::ExtractComponentPlugin,
|
extract_component::ExtractComponentPlugin,
|
||||||
@ -24,7 +23,6 @@ use bevy_render::{
|
|||||||
Extract, ExtractSchedule, Render, RenderSet,
|
Extract, ExtractSchedule, Render, RenderSet,
|
||||||
};
|
};
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
use bevy_transform::prelude::GlobalTransform;
|
||||||
use bevy_window::{PrimaryWindow, Window};
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
@ -355,7 +353,6 @@ impl<M: UiMaterial> Default for ExtractedUiMaterialNodes<M> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn extract_ui_material_nodes<M: UiMaterial>(
|
pub fn extract_ui_material_nodes<M: UiMaterial>(
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
|
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
|
||||||
materials: Extract<Res<Assets<M>>>,
|
materials: Extract<Res<Assets<M>>>,
|
||||||
@ -365,35 +362,20 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||||||
(
|
(
|
||||||
Entity,
|
Entity,
|
||||||
&Node,
|
&Node,
|
||||||
&Style,
|
|
||||||
&GlobalTransform,
|
&GlobalTransform,
|
||||||
&Handle<M>,
|
&Handle<M>,
|
||||||
&ViewVisibility,
|
&ViewVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
Option<&Parent>,
|
|
||||||
),
|
),
|
||||||
Without<BackgroundColor>,
|
Without<BackgroundColor>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
|
||||||
node_query: Extract<Query<&Node>>,
|
|
||||||
) {
|
) {
|
||||||
let ui_logical_viewport_size = windows
|
|
||||||
.get_single()
|
|
||||||
.map(Window::size)
|
|
||||||
.unwrap_or(Vec2::ZERO)
|
|
||||||
// The logical window resolution returned by `Window` only takes into account the window scale factor and not `UiScale`,
|
|
||||||
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
|
||||||
/ ui_scale.0;
|
|
||||||
|
|
||||||
// If there is only one camera, we use it as default
|
// If there is only one camera, we use it as default
|
||||||
let default_single_camera = default_ui_camera.get();
|
let default_single_camera = default_ui_camera.get();
|
||||||
|
|
||||||
for (entity, uinode, style, transform, handle, view_visibility, clip, camera, maybe_parent) in
|
for (entity, uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() {
|
||||||
uinode_query.iter()
|
|
||||||
{
|
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else {
|
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -408,25 +390,12 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
|
let border = [
|
||||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
uinode.border.left / uinode.size().x,
|
||||||
let parent_width = maybe_parent
|
uinode.border.right / uinode.size().x,
|
||||||
.and_then(|parent| node_query.get(parent.get()).ok())
|
uinode.border.top / uinode.size().y,
|
||||||
.map(|parent_node| parent_node.size().x)
|
uinode.border.bottom / uinode.size().y,
|
||||||
.unwrap_or(ui_logical_viewport_size.x);
|
];
|
||||||
|
|
||||||
let left =
|
|
||||||
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size)
|
|
||||||
/ uinode.size().x;
|
|
||||||
let right =
|
|
||||||
resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size)
|
|
||||||
/ uinode.size().x;
|
|
||||||
let top =
|
|
||||||
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size)
|
|
||||||
/ uinode.size().y;
|
|
||||||
let bottom =
|
|
||||||
resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size)
|
|
||||||
/ uinode.size().y;
|
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
entity,
|
entity,
|
||||||
@ -436,9 +405,9 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||||||
material: handle.id(),
|
material: handle.id(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
max: uinode.calculated_size,
|
max: uinode.size(),
|
||||||
},
|
},
|
||||||
border: [left, right, top, bottom],
|
border,
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
camera_entity,
|
camera_entity,
|
||||||
},
|
},
|
||||||
|
@ -2,13 +2,13 @@ use crate::{UiRect, Val};
|
|||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_color::Color;
|
use bevy_color::Color;
|
||||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{Camera, RenderTarget},
|
camera::{Camera, RenderTarget},
|
||||||
texture::{Image, TRANSPARENT_IMAGE_HANDLE},
|
texture::{Image, TRANSPARENT_IMAGE_HANDLE},
|
||||||
};
|
};
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_utils::warn_once;
|
use bevy_utils::warn_once;
|
||||||
use bevy_window::{PrimaryWindow, WindowRef};
|
use bevy_window::{PrimaryWindow, WindowRef};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@ -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 values in logical pixels
|
||||||
|
/// Border updates bypass change detection.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
pub(crate) border: BorderRect,
|
||||||
/// Resolved border radius values in logical pixels.
|
/// Resolved border radius values in logical pixels.
|
||||||
/// Border radius updates bypass change detection.
|
/// Border radius updates bypass change detection.
|
||||||
///
|
///
|
||||||
@ -72,6 +77,8 @@ impl Node {
|
|||||||
|
|
||||||
/// The order of the node in the UI layout.
|
/// The order of the node in the UI layout.
|
||||||
/// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.
|
/// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
pub const fn stack_index(&self) -> u32 {
|
pub const fn stack_index(&self) -> u32 {
|
||||||
self.stack_index
|
self.stack_index
|
||||||
}
|
}
|
||||||
@ -83,54 +90,91 @@ impl Node {
|
|||||||
self.unrounded_size
|
self.unrounded_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the size of the node in physical pixels based on the given scale factor and `UiScale`.
|
|
||||||
#[inline]
|
|
||||||
pub fn physical_size(&self, scale_factor: f32, ui_scale: f32) -> Vec2 {
|
|
||||||
Vec2::new(
|
|
||||||
self.calculated_size.x * scale_factor * ui_scale,
|
|
||||||
self.calculated_size.y * scale_factor * ui_scale,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the logical pixel coordinates of the UI node, based on its [`GlobalTransform`].
|
|
||||||
#[inline]
|
|
||||||
pub fn logical_rect(&self, transform: &GlobalTransform) -> Rect {
|
|
||||||
Rect::from_center_size(transform.translation().truncate(), self.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the physical pixel coordinates of the UI node, based on its [`GlobalTransform`] and the scale factor.
|
|
||||||
#[inline]
|
|
||||||
pub fn physical_rect(
|
|
||||||
&self,
|
|
||||||
transform: &GlobalTransform,
|
|
||||||
scale_factor: f32,
|
|
||||||
ui_scale: f32,
|
|
||||||
) -> Rect {
|
|
||||||
let rect = self.logical_rect(transform);
|
|
||||||
Rect {
|
|
||||||
min: Vec2::new(
|
|
||||||
rect.min.x * scale_factor * ui_scale,
|
|
||||||
rect.min.y * scale_factor * ui_scale,
|
|
||||||
),
|
|
||||||
max: Vec2::new(
|
|
||||||
rect.max.x * scale_factor * ui_scale,
|
|
||||||
rect.max.y * scale_factor * ui_scale,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Returns the thickness of the UI node's outline in logical pixels.
|
/// Returns the thickness of the UI node's outline in logical pixels.
|
||||||
/// If this value is negative or `0.` then no outline will be rendered.
|
/// If this value is negative or `0.` then no outline will be rendered.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
#[inline]
|
||||||
pub fn outline_width(&self) -> f32 {
|
pub fn outline_width(&self) -> f32 {
|
||||||
self.outline_width
|
self.outline_width
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Returns the amount of space between the outline and the edge of the node in logical pixels.
|
/// Returns the amount of space between the outline and the edge of the node in logical pixels.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
#[inline]
|
||||||
pub fn outline_offset(&self) -> f32 {
|
pub fn outline_offset(&self) -> f32 {
|
||||||
self.outline_offset
|
self.outline_offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the node when including its outline.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
#[inline]
|
||||||
|
pub fn outlined_node_size(&self) -> Vec2 {
|
||||||
|
self.size() + 2. * (self.outline_offset + self.outline_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the border radius for each corner of the outline
|
||||||
|
/// An outline's border radius is derived from the node's border-radius
|
||||||
|
/// so that the outline wraps the border equally at all points.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
#[inline]
|
||||||
|
pub fn outline_radius(&self) -> ResolvedBorderRadius {
|
||||||
|
let outer_distance = self.outline_width + self.outline_offset;
|
||||||
|
let compute_radius = |radius| {
|
||||||
|
if radius > 0. {
|
||||||
|
radius + outer_distance
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ResolvedBorderRadius {
|
||||||
|
top_left: compute_radius(self.border_radius.top_left),
|
||||||
|
top_right: compute_radius(self.border_radius.top_right),
|
||||||
|
bottom_left: compute_radius(self.border_radius.bottom_left),
|
||||||
|
bottom_right: compute_radius(self.border_radius.bottom_right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the thickness of the node's border on each edge in logical pixels.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
#[inline]
|
||||||
|
pub fn border(&self) -> BorderRect {
|
||||||
|
self.border
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the border radius for each of the node's corners in logical pixels.
|
||||||
|
///
|
||||||
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
|
#[inline]
|
||||||
|
pub fn border_radius(&self) -> ResolvedBorderRadius {
|
||||||
|
self.border_radius
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inner border radius for each of the node's corners in logical pixels.
|
||||||
|
pub fn inner_radius(&self) -> ResolvedBorderRadius {
|
||||||
|
fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {
|
||||||
|
let s = 0.5 * size + offset;
|
||||||
|
let sm = s.x.min(s.y);
|
||||||
|
r.min(sm)
|
||||||
|
}
|
||||||
|
let b = vec4(
|
||||||
|
self.border.left,
|
||||||
|
self.border.top,
|
||||||
|
self.border.right,
|
||||||
|
self.border.bottom,
|
||||||
|
);
|
||||||
|
let s = self.size() - b.xy() - b.zw();
|
||||||
|
ResolvedBorderRadius {
|
||||||
|
top_left: clamp_corner(self.border_radius.top_left, s, b.xy()),
|
||||||
|
top_right: clamp_corner(self.border_radius.top_right, s, b.zy()),
|
||||||
|
bottom_left: clamp_corner(self.border_radius.bottom_right, s, b.zw()),
|
||||||
|
bottom_right: clamp_corner(self.border_radius.bottom_left, s, b.xw()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@ -141,6 +185,7 @@ impl Node {
|
|||||||
outline_offset: 0.,
|
outline_offset: 0.,
|
||||||
unrounded_size: Vec2::ZERO,
|
unrounded_size: Vec2::ZERO,
|
||||||
border_radius: ResolvedBorderRadius::ZERO,
|
border_radius: ResolvedBorderRadius::ZERO,
|
||||||
|
border: BorderRect::ZERO,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2316,7 +2361,7 @@ impl BorderRadius {
|
|||||||
/// Represents the resolved border radius values for a UI node.
|
/// Represents the resolved border radius values for a UI node.
|
||||||
///
|
///
|
||||||
/// The values are in logical pixels.
|
/// The values are in logical pixels.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
|
||||||
pub struct ResolvedBorderRadius {
|
pub struct ResolvedBorderRadius {
|
||||||
pub top_left: f32,
|
pub top_left: f32,
|
||||||
pub top_right: f32,
|
pub top_right: f32,
|
||||||
|
@ -80,7 +80,8 @@ fn update_clipping(
|
|||||||
// current node's clip and the inherited clip. This handles the case
|
// current node's clip and the inherited clip. This handles the case
|
||||||
// of nested `Overflow::Hidden` nodes. If parent `clip` is not
|
// of nested `Overflow::Hidden` nodes. If parent `clip` is not
|
||||||
// defined, use the current node's clip.
|
// defined, use the current node's clip.
|
||||||
let mut node_rect = node.logical_rect(global_transform);
|
let mut node_rect =
|
||||||
|
Rect::from_center_size(global_transform.translation().truncate(), node.size());
|
||||||
if style.overflow.x == OverflowAxis::Visible {
|
if style.overflow.x == OverflowAxis::Visible {
|
||||||
node_rect.min.x = -f32::INFINITY;
|
node_rect.min.x = -f32::INFINITY;
|
||||||
node_rect.max.x = f32::INFINITY;
|
node_rect.max.x = f32::INFINITY;
|
||||||
|
@ -77,17 +77,17 @@ fn setup(mut commands: Commands) {
|
|||||||
UiRect::horizontal(Val::Px(10.)),
|
UiRect::horizontal(Val::Px(10.)),
|
||||||
UiRect::vertical(Val::Px(10.)),
|
UiRect::vertical(Val::Px(10.)),
|
||||||
UiRect {
|
UiRect {
|
||||||
left: Val::Px(10.),
|
left: Val::Px(20.),
|
||||||
top: Val::Px(10.),
|
top: Val::Px(10.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
UiRect {
|
UiRect {
|
||||||
left: Val::Px(10.),
|
left: Val::Px(10.),
|
||||||
bottom: Val::Px(10.),
|
bottom: Val::Px(20.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
UiRect {
|
UiRect {
|
||||||
right: Val::Px(10.),
|
right: Val::Px(20.),
|
||||||
top: Val::Px(10.),
|
top: Val::Px(10.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@ -98,7 +98,7 @@ fn setup(mut commands: Commands) {
|
|||||||
},
|
},
|
||||||
UiRect {
|
UiRect {
|
||||||
right: Val::Px(10.),
|
right: Val::Px(10.),
|
||||||
top: Val::Px(10.),
|
top: Val::Px(20.),
|
||||||
bottom: Val::Px(10.),
|
bottom: Val::Px(10.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@ -109,7 +109,7 @@ fn setup(mut commands: Commands) {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
UiRect {
|
UiRect {
|
||||||
left: Val::Px(10.),
|
left: Val::Px(20.),
|
||||||
right: Val::Px(10.),
|
right: Val::Px(10.),
|
||||||
top: Val::Px(10.),
|
top: Val::Px(10.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -117,7 +117,7 @@ fn setup(mut commands: Commands) {
|
|||||||
UiRect {
|
UiRect {
|
||||||
left: Val::Px(10.),
|
left: Val::Px(10.),
|
||||||
right: Val::Px(10.),
|
right: Val::Px(10.),
|
||||||
bottom: Val::Px(10.),
|
bottom: Val::Px(20.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user