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}