1
//! Basic geometry primitives (`LayoutPoint`, `LayoutSize`, `LayoutRect`) for
2
//! layout calculations, using `isize` coordinates (as opposed to the `f32`-based
3
//! logical coordinates in `core::geom`).
4

            
5
use core::fmt;
6

            
7
use 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
/// Only used for calculations: Point coordinate (x, y) in layout space.
13
#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
14
#[repr(C)]
15
pub struct LayoutPoint {
16
    pub x: isize,
17
    pub y: isize,
18
}
19

            
20
impl fmt::Debug for LayoutPoint {
21
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22
        write!(f, "{}", self)
23
    }
24
}
25
impl fmt::Display for LayoutPoint {
26
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27
        write!(f, "({}, {})", self.x, self.y)
28
    }
29
}
30

            
31
impl LayoutPoint {
32
    #[inline(always)]
33
46
    pub const fn new(x: isize, y: isize) -> Self {
34
46
        Self { x, y }
35
46
    }
36
    #[inline(always)]
37
    pub const fn zero() -> Self {
38
        Self::new(0, 0)
39
    }
40
}
41

            
42
impl_option!(
43
    LayoutPoint,
44
    OptionLayoutPoint,
45
    [Debug, Copy, Clone, PartialEq, PartialOrd]
46
);
47

            
48
/// Only used for calculations: Size (width, height) in layout space.
49
#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
50
#[repr(C)]
51
pub struct LayoutSize {
52
    pub width: isize,
53
    pub height: isize,
54
}
55

            
56
impl fmt::Debug for LayoutSize {
57
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58
        write!(f, "{}", self)
59
    }
60
}
61
impl 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

            
67
impl LayoutSize {
68
    #[inline(always)]
69
4
    pub const fn new(width: isize, height: isize) -> Self {
70
4
        Self { width, height }
71
4
    }
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

            
85
impl_option!(
86
    LayoutSize,
87
    OptionLayoutSize,
88
    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
89
);
90

            
91
/// Only used for calculations: Rectangle (x, y, width, height) in layout space.
92
#[derive(Copy, Clone, PartialEq, PartialOrd)]
93
#[repr(C)]
94
pub struct LayoutRect {
95
    pub origin: LayoutPoint,
96
    pub size: LayoutSize,
97
}
98

            
99
impl_option!(
100
    LayoutRect,
101
    OptionLayoutRect,
102
    [Debug, Copy, Clone, PartialEq, PartialOrd]
103
);
104
impl_vec!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor, LayoutRectVecDestructorType, LayoutRectVecSlice, OptionLayoutRect);
105
impl_vec_clone!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor);
106
impl_vec_debug!(LayoutRect, LayoutRectVec);
107
impl_vec_mut!(LayoutRect, LayoutRectVec);
108
impl_vec_partialeq!(LayoutRect, LayoutRectVec);
109
impl_vec_partialord!(LayoutRect, LayoutRectVec);
110

            
111
impl fmt::Debug for LayoutRect {
112
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113
        write!(f, "{}", self)
114
    }
115
}
116
impl fmt::Display for LayoutRect {
117
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118
        write!(f, "{} @ {}", self.size, self.origin)
119
    }
120
}
121

            
122
impl LayoutRect {
123
    #[inline(always)]
124
4
    pub const fn new(origin: LayoutPoint, size: LayoutSize) -> Self {
125
4
        Self { origin, size }
126
4
    }
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
    /// Like `contains()`, but returns the (x, y) offset of the hit point
171
    /// relative to the rectangle origin. Unlike `contains()`, points exactly
172
    /// on the boundary are excluded (returns `None`).
173
    #[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
    /// Returns the bounding rectangle that covers every rectangle in the slice,
187
    /// or `OptionLayoutRect::None` if the slice is empty.
188
    #[inline]
189
2
    pub fn union(rects: LayoutRectVecSlice) -> OptionLayoutRect {
190
2
        let mut iter = rects.as_slice().iter().copied();
191
2
        let Some(first) = iter.next() else {
192
1
            return OptionLayoutRect::None;
193
        };
194

            
195
1
        let mut min_x = first.origin.x;
196
1
        let mut min_y = first.origin.y;
197
1
        let mut max_x = first.origin.x + first.size.width;
198
1
        let mut max_y = first.origin.y + first.size.height;
199

            
200
        for Self {
201
2
            origin: LayoutPoint { x, y },
202
2
            size: LayoutSize { width, height },
203
3
        } in iter
204
2
        {
205
2
            max_x = max_x.max(x + width);
206
2
            max_y = max_y.max(y + height);
207
2
            min_x = min_x.min(x);
208
2
            min_y = min_y.min(y);
209
2
        }
210

            
211
1
        OptionLayoutRect::Some(Self {
212
1
            origin: LayoutPoint { x: min_x, y: min_y },
213
1
            size: LayoutSize {
214
1
                width: max_x - min_x,
215
1
                height: max_y - min_y,
216
1
            },
217
1
        })
218
2
    }
219

            
220
    /// Returns true if `b` is fully contained inside `self`.
221
    #[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)]
243
mod tests {
244
    use super::*;
245

            
246
4
    fn rect(x: isize, y: isize, w: isize, h: isize) -> LayoutRect {
247
4
        LayoutRect::new(LayoutPoint::new(x, y), LayoutSize::new(w, h))
248
4
    }
249

            
250
    #[test]
251
1
    fn union_slice_returns_bounding_rect() {
252
1
        let vec: LayoutRectVec =
253
1
            alloc::vec![rect(0, 0, 10, 10), rect(20, -5, 5, 30), rect(-3, 15, 4, 4)].into();
254
1
        let slice = vec.as_c_slice();
255

            
256
1
        match LayoutRect::union(slice) {
257
1
            OptionLayoutRect::Some(r) => {
258
1
                assert_eq!(r, rect(-3, -5, 28, 30));
259
            }
260
            OptionLayoutRect::None => panic!("expected Some bounding rect"),
261
        }
262
1
    }
263

            
264
    #[test]
265
1
    fn union_empty_slice_returns_none() {
266
1
        let vec: LayoutRectVec = LayoutRectVec::new();
267
1
        let slice = vec.as_c_slice();
268
1
        assert!(matches!(LayoutRect::union(slice), OptionLayoutRect::None));
269
1
    }
270
}