1use core::fmt;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub struct Position {
12 pub x: u16,
14 pub y: u16,
16}
17
18impl Position {
19 #[inline]
21 #[must_use]
22 pub const fn new(x: u16, y: u16) -> Self {
23 Self { x, y }
24 }
25
26 #[inline]
28 #[must_use]
29 pub fn distance_to(self, other: Self) -> f64 {
30 let dx = (other.x as f64) - (self.x as f64);
31 let dy = (other.y as f64) - (self.y as f64);
32 (dx * dx + dy * dy).sqrt()
33 }
34}
35
36impl core::ops::Add for Position {
37 type Output = Self;
38
39 fn add(self, rhs: Self) -> Self::Output {
40 Self {
41 x: self.x.saturating_add(rhs.x),
42 y: self.y.saturating_add(rhs.y),
43 }
44 }
45}
46
47impl core::ops::Sub for Position {
48 type Output = Self;
49
50 fn sub(self, rhs: Self) -> Self::Output {
51 Self {
52 x: self.x.saturating_sub(rhs.x),
53 y: self.y.saturating_sub(rhs.y),
54 }
55 }
56}
57
58impl core::ops::AddAssign for Position {
59 fn add_assign(&mut self, rhs: Self) {
60 self.x = self.x.saturating_add(rhs.x);
61 self.y = self.y.saturating_add(rhs.y);
62 }
63}
64
65impl core::ops::SubAssign for Position {
66 fn sub_assign(&mut self, rhs: Self) {
67 self.x = self.x.saturating_sub(rhs.x);
68 self.y = self.y.saturating_sub(rhs.y);
69 }
70}
71
72#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
77#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78pub struct Rect {
79 pub x: u16,
81 pub y: u16,
83 pub width: u16,
85 pub height: u16,
87}
88
89impl Rect {
90 #[inline]
102 #[must_use]
103 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
104 Self {
105 x,
106 y,
107 width,
108 height,
109 }
110 }
111
112 #[inline]
114 #[must_use]
115 pub const fn zero() -> Self {
116 Self::new(0, 0, 0, 0)
117 }
118
119 #[inline]
130 #[must_use]
131 pub const fn area(self) -> u32 {
132 self.width as u32 * self.height as u32
133 }
134
135 #[inline]
137 #[must_use]
138 pub const fn is_empty(self) -> bool {
139 self.width == 0 || self.height == 0
140 }
141
142 #[inline]
144 #[must_use]
145 pub const fn left(self) -> u16 {
146 self.x
147 }
148
149 #[inline]
151 #[must_use]
152 pub const fn right(self) -> u16 {
153 self.x.saturating_add(self.width)
154 }
155
156 #[inline]
158 #[must_use]
159 pub const fn top(self) -> u16 {
160 self.y
161 }
162
163 #[inline]
165 #[must_use]
166 pub const fn bottom(self) -> u16 {
167 self.y.saturating_add(self.height)
168 }
169
170 #[inline]
172 #[must_use]
173 pub const fn position(self) -> Position {
174 Position::new(self.x, self.y)
175 }
176
177 #[must_use]
189 pub const fn contains(self, pos: Position) -> bool {
190 pos.x >= self.x && pos.x < self.right() && pos.y >= self.y && pos.y < self.bottom()
191 }
192
193 #[must_use]
206 pub const fn intersection(self, other: Self) -> Self {
207 let x1 = if self.x > other.x { self.x } else { other.x };
208 let y1 = if self.y > other.y { self.y } else { other.y };
209 let x2 = if self.right() < other.right() {
210 self.right()
211 } else {
212 other.right()
213 };
214 let y2 = if self.bottom() < other.bottom() {
215 self.bottom()
216 } else {
217 other.bottom()
218 };
219 Self::new(x1, y1, x2.saturating_sub(x1), y2.saturating_sub(y1))
220 }
221
222 #[must_use]
234 pub const fn contains_rect(self, other: Self) -> bool {
235 other.x >= self.x
236 && other.y >= self.y
237 && other.right() <= self.right()
238 && other.bottom() <= self.bottom()
239 }
240
241 #[must_use]
243 pub const fn union(self, other: Self) -> Self {
244 let x1 = if self.x < other.x { self.x } else { other.x };
245 let y1 = if self.y < other.y { self.y } else { other.y };
246 let x2 = if self.right() > other.right() {
247 self.right()
248 } else {
249 other.right()
250 };
251 let y2 = if self.bottom() > other.bottom() {
252 self.bottom()
253 } else {
254 other.bottom()
255 };
256 Self::new(x1, y1, x2 - x1, y2 - y1)
257 }
258
259 #[must_use]
271 pub const fn inner(self, margin: Margin) -> Self {
272 let doubled_horizontal = margin.horizontal.saturating_mul(2);
273 let doubled_vertical = margin.vertical.saturating_mul(2);
274 Self::new(
275 self.x.saturating_add(margin.horizontal),
276 self.y.saturating_add(margin.vertical),
277 self.width.saturating_sub(doubled_horizontal),
278 self.height.saturating_sub(doubled_vertical),
279 )
280 }
281
282 #[must_use]
284 pub const fn clamp(self, other: Self) -> Self {
285 self.intersection(other)
286 }
287}
288
289impl fmt::Display for Rect {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
292 }
293}
294
295#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
297#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
298pub struct Margin {
299 pub horizontal: u16,
301 pub vertical: u16,
303}
304
305impl Margin {
306 #[inline]
318 #[must_use]
319 pub const fn new(horizontal: u16, vertical: u16) -> Self {
320 Self {
321 horizontal,
322 vertical,
323 }
324 }
325
326 #[inline]
328 #[must_use]
329 pub const fn uniform(value: u16) -> Self {
330 Self::new(value, value)
331 }
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
336#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
337pub enum Alignment {
338 Start,
340 Center,
342 End,
344}
345
346impl Default for Alignment {
347 fn default() -> Self {
348 Self::Start
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn rect_area() {
358 assert_eq!(Rect::new(0, 0, 10, 10).area(), 100);
359 assert_eq!(Rect::new(0, 0, 0, 10).area(), 0);
360 }
361
362 #[test]
363 fn rect_contains() {
364 let rect = Rect::new(5, 5, 10, 10);
365 assert!(rect.contains(Position::new(5, 5)));
366 assert!(rect.contains(Position::new(14, 14)));
367 assert!(!rect.contains(Position::new(4, 5)));
368 assert!(!rect.contains(Position::new(15, 5)));
369 }
370
371 #[test]
372 fn rect_intersection() {
373 let a = Rect::new(0, 0, 10, 10);
374 let b = Rect::new(5, 5, 10, 10);
375 assert_eq!(a.intersection(b), Rect::new(5, 5, 5, 5));
376
377 let c = Rect::new(20, 20, 10, 10);
378 assert_eq!(a.intersection(c), Rect::new(20, 20, 0, 0));
379 }
380
381 #[test]
382 fn rect_inner() {
383 let rect = Rect::new(0, 0, 10, 10);
384 let inner = rect.inner(Margin::new(1, 1));
385 assert_eq!(inner, Rect::new(1, 1, 8, 8));
386 }
387}