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!(
103    LayoutRect,
104    LayoutRectVec,
105    LayoutRectVecDestructor,
106    LayoutRectVecDestructorType
107);
108impl_vec_clone!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor);
109impl_vec_debug!(LayoutRect, LayoutRectVec);
110impl_vec_mut!(LayoutRect, LayoutRectVec);
111impl_vec_partialeq!(LayoutRect, LayoutRectVec);
112impl_vec_partialord!(LayoutRect, LayoutRectVec);
113
114impl fmt::Debug for LayoutRect {
115    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116        write!(f, "{}", self)
117    }
118}
119impl fmt::Display for LayoutRect {
120    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121        write!(f, "{} @ {}", self.size, self.origin)
122    }
123}
124
125impl LayoutRect {
126    #[inline(always)]
127    pub const fn new(origin: LayoutPoint, size: LayoutSize) -> Self {
128        Self { origin, size }
129    }
130    #[inline(always)]
131    pub const fn zero() -> Self {
132        Self::new(LayoutPoint::zero(), LayoutSize::zero())
133    }
134    #[inline(always)]
135    pub const fn max_x(&self) -> isize {
136        self.origin.x + self.size.width
137    }
138    #[inline(always)]
139    pub const fn min_x(&self) -> isize {
140        self.origin.x
141    }
142    #[inline(always)]
143    pub const fn max_y(&self) -> isize {
144        self.origin.y + self.size.height
145    }
146    #[inline(always)]
147    pub const fn min_y(&self) -> isize {
148        self.origin.y
149    }
150    #[inline(always)]
151    pub const fn width(&self) -> isize {
152        self.max_x() - self.min_x()
153    }
154    #[inline(always)]
155    pub const fn height(&self) -> isize {
156        self.max_y() - self.min_y()
157    }
158
159    pub const fn contains(&self, other: &LayoutPoint) -> bool {
160        self.min_x() <= other.x
161            && other.x < self.max_x()
162            && self.min_y() <= other.y
163            && other.y < self.max_y()
164    }
165
166    pub fn contains_f32(&self, other_x: f32, other_y: f32) -> bool {
167        self.min_x() as f32 <= other_x
168            && other_x < self.max_x() as f32
169            && self.min_y() as f32 <= other_y
170            && other_y < self.max_y() as f32
171    }
172
173    /// Same as `contains()`, but returns the (x, y) offset of the hit point
174    ///
175    /// On a regular computer this function takes ~3.2ns to run
176    #[inline]
177    pub const fn hit_test(&self, other: &LayoutPoint) -> Option<LayoutPoint> {
178        let dx_left_edge = other.x - self.min_x();
179        let dx_right_edge = self.max_x() - other.x;
180        let dy_top_edge = other.y - self.min_y();
181        let dy_bottom_edge = self.max_y() - other.y;
182        if dx_left_edge > 0 && dx_right_edge > 0 && dy_top_edge > 0 && dy_bottom_edge > 0 {
183            Some(LayoutPoint::new(dx_left_edge, dy_top_edge))
184        } else {
185            None
186        }
187    }
188
189    /// Faster union for a Vec<LayoutRect>
190    #[inline]
191    pub fn union<I: Iterator<Item = Self>>(mut rects: I) -> Option<Self> {
192        let first = rects.next()?;
193
194        let mut max_width = first.size.width;
195        let mut max_height = first.size.height;
196        let mut min_x = first.origin.x;
197        let mut min_y = first.origin.y;
198
199        while let Some(Self {
200            origin: LayoutPoint { x, y },
201            size: LayoutSize { width, height },
202        }) = rects.next()
203        {
204            let cur_lower_right_x = x + width;
205            let cur_lower_right_y = y + height;
206            max_width = max_width.max(cur_lower_right_x - min_x);
207            max_height = max_height.max(cur_lower_right_y - min_y);
208            min_x = min_x.min(x);
209            min_y = min_y.min(y);
210        }
211
212        Some(Self {
213            origin: LayoutPoint { x: min_x, y: min_y },
214            size: LayoutSize {
215                width: max_width,
216                height: max_height,
217            },
218        })
219    }
220
221    // Returns the scroll rect (not the union rect) of the parent / children
222    #[inline]
223    pub fn get_scroll_rect<I: Iterator<Item = Self>>(&self, children: I) -> Option<Self> {
224        let children_union = Self::union(children)?;
225        Self::union([*self, children_union].iter().map(|r| *r))
226    }
227
228    // Returns if b overlaps a
229    #[inline(always)]
230    pub const fn contains_rect(&self, b: &LayoutRect) -> bool {
231        let a = self;
232
233        let a_x = a.origin.x;
234        let a_y = a.origin.y;
235        let a_width = a.size.width;
236        let a_height = a.size.height;
237
238        let b_x = b.origin.x;
239        let b_y = b.origin.y;
240        let b_width = b.size.width;
241        let b_height = b.size.height;
242
243        b_x >= a_x
244            && b_y >= a_y
245            && b_x + b_width <= a_x + a_width
246            && b_y + b_height <= a_y + a_height
247    }
248}