use super::{BorderRect, TextureSlice};
use bevy_math::{vec2, Rect, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[derive(Debug, Clone, Reflect, PartialEq)]
#[reflect(Clone, PartialEq)]
pub struct TextureSlicer {
pub border: BorderRect,
pub center_scale_mode: SliceScaleMode,
pub sides_scale_mode: SliceScaleMode,
pub max_corner_scale: f32,
}
#[derive(Debug, Copy, Clone, Default, Reflect, PartialEq)]
#[reflect(Clone, PartialEq, Default)]
pub enum SliceScaleMode {
#[default]
Stretch,
Tile {
stretch_value: f32,
},
}
impl TextureSlicer {
#[must_use]
fn corner_slices(&self, base_rect: Rect, render_size: Vec2) -> [TextureSlice; 4] {
let coef = render_size / base_rect.size();
let BorderRect {
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);
[
TextureSlice {
texture_rect: Rect {
min: base_rect.min,
max: base_rect.min + vec2(left, top),
},
draw_size: vec2(left, top) * min_coef,
offset: vec2(
-render_size.x + left * min_coef,
render_size.y - top * min_coef,
) / 2.0,
},
TextureSlice {
texture_rect: Rect {
min: vec2(base_rect.max.x - right, base_rect.min.y),
max: vec2(base_rect.max.x, base_rect.min.y + top),
},
draw_size: vec2(right, top) * min_coef,
offset: vec2(
render_size.x - right * min_coef,
render_size.y - top * min_coef,
) / 2.0,
},
TextureSlice {
texture_rect: Rect {
min: vec2(base_rect.min.x, base_rect.max.y - bottom),
max: vec2(base_rect.min.x + left, base_rect.max.y),
},
draw_size: vec2(left, bottom) * min_coef,
offset: vec2(
-render_size.x + left * min_coef,
-render_size.y + bottom * min_coef,
) / 2.0,
},
TextureSlice {
texture_rect: Rect {
min: vec2(base_rect.max.x - right, base_rect.max.y - bottom),
max: base_rect.max,
},
draw_size: vec2(right, bottom) * min_coef,
offset: vec2(
render_size.x - right * min_coef,
-render_size.y + bottom * min_coef,
) / 2.0,
},
]
}
#[must_use]
fn horizontal_side_slices(
&self,
[tl_corner, tr_corner, bl_corner, br_corner]: &[TextureSlice; 4],
base_rect: Rect,
render_size: Vec2,
) -> [TextureSlice; 2] {
[
TextureSlice {
texture_rect: Rect {
min: base_rect.min + vec2(0.0, self.border.min_inset.y),
max: vec2(
base_rect.min.x + self.border.min_inset.x,
base_rect.max.y - self.border.max_inset.y,
),
},
draw_size: vec2(
tl_corner.draw_size.x,
render_size.y - (tl_corner.draw_size.y + bl_corner.draw_size.y),
),
offset: vec2(
tl_corner.draw_size.x - render_size.x,
bl_corner.draw_size.y - tl_corner.draw_size.y,
) / 2.0,
},
TextureSlice {
texture_rect: Rect {
min: vec2(
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.max_inset.y),
},
draw_size: vec2(
tr_corner.draw_size.x,
render_size.y - (tr_corner.draw_size.y + br_corner.draw_size.y),
),
offset: vec2(
render_size.x - tr_corner.draw_size.x,
br_corner.draw_size.y - tr_corner.draw_size.y,
) / 2.0,
},
]
}
#[must_use]
fn vertical_side_slices(
&self,
[tl_corner, tr_corner, bl_corner, br_corner]: &[TextureSlice; 4],
base_rect: Rect,
render_size: Vec2,
) -> [TextureSlice; 2] {
[
TextureSlice {
texture_rect: Rect {
min: base_rect.min + vec2(self.border.min_inset.x, 0.0),
max: vec2(
base_rect.max.x - self.border.max_inset.x,
base_rect.min.y + self.border.min_inset.y,
),
},
draw_size: vec2(
render_size.x - (tl_corner.draw_size.x + tr_corner.draw_size.x),
tl_corner.draw_size.y,
),
offset: vec2(
tl_corner.draw_size.x - tr_corner.draw_size.x,
render_size.y - tl_corner.draw_size.y,
) / 2.0,
},
TextureSlice {
texture_rect: Rect {
min: vec2(
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.max_inset.x, 0.0),
},
draw_size: vec2(
render_size.x - (bl_corner.draw_size.x + br_corner.draw_size.x),
bl_corner.draw_size.y,
),
offset: vec2(
bl_corner.draw_size.x - br_corner.draw_size.x,
bl_corner.draw_size.y - render_size.y,
) / 2.0,
},
]
}
#[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.min_inset + self.border.max_inset)
.cmpge(rect.size())
.any()
{
tracing::error!(
"TextureSlicer::border has out of bounds values. No slicing will be applied"
);
return vec![TextureSlice {
texture_rect: rect,
draw_size: render_size,
offset: Vec2::ZERO,
}];
}
let mut slices = Vec::with_capacity(9);
let corners = self.corner_slices(rect, render_size);
let vertical_sides = self.vertical_side_slices(&corners, rect, render_size);
let horizontal_sides = self.horizontal_side_slices(&corners, rect, render_size);
let center = TextureSlice {
texture_rect: Rect {
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),
render_size.y - (corners[0].draw_size.y + corners[2].draw_size.y),
),
offset: vec2(vertical_sides[0].offset.x, horizontal_sides[0].offset.y),
};
slices.extend(corners);
match self.center_scale_mode {
SliceScaleMode::Stretch => {
slices.push(center);
}
SliceScaleMode::Tile { stretch_value } => {
slices.extend(center.tiled(stretch_value, (true, true)));
}
}
match self.sides_scale_mode {
SliceScaleMode::Stretch => {
slices.extend(horizontal_sides);
slices.extend(vertical_sides);
}
SliceScaleMode::Tile { stretch_value } => {
slices.extend(
horizontal_sides
.into_iter()
.flat_map(|s| s.tiled(stretch_value, (false, true))),
);
slices.extend(
vertical_sides
.into_iter()
.flat_map(|s| s.tiled(stretch_value, (true, false))),
);
}
}
slices
}
}
impl Default for TextureSlicer {
fn default() -> Self {
Self {
border: Default::default(),
center_scale_mode: Default::default(),
sides_scale_mode: Default::default(),
max_corner_scale: 1.0,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_horizontal_sizes_uniform() {
let slicer = TextureSlicer {
border: BorderRect::all(10.),
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.0,
};
let base_rect = Rect {
min: Vec2::ZERO,
max: Vec2::splat(50.),
};
let render_rect = Vec2::splat(100.);
let slices = slicer.corner_slices(base_rect, render_rect);
assert_eq!(
slices[0],
TextureSlice {
texture_rect: Rect {
min: Vec2::ZERO,
max: Vec2::splat(10.0)
},
draw_size: Vec2::new(10.0, 10.0),
offset: Vec2::new(-45.0, 45.0),
}
);
}
#[test]
fn test_horizontal_sizes_non_uniform_bigger() {
let slicer = TextureSlicer {
border: BorderRect {
min_inset: Vec2::new(20., 10.),
max_inset: Vec2::splat(10.),
},
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.0,
};
let base_rect = Rect {
min: Vec2::ZERO,
max: Vec2::splat(50.),
};
let render_rect = Vec2::splat(100.);
let slices = slicer.corner_slices(base_rect, render_rect);
assert_eq!(
slices[0],
TextureSlice {
texture_rect: Rect {
min: Vec2::ZERO,
max: Vec2::new(20.0, 10.0)
},
draw_size: Vec2::new(20.0, 10.0),
offset: Vec2::new(-40.0, 45.0),
}
);
}
#[test]
fn test_horizontal_sizes_non_uniform_smaller() {
let slicer = TextureSlicer {
border: BorderRect {
min_inset: Vec2::new(5., 10.),
max_inset: Vec2::splat(10.),
},
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.0,
};
let rect = Rect {
min: Vec2::ZERO,
max: Vec2::splat(50.),
};
let render_size = Vec2::splat(100.);
let corners = slicer.corner_slices(rect, render_size);
let vertical_sides = slicer.vertical_side_slices(&corners, rect, render_size);
assert_eq!(
corners[0],
TextureSlice {
texture_rect: Rect {
min: Vec2::ZERO,
max: Vec2::new(5.0, 10.0)
},
draw_size: Vec2::new(5.0, 10.0),
offset: Vec2::new(-47.5, 45.0),
}
);
assert_eq!(
vertical_sides[0], TextureSlice {
texture_rect: Rect {
min: Vec2::new(5.0, 0.0),
max: Vec2::new(40.0, 10.0)
},
draw_size: Vec2::new(85.0, 10.0),
offset: Vec2::new(-2.5, 45.0),
}
);
}
#[test]
fn test_horizontal_sizes_non_uniform_zero() {
let slicer = TextureSlicer {
border: BorderRect {
min_inset: Vec2::new(0., 10.),
max_inset: Vec2::splat(10.),
},
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.0,
};
let base_rect = Rect {
min: Vec2::ZERO,
max: Vec2::splat(50.),
};
let render_rect = Vec2::splat(100.);
let slices = slicer.corner_slices(base_rect, render_rect);
assert_eq!(
slices[0],
TextureSlice {
texture_rect: Rect {
min: Vec2::ZERO,
max: Vec2::new(0.0, 10.0)
},
draw_size: Vec2::new(0.0, 10.0),
offset: Vec2::new(-50.0, 45.0),
}
);
}
}