Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 29 additions & 45 deletions crates/bevy_sprite/src/texture_slice/border_rect.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,56 @@
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};

/// Defines the extents of the border of a rectangle.
/// Defines border insets that shrink a rectangle from its minimum and maximum corners.
///
/// This struct is used to represent thickness or offsets from the edges
/// of a rectangle (left, right, top, and bottom), with values increasing inwards.
/// This struct is used to represent thickness or offsets from the four edges
/// of a rectangle, with values increasing inwards.
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq, Default)]
pub struct BorderRect {
/// Extent of the border along the left edge
pub left: f32,
/// Extent of the border along the right edge
pub right: f32,
/// Extent of the border along the top edge
pub top: f32,
/// Extent of the border along the bottom edge
pub bottom: f32,
/// Inset applied to the rectangle’s minimum corner
pub min_inset: Vec2,
/// Inset applied to the rectangle’s maximum corner
pub max_inset: Vec2,
}

impl BorderRect {
/// An empty border with zero thickness along each edge
pub const ZERO: Self = Self::all(0.);

/// Creates a border with the same `extent` along each edge
/// Creates a border with the same `inset` along each edge
#[must_use]
#[inline]
pub const fn all(extent: f32) -> Self {
pub const fn all(inset: f32) -> Self {
Self {
left: extent,
right: extent,
top: extent,
bottom: extent,
min_inset: Vec2::splat(inset),
max_inset: Vec2::splat(inset),
}
}

/// Creates a new border with the `left` and `right` extents equal to `horizontal`, and `top` and `bottom` extents equal to `vertical`.
/// Creates a new border with the `min.x` and `max.x` insets equal to `horizontal`, and the `min.y` and `max.y` insets equal to `vertical`.
#[must_use]
#[inline]
pub const fn axes(horizontal: f32, vertical: f32) -> Self {
let insets = Vec2::new(horizontal, vertical);
Self {
left: horizontal,
right: horizontal,
top: vertical,
bottom: vertical,
min_inset: insets,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: for consistency with the other constructors, you can assign these insets to separate Vec2::new’s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean, this?

    pub const fn axes(horizontal: f32, vertical: f32) -> Self {
        Self {
            min_inset: Vec2::new(horizontal, vertical),
            max_inset: Vec2::new(horizontal, vertical),
        }
    }

I prefer the the single Vec2 constructor version I think as less error prone, though the difference is very marginal.

Copy link
Contributor Author

@ickshonpe ickshonpe Nov 28, 2025

Choose a reason for hiding this comment

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

I also considered swapping axes parameters to a Vec2. I think it might be more ergonomic, but a vector implies direction. The inputs here are directionless, so the semantics with separate f32's feel better to me.

max_inset: insets,
}
}
}

impl From<f32> for BorderRect {
fn from(extent: f32) -> Self {
Self::all(extent)
fn from(inset: f32) -> Self {
Self::all(inset)
}
}

impl From<[f32; 4]> for BorderRect {
fn from([left, right, top, bottom]: [f32; 4]) -> Self {
fn from([min_x, max_x, min_y, max_y]: [f32; 4]) -> Self {
Self {
left,
right,
top,
bottom,
min_inset: Vec2::new(min_x, min_y),
max_inset: Vec2::new(max_x, max_y),
}
}
}
Expand All @@ -67,10 +59,8 @@ impl core::ops::Add for BorderRect {
type Output = Self;

fn add(mut self, rhs: Self) -> Self::Output {
self.left += rhs.left;
self.right += rhs.right;
self.top += rhs.top;
self.bottom += rhs.bottom;
self.min_inset += rhs.min_inset;
self.max_inset += rhs.max_inset;
self
}
}
Expand All @@ -79,10 +69,8 @@ impl core::ops::Sub for BorderRect {
type Output = Self;

fn sub(mut self, rhs: Self) -> Self::Output {
self.left -= rhs.left;
self.right -= rhs.right;
self.top -= rhs.top;
self.bottom -= rhs.bottom;
self.min_inset -= rhs.min_inset;
self.max_inset -= rhs.max_inset;
self
}
}
Expand All @@ -91,10 +79,8 @@ impl core::ops::Mul<f32> for BorderRect {
type Output = Self;

fn mul(mut self, rhs: f32) -> Self::Output {
self.left *= rhs;
self.right *= rhs;
self.top *= rhs;
self.bottom *= rhs;
self.min_inset *= rhs;
self.max_inset *= rhs;
self
}
}
Expand All @@ -103,10 +89,8 @@ impl core::ops::Div<f32> for BorderRect {
type Output = Self;

fn div(mut self, rhs: f32) -> Self::Output {
self.left /= rhs;
self.right /= rhs;
self.top /= rhs;
self.bottom /= rhs;
self.min_inset /= rhs;
self.max_inset /= rhs;
self
}
}
67 changes: 29 additions & 38 deletions crates/bevy_sprite/src/texture_slice/slicer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ impl TextureSlicer {
fn corner_slices(&self, base_rect: Rect, render_size: Vec2) -> [TextureSlice; 4] {
let coef = render_size / base_rect.size();
let BorderRect {
left,
right,
top,
bottom,
min_inset: Vec2 { x: left, y: top },
max_inset: Vec2 {
x: right,
y: bottom,
},
} = self.border;
let min_coef = coef.x.min(coef.y).min(self.max_corner_scale);
[
Expand Down Expand Up @@ -121,10 +122,10 @@ impl TextureSlicer {
// Left
TextureSlice {
texture_rect: Rect {
min: base_rect.min + vec2(0.0, self.border.top),
min: base_rect.min + vec2(0.0, self.border.min_inset.y),
max: vec2(
base_rect.min.x + self.border.left,
base_rect.max.y - self.border.bottom,
base_rect.min.x + self.border.min_inset.x,
base_rect.max.y - self.border.max_inset.y,
),
},
draw_size: vec2(
Expand All @@ -140,10 +141,10 @@ impl TextureSlicer {
TextureSlice {
texture_rect: Rect {
min: vec2(
base_rect.max.x - self.border.right,
base_rect.min.y + self.border.top,
base_rect.max.x - self.border.max_inset.x,
base_rect.min.y + self.border.min_inset.y,
),
max: base_rect.max - vec2(0.0, self.border.bottom),
max: base_rect.max - vec2(0.0, self.border.max_inset.y),
},
draw_size: vec2(
tr_corner.draw_size.x,
Expand All @@ -169,10 +170,10 @@ impl TextureSlicer {
// Top
TextureSlice {
texture_rect: Rect {
min: base_rect.min + vec2(self.border.left, 0.0),
min: base_rect.min + vec2(self.border.min_inset.x, 0.0),
max: vec2(
base_rect.max.x - self.border.right,
base_rect.min.y + self.border.top,
base_rect.max.x - self.border.max_inset.x,
base_rect.min.y + self.border.min_inset.y,
),
},
draw_size: vec2(
Expand All @@ -188,10 +189,10 @@ impl TextureSlicer {
TextureSlice {
texture_rect: Rect {
min: vec2(
base_rect.min.x + self.border.left,
base_rect.max.y - self.border.bottom,
base_rect.min.x + self.border.min_inset.x,
base_rect.max.y - self.border.max_inset.y,
),
max: base_rect.max - vec2(self.border.right, 0.0),
max: base_rect.max - vec2(self.border.max_inset.x, 0.0),
},
draw_size: vec2(
render_size.x - (bl_corner.draw_size.x + br_corner.draw_size.x),
Expand All @@ -216,8 +217,9 @@ impl TextureSlicer {
#[must_use]
pub fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
let render_size = render_size.unwrap_or_else(|| rect.size());
if self.border.left + self.border.right >= rect.size().x
|| self.border.top + self.border.bottom >= rect.size().y
if (self.border.min_inset + self.border.max_inset)
.cmpge(rect.size())
.any()
{
tracing::error!(
"TextureSlicer::border has out of bounds values. No slicing will be applied"
Expand All @@ -238,8 +240,8 @@ impl TextureSlicer {
// Center
let center = TextureSlice {
texture_rect: Rect {
min: rect.min + vec2(self.border.left, self.border.top),
max: rect.max - vec2(self.border.right, self.border.bottom),
min: rect.min + self.border.min_inset,
max: rect.max - self.border.max_inset,
},
draw_size: vec2(
render_size.x - (corners[0].draw_size.x + corners[1].draw_size.x),
Expand Down Expand Up @@ -296,12 +298,7 @@ mod test {
#[test]
fn test_horizontal_sizes_uniform() {
let slicer = TextureSlicer {
border: BorderRect {
left: 10.,
right: 10.,
top: 10.,
bottom: 10.,
},
border: BorderRect::all(10.),
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.0,
Expand Down Expand Up @@ -329,10 +326,8 @@ mod test {
fn test_horizontal_sizes_non_uniform_bigger() {
let slicer = TextureSlicer {
border: BorderRect {
left: 20.,
right: 10.,
top: 10.,
bottom: 10.,
min_inset: Vec2::new(20., 10.),
max_inset: Vec2::splat(10.),
},
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
Expand Down Expand Up @@ -361,10 +356,8 @@ mod test {
fn test_horizontal_sizes_non_uniform_smaller() {
let slicer = TextureSlicer {
border: BorderRect {
left: 5.,
right: 10.,
top: 10.,
bottom: 10.,
min_inset: Vec2::new(5., 10.),
max_inset: Vec2::splat(10.),
},
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
Expand Down Expand Up @@ -406,10 +399,8 @@ mod test {
fn test_horizontal_sizes_non_uniform_zero() {
let slicer = TextureSlicer {
border: BorderRect {
left: 0.,
right: 10.,
top: 10.,
bottom: 10.,
min_inset: Vec2::new(0., 10.),
max_inset: Vec2::splat(10.),
},
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
Expand Down
6 changes: 2 additions & 4 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,8 @@ pub fn ui_layout_system(
node.bypass_change_detection().content_size = content_size;

let taffy_rect_to_border_rect = |rect: taffy::Rect<f32>| BorderRect {
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
min_inset: Vec2::new(rect.left, rect.top),
max_inset: Vec2::new(rect.right, rect.bottom),
};

node.bypass_change_detection().border = taffy_rect_to_border_rect(layout.border);
Expand Down
15 changes: 4 additions & 11 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_camera::{visibility::Visibility, Camera, RenderTarget};
use bevy_color::{Alpha, Color};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_math::{vec4, BVec2, Rect, UVec2, Vec2, Vec4Swizzles};
use bevy_math::{BVec2, Rect, UVec2, Vec2, Vec4, Vec4Swizzles};
use bevy_reflect::prelude::*;
use bevy_sprite::BorderRect;
use bevy_utils::once;
Expand Down Expand Up @@ -196,12 +196,7 @@ impl ComputedNode {
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 b = Vec4::from((self.border.min_inset, self.border.max_inset));
let s = self.size() - b.xy() - b.zw();
ResolvedBorderRadius {
top_left: clamp_corner(self.border_radius.top_left, s, b.xy()),
Expand Down Expand Up @@ -282,10 +277,8 @@ impl ComputedNode {
OverflowClipBox::PaddingBox => self.border(),
};

clip_rect.min.x += clip_inset.left;
clip_rect.min.y += clip_inset.top;
clip_rect.max.x -= clip_inset.right;
clip_rect.max.y -= clip_inset.bottom;
clip_rect.min += clip_inset.min_inset;
clip_rect.max -= clip_inset.max_inset;

if overflow.x == OverflowAxis::Visible {
clip_rect.min.x = -f32::INFINITY;
Expand Down
6 changes: 2 additions & 4 deletions crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,8 @@ fn update_clipping(
crate::OverflowClipBox::PaddingBox => computed_node.border(),
};

clip_rect.min.x += clip_inset.left;
clip_rect.min.y += clip_inset.top;
clip_rect.max.x -= clip_inset.right + computed_node.scrollbar_size.x;
clip_rect.max.y -= clip_inset.bottom + computed_node.scrollbar_size.y;
clip_rect.min += clip_inset.min_inset;
clip_rect.max -= clip_inset.max_inset + computed_node.scrollbar_size;

clip_rect = clip_rect
.inflate(node.overflow_clip_margin.margin.max(0.) / computed_node.inverse_scale_factor);
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_ui_render/src/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,10 +864,10 @@ pub fn prepare_gradient(
gradient.border_radius.bottom_left,
],
border: [
gradient.border.left,
gradient.border.top,
gradient.border.right,
gradient.border.bottom,
gradient.border.min_inset.x,
gradient.border.min_inset.y,
gradient.border.max_inset.x,
gradient.border.max_inset.y,
],
size: rect_size.xy().into(),
g_start,
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_ui_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,12 @@ pub fn prepare_uinodes(
color,
flags: flags | shader_flags::CORNERS[i],
radius: (*border_radius).into(),
border: [border.left, border.top, border.right, border.bottom],
border: [
border.min_inset.x,
border.min_inset.y,
border.max_inset.x,
border.max_inset.y,
],
size: rect_size.into(),
point: points[i].into(),
});
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_ui_render/src/ui_material_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,10 +517,10 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
size: extracted_uinode.rect.size().into(),
radius: extracted_uinode.border_radius,
border: [
extracted_uinode.border.left,
extracted_uinode.border.top,
extracted_uinode.border.right,
extracted_uinode.border.bottom,
extracted_uinode.border.min_inset.x,
extracted_uinode.border.min_inset.y,
extracted_uinode.border.max_inset.x,
extracted_uinode.border.max_inset.y,
],
});
}
Expand Down
Loading