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 + Copy,
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 pub const fn size(&self) -> Size<T> {
37 Size::new(self.width, self.height)
38 }
39}
40
41impl Rectangle<f32> {
42 pub const INFINITE: Self = Self::new(
44 Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
45 Size::INFINITE,
46 );
47
48 pub const fn new(top_left: Point, size: Size) -> Self {
51 Self {
52 x: top_left.x,
53 y: top_left.y,
54 width: size.width,
55 height: size.height,
56 }
57 }
58
59 pub fn with_radius(radius: f32) -> Self {
62 Self {
63 x: -radius,
64 y: -radius,
65 width: radius * 2.0,
66 height: radius * 2.0,
67 }
68 }
69
70 pub fn with_vertices(
74 top_left: Point,
75 top_right: Point,
76 bottom_left: Point,
77 ) -> (Rectangle, Radians) {
78 let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y);
79
80 let height = (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
81
82 let rotation = (top_right.y - top_left.y).atan2(top_right.x - top_left.x);
83
84 let rotation = if rotation < 0.0 {
85 2.0 * std::f32::consts::PI + rotation
86 } else {
87 rotation
88 };
89
90 let position = {
91 let center = Point::new(
92 (top_right.x + bottom_left.x) / 2.0,
93 (top_right.y + bottom_left.y) / 2.0,
94 );
95
96 let rotation = -rotation - std::f32::consts::PI * 2.0;
97
98 Point::new(
99 center.x + (top_left.x - center.x) * rotation.cos()
100 - (top_left.y - center.y) * rotation.sin(),
101 center.y
102 + (top_left.x - center.x) * rotation.sin()
103 + (top_left.y - center.y) * rotation.cos(),
104 )
105 };
106
107 (
108 Rectangle::new(position, Size::new(width, height)),
109 Radians(rotation),
110 )
111 }
112
113 pub fn center(&self) -> Point {
115 Point::new(self.center_x(), self.center_y())
116 }
117
118 pub fn center_x(&self) -> f32 {
121 self.x + self.width / 2.0
122 }
123
124 pub fn center_y(&self) -> f32 {
127 self.y + self.height / 2.0
128 }
129
130 pub fn position(&self) -> Point {
132 Point::new(self.x, self.y)
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 round(self) -> Self {
243 let top_left = self.position().round();
244 let bottom_right = (self.position() + Vector::from(self.size())).round();
245
246 Self {
247 x: top_left.x,
248 y: top_left.y,
249 width: bottom_right.x - top_left.x,
250 height: bottom_right.y - top_left.y,
251 }
252 }
253
254 pub fn snap(self) -> Option<Rectangle<u32>> {
256 let rounded = self.round();
257
258 if rounded.width < 1.0 || rounded.height < 1.0 {
259 return None;
260 }
261
262 Some(Rectangle {
263 x: rounded.x as u32,
264 y: rounded.y as u32,
265 width: rounded.width as u32,
266 height: rounded.height as u32,
267 })
268 }
269
270 pub fn expand(self, padding: impl Into<Padding>) -> Self {
272 let padding = padding.into();
273
274 Self {
275 x: self.x - padding.left,
276 y: self.y - padding.top,
277 width: self.width + padding.x(),
278 height: self.height + padding.y(),
279 }
280 }
281
282 pub fn shrink(self, padding: impl Into<Padding>) -> Self {
284 let padding = padding.into();
285
286 Self {
287 x: self.x + padding.left,
288 y: self.y + padding.top,
289 width: self.width - padding.x(),
290 height: self.height - padding.y(),
291 }
292 }
293
294 pub fn rotate(self, rotation: Radians) -> Self {
297 let size = self.size().rotate(rotation);
298 let position = Point::new(
299 self.center_x() - size.width / 2.0,
300 self.center_y() - size.height / 2.0,
301 );
302
303 Self::new(position, size)
304 }
305
306 pub fn zoom(self, zoom: f32) -> Self {
309 Self {
310 x: self.x - (self.width * (zoom - 1.0)) / 2.0,
311 y: self.y - (self.height * (zoom - 1.0)) / 2.0,
312 width: self.width * zoom,
313 height: self.height * zoom,
314 }
315 }
316
317 pub fn anchor(
321 &self,
322 size: Size,
323 align_x: impl Into<alignment::Horizontal>,
324 align_y: impl Into<alignment::Vertical>,
325 ) -> Point {
326 let x = match align_x.into() {
327 alignment::Horizontal::Left => self.x,
328 alignment::Horizontal::Center => self.x + (self.width - size.width) / 2.0,
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 => self.y + (self.height - size.height) / 2.0,
335 alignment::Vertical::Bottom => self.y + self.height - size.height,
336 };
337
338 Point::new(x, y)
339 }
340
341 pub fn chord(&self, angle: impl Into<Radians>) -> (Point, Point) {
348 use std::f32::consts::FRAC_PI_2;
349
350 let angle = angle.into().0 - FRAC_PI_2;
351 let r = Vector::new(f32::cos(angle), f32::sin(angle));
352
353 let distance_to_rect = f32::max(
354 f32::abs(r.x * self.width / 2.0),
355 f32::abs(r.y * self.height / 2.0),
356 );
357
358 let center = self.center();
359 let start = center - r * distance_to_rect;
360 let end = center + r * distance_to_rect;
361
362 (start, end)
363 }
364}
365
366impl std::ops::Mul<f32> for Rectangle<f32> {
367 type Output = Self;
368
369 fn mul(self, scale: f32) -> Self {
370 Self {
371 x: self.x * scale,
372 y: self.y * scale,
373 width: self.width * scale,
374 height: self.height * scale,
375 }
376 }
377}
378
379impl From<Rectangle<u32>> for Rectangle<f32> {
380 fn from(rectangle: Rectangle<u32>) -> Rectangle<f32> {
381 Rectangle {
382 x: rectangle.x as f32,
383 y: rectangle.y as f32,
384 width: rectangle.width as f32,
385 height: rectangle.height as f32,
386 }
387 }
388}
389
390impl<T> std::ops::Add<Vector<T>> for Rectangle<T>
391where
392 T: std::ops::Add<Output = T>,
393{
394 type Output = Rectangle<T>;
395
396 fn add(self, translation: Vector<T>) -> Self {
397 Rectangle {
398 x: self.x + translation.x,
399 y: self.y + translation.y,
400 ..self
401 }
402 }
403}
404
405impl<T> std::ops::AddAssign<Vector<T>> for Rectangle<T>
406where
407 T: std::ops::AddAssign,
408{
409 fn add_assign(&mut self, translation: Vector<T>) {
410 self.x += translation.x;
411 self.y += translation.y;
412 }
413}
414
415impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
416where
417 T: std::ops::Sub<Output = T>,
418{
419 type Output = Rectangle<T>;
420
421 fn sub(self, translation: Vector<T>) -> Self {
422 Rectangle {
423 x: self.x - translation.x,
424 y: self.y - translation.y,
425 ..self
426 }
427 }
428}
429
430impl<T> std::ops::SubAssign<Vector<T>> for Rectangle<T>
431where
432 T: std::ops::SubAssign,
433{
434 fn sub_assign(&mut self, translation: Vector<T>) {
435 self.x -= translation.x;
436 self.y -= translation.y;
437 }
438}