1use crate::utilities::span_helpers::InvariantF64;
2use crate::{Matrix, PixelRect, Point, Rect, Thickness};
3use std::fmt;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct LtrbRect {
8 pub left: f64,
9 pub top: f64,
10 pub right: f64,
11 pub bottom: f64,
12}
13
14impl LtrbRect {
15 pub const INFINITE: LtrbRect =
16 LtrbRect { left: f64::NEG_INFINITY, top: f64::NEG_INFINITY, right: f64::INFINITY, bottom: f64::INFINITY };
17
18 pub const fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
19 Self { left, top, right, bottom }
20 }
21
22 pub fn from_rect(rc: Rect) -> Self {
23 let rc = rc.normalize();
24 Self { left: rc.x, top: rc.y, right: rc.right(), bottom: rc.bottom() }
25 }
26
27 pub fn width(&self) -> f64 {
28 self.right - self.left
29 }
30
31 pub fn height(&self) -> f64 {
32 self.bottom - self.top
33 }
34
35 pub fn is_well_ordered(&self) -> bool {
36 self.left <= self.right && self.top <= self.bottom
37 }
38
39 pub fn is_zero_size(&self) -> bool {
40 self.left == self.right || self.top == self.bottom
41 }
42
43 pub fn is_empty(&self) -> bool {
44 self.is_zero_size()
45 }
46
47 pub fn null_if_zero_size(self) -> Option<LtrbRect> {
48 (!self.is_zero_size()).then_some(self)
49 }
50
51 pub fn intersect_or_null(&self, rect: LtrbRect) -> Option<LtrbRect> {
52 let left = if rect.left > self.left { rect.left } else { self.left };
53 let top = if rect.top > self.top { rect.top } else { self.top };
54 let right = if rect.right < self.right { rect.right } else { self.right };
55 let bottom = if rect.bottom < self.bottom { rect.bottom } else { self.bottom };
56 (right > left && bottom > top).then_some(LtrbRect::new(left, top, right, bottom))
57 }
58
59 pub fn intersect_or_empty(&self, rect: LtrbRect) -> LtrbRect {
60 self.intersect_or_null(rect).unwrap_or_default()
61 }
62
63 pub fn intersects(&self, rect: LtrbRect) -> bool {
64 rect.left < self.right && self.left < rect.right && rect.top < self.bottom && self.top < rect.bottom
65 }
66
67 pub fn contains(&self, x: f64, y: f64) -> bool {
68 x >= self.left && x <= self.right && y >= self.top && y <= self.bottom
69 }
70
71 pub fn to_rect(&self) -> Rect {
72 Rect::new(self.left, self.top, self.right - self.left, self.bottom - self.top)
73 }
74
75 pub fn inflate(&self, thickness: Thickness) -> LtrbRect {
76 LtrbRect::new(
77 self.left - thickness.left,
78 self.top - thickness.top,
79 self.right + thickness.right,
80 self.bottom + thickness.bottom,
81 )
82 }
83
84 pub fn top_left(&self) -> Point {
87 Point::new(self.left, self.top)
88 }
89
90 pub fn top_right(&self) -> Point {
91 Point::new(self.right, self.top)
92 }
93
94 pub fn bottom_left(&self) -> Point {
95 Point::new(self.left, self.bottom)
96 }
97
98 pub fn bottom_right(&self) -> Point {
99 Point::new(self.right, self.bottom)
100 }
101
102 pub fn transform_to_aabb(&self, matrix: Matrix) -> LtrbRect {
103 let points = [
104 self.top_left().transform(matrix),
105 self.top_right().transform(matrix),
106 self.bottom_right().transform(matrix),
107 self.bottom_left().transform(matrix),
108 ];
109
110 let mut left = f64::MAX;
111 let mut right = f64::MIN;
112 let mut top = f64::MAX;
113 let mut bottom = f64::MIN;
114
115 for p in points {
116 if p.x < left {
117 left = p.x;
118 }
119 if p.x > right {
120 right = p.x;
121 }
122 if p.y < top {
123 top = p.y;
124 }
125 if p.y > bottom {
126 bottom = p.y;
127 }
128 }
129
130 LtrbRect::new(left, top, right, bottom)
131 }
132
133 pub fn full_union(left: Option<LtrbRect>, right: Option<LtrbRect>) -> Option<LtrbRect> {
136 match (left, right) {
137 (None, right) => right,
138 (left, None) => left,
139 (Some(left), Some(right)) => Some(right.union(left)),
140 }
141 }
142
143 pub fn full_union_rect(left: Option<LtrbRect>, right: Option<Rect>) -> Option<LtrbRect> {
146 match (left, right) {
147 (left, None) => left,
148 (None, Some(right)) => Some(LtrbRect::from_rect(right)),
149 (Some(left), Some(right)) => Some(left.union(LtrbRect::from_rect(right))),
150 }
151 }
152
153 pub fn union(&self, rect: LtrbRect) -> LtrbRect {
154 LtrbRect::new(
155 self.left.min(rect.left),
156 self.top.min(rect.top),
157 self.right.max(rect.right),
158 self.bottom.max(rect.bottom),
159 )
160 }
161
162 pub fn contains_rect(&self, rect: LtrbRect) -> bool {
164 rect.left >= self.left && rect.right <= self.right && rect.top >= self.top && rect.bottom <= self.bottom
165 }
166}
167
168impl fmt::Display for LtrbRect {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 write!(
171 f,
172 "{}:{}-{}:{} ({}x{})",
173 InvariantF64(self.left),
174 InvariantF64(self.top),
175 InvariantF64(self.right),
176 InvariantF64(self.bottom),
177 InvariantF64(self.width()),
178 InvariantF64(self.height())
179 )
180 }
181}
182
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
185pub struct LtrbPixelRect {
186 pub left: i32,
187 pub top: i32,
188 pub right: i32,
189 pub bottom: i32,
190}
191
192impl LtrbPixelRect {
193 pub const fn new(left: i32, top: i32, right: i32, bottom: i32) -> Self {
194 Self { left, top, right, bottom }
195 }
196
197 pub fn from_pixel_rect(rc: PixelRect) -> Self {
198 Self { left: rc.x, top: rc.y, right: rc.right(), bottom: rc.bottom() }
199 }
200
201 pub fn width(&self) -> i32 {
202 self.right - self.left
203 }
204
205 pub fn height(&self) -> i32 {
206 self.bottom - self.top
207 }
208
209 pub fn is_empty(&self) -> bool {
210 self.left == self.right && self.top == self.bottom
211 }
212
213 pub fn to_pixel_rect(&self) -> PixelRect {
214 PixelRect::new(self.left, self.top, self.right - self.left, self.bottom - self.top)
215 }
216
217 pub fn union(&self, rect: LtrbPixelRect) -> LtrbPixelRect {
218 LtrbPixelRect::new(
219 self.left.min(rect.left),
220 self.top.min(rect.top),
221 self.right.max(rect.right),
222 self.bottom.max(rect.bottom),
223 )
224 }
225
226 pub fn from_rect_with_dpi(rect: LtrbRect, scaling: f64) -> Self {
229 Self {
230 left: (rect.left * scaling).floor() as i32,
231 top: (rect.top * scaling).floor() as i32,
232 right: (rect.right * scaling).ceil() as i32,
233 bottom: (rect.bottom * scaling).ceil() as i32,
234 }
235 }
236
237 pub fn contains(&self, x: i32, y: i32) -> bool {
238 x >= self.left && x <= self.right && y >= self.top && y <= self.bottom
239 }
240
241 pub fn to_rect_unscaled(&self) -> Rect {
242 Rect::new(
243 f64::from(self.left),
244 f64::from(self.top),
245 f64::from(self.right - self.left),
246 f64::from(self.bottom - self.top),
247 )
248 }
249
250 pub fn to_ltrb_rect_unscaled(&self) -> LtrbRect {
251 LtrbRect::new(f64::from(self.left), f64::from(self.top), f64::from(self.right), f64::from(self.bottom))
252 }
253
254 pub fn from_rect_unscaled(rect: LtrbRect) -> Self {
255 Self {
256 left: rect.left as i32,
257 top: rect.top as i32,
258 right: rect.right.ceil() as i32,
259 bottom: rect.bottom.ceil() as i32,
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
268
269 #[test]
270 fn corners_and_containment() {
271 let rect = LtrbRect::new(1.0, 2.0, 5.0, 8.0);
272
273 assert_eq!(Point::new(1.0, 2.0), rect.top_left());
274 assert_eq!(Point::new(5.0, 2.0), rect.top_right());
275 assert_eq!(Point::new(1.0, 8.0), rect.bottom_left());
276 assert_eq!(Point::new(5.0, 8.0), rect.bottom_right());
277
278 assert!(rect.contains_rect(rect));
279 assert!(rect.contains_rect(LtrbRect::new(2.0, 3.0, 4.0, 4.0)));
280 assert!(!rect.contains_rect(LtrbRect::new(0.0, 3.0, 4.0, 4.0)));
281 assert_eq!("1:2-5:8 (4x6)", rect.to_string());
282 }
283
284 #[test]
285 fn full_union_with_a_rect() {
286 let left = LtrbRect::new(0.0, 0.0, 1.0, 1.0);
287 let right = Rect::new(2.0, 2.0, 2.0, 2.0);
288
289 assert_eq!(None, LtrbRect::full_union_rect(None, None));
290 assert_eq!(Some(left), LtrbRect::full_union_rect(Some(left), None));
291 assert_eq!(Some(LtrbRect::new(2.0, 2.0, 4.0, 4.0)), LtrbRect::full_union_rect(None, Some(right)));
292 assert_eq!(Some(LtrbRect::new(0.0, 0.0, 4.0, 4.0)), LtrbRect::full_union_rect(Some(left), Some(right)));
293 }
294
295 #[test]
296 fn pixel_rect_conversions_without_scaling() {
297 let rect = LtrbPixelRect::new(1, 2, 5, 8);
298
299 assert!(rect.contains(1, 2));
300 assert!(rect.contains(5, 8));
301 assert!(!rect.contains(6, 8));
302 assert_eq!(Rect::new(1.0, 2.0, 4.0, 6.0), rect.to_rect_unscaled());
303 assert_eq!(LtrbRect::new(1.0, 2.0, 5.0, 8.0), rect.to_ltrb_rect_unscaled());
304
305 let rounded = LtrbPixelRect::from_rect_unscaled(LtrbRect::new(1.7, 2.2, 4.1, 7.5));
306 assert_eq!(LtrbPixelRect::new(1, 2, 5, 8), rounded);
307 }
308}