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