1#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct Point {
8 pub x: f32,
9 pub y: f32,
10}
11
12impl Point {
13 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
14
15 #[must_use]
16 pub const fn new(x: f32, y: f32) -> Self {
17 Self { x, y }
18 }
19
20 #[must_use]
21 pub fn distance_to(self, other: Self) -> f32 {
22 let dx = self.x - other.x;
23 let dy = self.y - other.y;
24 (dx * dx + dy * dy).sqrt()
25 }
26
27 #[must_use]
28 pub fn offset(self, dx: f32, dy: f32) -> Self {
29 Self {
30 x: self.x + dx,
31 y: self.y + dy,
32 }
33 }
34}
35
36impl std::ops::Add<Offset> for Point {
37 type Output = Self;
38 fn add(self, rhs: Offset) -> Self {
39 Self {
40 x: self.x + rhs.dx,
41 y: self.y + rhs.dy,
42 }
43 }
44}
45
46impl std::ops::Sub for Point {
47 type Output = Offset;
48 fn sub(self, rhs: Self) -> Offset {
49 Offset {
50 dx: self.x - rhs.x,
51 dy: self.y - rhs.y,
52 }
53 }
54}
55
56impl std::ops::Sub<Offset> for Point {
57 type Output = Self;
58 fn sub(self, rhs: Offset) -> Self {
59 Self {
60 x: self.x - rhs.dx,
61 y: self.y - rhs.dy,
62 }
63 }
64}
65
66#[derive(Clone, Copy, Debug, Default, PartialEq)]
72pub struct Offset {
73 pub dx: f32,
74 pub dy: f32,
75}
76
77impl Offset {
78 pub const ZERO: Self = Self { dx: 0.0, dy: 0.0 };
79
80 #[must_use]
81 pub const fn new(dx: f32, dy: f32) -> Self {
82 Self { dx, dy }
83 }
84
85 #[must_use]
86 pub fn length(self) -> f32 {
87 (self.dx * self.dx + self.dy * self.dy).sqrt()
88 }
89}
90
91impl std::ops::Add for Offset {
92 type Output = Self;
93 fn add(self, rhs: Self) -> Self {
94 Self {
95 dx: self.dx + rhs.dx,
96 dy: self.dy + rhs.dy,
97 }
98 }
99}
100
101impl std::ops::Neg for Offset {
102 type Output = Self;
103 fn neg(self) -> Self {
104 Self {
105 dx: -self.dx,
106 dy: -self.dy,
107 }
108 }
109}
110
111#[derive(Clone, Copy, Debug, Default, PartialEq)]
113pub struct Size {
114 pub width: f32,
115 pub height: f32,
116}
117
118impl Size {
119 pub const ZERO: Self = Self {
120 width: 0.0,
121 height: 0.0,
122 };
123
124 #[must_use]
125 pub const fn new(width: f32, height: f32) -> Self {
126 Self { width, height }
127 }
128
129 #[must_use]
130 pub fn area(self) -> f32 {
131 self.width * self.height
132 }
133
134 #[must_use]
135 pub fn is_empty(self) -> bool {
136 self.width <= 0.0 || self.height <= 0.0
137 }
138
139 #[must_use]
140 pub fn contains(self, point: Point) -> bool {
141 point.x >= 0.0 && point.x < self.width && point.y >= 0.0 && point.y < self.height
142 }
143}
144
145#[derive(Clone, Copy, Debug, Default, PartialEq)]
150pub struct Rect {
151 pub origin: Point,
152 pub size: Size,
153}
154
155impl Rect {
156 pub const ZERO: Self = Self {
157 origin: Point::ZERO,
158 size: Size::ZERO,
159 };
160
161 #[must_use]
162 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
163 Self {
164 origin: Point::new(x, y),
165 size: Size::new(width, height),
166 }
167 }
168
169 #[must_use]
170 pub fn from_origin_size(origin: Point, size: Size) -> Self {
171 Self { origin, size }
172 }
173
174 #[must_use]
176 pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Self {
177 Self {
178 origin: Point::new(left, top),
179 size: Size::new(right - left, bottom - top),
180 }
181 }
182
183 #[must_use]
184 pub fn x(&self) -> f32 {
185 self.origin.x
186 }
187 #[must_use]
188 pub fn y(&self) -> f32 {
189 self.origin.y
190 }
191 #[must_use]
192 pub fn width(&self) -> f32 {
193 self.size.width
194 }
195 #[must_use]
196 pub fn height(&self) -> f32 {
197 self.size.height
198 }
199
200 #[must_use]
201 pub fn left(&self) -> f32 {
202 self.origin.x
203 }
204 #[must_use]
205 pub fn top(&self) -> f32 {
206 self.origin.y
207 }
208 #[must_use]
209 pub fn right(&self) -> f32 {
210 self.origin.x + self.size.width
211 }
212 #[must_use]
213 pub fn bottom(&self) -> f32 {
214 self.origin.y + self.size.height
215 }
216
217 #[must_use]
218 pub fn center(&self) -> Point {
219 Point::new(
220 self.origin.x + self.size.width * 0.5,
221 self.origin.y + self.size.height * 0.5,
222 )
223 }
224
225 #[must_use]
226 pub fn is_empty(&self) -> bool {
227 self.size.is_empty()
228 }
229
230 #[must_use]
231 pub fn contains_point(&self, p: Point) -> bool {
232 p.x >= self.left() && p.x < self.right() && p.y >= self.top() && p.y < self.bottom()
233 }
234
235 #[must_use]
236 pub fn contains_rect(&self, other: &Rect) -> bool {
237 other.left() >= self.left()
238 && other.top() >= self.top()
239 && other.right() <= self.right()
240 && other.bottom() <= self.bottom()
241 }
242
243 #[must_use]
244 pub fn intersects(&self, other: &Rect) -> bool {
245 self.left() < other.right()
246 && self.right() > other.left()
247 && self.top() < other.bottom()
248 && self.bottom() > other.top()
249 }
250
251 #[must_use]
253 pub fn intersection(&self, other: &Rect) -> Option<Self> {
254 if !self.intersects(other) {
255 return None;
256 }
257 let left = self.left().max(other.left());
258 let top = self.top().max(other.top());
259 let right = self.right().min(other.right());
260 let bottom = self.bottom().min(other.bottom());
261 Some(Self::from_ltrb(left, top, right, bottom))
262 }
263
264 #[must_use]
266 pub fn union(&self, other: &Rect) -> Self {
267 if self.is_empty() {
268 return *other;
269 }
270 if other.is_empty() {
271 return *self;
272 }
273 let left = self.left().min(other.left());
274 let top = self.top().min(other.top());
275 let right = self.right().max(other.right());
276 let bottom = self.bottom().max(other.bottom());
277 Self::from_ltrb(left, top, right, bottom)
278 }
279
280 #[must_use]
282 pub fn inflate(&self, dx: f32, dy: f32) -> Self {
283 Self::new(
284 self.origin.x - dx,
285 self.origin.y - dy,
286 self.size.width + dx * 2.0,
287 self.size.height + dy * 2.0,
288 )
289 }
290
291 #[must_use]
293 pub fn inset(&self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
294 Self::new(
295 self.origin.x + left,
296 self.origin.y + top,
297 (self.size.width - left - right).max(0.0),
298 (self.size.height - top - bottom).max(0.0),
299 )
300 }
301
302 #[must_use]
304 pub fn outset(&self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
305 Self::new(
306 self.origin.x - left,
307 self.origin.y - top,
308 self.size.width + left + right,
309 self.size.height + top + bottom,
310 )
311 }
312
313 #[must_use]
314 pub fn translate(&self, offset: Offset) -> Self {
315 Self {
316 origin: self.origin + offset,
317 size: self.size,
318 }
319 }
320}
321
322#[derive(Clone, Copy, Debug, Default, PartialEq)]
325pub struct Edges<T: Copy> {
326 pub top: T,
327 pub right: T,
328 pub bottom: T,
329 pub left: T,
330}
331
332impl<T: Copy> Edges<T> {
333 pub const fn new(top: T, right: T, bottom: T, left: T) -> Self {
334 Self {
335 top,
336 right,
337 bottom,
338 left,
339 }
340 }
341
342 pub fn all(value: T) -> Self {
343 Self {
344 top: value,
345 right: value,
346 bottom: value,
347 left: value,
348 }
349 }
350
351 pub fn symmetric(vertical: T, horizontal: T) -> Self {
352 Self {
353 top: vertical,
354 right: horizontal,
355 bottom: vertical,
356 left: horizontal,
357 }
358 }
359}
360
361#[derive(Clone, Copy, Debug, Default, PartialEq)]
364pub struct Corners<T: Copy> {
365 pub top_left: T,
366 pub top_right: T,
367 pub bottom_right: T,
368 pub bottom_left: T,
369}
370
371impl<T: Copy> Corners<T> {
372 pub const fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self {
373 Self {
374 top_left,
375 top_right,
376 bottom_right,
377 bottom_left,
378 }
379 }
380
381 pub fn all(value: T) -> Self {
382 Self {
383 top_left: value,
384 top_right: value,
385 bottom_right: value,
386 bottom_left: value,
387 }
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn point_offset_arithmetic() {
397 let a = Point::new(10.0, 20.0);
398 let b = Point::new(30.0, 50.0);
399
400 let delta = b - a;
401 assert_eq!(delta, Offset::new(20.0, 30.0));
402
403 let c = a + delta;
404 assert_eq!(c, b);
405 }
406
407 #[test]
408 fn point_distance() {
409 let a = Point::new(0.0, 0.0);
410 let b = Point::new(3.0, 4.0);
411 assert!((a.distance_to(b) - 5.0).abs() < f32::EPSILON);
412 }
413
414 #[test]
415 fn size_empty_and_area() {
416 assert!(Size::ZERO.is_empty());
417 assert!(!Size::new(10.0, 5.0).is_empty());
418 assert_eq!(Size::new(3.0, 4.0).area(), 12.0);
419 }
420
421 #[test]
422 fn rect_edges() {
423 let r = Rect::new(10.0, 20.0, 100.0, 50.0);
424 assert_eq!(r.left(), 10.0);
425 assert_eq!(r.top(), 20.0);
426 assert_eq!(r.right(), 110.0);
427 assert_eq!(r.bottom(), 70.0);
428 assert_eq!(r.center(), Point::new(60.0, 45.0));
429 }
430
431 #[test]
432 fn rect_from_ltrb() {
433 let r = Rect::from_ltrb(10.0, 20.0, 110.0, 70.0);
434 assert_eq!(r.origin, Point::new(10.0, 20.0));
435 assert_eq!(r.size, Size::new(100.0, 50.0));
436 }
437
438 #[test]
439 fn rect_contains_point() {
440 let r = Rect::new(0.0, 0.0, 100.0, 100.0);
441 assert!(r.contains_point(Point::new(50.0, 50.0)));
442 assert!(r.contains_point(Point::new(0.0, 0.0)));
443 assert!(!r.contains_point(Point::new(100.0, 100.0))); assert!(!r.contains_point(Point::new(-1.0, 50.0)));
445 }
446
447 #[test]
448 fn rect_intersection() {
449 let a = Rect::new(0.0, 0.0, 100.0, 100.0);
450 let b = Rect::new(50.0, 50.0, 100.0, 100.0);
451 let c = Rect::new(200.0, 200.0, 10.0, 10.0);
452
453 let ab = a.intersection(&b).unwrap();
454 assert_eq!(ab, Rect::new(50.0, 50.0, 50.0, 50.0));
455
456 assert!(a.intersection(&c).is_none());
457 }
458
459 #[test]
460 fn rect_union() {
461 let a = Rect::new(0.0, 0.0, 50.0, 50.0);
462 let b = Rect::new(25.0, 25.0, 50.0, 50.0);
463
464 let u = a.union(&b);
465 assert_eq!(u, Rect::new(0.0, 0.0, 75.0, 75.0));
466 }
467
468 #[test]
469 fn rect_inflate() {
470 let r = Rect::new(10.0, 10.0, 20.0, 20.0);
471 let expanded = r.inflate(5.0, 5.0);
472 assert_eq!(expanded, Rect::new(5.0, 5.0, 30.0, 30.0));
473 }
474
475 #[test]
476 fn rect_translate() {
477 let r = Rect::new(10.0, 20.0, 30.0, 40.0);
478 let moved = r.translate(Offset::new(5.0, -5.0));
479 assert_eq!(moved, Rect::new(15.0, 15.0, 30.0, 40.0));
480 }
481
482 #[test]
483 fn edges_constructors() {
484 let uniform = Edges::all(8.0f32);
485 assert_eq!(uniform.top, 8.0);
486 assert_eq!(uniform.right, 8.0);
487
488 let sym = Edges::symmetric(10.0f32, 20.0);
489 assert_eq!(sym.top, 10.0);
490 assert_eq!(sym.right, 20.0);
491 assert_eq!(sym.bottom, 10.0);
492 assert_eq!(sym.left, 20.0);
493 }
494
495 #[test]
496 fn corners_constructor() {
497 let c = Corners::all(4.0f32);
498 assert_eq!(c.top_left, 4.0);
499 assert_eq!(c.bottom_right, 4.0);
500 }
501
502 #[test]
503 fn offset_neg() {
504 let o = Offset::new(10.0, -5.0);
505 assert_eq!(-o, Offset::new(-10.0, 5.0));
506 }
507}