1use std::fmt;
4use std::str::FromStr;
5
6use crate::utilities::{FormatError, SpanStringTokenizer};
7use crate::{PixelPoint, PixelSize, PixelVector, Point, Rect, Vector};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
11pub struct PixelRect {
12 pub x: i32,
14 pub y: i32,
16 pub width: i32,
18 pub height: i32,
20}
21
22impl PixelRect {
23 #[inline]
25 pub const fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
26 Self {
27 x,
28 y,
29 width,
30 height,
31 }
32 }
33
34 #[inline]
36 pub const fn from_size(size: PixelSize) -> Self {
37 Self::new(0, 0, size.width, size.height)
38 }
39
40 #[inline]
42 pub const fn from_position_size(position: PixelPoint, size: PixelSize) -> Self {
43 Self::new(position.x, position.y, size.width, size.height)
44 }
45
46 #[inline]
48 pub const fn from_points(top_left: PixelPoint, bottom_right: PixelPoint) -> Self {
49 Self::new(
50 top_left.x,
51 top_left.y,
52 bottom_right.x.wrapping_sub(top_left.x),
53 bottom_right.y.wrapping_sub(top_left.y),
54 )
55 }
56
57 #[inline]
59 pub const fn position(&self) -> PixelPoint {
60 PixelPoint::new(self.x, self.y)
61 }
62
63 #[inline]
65 pub const fn size(&self) -> PixelSize {
66 PixelSize::new(self.width, self.height)
67 }
68
69 #[inline]
71 pub const fn right(&self) -> i32 {
72 self.x.wrapping_add(self.width)
73 }
74
75 #[inline]
77 pub const fn bottom(&self) -> i32 {
78 self.y.wrapping_add(self.height)
79 }
80
81 #[inline]
83 pub const fn top_left(&self) -> PixelPoint {
84 PixelPoint::new(self.x, self.y)
85 }
86
87 #[inline]
89 pub const fn top_right(&self) -> PixelPoint {
90 PixelPoint::new(self.right(), self.y)
91 }
92
93 #[inline]
95 pub const fn bottom_left(&self) -> PixelPoint {
96 PixelPoint::new(self.x, self.bottom())
97 }
98
99 #[inline]
101 pub const fn bottom_right(&self) -> PixelPoint {
102 PixelPoint::new(self.right(), self.bottom())
103 }
104
105 #[inline]
107 pub const fn center(&self) -> PixelPoint {
108 PixelPoint::new(
109 self.x.wrapping_add(self.width / 2),
110 self.y.wrapping_add(self.height / 2),
111 )
112 }
113
114 #[inline]
116 pub const fn contains(&self, p: PixelPoint) -> bool {
117 p.x >= self.x && p.x <= self.right() && p.y >= self.y && p.y <= self.bottom()
118 }
119
120 #[inline]
123 pub const fn contains_exclusive(&self, p: PixelPoint) -> bool {
124 p.x >= self.x
125 && p.x < self.x.wrapping_add(self.width)
126 && p.y >= self.y
127 && p.y < self.y.wrapping_add(self.height)
128 }
129
130 #[inline]
132 pub const fn contains_rect(&self, r: PixelRect) -> bool {
133 self.contains(r.top_left()) && self.contains(r.bottom_right())
134 }
135
136 #[inline]
138 pub const fn center_rect(&self, rect: PixelRect) -> PixelRect {
139 PixelRect::new(
140 self.x.wrapping_add(self.width.wrapping_sub(rect.width) / 2),
141 self.y
142 .wrapping_add(self.height.wrapping_sub(rect.height) / 2),
143 rect.width,
144 rect.height,
145 )
146 }
147
148 #[inline]
150 pub const fn equals(&self, other: PixelRect) -> bool {
151 self.x == other.x
152 && self.y == other.y
153 && self.width == other.width
154 && self.height == other.height
155 }
156
157 pub const fn intersect(&self, rect: PixelRect) -> PixelRect {
160 let new_left = if rect.x > self.x { rect.x } else { self.x };
161 let new_top = if rect.y > self.y { rect.y } else { self.y };
162 let new_right = if rect.right() < self.right() {
163 rect.right()
164 } else {
165 self.right()
166 };
167 let new_bottom = if rect.bottom() < self.bottom() {
168 rect.bottom()
169 } else {
170 self.bottom()
171 };
172
173 if (new_right > new_left) && (new_bottom > new_top) {
174 PixelRect::new(
175 new_left,
176 new_top,
177 new_right.wrapping_sub(new_left),
178 new_bottom.wrapping_sub(new_top),
179 )
180 } else {
181 PixelRect::new(0, 0, 0, 0)
182 }
183 }
184
185 #[inline]
187 pub const fn intersects(&self, rect: PixelRect) -> bool {
188 (rect.x < self.right())
189 && (self.x < rect.right())
190 && (rect.y < self.bottom())
191 && (self.y < rect.bottom())
192 }
193
194 #[inline]
196 pub fn translate(&self, offset: PixelVector) -> PixelRect {
197 PixelRect::from_position_size(self.position() + offset, self.size())
198 }
199
200 pub fn union(&self, rect: PixelRect) -> PixelRect {
203 if self.width == 0 && self.height == 0 {
204 rect
205 } else if rect.width == 0 && rect.height == 0 {
206 *self
207 } else {
208 let x1 = self.x.min(rect.x);
209 let x2 = self.right().max(rect.right());
210 let y1 = self.y.min(rect.y);
211 let y2 = self.bottom().max(rect.bottom());
212
213 PixelRect::from_points(PixelPoint::new(x1, y1), PixelPoint::new(x2, y2))
214 }
215 }
216
217 #[inline]
219 pub const fn with_x(&self, x: i32) -> PixelRect {
220 PixelRect::new(x, self.y, self.width, self.height)
221 }
222
223 #[inline]
225 pub const fn with_y(&self, y: i32) -> PixelRect {
226 PixelRect::new(self.x, y, self.width, self.height)
227 }
228
229 #[inline]
231 pub const fn with_width(&self, width: i32) -> PixelRect {
232 PixelRect::new(self.x, self.y, width, self.height)
233 }
234
235 #[inline]
237 pub const fn with_height(&self, height: i32) -> PixelRect {
238 PixelRect::new(self.x, self.y, self.width, height)
239 }
240
241 #[inline]
244 pub fn to_rect(&self, scale: f64) -> Rect {
245 Rect::from_position_size(self.position().to_point(scale), self.size().to_size(scale))
246 }
247
248 #[inline]
251 pub fn to_rect_vector(&self, scale: Vector) -> Rect {
252 Rect::from_position_size(
253 self.position().to_point_vector(scale),
254 self.size().to_size_vector(scale),
255 )
256 }
257
258 #[inline]
261 pub fn to_rect_with_dpi(&self, dpi: f64) -> Rect {
262 Rect::from_position_size(
263 self.position().to_point_with_dpi(dpi),
264 self.size().to_size_with_dpi(dpi),
265 )
266 }
267
268 #[inline]
271 pub fn to_rect_with_dpi_vector(&self, dpi: Vector) -> Rect {
272 Rect::from_position_size(
273 self.position().to_point_with_dpi_vector(dpi),
274 self.size().to_size_with_dpi_vector(dpi),
275 )
276 }
277
278 #[inline]
281 pub fn from_rect(rect: Rect, scale: f64) -> PixelRect {
282 PixelRect::from_points(
283 PixelPoint::from_point(rect.position(), scale),
284 Self::from_point_ceiling(rect.bottom_right(), Vector::new(scale, scale)),
285 )
286 }
287
288 #[inline]
290 pub fn from_rect_vector(rect: Rect, scale: Vector) -> PixelRect {
291 PixelRect::from_points(
292 PixelPoint::from_point_vector(rect.position(), scale),
293 Self::from_point_ceiling(rect.bottom_right(), scale),
294 )
295 }
296
297 #[inline]
299 pub fn from_rect_with_dpi(rect: Rect, dpi: f64) -> PixelRect {
300 PixelRect::from_points(
301 PixelPoint::from_point_with_dpi(rect.position(), dpi),
302 Self::from_point_ceiling(rect.bottom_right(), Vector::new(dpi / 96.0, dpi / 96.0)),
303 )
304 }
305
306 #[inline]
308 pub fn from_rect_with_dpi_vector(rect: Rect, dpi: Vector) -> PixelRect {
309 PixelRect::from_points(
310 PixelPoint::from_point_with_dpi_vector(rect.position(), dpi),
311 Self::from_point_ceiling(rect.bottom_right(), dpi / 96.0),
312 )
313 }
314
315 pub fn parse(s: &str) -> Result<Self, FormatError> {
317 SpanStringTokenizer::with_message(s, "Invalid PixelRect.").scope(|t| {
318 Ok(PixelRect::new(
319 t.read_int32()?,
320 t.read_int32()?,
321 t.read_int32()?,
322 t.read_int32()?,
323 ))
324 })
325 }
326
327 #[inline]
328 fn from_point_ceiling(point: Point, scale: Vector) -> PixelPoint {
329 PixelPoint::new(
330 (point.x * scale.x).ceil() as i32,
331 (point.y * scale.y).ceil() as i32,
332 )
333 }
334}
335
336impl FromStr for PixelRect {
337 type Err = FormatError;
338 #[inline]
339 fn from_str(s: &str) -> Result<Self, FormatError> {
340 PixelRect::parse(s)
341 }
342}
343
344impl fmt::Display for PixelRect {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 write!(f, "{}, {}, {}, {}", self.x, self.y, self.width, self.height)
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn from_rect_snaps_to_device_pixels() {
356 let rect = Rect::new(189.0, 189.0, 26.0, 164.0);
357 let result = PixelRect::from_rect(rect, 1.5);
358
359 assert_eq!(PixelRect::new(283, 283, 40, 247), result);
360 }
361
362 #[test]
363 fn from_rect_vector_snaps_to_device_pixels() {
364 let rect = Rect::new(189.0, 189.0, 26.0, 164.0);
365 let result = PixelRect::from_rect_vector(rect, Vector::new(1.5, 1.5));
366
367 assert_eq!(PixelRect::new(283, 283, 40, 247), result);
368 }
369
370 #[test]
371 fn from_rect_with_dpi_snaps_to_device_pixels() {
372 let rect = Rect::new(189.0, 189.0, 26.0, 164.0);
373 let result = PixelRect::from_rect_with_dpi(rect, 144.0);
374
375 assert_eq!(PixelRect::new(283, 283, 40, 247), result);
376 }
377
378 #[test]
379 fn from_rect_with_dpi_vector_snaps_to_device_pixels() {
380 let rect = Rect::new(189.0, 189.0, 26.0, 164.0);
381 let result = PixelRect::from_rect_with_dpi_vector(rect, Vector::new(144.0, 144.0));
382
383 assert_eq!(PixelRect::new(283, 283, 40, 247), result);
384 }
385
386 #[test]
389 fn geometry() {
390 let r = PixelRect::new(10, 20, 30, 41);
391 assert_eq!(r.right(), 40);
392 assert_eq!(r.bottom(), 61);
393 assert_eq!(r.center(), PixelPoint::new(25, 40));
394 assert_eq!(r.top_right(), PixelPoint::new(40, 20));
395 assert_eq!(r.bottom_left(), PixelPoint::new(10, 61));
396 assert!(r.contains(PixelPoint::new(40, 61)));
397 assert!(!r.contains_exclusive(PixelPoint::new(40, 61)));
398 assert!(r.contains_rect(PixelRect::new(11, 21, 5, 5)));
399 assert!(r.intersects(PixelRect::new(39, 60, 5, 5)));
400 assert!(!r.intersects(PixelRect::new(40, 61, 5, 5)));
401 assert_eq!(
402 r.intersect(PixelRect::new(20, 30, 100, 100)),
403 PixelRect::new(20, 30, 20, 31)
404 );
405 assert_eq!(
406 r.intersect(PixelRect::new(100, 100, 1, 1)),
407 PixelRect::default()
408 );
409 assert_eq!(
410 r.union(PixelRect::new(0, 0, 5, 5)),
411 PixelRect::new(0, 0, 40, 61)
412 );
413 assert_eq!(r.union(PixelRect::default()), r);
414 assert_eq!(
415 r.translate(PixelVector::new(1, 1)),
416 PixelRect::new(11, 21, 30, 41)
417 );
418 assert_eq!(
419 PixelRect::new(0, 0, 10, 10).center_rect(PixelRect::new(0, 0, 4, 4)),
420 PixelRect::new(3, 3, 4, 4)
421 );
422 }
423
424 #[test]
425 fn parse_display_and_to_rect() {
426 assert_eq!(
427 PixelRect::parse("1,2,3,4").unwrap(),
428 PixelRect::new(1, 2, 3, 4)
429 );
430 assert_eq!(
431 PixelRect::parse("1,2,3").unwrap_err().message(),
432 "Invalid PixelRect."
433 );
434 assert_eq!(PixelRect::new(1, 2, 3, 4).to_string(), "1, 2, 3, 4");
435 assert_eq!(
436 PixelRect::new(3, 6, 9, 12).to_rect(1.5),
437 Rect::new(2.0, 4.0, 6.0, 8.0)
438 );
439 assert_eq!(
440 PixelRect::new(3, 6, 9, 12).to_rect_with_dpi(144.0),
441 Rect::new(2.0, 4.0, 6.0, 8.0)
442 );
443 }
444}