mirror of
https://github.com/bevyengine/bevy
synced 2025-01-05 01:38:56 +00:00
268 lines
9.8 KiB
Rust
268 lines
9.8 KiB
Rust
|
use super::{BorderRect, TextureSlice};
|
||
|
use bevy_math::{vec2, Rect, Vec2};
|
||
|
use bevy_reflect::Reflect;
|
||
|
|
||
|
/// Slices a texture using the **9-slicing** technique. This allows to reuse an image at various sizes
|
||
|
/// without needing to prepare multiple assets. The associated texture will be split into nine portions,
|
||
|
/// so that on resize the different portions scale or tile in different ways to keep the texture in proportion.
|
||
|
///
|
||
|
/// For example, when resizing a 9-sliced texture the corners will remain unscaled while the other
|
||
|
/// sections will be scaled or tiled.
|
||
|
///
|
||
|
/// See [9-sliced](https://en.wikipedia.org/wiki/9-slice_scaling) textures.
|
||
|
#[derive(Debug, Clone, Reflect)]
|
||
|
pub struct TextureSlicer {
|
||
|
/// The sprite borders, defining the 9 sections of the image
|
||
|
pub border: BorderRect,
|
||
|
/// Defines how the center part of the 9 slices will scale
|
||
|
pub center_scale_mode: SliceScaleMode,
|
||
|
/// Defines how the 4 side parts of the 9 slices will scale
|
||
|
pub sides_scale_mode: SliceScaleMode,
|
||
|
/// Defines the maximum scale of the 4 corner slices (default to `1.0`)
|
||
|
pub max_corner_scale: f32,
|
||
|
}
|
||
|
|
||
|
/// Defines how a texture slice scales when resized
|
||
|
#[derive(Debug, Copy, Clone, Default, Reflect)]
|
||
|
pub enum SliceScaleMode {
|
||
|
/// The slice will be stretched to fit the area
|
||
|
#[default]
|
||
|
Stretch,
|
||
|
/// The slice will be tiled to fit the area
|
||
|
Tile {
|
||
|
/// The slice will repeat when the ratio between the *drawing dimensions* of texture and the
|
||
|
/// *original texture size* are above `stretch_value`.
|
||
|
///
|
||
|
/// Example: `1.0` means that a 10 pixel wide image would repeat after 10 screen pixels.
|
||
|
/// `2.0` means it would repeat after 20 screen pixels.
|
||
|
///
|
||
|
/// Note: The value should be inferior or equal to `1.0` to avoid quality loss.
|
||
|
///
|
||
|
/// Note: the value will be clamped to `0.001` if lower
|
||
|
stretch_value: f32,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
impl TextureSlicer {
|
||
|
/// Computes the 4 corner slices
|
||
|
#[must_use]
|
||
|
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,
|
||
|
} = self.border;
|
||
|
let min_coef = coef.x.min(coef.y).min(self.max_corner_scale);
|
||
|
[
|
||
|
// Top Left Corner
|
||
|
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,
|
||
|
},
|
||
|
// Top Right Corner
|
||
|
TextureSlice {
|
||
|
texture_rect: Rect {
|
||
|
min: vec2(base_rect.max.x - right, base_rect.min.y),
|
||
|
max: vec2(base_rect.max.x, top),
|
||
|
},
|
||
|
draw_size: vec2(right, top) * min_coef,
|
||
|
offset: vec2(
|
||
|
render_size.x - right * min_coef,
|
||
|
render_size.y - top * min_coef,
|
||
|
) / 2.0,
|
||
|
},
|
||
|
// Bottom Left
|
||
|
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,
|
||
|
},
|
||
|
// Bottom Right Corner
|
||
|
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,
|
||
|
},
|
||
|
]
|
||
|
}
|
||
|
|
||
|
/// Computes the 2 horizontal side slices (left and right borders)
|
||
|
#[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] {
|
||
|
[
|
||
|
// left
|
||
|
TextureSlice {
|
||
|
texture_rect: Rect {
|
||
|
min: base_rect.min + vec2(0.0, self.border.top),
|
||
|
max: vec2(
|
||
|
base_rect.min.x + self.border.left,
|
||
|
base_rect.max.y - self.border.bottom,
|
||
|
),
|
||
|
},
|
||
|
draw_size: vec2(
|
||
|
bl_corner.draw_size.x,
|
||
|
render_size.y - bl_corner.draw_size.y - tl_corner.draw_size.y,
|
||
|
),
|
||
|
offset: vec2(-render_size.x + bl_corner.draw_size.x, 0.0) / 2.0,
|
||
|
},
|
||
|
// right
|
||
|
TextureSlice {
|
||
|
texture_rect: Rect {
|
||
|
min: vec2(
|
||
|
base_rect.max.x - self.border.right,
|
||
|
base_rect.min.y + self.border.bottom,
|
||
|
),
|
||
|
max: vec2(base_rect.max.x, base_rect.max.y - self.border.top),
|
||
|
},
|
||
|
draw_size: vec2(
|
||
|
br_corner.draw_size.x,
|
||
|
render_size.y - (br_corner.draw_size.y + tr_corner.draw_size.y),
|
||
|
),
|
||
|
offset: vec2(render_size.x - br_corner.draw_size.x, 0.0) / 2.0,
|
||
|
},
|
||
|
]
|
||
|
}
|
||
|
|
||
|
/// Computes the 2 vertical side slices (bottom and top borders)
|
||
|
#[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] {
|
||
|
[
|
||
|
// Bottom
|
||
|
TextureSlice {
|
||
|
texture_rect: Rect {
|
||
|
min: vec2(
|
||
|
base_rect.min.x + self.border.left,
|
||
|
base_rect.max.y - self.border.bottom,
|
||
|
),
|
||
|
max: vec2(base_rect.max.x - self.border.right, base_rect.max.y),
|
||
|
},
|
||
|
draw_size: vec2(
|
||
|
render_size.x - (bl_corner.draw_size.x + br_corner.draw_size.x),
|
||
|
bl_corner.draw_size.y,
|
||
|
),
|
||
|
offset: vec2(0.0, bl_corner.offset.y),
|
||
|
},
|
||
|
// Top
|
||
|
TextureSlice {
|
||
|
texture_rect: Rect {
|
||
|
min: base_rect.min + vec2(self.border.left, 0.0),
|
||
|
max: vec2(
|
||
|
base_rect.max.x - self.border.right,
|
||
|
base_rect.min.y + self.border.top,
|
||
|
),
|
||
|
},
|
||
|
draw_size: vec2(
|
||
|
render_size.x - (tl_corner.draw_size.x + tr_corner.draw_size.x),
|
||
|
tl_corner.draw_size.y,
|
||
|
),
|
||
|
offset: vec2(0.0, tl_corner.offset.y),
|
||
|
},
|
||
|
]
|
||
|
}
|
||
|
|
||
|
/// Slices the given `rect` into at least 9 sections. If the center and/or side parts are set to tile,
|
||
|
/// a bigger number of sections will be computed.
|
||
|
///
|
||
|
/// # Arguments
|
||
|
///
|
||
|
/// * `rect` - The section of the texture to slice in 9 parts
|
||
|
/// * `render_size` - The optional draw size of the texture. If not set the `rect` size will be used.
|
||
|
#[must_use]
|
||
|
pub(crate) fn compute_slices(
|
||
|
&self,
|
||
|
rect: Rect,
|
||
|
render_size: Option<Vec2>,
|
||
|
) -> Vec<TextureSlice> {
|
||
|
let render_size = render_size.unwrap_or_else(|| rect.size());
|
||
|
let mut slices = Vec::with_capacity(9);
|
||
|
// Corners
|
||
|
let corners = self.corner_slices(rect, render_size);
|
||
|
// Sides
|
||
|
let vertical_sides = self.vertical_side_slices(&corners, rect, render_size);
|
||
|
let horizontal_sides = self.horizontal_side_slices(&corners, rect, render_size);
|
||
|
// Center
|
||
|
let center = TextureSlice {
|
||
|
texture_rect: Rect {
|
||
|
min: rect.min + vec2(self.border.left, self.border.bottom),
|
||
|
max: vec2(rect.max.x - self.border.right, rect.max.y - self.border.top),
|
||
|
},
|
||
|
draw_size: vec2(
|
||
|
render_size.x - (corners[2].draw_size.x + corners[3].draw_size.x),
|
||
|
render_size.y - (corners[2].draw_size.y + corners[0].draw_size.y),
|
||
|
),
|
||
|
offset: Vec2::ZERO,
|
||
|
};
|
||
|
|
||
|
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,
|
||
|
}
|
||
|
}
|
||
|
}
|