1use skia_safe::Rect;
2
3#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct BoundingBox {
6 pub x: f32,
8 pub y: f32,
10 pub w: f32,
12 pub h: f32,
14}
15
16impl std::fmt::Display for BoundingBox {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "{{ x: {:?}, y: {:?}, w: {:?}, h:{:?} }}", self.x, self.y, self.w, self.h)
19 }
20}
21
22impl Default for BoundingBox {
23 fn default() -> Self {
24 Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0 }
25 }
26}
27
28impl BoundingBox {
29 #[inline(always)]
31 pub fn from_min_max(min_x: f32, min_y: f32, max_x: f32, max_y: f32) -> BoundingBox {
32 BoundingBox { x: min_x, y: min_y, w: max_x - min_x, h: max_y - min_y }
33 }
34
35 #[inline(always)]
37 pub fn left(&self) -> f32 {
38 self.x
39 }
40
41 #[inline(always)]
43 pub fn top(&self) -> f32 {
44 self.y
45 }
46
47 #[inline(always)]
49 pub fn width(&self) -> f32 {
50 self.w
51 }
52
53 #[inline(always)]
55 pub fn height(&self) -> f32 {
56 self.h
57 }
58
59 #[inline(always)]
61 pub fn right(&self) -> f32 {
62 self.left() + self.width()
63 }
64
65 #[inline(always)]
67 pub fn bottom(&self) -> f32 {
68 self.top() + self.height()
69 }
70
71 #[inline(always)]
73 pub fn center(&self) -> (f32, f32) {
74 ((self.width() / 2f32) + self.x, (self.height() / 2f32) + self.y)
75 }
76
77 #[inline(always)]
79 pub fn center_left(&self) -> (f32, f32) {
80 (self.left(), (self.height() / 2f32) + self.top())
81 }
82
83 #[inline(always)]
85 pub fn center_right(&self) -> (f32, f32) {
86 (self.right(), (self.height() / 2f32) + self.top())
87 }
88
89 #[inline(always)]
91 pub fn center_top(&self) -> (f32, f32) {
92 ((self.width() / 2f32) + self.left(), self.top())
93 }
94
95 #[inline(always)]
97 pub fn center_bottom(&self) -> (f32, f32) {
98 ((self.width() / 2f32) + self.left(), self.bottom())
99 }
100
101 #[inline(always)]
103 pub fn bottom_left(&self) -> (f32, f32) {
104 (self.left(), self.bottom())
105 }
106
107 #[inline(always)]
109 pub fn bottom_right(&self) -> (f32, f32) {
110 (self.right(), self.bottom())
111 }
112
113 #[inline(always)]
115 pub fn top_left(&self) -> (f32, f32) {
116 (self.left(), self.top())
117 }
118
119 #[inline(always)]
121 pub fn top_right(&self) -> (f32, f32) {
122 (self.right(), self.top())
123 }
124
125 #[inline(always)]
127 #[must_use]
128 pub fn shrink(&self, amount: f32) -> BoundingBox {
129 BoundingBox::from_min_max(
130 self.left() + amount,
131 self.top() + amount,
132 self.right() - amount,
133 self.bottom() - amount,
134 )
135 }
136
137 #[inline(always)]
139 #[must_use]
140 pub fn shrink_horizontal(&self, amount: f32) -> BoundingBox {
141 BoundingBox::from_min_max(
142 self.left() + amount,
143 self.top(),
144 self.right() - amount,
145 self.bottom(),
146 )
147 }
148
149 #[inline(always)]
151 #[must_use]
152 pub fn shrink_vertical(&self, amount: f32) -> BoundingBox {
153 BoundingBox::from_min_max(
154 self.left(),
155 self.top() + amount,
156 self.right(),
157 self.bottom() - amount,
158 )
159 }
160
161 pub fn shrink_sides(&self, left: f32, top: f32, right: f32, bottom: f32) -> BoundingBox {
163 BoundingBox::from_min_max(
164 self.left() + left,
165 self.top() + top,
166 self.right() - right,
167 self.bottom() - bottom,
168 )
169 }
170
171 pub fn expand_sides(&self, left: f32, top: f32, right: f32, bottom: f32) -> BoundingBox {
173 BoundingBox::from_min_max(
174 self.left() - left,
175 self.top() - top,
176 self.right() + right,
177 self.bottom() + bottom,
178 )
179 }
180
181 pub fn offset(&self, x: f32, y: f32) -> BoundingBox {
183 BoundingBox::from_min_max(
184 self.left() + x,
185 self.top() + y,
186 self.right() + x,
187 self.bottom() + y,
188 )
189 }
190
191 #[inline(always)]
193 #[must_use]
194 pub fn expand(&self, amount: f32) -> BoundingBox {
195 BoundingBox::from_min_max(
196 self.left() - amount,
197 self.top() - amount,
198 self.right() + amount,
199 self.bottom() + amount,
200 )
201 }
202
203 #[inline(always)]
205 #[must_use]
206 pub fn expand_horizontal(&self, amount: f32) -> BoundingBox {
207 BoundingBox::from_min_max(
208 self.left() - amount,
209 self.top(),
210 self.right() + amount,
211 self.bottom(),
212 )
213 }
214
215 #[inline(always)]
217 #[must_use]
218 pub fn expand_vertical(&self, amount: f32) -> BoundingBox {
219 BoundingBox::from_min_max(
220 self.left(),
221 self.top() - amount,
222 self.right(),
223 self.bottom() + amount,
224 )
225 }
226
227 pub fn intersection(&self, other: &Self) -> Self {
229 let left = self.left().max(other.left());
230 let right = self.right().min(other.right());
231 let top = self.top().max(other.top());
232 let bottom = self.bottom().min(other.bottom());
233 BoundingBox::from_min_max(left, top, right, bottom)
234 }
235
236 pub fn union(&self, other: &Self) -> Self {
238 let left = self.left().min(other.left());
239 let right = self.right().max(other.right());
240 let top = self.top().min(other.top());
241 let bottom = self.bottom().max(other.bottom());
242 BoundingBox::from_min_max(left, top, right, bottom)
243 }
244
245 pub fn intersects(&self, other: &Self) -> bool {
247 let x_hit = (self.x >= other.x && self.x < other.x + other.w)
248 || (other.x >= self.x && other.x < self.x + self.w);
249 let y_hit = (self.y >= other.y && self.y < other.y + other.h)
250 || (other.y >= self.y && other.y < self.y + self.h);
251 x_hit && y_hit
252 }
253
254 pub fn contains(&self, other: &Self) -> bool {
256 let x_hit = other.x >= self.x && other.x + other.w < self.x + self.w;
257 let y_hit = other.y >= self.y && other.y + other.h < self.y + self.h;
258 x_hit && y_hit
259 }
260
261 pub fn contains_point(&self, x: f32, y: f32) -> bool {
263 let x_hit = x >= self.x && x < self.x + self.w;
264 let y_hit = y >= self.y && y < self.y + self.h;
265 x_hit && y_hit
266 }
267
268 pub fn diagonal(&self) -> f32 {
270 (self.width() * self.width() + self.height() * self.height()).sqrt()
271 }
272
273 }
279
280impl From<BoundingBox> for skia_safe::Rect {
281 fn from(bb: BoundingBox) -> Self {
282 skia_safe::Rect { left: bb.left(), top: bb.top(), right: bb.right(), bottom: bb.bottom() }
283 }
284}
285
286impl From<skia_safe::Rect> for BoundingBox {
287 fn from(bb: Rect) -> Self {
288 BoundingBox { x: bb.left(), y: bb.top(), w: bb.width(), h: bb.height() }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 fn rect() -> BoundingBox {
296 BoundingBox { x: 100f32, y: 100f32, w: 100f32, h: 100f32 }
297 }
298
299 #[test]
300 fn get_center() {
301 let rect = rect();
302 assert_eq!(rect.center(), (150f32, 150f32));
303 }
304
305 #[test]
306 fn get_center_top() {
307 let rect = rect();
308 assert_eq!(rect.center_top(), (150f32, 100f32));
309 }
310
311 #[test]
312 fn get_center_bottom() {
313 let rect = rect();
314 assert_eq!(rect.center_bottom(), (150f32, 200f32));
315 }
316
317 #[test]
318 fn get_center_left() {
319 let rect = rect();
320 assert_eq!(rect.center_left(), (100f32, 150f32));
321 }
322
323 #[test]
324 fn get_center_right() {
325 let rect = rect();
326 assert_eq!(rect.center_right(), (200f32, 150f32));
327 }
328
329 #[test]
330 fn get_left() {
331 let rect = rect();
332 assert_eq!(rect.left(), 100f32);
333 }
334
335 #[test]
336 fn get_right() {
337 let rect = rect();
338 assert_eq!(rect.right(), 200f32);
339 }
340
341 #[test]
342 fn get_top() {
343 let rect = rect();
344 assert_eq!(rect.top(), 100f32);
345 }
346
347 #[test]
348 fn get_bottom() {
349 let rect = rect();
350 assert_eq!(rect.bottom(), 200f32);
351 }
352
353 #[test]
354 fn get_shrunken() {
355 let rect = rect();
356 let a = rect.shrink(25f32);
357 let b = BoundingBox { x: 125f32, y: 125f32, w: 50f32, h: 50f32 };
358 assert_eq!(a, b);
359 }
360
361 #[test]
362 fn get_shrunken_horizontal() {
363 let rect = rect();
364 let a = rect.shrink_horizontal(25f32);
365 let b = BoundingBox { x: 125f32, y: 100f32, w: 50f32, h: 100f32 };
366 assert_eq!(a, b);
367 }
368
369 #[test]
370 fn get_shrunken_vertical() {
371 let rect = rect();
372 let a = rect.shrink_vertical(25f32);
373 let b = BoundingBox { x: 100f32, y: 125f32, w: 100f32, h: 50f32 };
374 assert_eq!(a, b);
375 }
376
377 #[test]
378 fn get_expanded() {
379 let rect = rect();
380 let a = rect.expand(25f32);
381 let b = BoundingBox { x: 75f32, y: 75f32, w: 150f32, h: 150f32 };
382 assert_eq!(a, b);
383 }
384
385 #[test]
386 fn get_expanded_horizontal() {
387 let rect = rect();
388 let a = rect.expand_horizontal(25f32);
389 let b = BoundingBox { x: 75f32, y: 100f32, w: 150f32, h: 100f32 };
390 assert_eq!(a, b);
391 }
392
393 #[test]
394 fn get_expanded_vertical() {
395 let rect = rect();
396 let a = rect.expand_vertical(25f32);
397 let b = BoundingBox { x: 100f32, y: 75f32, w: 100f32, h: 150f32 };
398 assert_eq!(a, b);
399 }
400}