Skip to main content

lunar_math/
screen_rect.rs

1/// integer pixel-space rectangle, used for scissor regions and screen-space bounds.
2///
3/// origin is top-left. x1 < x2, y1 < y2. the rect is inclusive on all edges.
4///
5/// # usage
6///
7/// ```ignore
8/// let screen = ScreenRect::full(window_w, window_h);
9/// let clipped = screen.intersect(ScreenRect::new(10, 10, 200, 150));
10/// if !clipped.is_empty() {
11///     // region is on screen
12/// }
13/// ```
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ScreenRect {
16	pub x1: i16,
17	pub y1: i16,
18	pub x2: i16,
19	pub y2: i16,
20}
21
22impl ScreenRect {
23	/// an empty rect that contains no pixels.
24	pub const EMPTY: Self = Self {
25		x1: i16::MAX,
26		y1: i16::MAX,
27		x2: i16::MIN,
28		y2: i16::MIN,
29	};
30
31	/// the full screen at the given dimensions.
32	#[must_use]
33	pub const fn full(width: u16, height: u16) -> Self {
34		Self {
35			x1: 0,
36			y1: 0,
37			x2: width as i16 - 1,
38			y2: height as i16 - 1,
39		}
40	}
41
42	#[must_use]
43	pub const fn new(x1: i16, y1: i16, x2: i16, y2: i16) -> Self {
44		Self { x1, y1, x2, y2 }
45	}
46
47	/// true if the rect has no area.
48	#[must_use]
49	pub const fn is_empty(self) -> bool {
50		self.x1 > self.x2 || self.y1 > self.y2
51	}
52
53	/// expand the rect to include a screen-space pixel point.
54	pub fn add_point(&mut self, x: i16, y: i16) {
55		if x < self.x1 {
56			self.x1 = x;
57		}
58		if x > self.x2 {
59			self.x2 = x;
60		}
61		if y < self.y1 {
62			self.y1 = y;
63		}
64		if y > self.y2 {
65			self.y2 = y;
66		}
67	}
68
69	/// smallest rect containing both self and other.
70	#[must_use]
71	pub const fn union(self, other: Self) -> Self {
72		Self {
73			x1: if self.x1 < other.x1 {
74				self.x1
75			} else {
76				other.x1
77			},
78			y1: if self.y1 < other.y1 {
79				self.y1
80			} else {
81				other.y1
82			},
83			x2: if self.x2 > other.x2 {
84				self.x2
85			} else {
86				other.x2
87			},
88			y2: if self.y2 > other.y2 {
89				self.y2
90			} else {
91				other.y2
92			},
93		}
94	}
95
96	/// largest rect contained within both self and other (intersection).
97	#[must_use]
98	pub const fn intersect(self, other: Self) -> Self {
99		Self {
100			x1: if self.x1 > other.x1 {
101				self.x1
102			} else {
103				other.x1
104			},
105			y1: if self.y1 > other.y1 {
106				self.y1
107			} else {
108				other.y1
109			},
110			x2: if self.x2 < other.x2 {
111				self.x2
112			} else {
113				other.x2
114			},
115			y2: if self.y2 < other.y2 {
116				self.y2
117			} else {
118				other.y2
119			},
120		}
121	}
122
123	/// width in pixels (0 if empty).
124	#[must_use]
125	pub const fn width(self) -> u16 {
126		if self.x2 >= self.x1 {
127			(self.x2 - self.x1 + 1) as u16
128		} else {
129			0
130		}
131	}
132
133	/// height in pixels (0 if empty).
134	#[must_use]
135	pub const fn height(self) -> u16 {
136		if self.y2 >= self.y1 {
137			(self.y2 - self.y1 + 1) as u16
138		} else {
139			0
140		}
141	}
142}
143
144#[cfg(test)]
145mod tests {
146	use super::*;
147
148	#[test]
149	fn empty_has_no_area() {
150		assert!(ScreenRect::EMPTY.is_empty());
151	}
152
153	#[test]
154	fn full_covers_screen() {
155		let r = ScreenRect::full(1920, 1080);
156		assert_eq!(r.width(), 1920);
157		assert_eq!(r.height(), 1080);
158		assert!(!r.is_empty());
159	}
160
161	#[test]
162	fn add_point_expands() {
163		let mut r = ScreenRect::EMPTY;
164		r.add_point(10, 20);
165		r.add_point(50, 80);
166		assert_eq!(r.x1, 10);
167		assert_eq!(r.y1, 20);
168		assert_eq!(r.x2, 50);
169		assert_eq!(r.y2, 80);
170	}
171
172	#[test]
173	fn union_encloses_both() {
174		let a = ScreenRect::new(0, 0, 10, 10);
175		let b = ScreenRect::new(5, 5, 20, 20);
176		let u = a.union(b);
177		assert_eq!(u, ScreenRect::new(0, 0, 20, 20));
178	}
179
180	#[test]
181	fn intersect_clips_to_overlap() {
182		let a = ScreenRect::new(0, 0, 20, 20);
183		let b = ScreenRect::new(10, 10, 30, 30);
184		let i = a.intersect(b);
185		assert_eq!(i, ScreenRect::new(10, 10, 20, 20));
186	}
187
188	#[test]
189	fn intersect_non_overlapping_is_empty() {
190		let a = ScreenRect::new(0, 0, 5, 5);
191		let b = ScreenRect::new(10, 10, 20, 20);
192		assert!(a.intersect(b).is_empty());
193	}
194}