ponsic_types/
rect.rs

1use super::{Point, Size};
2use std::ops::{Add, AddAssign, Div, Sub};
3
4/// 矩形结构体
5///
6/// # Note
7/// 若启用`perf`feature,所有方法都将不会进行标准化检查,以提高性能,
8/// 该情况下,如有必要,需手动进行标准化检查,以保证方法计算结果的正确性
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
10pub struct Rect<T: Copy> {
11    left: T,
12    top: T,
13    right: T,
14    bottom: T,
15}
16
17impl<T: Copy + Ord> Rect<T> {
18    /// 创建一个新的矩形
19    ///
20    /// # Example
21    /// ```
22    /// use ponsic_types::Rect;
23    /// let rect = Rect::new(1, 2, 3, 4);
24    /// ```
25    #[cfg(not(feature = "perf"))]
26    #[inline]
27    pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
28        let mut res = Self {
29            left,
30            top,
31            right,
32            bottom,
33        };
34        res.normalize();
35        res
36    }
37}
38
39impl<T: Copy> Rect<T> {
40    /// 创建一个新的矩形
41    ///
42    /// # Example
43    /// ```
44    /// use wy_core::Rect;
45    /// let rect = Rect::new(1, 2, 3, 4);
46    /// ```
47    #[cfg(feature = "perf")]
48    #[inline]
49    pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
50        Self {
51            left,
52            top,
53            right,
54            bottom,
55        }
56    }
57
58    /// 返回矩形的左边界坐标
59    ///
60    /// # Note
61    /// 若矩形不是标准化的,该函数可能不会返回实际的左边界坐标,
62    /// 若要标准化矩形,请使用 \[`normalize()`\]
63    #[inline]
64    pub fn left(&self) -> T {
65        self.left
66    }
67
68    /// 返回矩形的上边界坐标
69    ///
70    /// # Note
71    /// 若矩形不是标准化的,该函数可能不会返回实际的上边界坐标,
72    /// 若要标准化矩形,请使用 \[`normalize()`\]
73    #[inline]
74    pub fn top(&self) -> T {
75        self.top
76    }
77
78    /// 返回矩形的右边界坐标
79    ///
80    /// # Note
81    /// 若矩形不是标准化的,该函数可能不会返回实际的右边界坐标,
82    /// 若要标准化矩形,请使用 \[`normalize()`\]
83    #[inline]
84    pub fn right(&self) -> T {
85        self.right
86    }
87
88    /// 返回矩形的下边界坐标
89    ///
90    /// # Note
91    /// 若矩形不是标准化的,该函数可能不会返回实际的下边界坐标,
92    /// 若要标准化矩形,请使用 \[`normalize()`\]
93    #[inline]
94    pub fn bottom(&self) -> T {
95        self.bottom
96    }
97
98    /// 返回矩形的左上角坐标
99    ///
100    /// # Note
101    /// 若矩形不是标准化的,该函数可能不会返回实际的左上角坐标,
102    /// 若要标准化矩形,请使用 \[`normalize()`\]
103    #[inline]
104    pub fn left_top(&self) -> Point<T> {
105        Point::new(self.left, self.top)
106    }
107
108    /// 返回矩形的右上角坐标
109    ///
110    /// # Note
111    /// 若矩形不是标准化的,该函数可能不会返回实际的右上角坐标,
112    /// 若要标准化矩形,请使用 \[`normalize()`\]
113    #[inline]
114    pub fn right_top(&self) -> Point<T> {
115        Point::new(self.right, self.top)
116    }
117
118    /// 返回矩形的左下角坐标
119    ///
120    /// # Note
121    /// 若矩形不是标准化的,该函数可能不会返回实际的左下角坐标,
122    /// 若要标准化矩形,请使用 \[`normalize()`\]
123    #[inline]
124    pub fn left_bottom(&self) -> Point<T> {
125        Point::new(self.left, self.bottom)
126    }
127
128    /// 返回矩形的右下角坐标
129    ///
130    /// # Note
131    /// 若矩形不是标准化的,该函数可能不会返回实际的右下角坐标,
132    /// 若要标准化矩形,请使用 \[`normalize()`\]
133    #[inline]
134    pub fn right_bottom(&self) -> Point<T> {
135        Point::new(self.right, self.bottom)
136    }
137}
138
139// 启用`perf`feature时,实现以下方法
140// 这些方法不进行标准化检查,以提高性能
141#[cfg(feature = "perf")]
142impl<T: Copy> Rect<T> {
143    /// 设置矩形的左边界坐标
144    #[inline]
145    pub fn set_left(&mut self, left: T) {
146        self.left = left;
147    }
148
149    /// 设置矩形的上边界坐标
150    #[inline]
151    pub fn set_top(&mut self, top: T) {
152        self.top = top;
153    }
154
155    /// 设置矩形的右边界坐标
156    #[inline]
157    pub fn set_right(&mut self, right: T) {
158        self.right = right;
159    }
160
161    /// 设置矩形的下边界坐标
162    #[inline]
163    pub fn set_bottom(&mut self, bottom: T) {
164        self.bottom = bottom;
165    }
166
167    /// 设置矩形的左上角坐标
168    #[inline]
169    pub fn set_left_top(&mut self, left: T, top: T) {
170        self.left = left;
171        self.top = top;
172    }
173
174    /// 设置矩形的右上角坐标
175    #[inline]
176    pub fn set_right_top(&mut self, right: T, top: T) {
177        self.right = right;
178        self.top = top;
179    }
180
181    /// 设置矩形的左下角坐标
182    #[inline]
183    pub fn set_left_bottom(&mut self, left: T, bottom: T) {
184        self.left = left;
185        self.bottom = bottom;
186    }
187
188    /// 设置矩形的右下角坐标
189    #[inline]
190    pub fn set_right_bottom(&mut self, right: T, bottom: T) {
191        self.right = right;
192        self.bottom = bottom;
193    }
194}
195
196/// 若启用`perf`feature,以下方法将不会进行标准化检查,以提高性能
197#[cfg(not(feature = "perf"))]
198impl<T: Copy + Ord> Rect<T> {
199    /// 设置矩形的左边界坐标
200    #[inline]
201    pub fn set_left(&mut self, left: T) {
202        self.left = left;
203        self.normalize();
204    }
205
206    /// 设置矩形的上边界坐标
207    #[inline]
208    pub fn set_top(&mut self, top: T) {
209        self.top = top;
210        self.normalize();
211    }
212
213    /// 设置矩形的右边界坐标
214    #[inline]
215    pub fn set_right(&mut self, right: T) {
216        self.right = right;
217        self.normalize();
218    }
219
220    /// 设置矩形的下边界坐标
221    #[inline]
222    pub fn set_bottom(&mut self, bottom: T) {
223        self.bottom = bottom;
224        self.normalize();
225    }
226
227    /// 设置矩形的左上角坐标
228    #[inline]
229    pub fn set_left_top(&mut self, left: T, top: T) {
230        self.left = left;
231        self.top = top;
232        self.normalize();
233    }
234
235    /// 设置矩形的右上角坐标
236    #[inline]
237    pub fn set_right_top(&mut self, right: T, top: T) {
238        self.right = right;
239        self.top = top;
240        self.normalize();
241    }
242
243    /// 设置矩形的左下角坐标
244    #[inline]
245    pub fn set_left_bottom(&mut self, left: T, bottom: T) {
246        self.left = left;
247        self.bottom = bottom;
248        self.normalize();
249    }
250
251    /// 设置矩形的右下角坐标
252    #[inline]
253    pub fn set_right_bottom(&mut self, right: T, bottom: T) {
254        self.right = right;
255        self.bottom = bottom;
256        self.normalize();
257    }
258}
259
260impl<T: Copy + Sub<Output = T>> Rect<T> {
261    /// 返回矩形的宽度
262    ///
263    /// # Note
264    /// 若矩形不是标准化的,该函数可能会返回负值,
265    /// 若要标准化矩形,请使用 \[`normalize()`\]
266    #[inline]
267    pub fn width(&self) -> T {
268        self.right - self.left
269    }
270
271    /// 返回矩形的高度
272    ///
273    /// # Note
274    /// 若矩形不是标准化的,该函数可能会返回负值,
275    /// 若要标准化矩形,请使用 \[`normalize()`\]
276    #[inline]
277    pub fn height(&self) -> T {
278        self.bottom - self.top
279    }
280
281    /// 返回矩形的尺寸
282    ///
283    /// # Note
284    /// 若矩形不是标准化的,该函数返回值可能包含负值,
285    /// 若要标准化矩形,请使用 \[`normalize()`\]
286    #[inline]
287    pub fn size(&self) -> Size<T> {
288        Size::new(self.width(), self.height())
289    }
290}
291
292impl<T: Copy + Ord> Rect<T> {
293    /// 判断矩形是否标准
294    ///
295    /// # Note
296    /// 若矩形不是标准的,可以通过 \[`normalize()`\] 进行标准化
297    #[cfg(feature = "perf")]
298    #[inline]
299    pub fn is_normalized(&self) -> bool {
300        self.left <= self.right && self.top <= self.bottom
301    }
302
303    /// 将矩形标准化
304    #[cfg(feature = "perf")]
305    #[inline]
306    pub fn normalize(&mut self) {
307        if self.is_normalized() {
308            return;
309        }
310        if self.left > self.right {
311            std::mem::swap(&mut self.left, &mut self.right);
312        }
313        if self.top > self.bottom {
314            std::mem::swap(&mut self.top, &mut self.bottom);
315        }
316    }
317
318    #[allow(dead_code)]
319    #[cfg(not(feature = "perf"))]
320    #[inline]
321    fn is_normalized(&self) -> bool {
322        self.left <= self.right && self.top <= self.bottom
323    }
324
325    #[allow(dead_code)]
326    #[cfg(not(feature = "perf"))]
327    #[inline]
328    fn normalize(&mut self) {
329        if self.is_normalized() {
330            return;
331        }
332        if self.left > self.right {
333            std::mem::swap(&mut self.left, &mut self.right);
334        }
335        if self.top > self.bottom {
336            std::mem::swap(&mut self.top, &mut self.bottom);
337        }
338    }
339
340    /// 判断点是否在矩形区域内
341    ///
342    /// # Note
343    /// 如果点在矩形的边界上,此函数也会返回 `false`;
344    /// 如果需要包含点在矩形边界上的情况,请使用 \[`contains_with_bound()`\]
345    #[inline]
346    pub fn contains(&self, point: Point<T>) -> bool {
347        point.x > self.left && point.x < self.right && point.y > self.top && point.y < self.bottom
348    }
349
350    /// 判断点是否在矩形区域内
351    ///
352    /// # Note
353    /// 如果点在矩形的边界上,此函数也会返回 `true`;
354    /// 如果需要排除点在矩形边界上的情况,请使用 \[`contains_with_bound()`\]
355    #[inline]
356    pub fn contains_with_bound(&self, point: Point<T>) -> bool {
357        point.x >= self.left
358            && point.x <= self.right
359            && point.y >= self.top
360            && point.y <= self.bottom
361    }
362
363    /// 判断此矩形与另一个矩形是否存在交集
364    ///
365    /// 如果另一个矩形与此矩形存在公共区域,则返回 `true`
366    #[inline]
367    pub fn intersects(&self, other: &Rect<T>) -> bool {
368        self.left <= other.right
369            && self.top <= other.bottom
370            && self.right >= other.left
371            && self.bottom >= other.top
372    }
373
374    /// 计算两个矩形的交集
375    ///
376    /// 此函数会计算两个矩形的重叠部分,并返回一个包含该信息的新矩形
377    ///
378    /// # Note
379    /// 若两个矩形没有交集,则返回 `None`
380    #[inline]
381    pub fn intersected(&self, other: &Self) -> Option<Self> {
382        if self.intersects(other) {
383            Some(Self {
384                left: self.left.max(other.left),
385                top: self.top.max(other.top),
386                right: self.right.min(other.right),
387                bottom: self.bottom.min(other.bottom),
388            })
389        } else {
390            None
391        }
392    }
393
394    /// 计算两个矩形的并集
395    ///
396    /// 此函数会计算两个矩形所在位置的最小轮廓矩形,并返回一个包含该信息的新矩形
397    #[inline]
398    pub fn united(&self, other: &Rect<T>) -> Rect<T> {
399        Self {
400            left: self.left.min(other.left),
401            top: self.top.min(other.top),
402            right: self.right.max(other.right),
403            bottom: self.bottom.max(other.bottom),
404        }
405    }
406}
407
408#[cfg(feature = "perf")]
409impl<T: Copy + AddAssign> Rect<T> {
410    /// 调整矩形的位置
411    #[inline]
412    pub fn adjust(&mut self, left: T, top: T, right: T, bottom: T) {
413        self.left += left;
414        self.top += top;
415        self.right += right;
416        self.bottom += bottom;
417    }
418}
419
420#[cfg(not(feature = "perf"))]
421impl<T: Copy + AddAssign + Ord> Rect<T> {
422    /// 调整矩形的位置
423    #[inline]
424    pub fn adjust(&mut self, left: T, top: T, right: T, bottom: T) {
425        self.left += left;
426        self.top += top;
427        self.right += right;
428        self.bottom += bottom;
429        self.normalize();
430    }
431}
432
433impl<T: Copy + Add<Output = T> + Div<i32, Output = T>> Rect<T> {
434    /// 返回矩形的中心点坐标
435    #[inline]
436    pub fn center(&self) -> Point<T> {
437        Point::new((self.right + self.left) / 2, (self.bottom + self.top) / 2)
438    }
439}
440
441impl<T: Copy + Add<Output = T>> From<(Point<T>, Size<T>)> for Rect<T> {
442    #[inline]
443    fn from((pos, size): (Point<T>, Size<T>)) -> Self {
444        Self {
445            left: pos.x,
446            top: pos.y,
447            right: pos.x + size.width,
448            bottom: pos.y + size.height,
449        }
450    }
451}
452
453mod operator_overload {
454    use super::*;
455
456    use std::ops::BitAnd;
457    impl<T: Copy + Ord> BitAnd for Rect<T> {
458        type Output = Option<Self>;
459
460        fn bitand(self, rhs: Self) -> Self::Output {
461            self.intersected(&rhs)
462        }
463    }
464
465    use std::ops::BitOr;
466    impl<T: Copy + Ord> BitOr for Rect<T> {
467        type Output = Self;
468
469        fn bitor(self, rhs: Self) -> Self::Output {
470            self.united(&rhs)
471        }
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    use super::*;
478
479    #[test]
480    #[cfg(feature = "perf")]
481    fn is_normalized_test() {
482        let rect = Rect::new(1, 2, 3, 4);
483        assert!(rect.is_normalized());
484        let rect = Rect::new(3, 2, 1, 4);
485        assert!(!rect.is_normalized());
486    }
487
488    #[test]
489    fn normalize_test() {
490        let mut rect = Rect::new(3, 2, 1, 4);
491        rect.normalize();
492        assert_eq!(rect, Rect::new(1, 2, 3, 4));
493    }
494
495    #[test]
496    fn contains_test() {
497        let rect = Rect::new(1, 2, 3, 4);
498        assert!(rect.contains(Point::new(2, 3)));
499        assert!(!rect.contains(Point::new(0, 0)));
500    }
501
502    #[test]
503    fn contains_with_bound_test() {
504        let rect = Rect::new(1, 2, 3, 4);
505        assert!(rect.contains_with_bound(Point::new(2, 3)));
506        assert!(rect.contains_with_bound(Point::new(1, 2)));
507        assert!(!rect.contains_with_bound(Point::new(0, 0)));
508    }
509
510    #[test]
511    fn intersects_test() {
512        let rect1 = Rect::new(1, 2, 3, 4);
513        let rect2 = Rect::new(2, 3, 4, 5);
514        assert!(rect1.intersects(&rect2));
515        let rect3 = Rect::new(4, 5, 6, 7);
516        assert!(!rect1.intersects(&rect3));
517    }
518
519    #[test]
520    fn intersected_test() {
521        let rect1 = Rect::new(1, 2, 3, 4);
522        let rect2 = Rect::new(2, 3, 4, 5);
523        assert_eq!(rect1.intersected(&rect2), Some(Rect::new(2, 3, 3, 4)));
524        let rect3 = Rect::new(4, 5, 6, 7);
525        assert_eq!(rect1.intersected(&rect3), None);
526    }
527
528    #[test]
529    fn united_test() {
530        let rect1 = Rect::new(1, 2, 3, 4);
531        let rect2 = Rect::new(2, 3, 4, 5);
532        assert_eq!(rect1.united(&rect2), Rect::new(1, 2, 4, 5));
533    }
534
535    #[test]
536    fn adjust_test() {
537        let mut rect = Rect::new(1, 2, 3, 4);
538        rect.adjust(1, 2, 3, 4);
539        assert_eq!(rect, Rect::new(2, 4, 6, 8));
540    }
541
542    #[test]
543    fn center_test() {
544        let rect = Rect::new(1, 2, 3, 4);
545        assert_eq!(rect.center(), Point::new(2, 3));
546    }
547}