1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
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 fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
let render_size = render_size.unwrap_or_else(|| rect.size());
let rect_size = rect.size() / 2.0;
if self.border.left >= rect_size.x
|| self.border.right >= rect_size.x
|| self.border.top >= rect_size.y
|| self.border.bottom >= rect_size.y
{
bevy_log::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);
// 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,
}
}
}