1use super::{BorderRect, TextureSlice};
2use bevy_math::{vec2, Rect, Vec2};
3use bevy_reflect::{std_traits::ReflectDefault, Reflect};
4
5#[derive(Debug, Clone, Reflect, PartialEq)]
14#[reflect(Clone, PartialEq)]
15pub struct TextureSlicer {
16 pub border: BorderRect,
18 pub center_scale_mode: SliceScaleMode,
20 pub sides_scale_mode: SliceScaleMode,
22 pub max_corner_scale: f32,
24}
25
26#[derive(Debug, Copy, Clone, Default, Reflect, PartialEq)]
28#[reflect(Clone, PartialEq, Default)]
29pub enum SliceScaleMode {
30 #[default]
32 Stretch,
33 Tile {
35 stretch_value: f32,
45 },
46}
47
48impl TextureSlicer {
49 #[must_use]
51 fn corner_slices(&self, base_rect: Rect, render_size: Vec2) -> [TextureSlice; 4] {
52 let coef = render_size / base_rect.size();
53 let BorderRect {
54 min_inset: Vec2 { x: left, y: top },
55 max_inset: Vec2 {
56 x: right,
57 y: bottom,
58 },
59 } = self.border;
60 let min_coef = coef.x.min(coef.y).min(self.max_corner_scale);
61 [
62 TextureSlice {
64 texture_rect: Rect {
65 min: base_rect.min,
66 max: base_rect.min + vec2(left, top),
67 },
68 draw_size: vec2(left, top) * min_coef,
69 offset: vec2(
70 -render_size.x + left * min_coef,
71 render_size.y - top * min_coef,
72 ) / 2.0,
73 },
74 TextureSlice {
76 texture_rect: Rect {
77 min: vec2(base_rect.max.x - right, base_rect.min.y),
78 max: vec2(base_rect.max.x, base_rect.min.y + top),
79 },
80 draw_size: vec2(right, top) * min_coef,
81 offset: vec2(
82 render_size.x - right * min_coef,
83 render_size.y - top * min_coef,
84 ) / 2.0,
85 },
86 TextureSlice {
88 texture_rect: Rect {
89 min: vec2(base_rect.min.x, base_rect.max.y - bottom),
90 max: vec2(base_rect.min.x + left, base_rect.max.y),
91 },
92 draw_size: vec2(left, bottom) * min_coef,
93 offset: vec2(
94 -render_size.x + left * min_coef,
95 -render_size.y + bottom * min_coef,
96 ) / 2.0,
97 },
98 TextureSlice {
100 texture_rect: Rect {
101 min: vec2(base_rect.max.x - right, base_rect.max.y - bottom),
102 max: base_rect.max,
103 },
104 draw_size: vec2(right, bottom) * min_coef,
105 offset: vec2(
106 render_size.x - right * min_coef,
107 -render_size.y + bottom * min_coef,
108 ) / 2.0,
109 },
110 ]
111 }
112
113 #[must_use]
115 fn horizontal_side_slices(
116 &self,
117 [tl_corner, tr_corner, bl_corner, br_corner]: &[TextureSlice; 4],
118 base_rect: Rect,
119 render_size: Vec2,
120 ) -> [TextureSlice; 2] {
121 [
122 TextureSlice {
124 texture_rect: Rect {
125 min: base_rect.min + vec2(0.0, self.border.min_inset.y),
126 max: vec2(
127 base_rect.min.x + self.border.min_inset.x,
128 base_rect.max.y - self.border.max_inset.y,
129 ),
130 },
131 draw_size: vec2(
132 tl_corner.draw_size.x,
133 render_size.y - (tl_corner.draw_size.y + bl_corner.draw_size.y),
134 ),
135 offset: vec2(
136 tl_corner.draw_size.x - render_size.x,
137 bl_corner.draw_size.y - tl_corner.draw_size.y,
138 ) / 2.0,
139 },
140 TextureSlice {
142 texture_rect: Rect {
143 min: vec2(
144 base_rect.max.x - self.border.max_inset.x,
145 base_rect.min.y + self.border.min_inset.y,
146 ),
147 max: base_rect.max - vec2(0.0, self.border.max_inset.y),
148 },
149 draw_size: vec2(
150 tr_corner.draw_size.x,
151 render_size.y - (tr_corner.draw_size.y + br_corner.draw_size.y),
152 ),
153 offset: vec2(
154 render_size.x - tr_corner.draw_size.x,
155 br_corner.draw_size.y - tr_corner.draw_size.y,
156 ) / 2.0,
157 },
158 ]
159 }
160
161 #[must_use]
163 fn vertical_side_slices(
164 &self,
165 [tl_corner, tr_corner, bl_corner, br_corner]: &[TextureSlice; 4],
166 base_rect: Rect,
167 render_size: Vec2,
168 ) -> [TextureSlice; 2] {
169 [
170 TextureSlice {
172 texture_rect: Rect {
173 min: base_rect.min + vec2(self.border.min_inset.x, 0.0),
174 max: vec2(
175 base_rect.max.x - self.border.max_inset.x,
176 base_rect.min.y + self.border.min_inset.y,
177 ),
178 },
179 draw_size: vec2(
180 render_size.x - (tl_corner.draw_size.x + tr_corner.draw_size.x),
181 tl_corner.draw_size.y,
182 ),
183 offset: vec2(
184 tl_corner.draw_size.x - tr_corner.draw_size.x,
185 render_size.y - tl_corner.draw_size.y,
186 ) / 2.0,
187 },
188 TextureSlice {
190 texture_rect: Rect {
191 min: vec2(
192 base_rect.min.x + self.border.min_inset.x,
193 base_rect.max.y - self.border.max_inset.y,
194 ),
195 max: base_rect.max - vec2(self.border.max_inset.x, 0.0),
196 },
197 draw_size: vec2(
198 render_size.x - (bl_corner.draw_size.x + br_corner.draw_size.x),
199 bl_corner.draw_size.y,
200 ),
201 offset: vec2(
202 bl_corner.draw_size.x - br_corner.draw_size.x,
203 bl_corner.draw_size.y - render_size.y,
204 ) / 2.0,
205 },
206 ]
207 }
208
209 #[must_use]
218 pub fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
219 let render_size = render_size.unwrap_or_else(|| rect.size());
220 if (self.border.min_inset + self.border.max_inset)
221 .cmpge(rect.size())
222 .any()
223 {
224 tracing::error!(
225 "TextureSlicer::border has out of bounds values. No slicing will be applied"
226 );
227 return vec![TextureSlice {
228 texture_rect: rect,
229 draw_size: render_size,
230 offset: Vec2::ZERO,
231 }];
232 }
233 let mut slices = Vec::with_capacity(9);
234 let corners = self.corner_slices(rect, render_size);
236 let vertical_sides = self.vertical_side_slices(&corners, rect, render_size);
238 let horizontal_sides = self.horizontal_side_slices(&corners, rect, render_size);
240 let center = TextureSlice {
242 texture_rect: Rect {
243 min: rect.min + self.border.min_inset,
244 max: rect.max - self.border.max_inset,
245 },
246 draw_size: vec2(
247 render_size.x - (corners[0].draw_size.x + corners[1].draw_size.x),
248 render_size.y - (corners[0].draw_size.y + corners[2].draw_size.y),
249 ),
250 offset: vec2(vertical_sides[0].offset.x, horizontal_sides[0].offset.y),
251 };
252
253 slices.extend(corners);
254 match self.center_scale_mode {
255 SliceScaleMode::Stretch => {
256 slices.push(center);
257 }
258 SliceScaleMode::Tile { stretch_value } => {
259 slices.extend(center.tiled(stretch_value, (true, true)));
260 }
261 }
262 match self.sides_scale_mode {
263 SliceScaleMode::Stretch => {
264 slices.extend(horizontal_sides);
265 slices.extend(vertical_sides);
266 }
267 SliceScaleMode::Tile { stretch_value } => {
268 slices.extend(
269 horizontal_sides
270 .into_iter()
271 .flat_map(|s| s.tiled(stretch_value, (false, true))),
272 );
273 slices.extend(
274 vertical_sides
275 .into_iter()
276 .flat_map(|s| s.tiled(stretch_value, (true, false))),
277 );
278 }
279 }
280 slices
281 }
282}
283
284impl Default for TextureSlicer {
285 fn default() -> Self {
286 Self {
287 border: Default::default(),
288 center_scale_mode: Default::default(),
289 sides_scale_mode: Default::default(),
290 max_corner_scale: 1.0,
291 }
292 }
293}
294
295#[cfg(test)]
296mod test {
297 use super::*;
298 #[test]
299 fn test_horizontal_sizes_uniform() {
300 let slicer = TextureSlicer {
301 border: BorderRect::all(10.),
302 center_scale_mode: SliceScaleMode::Stretch,
303 sides_scale_mode: SliceScaleMode::Stretch,
304 max_corner_scale: 1.0,
305 };
306 let base_rect = Rect {
307 min: Vec2::ZERO,
308 max: Vec2::splat(50.),
309 };
310 let render_rect = Vec2::splat(100.);
311 let slices = slicer.corner_slices(base_rect, render_rect);
312 assert_eq!(
313 slices[0],
314 TextureSlice {
315 texture_rect: Rect {
316 min: Vec2::ZERO,
317 max: Vec2::splat(10.0)
318 },
319 draw_size: Vec2::new(10.0, 10.0),
320 offset: Vec2::new(-45.0, 45.0),
321 }
322 );
323 }
324
325 #[test]
326 fn test_horizontal_sizes_non_uniform_bigger() {
327 let slicer = TextureSlicer {
328 border: BorderRect {
329 min_inset: Vec2::new(20., 10.),
330 max_inset: Vec2::splat(10.),
331 },
332 center_scale_mode: SliceScaleMode::Stretch,
333 sides_scale_mode: SliceScaleMode::Stretch,
334 max_corner_scale: 1.0,
335 };
336 let base_rect = Rect {
337 min: Vec2::ZERO,
338 max: Vec2::splat(50.),
339 };
340 let render_rect = Vec2::splat(100.);
341 let slices = slicer.corner_slices(base_rect, render_rect);
342 assert_eq!(
343 slices[0],
344 TextureSlice {
345 texture_rect: Rect {
346 min: Vec2::ZERO,
347 max: Vec2::new(20.0, 10.0)
348 },
349 draw_size: Vec2::new(20.0, 10.0),
350 offset: Vec2::new(-40.0, 45.0),
351 }
352 );
353 }
354
355 #[test]
356 fn test_horizontal_sizes_non_uniform_smaller() {
357 let slicer = TextureSlicer {
358 border: BorderRect {
359 min_inset: Vec2::new(5., 10.),
360 max_inset: Vec2::splat(10.),
361 },
362 center_scale_mode: SliceScaleMode::Stretch,
363 sides_scale_mode: SliceScaleMode::Stretch,
364 max_corner_scale: 1.0,
365 };
366 let rect = Rect {
367 min: Vec2::ZERO,
368 max: Vec2::splat(50.),
369 };
370 let render_size = Vec2::splat(100.);
371 let corners = slicer.corner_slices(rect, render_size);
372
373 let vertical_sides = slicer.vertical_side_slices(&corners, rect, render_size);
374 assert_eq!(
375 corners[0],
376 TextureSlice {
377 texture_rect: Rect {
378 min: Vec2::ZERO,
379 max: Vec2::new(5.0, 10.0)
380 },
381 draw_size: Vec2::new(5.0, 10.0),
382 offset: Vec2::new(-47.5, 45.0),
383 }
384 );
385 assert_eq!(
386 vertical_sides[0], TextureSlice {
388 texture_rect: Rect {
389 min: Vec2::new(5.0, 0.0),
390 max: Vec2::new(40.0, 10.0)
391 },
392 draw_size: Vec2::new(85.0, 10.0),
393 offset: Vec2::new(-2.5, 45.0),
394 }
395 );
396 }
397
398 #[test]
399 fn test_horizontal_sizes_non_uniform_zero() {
400 let slicer = TextureSlicer {
401 border: BorderRect {
402 min_inset: Vec2::new(0., 10.),
403 max_inset: Vec2::splat(10.),
404 },
405 center_scale_mode: SliceScaleMode::Stretch,
406 sides_scale_mode: SliceScaleMode::Stretch,
407 max_corner_scale: 1.0,
408 };
409 let base_rect = Rect {
410 min: Vec2::ZERO,
411 max: Vec2::splat(50.),
412 };
413 let render_rect = Vec2::splat(100.);
414 let slices = slicer.corner_slices(base_rect, render_rect);
415 assert_eq!(
416 slices[0],
417 TextureSlice {
418 texture_rect: Rect {
419 min: Vec2::ZERO,
420 max: Vec2::new(0.0, 10.0)
421 },
422 draw_size: Vec2::new(0.0, 10.0),
423 offset: Vec2::new(-50.0, 45.0),
424 }
425 );
426 }
427}