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 = (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
76
77 let rotation = (top_right.y - top_left.y).atan2(top_right.x - top_left.x);
78
79 let rotation = if rotation < 0.0 {
80 2.0 * std::f32::consts::PI + rotation
81 } else {
82 rotation
83 };
84
85 let position = {
86 let center = Point::new(
87 (top_right.x + bottom_left.x) / 2.0,
88 (top_right.y + bottom_left.y) / 2.0,
89 );
90
91 let rotation = -rotation - std::f32::consts::PI * 2.0;
92
93 Point::new(
94 center.x + (top_left.x - center.x) * rotation.cos()
95 - (top_left.y - center.y) * rotation.sin(),
96 center.y
97 + (top_left.x - center.x) * rotation.sin()
98 + (top_left.y - center.y) * rotation.cos(),
99 )
100 };
101
102 (
103 Rectangle::new(position, Size::new(width, height)),
104 Radians(rotation),
105 )
106 }
107
108 pub fn center(&self) -> Point {
110 Point::new(self.center_x(), self.center_y())
111 }
112
113 pub fn center_x(&self) -> f32 {
116 self.x + self.width / 2.0
117 }
118
119 pub fn center_y(&self) -> f32 {
122 self.y + self.height / 2.0
123 }
124
125 pub fn position(&self) -> Point {
127 Point::new(self.x, self.y)
128 }
129
130 pub fn size(&self) -> Size {
132 Size::new(self.width, self.height)
133 }
134
135 pub fn area(&self) -> f32 {
137 self.width * self.height
138 }
139
140 pub fn contains(&self, point: Point) -> bool {
143 self.x <= point.x
144 && point.x < self.x + self.width
145 && self.y <= point.y
146 && point.y < self.y + self.height
147 }
148
149 pub fn distance(&self, point: Point) -> f32 {
152 let center = self.center();
153
154 let distance_x = ((point.x - center.x).abs() - self.width / 2.0).max(0.0);
155
156 let distance_y = ((point.y - center.y).abs() - self.height / 2.0).max(0.0);
157
158 distance_x.hypot(distance_y)
159 }
160
161 pub fn offset(&self, container: &Rectangle) -> Vector {
164 let Some(intersection) = self.intersection(container) else {
165 return Vector::ZERO;
166 };
167
168 let left = intersection.x - self.x;
169 let top = intersection.y - self.y;
170
171 Vector::new(
172 if left > 0.0 {
173 left
174 } else {
175 intersection.x + intersection.width - self.x - self.width
176 },
177 if top > 0.0 {
178 top
179 } else {
180 intersection.y + intersection.height - self.y - self.height
181 },
182 )
183 }
184
185 pub fn is_within(&self, container: &Rectangle) -> bool {
188 self.x >= container.x
189 && self.y >= container.y
190 && self.x + self.width <= container.x + container.width
191 && self.y + self.height <= container.y + container.height
192 }
193
194 pub fn intersection(&self, other: &Rectangle<f32>) -> Option<Rectangle<f32>> {
196 let x = self.x.max(other.x);
197 let y = self.y.max(other.y);
198
199 let lower_right_x = (self.x + self.width).min(other.x + other.width);
200 let lower_right_y = (self.y + self.height).min(other.y + other.height);
201
202 let width = lower_right_x - x;
203 let height = lower_right_y - y;
204
205 if width > 0.0 && height > 0.0 {
206 Some(Rectangle {
207 x,
208 y,
209 width,
210 height,
211 })
212 } else {
213 None
214 }
215 }
216
217 pub fn intersects(&self, other: &Self) -> bool {
219 self.intersection(other).is_some()
220 }
221
222 pub fn union(&self, other: &Self) -> Self {
224 let x = self.x.min(other.x);
225 let y = self.y.min(other.y);
226
227 let lower_right_x = (self.x + self.width).max(other.x + other.width);
228 let lower_right_y = (self.y + self.height).max(other.y + other.height);
229
230 let width = lower_right_x - x;
231 let height = lower_right_y - y;
232
233 Rectangle {
234 x,
235 y,
236 width,
237 height,
238 }
239 }
240
241 pub fn snap(self) -> Option<Rectangle<u32>> {
243 let top_left = self.position().snap();
244 let bottom_right = (self.position() + Vector::from(self.size())).snap();
245
246 let width = bottom_right.x.checked_sub(top_left.x)?;
247 let height = bottom_right.y.checked_sub(top_left.y)?;
248
249 if width < 1 || height < 1 {
250 return None;
251 }
252
253 Some(Rectangle {
254 x: top_left.x,
255 y: top_left.y,
256 width,
257 height,
258 })
259 }
260
261 pub fn expand(self, padding: impl Into<Padding>) -> Self {
263 let padding = padding.into();
264
265 Self {
266 x: self.x - padding.left,
267 y: self.y - padding.top,
268 width: self.width + padding.x(),
269 height: self.height + padding.y(),
270 }
271 }
272
273 pub fn shrink(self, padding: impl Into<Padding>) -> Self {
275 let padding = padding.into();
276
277 Self {
278 x: self.x + padding.left,
279 y: self.y + padding.top,
280 width: self.width - padding.x(),
281 height: self.height - padding.y(),
282 }
283 }
284
285 pub fn rotate(self, rotation: Radians) -> Self {
288 let size = self.size().rotate(rotation);
289 let position = Point::new(
290 self.center_x() - size.width / 2.0,
291 self.center_y() - size.height / 2.0,
292 );
293
294 Self::new(position, size)
295 }
296
297 pub fn zoom(self, zoom: f32) -> Self {
300 Self {
301 x: self.x - (self.width * (zoom - 1.0)) / 2.0,
302 y: self.y - (self.height * (zoom - 1.0)) / 2.0,
303 width: self.width * zoom,
304 height: self.height * zoom,
305 }
306 }
307
308 pub fn anchor(
312 &self,
313 size: Size,
314 align_x: impl Into<alignment::Horizontal>,
315 align_y: impl Into<alignment::Vertical>,
316 ) -> Point {
317 let x = match align_x.into() {
318 alignment::Horizontal::Left => self.x,
319 alignment::Horizontal::Center => self.x + (self.width - size.width) / 2.0,
320 alignment::Horizontal::Right => self.x + self.width - size.width,
321 };
322
323 let y = match align_y.into() {
324 alignment::Vertical::Top => self.y,
325 alignment::Vertical::Center => self.y + (self.height - size.height) / 2.0,
326 alignment::Vertical::Bottom => self.y + self.height - size.height,
327 };
328
329 Point::new(x, y)
330 }
331}
332
333impl std::ops::Mul<f32> for Rectangle<f32> {
334 type Output = Self;
335
336 fn mul(self, scale: f32) -> Self {
337 Self {
338 x: self.x * scale,
339 y: self.y * scale,
340 width: self.width * scale,
341 height: self.height * scale,
342 }
343 }
344}
345
346impl From<Rectangle<u32>> for Rectangle<f32> {
347 fn from(rectangle: Rectangle<u32>) -> Rectangle<f32> {
348 Rectangle {
349 x: rectangle.x as f32,
350 y: rectangle.y as f32,
351 width: rectangle.width as f32,
352 height: rectangle.height as f32,
353 }
354 }
355}
356
357impl<T> std::ops::Add<Vector<T>> for Rectangle<T>
358where
359 T: std::ops::Add<Output = T>,
360{
361 type Output = Rectangle<T>;
362
363 fn add(self, translation: Vector<T>) -> Self {
364 Rectangle {
365 x: self.x + translation.x,
366 y: self.y + translation.y,
367 ..self
368 }
369 }
370}
371
372impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
373where
374 T: std::ops::Sub<Output = T>,
375{
376 type Output = Rectangle<T>;
377
378 fn sub(self, translation: Vector<T>) -> Self {
379 Rectangle {
380 x: self.x - translation.x,
381 y: self.y - translation.y,
382 ..self
383 }
384 }
385}