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 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
342impl std::ops::Mul<f32> for Rectangle<f32> {
343 type Output = Self;
344
345 fn mul(self, scale: f32) -> Self {
346 Self {
347 x: self.x * scale,
348 y: self.y * scale,
349 width: self.width * scale,
350 height: self.height * scale,
351 }
352 }
353}
354
355impl From<Rectangle<u32>> for Rectangle<f32> {
356 fn from(rectangle: Rectangle<u32>) -> Rectangle<f32> {
357 Rectangle {
358 x: rectangle.x as f32,
359 y: rectangle.y as f32,
360 width: rectangle.width as f32,
361 height: rectangle.height as f32,
362 }
363 }
364}
365
366impl<T> std::ops::Add<Vector<T>> for Rectangle<T>
367where
368 T: std::ops::Add<Output = T>,
369{
370 type Output = Rectangle<T>;
371
372 fn add(self, translation: Vector<T>) -> Self {
373 Rectangle {
374 x: self.x + translation.x,
375 y: self.y + translation.y,
376 ..self
377 }
378 }
379}
380
381impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
382where
383 T: std::ops::Sub<Output = T>,
384{
385 type Output = Rectangle<T>;
386
387 fn sub(self, translation: Vector<T>) -> Self {
388 Rectangle {
389 x: self.x - translation.x,
390 y: self.y - translation.y,
391 ..self
392 }
393 }
394}