1use std::{
2 fmt,
3 ops::{Add, Div, Mul, Rem, Sub},
4};
5
6use num::{
7 CheckedAdd,
8 CheckedDiv,
9 CheckedMul,
10 CheckedSub,
11 One,
12 traits::CheckedRem,
13};
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17use crate::{
18 coords::CoordPair,
19 orientation::{Direction, DirectionVec},
20};
21
22#[derive(Debug, Error)]
23#[error("Invalid point {point} for rectangle {rect}")]
24pub struct InvalidPoint<C>
25where
26 C: fmt::Display,
27{
28 pub point: CoordPair<C>,
29 pub rect: Rect<C>,
30}
31
32#[derive(Debug, Error)]
33#[error("Invalid area {area} for partition of rectangle {rect}")]
34pub struct InvalidArea<C>
35where
36 C: fmt::Display,
37{
38 pub area: C,
39 pub rect: Rect<C>,
40}
41
42#[derive(Debug, Error)]
43pub enum HorzAreaError<C>
44where
45 C: fmt::Display,
46{
47 #[error("Point outside of rectangle defines no internal area")]
48 InvalidRectPoint(
49 #[from]
50 #[source]
51 InvalidPoint<C>,
52 ),
53 #[error("Arithmetic overflow computing area for rectangle of size {size}")]
54 Overflow { size: CoordPair<C> },
55}
56
57#[derive(
58 Debug,
59 Clone,
60 Copy,
61 PartialEq,
62 Eq,
63 PartialOrd,
64 Ord,
65 Hash,
66 Default,
67 Serialize,
68 Deserialize,
69)]
70pub struct Rect<C> {
71 pub top_left: CoordPair<C>,
72 pub size: CoordPair<C>,
73}
74
75impl<C> Rect<C> {
76 pub fn as_ref(&self) -> Rect<&C> {
77 Rect { top_left: self.top_left.as_ref(), size: self.size.as_ref() }
78 }
79
80 pub fn as_mut(&mut self) -> Rect<&mut C> {
81 Rect { top_left: self.top_left.as_mut(), size: self.size.as_mut() }
82 }
83
84 pub fn map<F, C0>(self, mut mapper: F) -> Rect<C0>
85 where
86 F: FnMut(C) -> C0,
87 {
88 Rect {
89 top_left: self.top_left.map(&mut mapper),
90 size: self.size.map(mapper),
91 }
92 }
93
94 pub fn try_map<F, C0, E>(self, mut mapper: F) -> Result<Rect<C0>, E>
95 where
96 F: FnMut(C) -> Result<C0, E>,
97 {
98 Ok(Rect {
99 top_left: self.top_left.try_map(&mut mapper)?,
100 size: self.size.try_map(mapper)?,
101 })
102 }
103
104 pub fn bottom_right<D>(self) -> CoordPair<D>
105 where
106 C: Add<Output = D>,
107 {
108 self.top_left + self.size
109 }
110
111 pub fn checked_bottom_right(&self) -> Option<CoordPair<C>>
112 where
113 C: CheckedAdd,
114 {
115 self.top_left.checked_add(&self.size)
116 }
117
118 pub fn contains_point(self, point: CoordPair<C>) -> bool
119 where
120 C: Sub<Output = C> + PartialOrd,
121 {
122 self.top_left
123 .zip3(self.size, point)
124 .all(|(start, size, coord)| coord >= start && coord - start < size)
125 }
126
127 pub fn checked_horz_area_down_to(
128 self,
129 point: CoordPair<C>,
130 ) -> Result<C, HorzAreaError<C>>
131 where
132 C: Sub<Output = C> + CheckedAdd + CheckedMul,
133 C: fmt::Display + PartialOrd + Clone,
134 {
135 if self.clone().contains_point(point.clone()) {
136 let from_origin = point - self.top_left;
137 self.size
138 .x
139 .checked_mul(&from_origin.y)
140 .and_then(|scaled| scaled.checked_add(&from_origin.x))
141 .ok_or(HorzAreaError::Overflow { size: self.size })
142 } else {
143 Err(InvalidPoint { point, rect: self })?
144 }
145 }
146
147 pub fn horz_area_down_to(self, point: CoordPair<C>) -> C
148 where
149 C: Sub<Output = C> + Mul<Output = C> + Add<Output = C>,
150 C: fmt::Display + PartialOrd + Clone,
151 {
152 let from_origin = point - self.top_left;
153 self.size.x * from_origin.y + from_origin.x
154 }
155
156 pub fn checked_bot_right_of_horz_area(
157 &self,
158 area: &C,
159 ) -> Result<CoordPair<C>, InvalidArea<C>>
160 where
161 C: CheckedAdd + CheckedDiv + CheckedRem + Clone,
162 C: fmt::Display,
163 {
164 let optional_coords = CoordPair {
165 x: area.checked_rem(&self.size.x),
166 y: area.checked_div(&self.size.x),
167 };
168
169 optional_coords
170 .transpose()
171 .and_then(|from_origin| self.top_left.checked_add(&from_origin))
172 .ok_or(InvalidArea { area: area.clone(), rect: self.clone() })
173 }
174
175 pub fn bot_right_of_horz_area(self, area: C) -> CoordPair<C>
176 where
177 C: Add<Output = C> + Div<Output = C> + Rem<Output = C> + Clone,
178 {
179 let x = area.clone() % self.size.x;
180 let y = area / self.size.y;
181 let from_origin = CoordPair { x, y };
182 self.top_left + from_origin
183 }
184
185 pub fn total_area<D>(self) -> D
186 where
187 C: Mul<Output = D>,
188 {
189 self.size.x * self.size.y
190 }
191
192 pub fn checked_total_area(&self) -> Option<C>
193 where
194 C: CheckedMul,
195 {
196 self.as_ref().checked_total_area_by_ref()
197 }
198
199 pub fn checked_move_point_unit(
200 self,
201 point: CoordPair<C>,
202 direction: Direction,
203 ) -> Result<CoordPair<C>, InvalidPoint<C>>
204 where
205 C: Add<Output = C> + Sub<Output = C> + CheckedAdd + CheckedSub + One,
206 C: Clone + PartialOrd + fmt::Display,
207 {
208 self.checked_move_point_by(
209 point,
210 DirectionVec::unit(direction).as_ref(),
211 )
212 }
213
214 pub fn checked_move_point_by(
215 self,
216 point: CoordPair<C>,
217 vector: DirectionVec<&C>,
218 ) -> Result<CoordPair<C>, InvalidPoint<C>>
219 where
220 C: Add<Output = C> + Sub<Output = C> + CheckedAdd + CheckedSub,
221 C: Clone + PartialOrd + fmt::Display,
222 {
223 if let Some(output) = point
224 .checked_move_by(vector)
225 .filter(|output| self.clone().contains_point(output.clone()))
226 {
227 Ok(output)
228 } else {
229 Err(InvalidPoint { point, rect: self })
230 }
231 }
232}
233
234impl<'a, C> Rect<&'a C> {
235 pub fn copied(self) -> Rect<C>
236 where
237 C: Copy,
238 {
239 Rect { top_left: self.top_left.copied(), size: self.size.copied() }
240 }
241
242 pub fn cloned(self) -> Rect<C>
243 where
244 C: Clone,
245 {
246 Rect { top_left: self.top_left.cloned(), size: self.size.cloned() }
247 }
248
249 pub fn checked_bottom_right_by_ref(self) -> Option<CoordPair<C>>
250 where
251 C: CheckedAdd,
252 {
253 self.top_left.checked_add_by_ref(self.size)
254 }
255
256 pub fn checked_total_area_by_ref(self) -> Option<C>
257 where
258 C: CheckedMul,
259 {
260 self.size.x.checked_mul(self.size.y)
261 }
262}
263
264impl<'a, C> Rect<&'a mut C> {
265 pub fn copied(self) -> Rect<C>
266 where
267 C: Copy,
268 {
269 Rect { top_left: self.top_left.copied(), size: self.size.copied() }
270 }
271
272 pub fn cloned(self) -> Rect<C>
273 where
274 C: Clone,
275 {
276 Rect { top_left: self.top_left.cloned(), size: self.size.cloned() }
277 }
278
279 pub fn share(self) -> Rect<&'a C> {
280 Rect { top_left: self.top_left.share(), size: self.size.share() }
281 }
282}
283
284impl<C> fmt::Display for Rect<C>
285where
286 C: fmt::Display,
287{
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 write!(f, "{}[{}]", self.top_left, self.size)
290 }
291}