1use crate::alignment;
2use crate::{Padding, Point, Radians, Size, Vector};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub struct Rectangle<T = f32> {
7 pub x: T,
9
10 pub y: T,
12
13 pub width: T,
15
16 pub height: T,
18}
19
20impl<T> Rectangle<T>
21where
22 T: Default,
23{
24 pub fn with_size(size: Size<T>) -> Self {
27 Self {
28 x: T::default(),
29 y: T::default(),
30 width: size.width,
31 height: size.height,
32 }
33 }
34}
35
36impl Rectangle<f32> {
37 pub const INFINITE: Self = Self::new(
39 Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
40 Size::INFINITE,
41 );
42
43 pub const fn new(top_left: Point, size: Size) -> Self {
46 Self {
47 x: top_left.x,
48 y: top_left.y,
49 width: size.width,
50 height: size.height,
51 }
52 }
53
54 pub fn with_radius(radius: f32) -> Self {
57 Self {
58 x: -radius,
59 y: -radius,
60 width: radius * 2.0,
61 height: radius * 2.0,
62 }
63 }
64
65 pub fn with_vertices(
69 top_left: Point,
70 top_right: Point,
71 bottom_left: Point,
72 ) -> (Rectangle, Radians) {
73 let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y);
74
75 let height =
76 (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
77
78 let rotation =
79 (top_right.y - top_left.y).atan2(top_right.x - top_left.x);
80
81 let rotation = if rotation < 0.0 {
82 2.0 * std::f32::consts::PI + rotation
83 } else {
84 rotation
85 };
86
87 let position = {
88 let center = Point::new(
89 (top_right.x + bottom_left.x) / 2.0,
90 (top_right.y + bottom_left.y) / 2.0,
91 );
92
93 let rotation = -rotation - std::f32::consts::PI * 2.0;
94
95 Point::new(
96 center.x + (top_left.x - center.x) * rotation.cos()
97 - (top_left.y - center.y) * rotation.sin(),
98 center.y
99 + (top_left.x - center.x) * rotation.sin()
100 + (top_left.y - center.y) * rotation.cos(),
101 )
102 };
103
104 (
105 Rectangle::new(position, Size::new(width, height)),
106 Radians(rotation),
107 )
108 }
109
110 pub fn center(&self) -> Point {
112 Point::new(self.center_x(), self.center_y())
113 }
114
115 pub fn center_x(&self) -> f32 {
118 self.x + self.width / 2.0
119 }
120
121 pub fn center_y(&self) -> f32 {
124 self.y + self.height / 2.0
125 }
126
127 pub fn position(&self) -> Point {
129 Point::new(self.x, self.y)
130 }
131
132 pub fn size(&self) -> Size {
134 Size::new(self.width, self.height)
135 }
136
137 pub fn area(&self) -> f32 {
139 self.width * self.height
140 }
141
142 pub fn contains(&self, point: Point) -> bool {
145 self.x <= point.x
146 && point.x < self.x + self.width
147 && self.y <= point.y
148 && point.y < self.y + self.height
149 }
150
151 pub fn distance(&self, point: Point) -> f32 {
154 let center = self.center();
155
156 let distance_x =
157 ((point.x - center.x).abs() - self.width / 2.0).max(0.0);
158
159 let distance_y =
160 ((point.y - center.y).abs() - self.height / 2.0).max(0.0);
161
162 distance_x.hypot(distance_y)
163 }
164
165 pub fn offset(&self, container: &Rectangle) -> Vector {
168 let Some(intersection) = self.intersection(container) else {
169 return Vector::ZERO;
170 };
171
172 let left = intersection.x - self.x;
173 let top = intersection.y - self.y;
174
175 Vector::new(
176 if left > 0.0 {
177 left
178 } else {
179 intersection.x + intersection.width - self.x - self.width
180 },
181 if top > 0.0 {
182 top
183 } else {
184 intersection.y + intersection.height - self.y - self.height
185 },
186 )
187 }
188
189 pub fn is_within(&self, container: &Rectangle) -> bool {
192 self.x >= container.x
193 && self.y >= container.y
194 && self.x + self.width <= container.x + container.width
195 && self.y + self.height <= container.y + container.height
196 }
197
198 pub fn intersection(
200 &self,
201 other: &Rectangle<f32>,
202 ) -> Option<Rectangle<f32>> {
203 let x = self.x.max(other.x);
204 let y = self.y.max(other.y);
205
206 let lower_right_x = (self.x + self.width).min(other.x + other.width);
207 let lower_right_y = (self.y + self.height).min(other.y + other.height);
208
209 let width = lower_right_x - x;
210 let height = lower_right_y - y;
211
212 if width > 0.0 && height > 0.0 {
213 Some(Rectangle {
214 x,
215 y,
216 width,
217 height,
218 })
219 } else {
220 None
221 }
222 }
223
224 pub fn intersects(&self, other: &Self) -> bool {
226 self.intersection(other).is_some()
227 }
228
229 pub fn union(&self, other: &Self) -> Self {
231 let x = self.x.min(other.x);
232 let y = self.y.min(other.y);
233
234 let lower_right_x = (self.x + self.width).max(other.x + other.width);
235 let lower_right_y = (self.y + self.height).max(other.y + other.height);
236
237 let width = lower_right_x - x;
238 let height = lower_right_y - y;
239
240 Rectangle {
241 x,
242 y,
243 width,
244 height,
245 }
246 }
247
248 pub fn snap(self) -> Option<Rectangle<u32>> {
250 let top_left = self.position().snap();
251 let bottom_right = (self.position() + Vector::from(self.size())).snap();
252
253 let width = bottom_right.x.checked_sub(top_left.x)?;
254 let height = bottom_right.y.checked_sub(top_left.y)?;
255
256 if width < 1 || height < 1 {
257 return None;
258 }
259
260 Some(Rectangle {
261 x: top_left.x,
262 y: top_left.y,
263 width,
264 height,
265 })
266 }
267
268 pub fn expand(self, padding: impl Into<Padding>) -> Self {
270 let padding = padding.into();
271
272 Self {
273 x: self.x - padding.left,
274 y: self.y - padding.top,
275 width: self.width + padding.x(),
276 height: self.height + padding.y(),
277 }
278 }
279
280 pub fn shrink(self, padding: impl Into<Padding>) -> Self {
282 let padding = padding.into();
283
284 Self {
285 x: self.x + padding.left,
286 y: self.y + padding.top,
287 width: self.width - padding.x(),
288 height: self.height - padding.y(),
289 }
290 }
291
292 pub fn rotate(self, rotation: Radians) -> Self {
295 let size = self.size().rotate(rotation);
296 let position = Point::new(
297 self.center_x() - size.width / 2.0,
298 self.center_y() - size.height / 2.0,
299 );
300
301 Self::new(position, size)
302 }
303
304 pub fn zoom(self, zoom: f32) -> Self {
307 Self {
308 x: self.x - (self.width * (zoom - 1.0)) / 2.0,
309 y: self.y - (self.height * (zoom - 1.0)) / 2.0,
310 width: self.width * zoom,
311 height: self.height * zoom,
312 }
313 }
314
315 pub fn anchor(
319 &self,
320 size: Size,
321 align_x: impl Into<alignment::Horizontal>,
322 align_y: impl Into<alignment::Vertical>,
323 ) -> Point {
324 let x = match align_x.into() {
325 alignment::Horizontal::Left => self.x,
326 alignment::Horizontal::Center => {
327 self.x + (self.width - size.width) / 2.0
328 }
329 alignment::Horizontal::Right => self.x + self.width - size.width,
330 };
331
332 let y = match align_y.into() {
333 alignment::Vertical::Top => self.y,
334 alignment::Vertical::Center => {
335 self.y + (self.height - size.height) / 2.0
336 }
337 alignment::Vertical::Bottom => self.y + self.height - size.height,
338 };
339
340 Point::new(x, y)
341 }
342}
343
344impl std::ops::Mul<f32> for Rectangle<f32> {
345 type Output = Self;
346
347 fn mul(self, scale: f32) -> Self {
348 Self {
349 x: self.x * scale,
350 y: self.y * scale,
351 width: self.width * scale,
352 height: self.height * scale,
353 }
354 }
355}
356
357impl From<Rectangle<u32>> for Rectangle<f32> {
358 fn from(rectangle: Rectangle<u32>) -> Rectangle<f32> {
359 Rectangle {
360 x: rectangle.x as f32,
361 y: rectangle.y as f32,
362 width: rectangle.width as f32,
363 height: rectangle.height as f32,
364 }
365 }
366}
367
368impl<T> std::ops::Add<Vector<T>> for Rectangle<T>
369where
370 T: std::ops::Add<Output = T>,
371{
372 type Output = Rectangle<T>;
373
374 fn add(self, translation: Vector<T>) -> Self {
375 Rectangle {
376 x: self.x + translation.x,
377 y: self.y + translation.y,
378 ..self
379 }
380 }
381}
382
383impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
384where
385 T: std::ops::Sub<Output = T>,
386{
387 type Output = Rectangle<T>;
388
389 fn sub(self, translation: Vector<T>) -> Self {
390 Rectangle {
391 x: self.x - translation.x,
392 y: self.y - translation.y,
393 ..self
394 }
395 }
396}