1use core::fmt;
6
7use crate::{
8 impl_option, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_mut, impl_vec_partialeq,
9 impl_vec_partialord,
10};
11
12#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
14#[repr(C)]
15pub struct LayoutPoint {
16 pub x: isize,
17 pub y: isize,
18}
19
20impl fmt::Debug for LayoutPoint {
21 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22 write!(f, "{}", self)
23 }
24}
25impl fmt::Display for LayoutPoint {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 write!(f, "({}, {})", self.x, self.y)
28 }
29}
30
31impl LayoutPoint {
32 #[inline(always)]
33 pub const fn new(x: isize, y: isize) -> Self {
34 Self { x, y }
35 }
36 #[inline(always)]
37 pub const fn zero() -> Self {
38 Self::new(0, 0)
39 }
40}
41
42impl_option!(
43 LayoutPoint,
44 OptionLayoutPoint,
45 [Debug, Copy, Clone, PartialEq, PartialOrd]
46);
47
48#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
50#[repr(C)]
51pub struct LayoutSize {
52 pub width: isize,
53 pub height: isize,
54}
55
56impl fmt::Debug for LayoutSize {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 write!(f, "{}", self)
59 }
60}
61impl fmt::Display for LayoutSize {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 write!(f, "{}x{}", self.width, self.height)
64 }
65}
66
67impl LayoutSize {
68 #[inline(always)]
69 pub const fn new(width: isize, height: isize) -> Self {
70 Self { width, height }
71 }
72 #[inline(always)]
73 pub const fn zero() -> Self {
74 Self::new(0, 0)
75 }
76 #[inline]
77 pub fn round(width: f32, height: f32) -> Self {
78 Self {
79 width: libm::roundf(width) as isize,
80 height: libm::roundf(height) as isize,
81 }
82 }
83}
84
85impl_option!(
86 LayoutSize,
87 OptionLayoutSize,
88 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
89);
90
91#[derive(Copy, Clone, PartialEq, PartialOrd)]
93#[repr(C)]
94pub struct LayoutRect {
95 pub origin: LayoutPoint,
96 pub size: LayoutSize,
97}
98
99impl_option!(
100 LayoutRect,
101 OptionLayoutRect,
102 [Debug, Copy, Clone, PartialEq, PartialOrd]
103);
104impl_vec!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor, LayoutRectVecDestructorType, LayoutRectVecSlice, OptionLayoutRect);
105impl_vec_clone!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor);
106impl_vec_debug!(LayoutRect, LayoutRectVec);
107impl_vec_mut!(LayoutRect, LayoutRectVec);
108impl_vec_partialeq!(LayoutRect, LayoutRectVec);
109impl_vec_partialord!(LayoutRect, LayoutRectVec);
110
111impl fmt::Debug for LayoutRect {
112 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113 write!(f, "{}", self)
114 }
115}
116impl fmt::Display for LayoutRect {
117 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118 write!(f, "{} @ {}", self.size, self.origin)
119 }
120}
121
122impl LayoutRect {
123 #[inline(always)]
124 pub const fn new(origin: LayoutPoint, size: LayoutSize) -> Self {
125 Self { origin, size }
126 }
127 #[inline(always)]
128 pub const fn zero() -> Self {
129 Self::new(LayoutPoint::zero(), LayoutSize::zero())
130 }
131 #[inline(always)]
132 pub const fn max_x(&self) -> isize {
133 self.origin.x + self.size.width
134 }
135 #[inline(always)]
136 pub const fn min_x(&self) -> isize {
137 self.origin.x
138 }
139 #[inline(always)]
140 pub const fn max_y(&self) -> isize {
141 self.origin.y + self.size.height
142 }
143 #[inline(always)]
144 pub const fn min_y(&self) -> isize {
145 self.origin.y
146 }
147 #[inline(always)]
148 pub const fn width(&self) -> isize {
149 self.size.width
150 }
151 #[inline(always)]
152 pub const fn height(&self) -> isize {
153 self.size.height
154 }
155
156 pub const fn contains(&self, other: &LayoutPoint) -> bool {
157 self.min_x() <= other.x
158 && other.x < self.max_x()
159 && self.min_y() <= other.y
160 && other.y < self.max_y()
161 }
162
163 pub fn contains_f32(&self, other_x: f32, other_y: f32) -> bool {
164 self.min_x() as f32 <= other_x
165 && other_x < self.max_x() as f32
166 && self.min_y() as f32 <= other_y
167 && other_y < self.max_y() as f32
168 }
169
170 #[inline]
174 pub const fn hit_test(&self, other: &LayoutPoint) -> Option<LayoutPoint> {
175 let dx_left_edge = other.x - self.min_x();
176 let dx_right_edge = self.max_x() - other.x;
177 let dy_top_edge = other.y - self.min_y();
178 let dy_bottom_edge = self.max_y() - other.y;
179 if dx_left_edge > 0 && dx_right_edge > 0 && dy_top_edge > 0 && dy_bottom_edge > 0 {
180 Some(LayoutPoint::new(dx_left_edge, dy_top_edge))
181 } else {
182 None
183 }
184 }
185
186 #[inline]
189 pub fn union(rects: LayoutRectVecSlice) -> OptionLayoutRect {
190 let mut iter = rects.as_slice().iter().copied();
191 let Some(first) = iter.next() else {
192 return OptionLayoutRect::None;
193 };
194
195 let mut min_x = first.origin.x;
196 let mut min_y = first.origin.y;
197 let mut max_x = first.origin.x + first.size.width;
198 let mut max_y = first.origin.y + first.size.height;
199
200 for Self {
201 origin: LayoutPoint { x, y },
202 size: LayoutSize { width, height },
203 } in iter
204 {
205 max_x = max_x.max(x + width);
206 max_y = max_y.max(y + height);
207 min_x = min_x.min(x);
208 min_y = min_y.min(y);
209 }
210
211 OptionLayoutRect::Some(Self {
212 origin: LayoutPoint { x: min_x, y: min_y },
213 size: LayoutSize {
214 width: max_x - min_x,
215 height: max_y - min_y,
216 },
217 })
218 }
219
220 #[inline(always)]
222 pub const fn contains_rect(&self, b: &LayoutRect) -> bool {
223 let a = self;
224
225 let a_x = a.origin.x;
226 let a_y = a.origin.y;
227 let a_width = a.size.width;
228 let a_height = a.size.height;
229
230 let b_x = b.origin.x;
231 let b_y = b.origin.y;
232 let b_width = b.size.width;
233 let b_height = b.size.height;
234
235 b_x >= a_x
236 && b_y >= a_y
237 && b_x + b_width <= a_x + a_width
238 && b_y + b_height <= a_y + a_height
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 fn rect(x: isize, y: isize, w: isize, h: isize) -> LayoutRect {
247 LayoutRect::new(LayoutPoint::new(x, y), LayoutSize::new(w, h))
248 }
249
250 #[test]
251 fn union_slice_returns_bounding_rect() {
252 let vec: LayoutRectVec =
253 alloc::vec![rect(0, 0, 10, 10), rect(20, -5, 5, 30), rect(-3, 15, 4, 4)].into();
254 let slice = vec.as_c_slice();
255
256 match LayoutRect::union(slice) {
257 OptionLayoutRect::Some(r) => {
258 assert_eq!(r, rect(-3, -5, 28, 30));
259 }
260 OptionLayoutRect::None => panic!("expected Some bounding rect"),
261 }
262 }
263
264 #[test]
265 fn union_empty_slice_returns_none() {
266 let vec: LayoutRectVec = LayoutRectVec::new();
267 let slice = vec.as_c_slice();
268 assert!(matches!(LayoutRect::union(slice), OptionLayoutRect::None));
269 }
270}