Skip to main content

azul_css/props/basic/
geometry.rs

1//! Basic geometry primitives for layout calculations.
2
3use core::fmt;
4
5use crate::{
6    impl_option, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_mut, impl_vec_partialeq,
7    impl_vec_partialord,
8};
9
10/// Only used for calculations: Point coordinate (x, y) in layout space.
11#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
12#[repr(C)]
13pub struct LayoutPoint {
14    pub x: isize,
15    pub y: isize,
16}
17
18impl fmt::Debug for LayoutPoint {
19    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20        write!(f, "{}", self)
21    }
22}
23impl fmt::Display for LayoutPoint {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        write!(f, "({}, {})", self.x, self.y)
26    }
27}
28
29impl LayoutPoint {
30    #[inline(always)]
31    pub const fn new(x: isize, y: isize) -> Self {
32        Self { x, y }
33    }
34    #[inline(always)]
35    pub const fn zero() -> Self {
36        Self::new(0, 0)
37    }
38}
39
40impl_option!(
41    LayoutPoint,
42    OptionLayoutPoint,
43    [Debug, Copy, Clone, PartialEq, PartialOrd]
44);
45
46/// Only used for calculations: Size (width, height) in layout space.
47#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
48#[repr(C)]
49pub struct LayoutSize {
50    pub width: isize,
51    pub height: isize,
52}
53
54impl fmt::Debug for LayoutSize {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56        write!(f, "{}", self)
57    }
58}
59impl fmt::Display for LayoutSize {
60    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61        write!(f, "{}x{}", self.width, self.height)
62    }
63}
64
65impl LayoutSize {
66    #[inline(always)]
67    pub const fn new(width: isize, height: isize) -> Self {
68        Self { width, height }
69    }
70    #[inline(always)]
71    pub const fn zero() -> Self {
72        Self::new(0, 0)
73    }
74    #[inline]
75    pub fn round(width: f32, height: f32) -> Self {
76        Self {
77            width: libm::roundf(width) as isize,
78            height: libm::roundf(height) as isize,
79        }
80    }
81}
82
83impl_option!(
84    LayoutSize,
85    OptionLayoutSize,
86    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
87);
88
89/// Only used for calculations: Rectangle (x, y, width, height) in layout space.
90#[derive(Copy, Clone, PartialEq, PartialOrd)]
91#[repr(C)]
92pub struct LayoutRect {
93    pub origin: LayoutPoint,
94    pub size: LayoutSize,
95}
96
97impl_option!(
98    LayoutRect,
99    OptionLayoutRect,
100    [Debug, Copy, Clone, PartialEq, PartialOrd]
101);
102impl_vec!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor, LayoutRectVecDestructorType, LayoutRectVecSlice, OptionLayoutRect);
103impl_vec_clone!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor);
104impl_vec_debug!(LayoutRect, LayoutRectVec);
105impl_vec_mut!(LayoutRect, LayoutRectVec);
106impl_vec_partialeq!(LayoutRect, LayoutRectVec);
107impl_vec_partialord!(LayoutRect, LayoutRectVec);
108
109impl fmt::Debug for LayoutRect {
110    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111        write!(f, "{}", self)
112    }
113}
114impl fmt::Display for LayoutRect {
115    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116        write!(f, "{} @ {}", self.size, self.origin)
117    }
118}
119
120impl LayoutRect {
121    #[inline(always)]
122    pub const fn new(origin: LayoutPoint, size: LayoutSize) -> Self {
123        Self { origin, size }
124    }
125    #[inline(always)]
126    pub const fn zero() -> Self {
127        Self::new(LayoutPoint::zero(), LayoutSize::zero())
128    }
129    #[inline(always)]
130    pub const fn max_x(&self) -> isize {
131        self.origin.x + self.size.width
132    }
133    #[inline(always)]
134    pub const fn min_x(&self) -> isize {
135        self.origin.x
136    }
137    #[inline(always)]
138    pub const fn max_y(&self) -> isize {
139        self.origin.y + self.size.height
140    }
141    #[inline(always)]
142    pub const fn min_y(&self) -> isize {
143        self.origin.y
144    }
145    #[inline(always)]
146    pub const fn width(&self) -> isize {
147        self.max_x() - self.min_x()
148    }
149    #[inline(always)]
150    pub const fn height(&self) -> isize {
151        self.max_y() - self.min_y()
152    }
153
154    pub const fn contains(&self, other: &LayoutPoint) -> bool {
155        self.min_x() <= other.x
156            && other.x < self.max_x()
157            && self.min_y() <= other.y
158            && other.y < self.max_y()
159    }
160
161    pub fn contains_f32(&self, other_x: f32, other_y: f32) -> bool {
162        self.min_x() as f32 <= other_x
163            && other_x < self.max_x() as f32
164            && self.min_y() as f32 <= other_y
165            && other_y < self.max_y() as f32
166    }
167
168    /// Same as `contains()`, but returns the (x, y) offset of the hit point
169    ///
170    /// On a regular computer this function takes ~3.2ns to run
171    #[inline]
172    pub const fn hit_test(&self, other: &LayoutPoint) -> Option<LayoutPoint> {
173        let dx_left_edge = other.x - self.min_x();
174        let dx_right_edge = self.max_x() - other.x;
175        let dy_top_edge = other.y - self.min_y();
176        let dy_bottom_edge = self.max_y() - other.y;
177        if dx_left_edge > 0 && dx_right_edge > 0 && dy_top_edge > 0 && dy_bottom_edge > 0 {
178            Some(LayoutPoint::new(dx_left_edge, dy_top_edge))
179        } else {
180            None
181        }
182    }
183
184    /// Faster union for a Vec<LayoutRect>
185    #[inline]
186    pub fn union<I: Iterator<Item = Self>>(mut rects: I) -> Option<Self> {
187        let first = rects.next()?;
188
189        let mut max_width = first.size.width;
190        let mut max_height = first.size.height;
191        let mut min_x = first.origin.x;
192        let mut min_y = first.origin.y;
193
194        while let Some(Self {
195            origin: LayoutPoint { x, y },
196            size: LayoutSize { width, height },
197        }) = rects.next()
198        {
199            let cur_lower_right_x = x + width;
200            let cur_lower_right_y = y + height;
201            max_width = max_width.max(cur_lower_right_x - min_x);
202            max_height = max_height.max(cur_lower_right_y - min_y);
203            min_x = min_x.min(x);
204            min_y = min_y.min(y);
205        }
206
207        Some(Self {
208            origin: LayoutPoint { x: min_x, y: min_y },
209            size: LayoutSize {
210                width: max_width,
211                height: max_height,
212            },
213        })
214    }
215
216    // Returns the scroll rect (not the union rect) of the parent / children
217    #[inline]
218    pub fn get_scroll_rect<I: Iterator<Item = Self>>(&self, children: I) -> Option<Self> {
219        let children_union = Self::union(children)?;
220        Self::union([*self, children_union].iter().map(|r| *r))
221    }
222
223    // Returns if b overlaps a
224    #[inline(always)]
225    pub const fn contains_rect(&self, b: &LayoutRect) -> bool {
226        let a = self;
227
228        let a_x = a.origin.x;
229        let a_y = a.origin.y;
230        let a_width = a.size.width;
231        let a_height = a.size.height;
232
233        let b_x = b.origin.x;
234        let b_y = b.origin.y;
235        let b_width = b.size.width;
236        let b_height = b.size.height;
237
238        b_x >= a_x
239            && b_y >= a_y
240            && b_x + b_width <= a_x + a_width
241            && b_y + b_height <= a_y + a_height
242    }
243}