mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
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
10 changed files with 191 additions and 326 deletions
|
@ -14,6 +14,9 @@ pub struct 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
|
||||
#[must_use]
|
||||
#[inline]
|
||||
|
|
|
@ -243,7 +243,10 @@ pub fn ui_focus_system(
|
|||
.map(TargetCamera::entity)
|
||||
.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
|
||||
let visible_rect = node
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
BorderRadius, ContentSize, DefaultUiCamera, Node, Outline, OverflowAxis, ScrollPosition, Style,
|
||||
TargetCamera, UiScale,
|
||||
BorderRadius, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis,
|
||||
ScrollPosition, Style, TargetCamera, UiScale,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
change_detection::{DetectChanges, DetectChangesMut},
|
||||
|
@ -14,6 +14,7 @@ use bevy_ecs::{
|
|||
use bevy_hierarchy::{Children, Parent};
|
||||
use bevy_math::{UVec2, Vec2};
|
||||
use bevy_render::camera::{Camera, NormalizedRenderTarget};
|
||||
use bevy_sprite::BorderRect;
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::{CosmicBuffer, TextPipeline};
|
||||
use bevy_transform::components::Transform;
|
||||
|
@ -344,6 +345,13 @@ pub fn ui_layout_system(
|
|||
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);
|
||||
|
||||
if let Some(border_radius) = maybe_border_radius {
|
||||
|
@ -355,11 +363,15 @@ pub fn ui_layout_system(
|
|||
if let Some(outline) = maybe_outline {
|
||||
// don't trigger change detection when only outlines are changed
|
||||
let node = node.bypass_change_detection();
|
||||
node.outline_width = outline
|
||||
.width
|
||||
.resolve(node.size().x, viewport_size)
|
||||
.unwrap_or(0.)
|
||||
.max(0.);
|
||||
node.outline_width = if style.display != Display::None {
|
||||
outline
|
||||
.width
|
||||
.resolve(node.size().x, viewport_size)
|
||||
.unwrap_or(0.)
|
||||
.max(0.)
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
node.outline_offset = outline
|
||||
.offset
|
||||
|
@ -834,7 +846,10 @@ mod tests {
|
|||
.fold(
|
||||
Option::<(Rect, bool)>::None,
|
||||
|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!(
|
||||
current_rect.height().abs() + current_rect.width().abs() > 0.,
|
||||
"root ui node {entity:?} doesn't have a logical size"
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
use crate::{focus::pick_rounded_rect, prelude::*, UiStack};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::{prelude::*, query::QueryData};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_render::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::hashbrown::HashMap;
|
||||
|
@ -139,7 +139,10 @@ pub fn ui_picking(
|
|||
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
|
||||
if node_rect.size() == Vec2::ZERO {
|
||||
|
|
|
@ -3,43 +3,21 @@ mod render_pass;
|
|||
mod ui_material_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::{
|
||||
graph::{NodeUi, SubGraphUi},
|
||||
BackgroundColor, BorderColor, CalculatedClip, DefaultUiCamera, Display, Node, Outline, Style,
|
||||
TargetCamera, UiAntiAlias, UiImage, UiScale, Val,
|
||||
BackgroundColor, BorderColor, CalculatedClip, DefaultUiCamera, Node, Outline,
|
||||
ResolvedBorderRadius, TargetCamera, UiAntiAlias, UiImage, UiScale,
|
||||
};
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
|
||||
use bevy_ecs::{
|
||||
entity::{EntityHashMap, EntityHashSet},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
||||
use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d};
|
||||
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
|
||||
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||
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::{
|
||||
camera::Camera,
|
||||
render_asset::RenderAssets,
|
||||
|
@ -51,13 +29,25 @@ use bevy_render::{
|
|||
view::{ExtractedView, ViewUniforms},
|
||||
Extract, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_render::{
|
||||
render_phase::{PhaseItem, PhaseItemExtraIndex},
|
||||
texture::GpuImage,
|
||||
view::ViewVisibility,
|
||||
ExtractSchedule, Render,
|
||||
};
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas};
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::HashMap;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use graph::{NodeUi, SubGraphUi};
|
||||
pub use pipeline::*;
|
||||
pub use render_pass::*;
|
||||
use std::ops::Range;
|
||||
pub use ui_material_pipeline::*;
|
||||
use ui_texture_slice_pipeline::UiTextureSlicerPlugin;
|
||||
|
||||
pub mod graph {
|
||||
use bevy_render::render_graph::{RenderLabel, RenderSubGraph};
|
||||
|
@ -183,11 +173,9 @@ pub struct ExtractedUiNode {
|
|||
// Nodes with ambiguous camera will be ignored.
|
||||
pub camera_entity: Entity,
|
||||
/// Border radius of the UI node.
|
||||
/// Ordering: top left, top right, bottom right, bottom left.
|
||||
pub border_radius: [f32; 4],
|
||||
pub border_radius: ResolvedBorderRadius,
|
||||
/// Border thickness of the UI node.
|
||||
/// Ordering: left, top, right, bottom.
|
||||
pub border: [f32; 4],
|
||||
pub border: BorderRect,
|
||||
pub node_type: NodeType,
|
||||
}
|
||||
|
||||
|
@ -198,9 +186,7 @@ pub struct ExtractedUiNodes {
|
|||
|
||||
pub fn extract_uinode_background_colors(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
|
@ -210,23 +196,11 @@ pub fn extract_uinode_background_colors(
|
|||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
&BackgroundColor,
|
||||
&Style,
|
||||
Option<&Parent>,
|
||||
)>,
|
||||
>,
|
||||
node_query: Extract<Query<&Node>>,
|
||||
) {
|
||||
for (
|
||||
entity,
|
||||
uinode,
|
||||
transform,
|
||||
view_visibility,
|
||||
clip,
|
||||
camera,
|
||||
background_color,
|
||||
style,
|
||||
parent,
|
||||
) in &uinode_query
|
||||
for (entity, uinode, transform, view_visibility, clip, camera, background_color) in
|
||||
&uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
|
@ -238,39 +212,6 @@ pub fn extract_uinode_background_colors(
|
|||
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(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
|
@ -287,8 +228,8 @@ pub fn extract_uinode_background_colors(
|
|||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
border,
|
||||
border_radius,
|
||||
border: uinode.border(),
|
||||
border_radius: uinode.border_radius(),
|
||||
node_type: NodeType::Rect,
|
||||
},
|
||||
);
|
||||
|
@ -299,9 +240,7 @@ pub fn extract_uinode_background_colors(
|
|||
pub fn extract_uinode_images(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<
|
||||
|
@ -313,17 +252,12 @@ pub fn extract_uinode_images(
|
|||
Option<&TargetCamera>,
|
||||
&UiImage,
|
||||
Option<&TextureAtlas>,
|
||||
Option<&Parent>,
|
||||
&Style,
|
||||
),
|
||||
Without<ImageScaleMode>,
|
||||
>,
|
||||
>,
|
||||
node_query: Extract<Query<&Node>>,
|
||||
) {
|
||||
for (uinode, transform, view_visibility, clip, camera, image, atlas, parent, style) in
|
||||
&uinode_query
|
||||
{
|
||||
for (uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
|
@ -364,39 +298,6 @@ pub fn extract_uinode_images(
|
|||
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(
|
||||
commands.spawn_empty().id(),
|
||||
ExtractedUiNode {
|
||||
|
@ -410,54 +311,18 @@ pub fn extract_uinode_images(
|
|||
flip_x: image.flip_x,
|
||||
flip_y: image.flip_y,
|
||||
camera_entity,
|
||||
border,
|
||||
border_radius,
|
||||
border: uinode.border,
|
||||
border_radius: uinode.border_radius,
|
||||
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(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
&Node,
|
||||
|
@ -465,12 +330,9 @@ pub fn extract_uinode_borders(
|
|||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
Option<&Parent>,
|
||||
&Style,
|
||||
AnyOf<(&BorderColor, &Outline)>,
|
||||
)>,
|
||||
>,
|
||||
node_query: Extract<Query<&Node>>,
|
||||
) {
|
||||
let image = AssetId::<Image>::default();
|
||||
|
||||
|
@ -480,8 +342,6 @@ pub fn extract_uinode_borders(
|
|||
view_visibility,
|
||||
maybe_clip,
|
||||
maybe_camera,
|
||||
maybe_parent,
|
||||
style,
|
||||
(maybe_border_color, maybe_outline),
|
||||
) in &uinode_query
|
||||
{
|
||||
|
@ -494,50 +354,14 @@ pub fn extract_uinode_borders(
|
|||
|
||||
// Skip invisible borders
|
||||
if !view_visibility.get()
|
||||
|| style.display == Display::None
|
||||
|| 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())
|
||||
{
|
||||
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).
|
||||
if !uinode.is_empty() && border != [0.; 4] {
|
||||
if !uinode.is_empty() && uinode.border() != BorderRect::ZERO {
|
||||
if let Some(border_color) = maybe_border_color {
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
|
@ -555,8 +379,8 @@ pub fn extract_uinode_borders(
|
|||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
border_radius,
|
||||
border,
|
||||
border_radius: uinode.border_radius(),
|
||||
border: uinode.border(),
|
||||
node_type: NodeType::Border,
|
||||
},
|
||||
);
|
||||
|
@ -564,15 +388,7 @@ pub fn extract_uinode_borders(
|
|||
}
|
||||
|
||||
if let Some(outline) = maybe_outline {
|
||||
let outer_distance = uinode.outline_offset() + uinode.outline_width();
|
||||
let outline_radius = border_radius.map(|radius| {
|
||||
if radius > 0. {
|
||||
radius + outer_distance
|
||||
} else {
|
||||
0.
|
||||
}
|
||||
});
|
||||
let outline_size = uinode.size() + 2. * outer_distance;
|
||||
let outline_size = uinode.outlined_node_size();
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
ExtractedUiNode {
|
||||
|
@ -589,8 +405,8 @@ pub fn extract_uinode_borders(
|
|||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
border: [uinode.outline_width(); 4],
|
||||
border_radius: outline_radius,
|
||||
border: BorderRect::square(uinode.outline_width()),
|
||||
border_radius: uinode.outline_radius(),
|
||||
node_type: NodeType::Border,
|
||||
},
|
||||
);
|
||||
|
@ -775,8 +591,8 @@ pub fn extract_uinode_text(
|
|||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
border: [0.; 4],
|
||||
border_radius: [0.; 4],
|
||||
border: BorderRect::ZERO,
|
||||
border_radius: ResolvedBorderRadius::ZERO,
|
||||
node_type: NodeType::Rect,
|
||||
},
|
||||
);
|
||||
|
@ -1130,8 +946,18 @@ pub fn prepare_uinodes(
|
|||
uv: uvs[i].into(),
|
||||
color,
|
||||
flags: flags | shader_flags::CORNERS[i],
|
||||
radius: extracted_uinode.border_radius,
|
||||
border: extracted_uinode.border,
|
||||
radius: [
|
||||
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(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
|
||||
|
||||
if enabled(in.flags, BORDER) {
|
||||
return draw(in, texture_color);
|
||||
return draw(in, texture_color);
|
||||
} else {
|
||||
return draw_background(in, texture_color);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ use bevy_ecs::{
|
|||
*,
|
||||
},
|
||||
};
|
||||
use bevy_hierarchy::Parent;
|
||||
use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles};
|
||||
use bevy_render::{
|
||||
extract_component::ExtractComponentPlugin,
|
||||
|
@ -24,7 +23,6 @@ use bevy_render::{
|
|||
Extract, ExtractSchedule, Render, RenderSet,
|
||||
};
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
use bevy_window::{PrimaryWindow, Window};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
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>(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
|
||||
materials: Extract<Res<Assets<M>>>,
|
||||
|
@ -365,35 +362,20 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||
(
|
||||
Entity,
|
||||
&Node,
|
||||
&Style,
|
||||
&GlobalTransform,
|
||||
&Handle<M>,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
Option<&Parent>,
|
||||
),
|
||||
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
|
||||
let default_single_camera = default_ui_camera.get();
|
||||
|
||||
for (entity, uinode, style, transform, handle, view_visibility, clip, camera, maybe_parent) in
|
||||
uinode_query.iter()
|
||||
{
|
||||
for (entity, uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else {
|
||||
continue;
|
||||
};
|
||||
|
@ -408,25 +390,12 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||
continue;
|
||||
}
|
||||
|
||||
// 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)
|
||||
/ 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;
|
||||
let border = [
|
||||
uinode.border.left / uinode.size().x,
|
||||
uinode.border.right / uinode.size().x,
|
||||
uinode.border.top / uinode.size().y,
|
||||
uinode.border.bottom / uinode.size().y,
|
||||
];
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
|
@ -436,9 +405,9 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||
material: handle.id(),
|
||||
rect: Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
max: uinode.size(),
|
||||
},
|
||||
border: [left, right, top, bottom],
|
||||
border,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
camera_entity,
|
||||
},
|
||||
|
|
|
@ -2,13 +2,13 @@ use crate::{UiRect, Val};
|
|||
use bevy_asset::Handle;
|
||||
use bevy_color::Color;
|
||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles};
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::{Camera, RenderTarget},
|
||||
texture::{Image, TRANSPARENT_IMAGE_HANDLE},
|
||||
};
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
use bevy_sprite::BorderRect;
|
||||
use bevy_utils::warn_once;
|
||||
use bevy_window::{PrimaryWindow, WindowRef};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -48,6 +48,11 @@ pub struct Node {
|
|||
///
|
||||
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||
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.
|
||||
/// Border radius updates bypass change detection.
|
||||
///
|
||||
|
@ -72,6 +77,8 @@ impl Node {
|
|||
|
||||
/// 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.
|
||||
///
|
||||
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||
pub const fn stack_index(&self) -> u32 {
|
||||
self.stack_index
|
||||
}
|
||||
|
@ -83,54 +90,91 @@ impl Node {
|
|||
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.
|
||||
/// 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 {
|
||||
self.outline_width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// 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 {
|
||||
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 {
|
||||
|
@ -141,6 +185,7 @@ impl Node {
|
|||
outline_offset: 0.,
|
||||
unrounded_size: Vec2::ZERO,
|
||||
border_radius: ResolvedBorderRadius::ZERO,
|
||||
border: BorderRect::ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2316,7 +2361,7 @@ impl BorderRadius {
|
|||
/// Represents the resolved border radius values for a UI node.
|
||||
///
|
||||
/// The values are in logical pixels.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
|
||||
pub struct ResolvedBorderRadius {
|
||||
pub top_left: f32,
|
||||
pub top_right: f32,
|
||||
|
|
|
@ -80,7 +80,8 @@ fn update_clipping(
|
|||
// current node's clip and the inherited clip. This handles the case
|
||||
// of nested `Overflow::Hidden` nodes. If parent `clip` is not
|
||||
// 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 {
|
||||
node_rect.min.x = -f32::INFINITY;
|
||||
node_rect.max.x = f32::INFINITY;
|
||||
|
|
|
@ -77,17 +77,17 @@ fn setup(mut commands: Commands) {
|
|||
UiRect::horizontal(Val::Px(10.)),
|
||||
UiRect::vertical(Val::Px(10.)),
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
left: Val::Px(20.),
|
||||
top: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
bottom: Val::Px(20.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
right: Val::Px(10.),
|
||||
right: Val::Px(20.),
|
||||
top: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -98,7 +98,7 @@ fn setup(mut commands: Commands) {
|
|||
},
|
||||
UiRect {
|
||||
right: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
top: Val::Px(20.),
|
||||
bottom: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -109,7 +109,7 @@ fn setup(mut commands: Commands) {
|
|||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
left: Val::Px(20.),
|
||||
right: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
..Default::default()
|
||||
|
@ -117,7 +117,7 @@ fn setup(mut commands: Commands) {
|
|||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
right: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
bottom: Val::Px(20.),
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue