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