ailloli_ui_core/geometry/rect.rs
1//! Axis-aligned rectangles in logical coordinate spaces.
2
3use super::Offset;
4
5/// Axis-aligned rectangle in logical space: origin `(x, y)` and size `(w, h)`.
6///
7/// # Examples
8///
9/// ```
10/// use ailloli_ui_core::Rect;
11/// let rect = Rect::new(10.0, 20.0, 30.0, 40.0);
12/// assert_eq!(rect.max(), (40.0, 60.0));
13/// ```
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct Rect {
16 /// Left edge.
17 pub x: f32,
18 /// Top edge.
19 pub y: f32,
20 /// Width in logical pixels; negative values are stored but usually empty.
21 pub w: f32,
22 /// Height in logical pixels; negative values are stored but usually empty.
23 pub h: f32,
24}
25
26impl Rect {
27 /// Creates a rectangle from origin and size without normalizing dimensions.
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// use ailloli_ui_core::Rect;
33 /// assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0).w, 3.0);
34 /// ```
35 pub const fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
36 Self { x, y, w, h }
37 }
38
39 /// Right edge (`x + w`).
40 ///
41 /// # Examples
42 ///
43 /// ```
44 /// use ailloli_ui_core::Rect;
45 /// assert_eq!(Rect::new(10.0, 0.0, 30.0, 1.0).right(), 40.0);
46 /// ```
47 pub fn right(&self) -> f32 {
48 self.x + self.w
49 }
50
51 /// Bottom edge (`y + h`).
52 ///
53 /// # Examples
54 ///
55 /// ```
56 /// use ailloli_ui_core::Rect;
57 /// assert_eq!(Rect::new(0.0, 20.0, 1.0, 40.0).bottom(), 60.0);
58 /// ```
59 pub fn bottom(&self) -> f32 {
60 self.y + self.h
61 }
62
63 /// Top-left corner `(x, y)`.
64 ///
65 /// # Examples
66 ///
67 /// ```
68 /// use ailloli_ui_core::Rect;
69 /// assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0).min(), (1.0, 2.0));
70 /// ```
71 pub fn min(&self) -> (f32, f32) {
72 (self.x, self.y)
73 }
74
75 /// Bottom-right corner `(right, bottom)`.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use ailloli_ui_core::Rect;
81 /// assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0).max(), (4.0, 6.0));
82 /// ```
83 pub fn max(&self) -> (f32, f32) {
84 (self.right(), self.bottom())
85 }
86
87 /// Returns `true` if `(px, py)` lies inside the rectangle, including edges.
88 ///
89 /// A negative dimension or non-finite operand normally yields `false`.
90 ///
91 /// # Examples
92 ///
93 /// ```
94 /// use ailloli_ui_core::Rect;
95 /// assert!(Rect::new(0.0, 0.0, 10.0, 10.0).contains(5.0, 5.0));
96 /// ```
97 pub fn contains(&self, px: f32, py: f32) -> bool {
98 px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h
99 }
100
101 /// Translates the rectangle by `by` without changing size.
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// use ailloli_ui_core::{Offset, Rect};
107 /// assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0).translate(Offset::new(5.0, 6.0)), Rect::new(6.0, 8.0, 3.0, 4.0));
108 /// ```
109 pub fn translate(&self, by: Offset) -> Rect {
110 Rect::new(self.x + by.x, self.y + by.y, self.w, self.h)
111 }
112
113 /// Expands the rectangle by `dx`/`dy` on both matching sides.
114 ///
115 /// Negative deltas deflate the rectangle and may produce negative sizes.
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// use ailloli_ui_core::Rect;
121 /// assert_eq!(Rect::new(1.0, 2.0, 3.0, 4.0).inflate(1.0, 2.0), Rect::new(0.0, 0.0, 5.0, 8.0));
122 /// ```
123 pub fn inflate(&self, dx: f32, dy: f32) -> Rect {
124 Rect::new(
125 self.x - dx,
126 self.y - dy,
127 self.w + 2.0 * dx,
128 self.h + 2.0 * dy,
129 )
130 }
131
132 /// Returns the positive-area overlap, or `None` for disjoint/edge contact.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use ailloli_ui_core::Rect;
138 /// let overlap = Rect::new(0.0, 0.0, 10.0, 10.0).intersection(Rect::new(5.0, 5.0, 10.0, 10.0));
139 /// assert_eq!(overlap, Some(Rect::new(5.0, 5.0, 5.0, 5.0)));
140 /// ```
141 pub fn intersection(&self, other: Rect) -> Option<Rect> {
142 let x0 = self.x.max(other.x);
143 let y0 = self.y.max(other.y);
144 let x1 = self.right().min(other.right());
145 let y1 = self.bottom().min(other.bottom());
146 let w = x1 - x0;
147 let h = y1 - y0;
148 if w > 0.0 && h > 0.0 {
149 Some(Rect::new(x0, y0, w, h))
150 } else {
151 None
152 }
153 }
154}