1use crate::prelude::Point;
2use std::collections::HashSet;
3use std::convert::TryInto;
4use std::ops;
5
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[derive(PartialEq, Eq, Copy, Clone, Debug)]
9pub struct Rect {
10 pub x1: i32,
12 pub x2: i32,
14 pub y1: i32,
16 pub y2: i32,
18}
19
20#[cfg(feature = "specs")]
21impl specs::prelude::Component for Rect {
22 type Storage = specs::prelude::VecStorage<Self>;
23}
24
25impl Default for Rect {
26 fn default() -> Rect {
27 Rect::zero()
28 }
29}
30
31impl Rect {
32 pub fn with_size<T>(x: T, y: T, w: T, h: T) -> Rect
34 where
35 T: TryInto<i32>,
36 {
37 let x_i32: i32 = x.try_into().ok().unwrap();
38 let y_i32: i32 = y.try_into().ok().unwrap();
39 Rect {
40 x1: x_i32,
41 y1: y_i32,
42 x2: x_i32 + w.try_into().ok().unwrap(),
43 y2: y_i32 + h.try_into().ok().unwrap(),
44 }
45 }
46
47 pub fn with_exact<T>(x1: T, y1: T, x2: T, y2: T) -> Rect
49 where
50 T: TryInto<i32>,
51 {
52 Rect {
53 x1: x1.try_into().ok().unwrap(),
54 y1: y1.try_into().ok().unwrap(),
55 x2: x2.try_into().ok().unwrap(),
56 y2: y2.try_into().ok().unwrap(),
57 }
58 }
59
60 pub fn zero() -> Rect {
62 Rect {
63 x1: 0,
64 y1: 0,
65 x2: 0,
66 y2: 0,
67 }
68 }
69
70 #[must_use]
72 pub fn intersect(&self, other: &Rect) -> bool {
73 self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
74 }
75
76 #[must_use]
78 pub fn center(&self) -> Point {
79 Point::new((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
80 }
81
82 #[must_use]
84 pub fn point_in_rect(&self, point: Point) -> bool {
85 point.x >= self.x1 && point.x < self.x2 && point.y >= self.y1 && point.y < self.y2
86 }
87
88 pub fn for_each<F>(&self, mut f: F)
90 where
91 F: FnMut(Point),
92 {
93 for y in self.y1..self.y2 {
94 for x in self.x1..self.x2 {
95 f(Point::new(x, y));
96 }
97 }
98 }
99
100 #[must_use]
102 pub fn point_set(&self) -> HashSet<Point> {
103 let mut result = HashSet::new();
104 for y in self.y1..self.y2 {
105 for x in self.x1..self.x2 {
106 result.insert(Point::new(x, y));
107 }
108 }
109 result
110 }
111
112 #[must_use]
114 pub fn width(&self) -> i32 {
115 i32::abs(self.x2 - self.x1)
116 }
117
118 #[must_use]
120 pub fn height(&self) -> i32 {
121 i32::abs(self.y2 - self.y1)
122 }
123}
124
125impl ops::Add<Rect> for Rect {
126 type Output = Rect;
127 fn add(mut self, rhs: Rect) -> Rect {
128 let w = self.width();
129 let h = self.height();
130 self.x1 += rhs.x1;
131 self.x2 = self.x1 + w;
132 self.y1 += rhs.y1;
133 self.y2 = self.y1 + h;
134 self
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use crate::prelude::{Point, Rect};
141
142 #[test]
143 fn test_dimensions() {
144 let rect = Rect::with_size(0, 0, 10, 10);
145 assert!(rect.width() == 10);
146 assert!(rect.height() == 10);
147 }
148
149 #[test]
150 fn test_add() {
151 let rect = Rect::with_size(0, 0, 10, 10) + Rect::with_size(1, 1, 1, 1);
152 assert!(rect.x1 == 1 && rect.y1 == 1);
153 assert!(rect.x2 == 11 && rect.y2 == 11);
154 }
155
156 #[test]
157 fn test_intersect() {
158 let r1 = Rect::with_size(0, 0, 10, 10);
159 let r2 = Rect::with_size(5, 5, 10, 10);
160 let r3 = Rect::with_size(100, 100, 5, 5);
161 assert!(r1.intersect(&r2));
162 assert!(!r1.intersect(&r3));
163 }
164
165 #[test]
166 fn test_center() {
167 let r1 = Rect::with_size(0, 0, 10, 10);
168 let center = r1.center();
169 assert!(center.x == 5 && center.y == 5);
170 }
171
172 #[test]
173 fn test_point_in_rect() {
174 let r1 = Rect::with_size(0, 0, 10, 10);
175 assert!(r1.point_in_rect(Point::new(5, 5)));
176 assert!(!r1.point_in_rect(Point::new(100, 100)));
177 }
178
179 #[test]
180 fn test_rect_set() {
181 let r1 = Rect::with_size(0, 0, 1, 1);
182 let points = r1.point_set();
183 assert!(points.contains(&Point::new(0, 0)));
184 assert!(!points.contains(&Point::new(1, 0)));
185 assert!(!points.contains(&Point::new(0, 1)));
186 assert!(!points.contains(&Point::new(1, 1)));
187 }
188
189 #[test]
190 fn test_rect_callback() {
191 use std::collections::HashSet;
192
193 let r1 = Rect::with_size(0, 0, 1, 1);
194 let mut points: HashSet<Point> = HashSet::new();
195 r1.for_each(|p| {
196 points.insert(p);
197 });
198 assert!(points.contains(&Point::new(0, 0)));
199 assert!(!points.contains(&Point::new(1, 0)));
200 assert!(!points.contains(&Point::new(0, 1)));
201 assert!(!points.contains(&Point::new(1, 1)));
202 }
203}