1use serde::{Deserialize, Serialize};
32use std::ops::{Add, Sub};
33
34#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
36pub struct Point {
37 pub x: f32,
39 pub y: f32,
41}
42
43impl Point {
44 pub const ORIGIN: Self = Self { x: 0.0, y: 0.0 };
46
47 #[must_use]
49 pub const fn new(x: f32, y: f32) -> Self {
50 Self { x, y }
51 }
52
53 #[must_use]
55 pub fn distance(&self, other: &Self) -> f32 {
56 let dx = self.x - other.x;
57 let dy = self.y - other.y;
58 dx.hypot(dy)
59 }
60
61 #[must_use]
63 pub fn lerp(&self, other: &Self, t: f32) -> Self {
64 Self::new(
65 (other.x - self.x).mul_add(t, self.x),
66 (other.y - self.y).mul_add(t, self.y),
67 )
68 }
69}
70
71impl Default for Point {
72 fn default() -> Self {
73 Self::ORIGIN
74 }
75}
76
77impl Add for Point {
78 type Output = Self;
79
80 fn add(self, rhs: Self) -> Self::Output {
81 Self::new(self.x + rhs.x, self.y + rhs.y)
82 }
83}
84
85impl Sub for Point {
86 type Output = Self;
87
88 fn sub(self, rhs: Self) -> Self::Output {
89 Self::new(self.x - rhs.x, self.y - rhs.y)
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
95pub struct Size {
96 pub width: f32,
98 pub height: f32,
100}
101
102impl Size {
103 pub const ZERO: Self = Self {
105 width: 0.0,
106 height: 0.0,
107 };
108
109 #[must_use]
111 pub const fn new(width: f32, height: f32) -> Self {
112 Self { width, height }
113 }
114
115 #[must_use]
117 pub fn area(&self) -> f32 {
118 self.width * self.height
119 }
120
121 #[must_use]
123 pub fn aspect_ratio(&self) -> f32 {
124 if self.height == 0.0 {
125 0.0
126 } else {
127 self.width / self.height
128 }
129 }
130
131 #[must_use]
133 pub fn contains(&self, other: &Self) -> bool {
134 self.width >= other.width && self.height >= other.height
135 }
136
137 #[must_use]
139 pub fn scale(&self, factor: f32) -> Self {
140 Self::new(self.width * factor, self.height * factor)
141 }
142}
143
144impl Default for Size {
145 fn default() -> Self {
146 Self::ZERO
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
152pub struct Rect {
153 pub x: f32,
155 pub y: f32,
157 pub width: f32,
159 pub height: f32,
161}
162
163impl Rect {
164 #[must_use]
166 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
167 Self {
168 x,
169 y,
170 width,
171 height,
172 }
173 }
174
175 #[must_use]
177 pub fn from_points(top_left: Point, bottom_right: Point) -> Self {
178 Self::new(
179 top_left.x,
180 top_left.y,
181 bottom_right.x - top_left.x,
182 bottom_right.y - top_left.y,
183 )
184 }
185
186 #[must_use]
188 pub const fn from_size(size: Size) -> Self {
189 Self::new(0.0, 0.0, size.width, size.height)
190 }
191
192 #[must_use]
194 pub const fn origin(&self) -> Point {
195 Point::new(self.x, self.y)
196 }
197
198 #[must_use]
200 pub const fn size(&self) -> Size {
201 Size::new(self.width, self.height)
202 }
203
204 #[must_use]
206 pub fn area(&self) -> f32 {
207 self.width * self.height
208 }
209
210 #[must_use]
212 pub const fn top_left(&self) -> Point {
213 Point::new(self.x, self.y)
214 }
215
216 #[must_use]
218 pub fn top_right(&self) -> Point {
219 Point::new(self.x + self.width, self.y)
220 }
221
222 #[must_use]
224 pub fn bottom_left(&self) -> Point {
225 Point::new(self.x, self.y + self.height)
226 }
227
228 #[must_use]
230 pub fn bottom_right(&self) -> Point {
231 Point::new(self.x + self.width, self.y + self.height)
232 }
233
234 #[must_use]
236 pub fn center(&self) -> Point {
237 Point::new(self.x + self.width / 2.0, self.y + self.height / 2.0)
238 }
239
240 #[must_use]
242 pub fn contains_point(&self, point: &Point) -> bool {
243 point.x >= self.x
244 && point.x <= self.x + self.width
245 && point.y >= self.y
246 && point.y <= self.y + self.height
247 }
248
249 #[must_use]
251 pub fn intersects(&self, other: &Self) -> bool {
252 self.x < other.x + other.width
253 && self.x + self.width > other.x
254 && self.y < other.y + other.height
255 && self.y + self.height > other.y
256 }
257
258 #[must_use]
260 pub fn intersection(&self, other: &Self) -> Option<Self> {
261 let x = self.x.max(other.x);
262 let y = self.y.max(other.y);
263 let right = (self.x + self.width).min(other.x + other.width);
264 let bottom = (self.y + self.height).min(other.y + other.height);
265
266 if right > x && bottom > y {
267 Some(Self::new(x, y, right - x, bottom - y))
268 } else {
269 None
270 }
271 }
272
273 #[must_use]
275 pub fn union(&self, other: &Self) -> Self {
276 let x = self.x.min(other.x);
277 let y = self.y.min(other.y);
278 let right = (self.x + self.width).max(other.x + other.width);
279 let bottom = (self.y + self.height).max(other.y + other.height);
280
281 Self::new(x, y, right - x, bottom - y)
282 }
283
284 #[must_use]
286 pub fn inset(&self, amount: f32) -> Self {
287 Self::new(
288 self.x + amount,
289 self.y + amount,
290 2.0f32.mul_add(-amount, self.width).max(0.0),
291 2.0f32.mul_add(-amount, self.height).max(0.0),
292 )
293 }
294
295 #[must_use]
297 pub const fn with_origin(&self, origin: Point) -> Self {
298 Self::new(origin.x, origin.y, self.width, self.height)
299 }
300
301 #[must_use]
303 pub const fn with_size(&self, size: Size) -> Self {
304 Self::new(self.x, self.y, size.width, size.height)
305 }
306}
307
308impl Default for Rect {
309 fn default() -> Self {
310 Self::new(0.0, 0.0, 0.0, 0.0)
311 }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
316pub struct CornerRadius {
317 pub top_left: f32,
319 pub top_right: f32,
321 pub bottom_right: f32,
323 pub bottom_left: f32,
325}
326
327impl CornerRadius {
328 pub const ZERO: Self = Self {
330 top_left: 0.0,
331 top_right: 0.0,
332 bottom_right: 0.0,
333 bottom_left: 0.0,
334 };
335
336 #[must_use]
338 pub const fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
339 Self {
340 top_left,
341 top_right,
342 bottom_right,
343 bottom_left,
344 }
345 }
346
347 #[must_use]
349 pub const fn uniform(radius: f32) -> Self {
350 Self::new(radius, radius, radius, radius)
351 }
352
353 #[must_use]
355 pub fn is_zero(&self) -> bool {
356 self.top_left == 0.0
357 && self.top_right == 0.0
358 && self.bottom_right == 0.0
359 && self.bottom_left == 0.0
360 }
361
362 #[must_use]
364 pub fn is_uniform(&self) -> bool {
365 self.top_left == self.top_right
366 && self.top_right == self.bottom_right
367 && self.bottom_right == self.bottom_left
368 }
369}
370
371impl Default for CornerRadius {
372 fn default() -> Self {
373 Self::ZERO
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn test_point_default() {
383 assert_eq!(Point::default(), Point::ORIGIN);
384 }
385
386 #[test]
387 fn test_point_lerp() {
388 let p1 = Point::new(0.0, 0.0);
389 let p2 = Point::new(10.0, 10.0);
390 let mid = p1.lerp(&p2, 0.5);
391 assert_eq!(mid, Point::new(5.0, 5.0));
392 }
393
394 #[test]
395 fn test_size_default() {
396 assert_eq!(Size::default(), Size::ZERO);
397 }
398
399 #[test]
400 fn test_size_scale() {
401 let s = Size::new(10.0, 20.0);
402 assert_eq!(s.scale(2.0), Size::new(20.0, 40.0));
403 }
404
405 #[test]
406 fn test_rect_default() {
407 let r = Rect::default();
408 assert_eq!(r.x, 0.0);
409 assert_eq!(r.area(), 0.0);
410 }
411
412 #[test]
413 fn test_corner_radius_is_uniform() {
414 assert!(CornerRadius::uniform(10.0).is_uniform());
415 assert!(!CornerRadius::new(1.0, 2.0, 3.0, 4.0).is_uniform());
416 }
417
418 #[test]
419 fn test_corner_radius_is_zero() {
420 assert!(CornerRadius::ZERO.is_zero());
421 assert!(!CornerRadius::uniform(1.0).is_zero());
422 }
423
424 #[test]
427 fn test_point_new() {
428 let p = Point::new(10.0, 20.0);
429 assert_eq!(p.x, 10.0);
430 assert_eq!(p.y, 20.0);
431 }
432
433 #[test]
434 fn test_point_distance() {
435 let p1 = Point::new(0.0, 0.0);
436 let p2 = Point::new(3.0, 4.0);
437 assert_eq!(p1.distance(&p2), 5.0);
438 }
439
440 #[test]
441 fn test_point_add() {
442 let p1 = Point::new(1.0, 2.0);
443 let p2 = Point::new(3.0, 4.0);
444 assert_eq!(p1 + p2, Point::new(4.0, 6.0));
445 }
446
447 #[test]
448 fn test_point_sub() {
449 let p1 = Point::new(5.0, 7.0);
450 let p2 = Point::new(2.0, 3.0);
451 assert_eq!(p1 - p2, Point::new(3.0, 4.0));
452 }
453
454 #[test]
457 fn test_size_new() {
458 let s = Size::new(100.0, 50.0);
459 assert_eq!(s.width, 100.0);
460 assert_eq!(s.height, 50.0);
461 }
462
463 #[test]
464 fn test_size_area() {
465 let s = Size::new(10.0, 20.0);
466 assert_eq!(s.area(), 200.0);
467 }
468
469 #[test]
470 fn test_size_aspect_ratio() {
471 let s = Size::new(16.0, 9.0);
472 assert!((s.aspect_ratio() - 1.777).abs() < 0.01);
473 }
474
475 #[test]
476 fn test_size_aspect_ratio_zero_height() {
477 let s = Size::new(10.0, 0.0);
478 assert_eq!(s.aspect_ratio(), 0.0);
479 }
480
481 #[test]
482 fn test_size_contains() {
483 let big = Size::new(100.0, 100.0);
484 let small = Size::new(50.0, 50.0);
485 assert!(big.contains(&small));
486 assert!(!small.contains(&big));
487 }
488
489 #[test]
492 fn test_rect_from_points() {
493 let r = Rect::from_points(Point::new(10.0, 20.0), Point::new(50.0, 70.0));
494 assert_eq!(r.x, 10.0);
495 assert_eq!(r.y, 20.0);
496 assert_eq!(r.width, 40.0);
497 assert_eq!(r.height, 50.0);
498 }
499
500 #[test]
501 fn test_rect_from_size() {
502 let r = Rect::from_size(Size::new(100.0, 50.0));
503 assert_eq!(r.x, 0.0);
504 assert_eq!(r.y, 0.0);
505 assert_eq!(r.width, 100.0);
506 }
507
508 #[test]
509 fn test_rect_corners() {
510 let r = Rect::new(10.0, 20.0, 100.0, 50.0);
511 assert_eq!(r.top_left(), Point::new(10.0, 20.0));
512 assert_eq!(r.top_right(), Point::new(110.0, 20.0));
513 assert_eq!(r.bottom_left(), Point::new(10.0, 70.0));
514 assert_eq!(r.bottom_right(), Point::new(110.0, 70.0));
515 }
516
517 #[test]
518 fn test_rect_center() {
519 let r = Rect::new(0.0, 0.0, 100.0, 50.0);
520 assert_eq!(r.center(), Point::new(50.0, 25.0));
521 }
522
523 #[test]
524 fn test_rect_contains_point() {
525 let r = Rect::new(10.0, 10.0, 100.0, 100.0);
526 assert!(r.contains_point(&Point::new(50.0, 50.0)));
527 assert!(r.contains_point(&Point::new(10.0, 10.0))); assert!(!r.contains_point(&Point::new(5.0, 50.0)));
529 }
530
531 #[test]
532 fn test_rect_intersects() {
533 let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
534 let r2 = Rect::new(50.0, 50.0, 100.0, 100.0);
535 let r3 = Rect::new(200.0, 200.0, 50.0, 50.0);
536 assert!(r1.intersects(&r2));
537 assert!(!r1.intersects(&r3));
538 }
539
540 #[test]
541 fn test_rect_intersection() {
542 let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
543 let r2 = Rect::new(50.0, 50.0, 100.0, 100.0);
544 let inter = r1.intersection(&r2).unwrap();
545 assert_eq!(inter, Rect::new(50.0, 50.0, 50.0, 50.0));
546 }
547
548 #[test]
549 fn test_rect_intersection_none() {
550 let r1 = Rect::new(0.0, 0.0, 50.0, 50.0);
551 let r2 = Rect::new(100.0, 100.0, 50.0, 50.0);
552 assert!(r1.intersection(&r2).is_none());
553 }
554
555 #[test]
556 fn test_rect_union() {
557 let r1 = Rect::new(0.0, 0.0, 50.0, 50.0);
558 let r2 = Rect::new(25.0, 25.0, 50.0, 50.0);
559 let u = r1.union(&r2);
560 assert_eq!(u, Rect::new(0.0, 0.0, 75.0, 75.0));
561 }
562
563 #[test]
564 fn test_rect_inset() {
565 let r = Rect::new(0.0, 0.0, 100.0, 100.0);
566 let inset = r.inset(10.0);
567 assert_eq!(inset, Rect::new(10.0, 10.0, 80.0, 80.0));
568 }
569
570 #[test]
571 fn test_rect_inset_clamps() {
572 let r = Rect::new(0.0, 0.0, 20.0, 20.0);
573 let inset = r.inset(15.0);
574 assert_eq!(inset.width, 0.0);
575 assert_eq!(inset.height, 0.0);
576 }
577
578 #[test]
579 fn test_rect_with_origin() {
580 let r = Rect::new(0.0, 0.0, 100.0, 50.0);
581 let moved = r.with_origin(Point::new(20.0, 30.0));
582 assert_eq!(moved.x, 20.0);
583 assert_eq!(moved.y, 30.0);
584 assert_eq!(moved.width, 100.0);
585 }
586
587 #[test]
588 fn test_rect_with_size() {
589 let r = Rect::new(10.0, 20.0, 100.0, 50.0);
590 let resized = r.with_size(Size::new(200.0, 100.0));
591 assert_eq!(resized.x, 10.0);
592 assert_eq!(resized.width, 200.0);
593 }
594}