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