Skip to main content

iced_core/
rectangle.rs

1use crate::alignment;
2use crate::{Padding, Point, Radians, Size, Vector};
3
4/// An axis-aligned rectangle.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub struct Rectangle<T = f32> {
7    /// X coordinate of the top-left corner.
8    pub x: T,
9
10    /// Y coordinate of the top-left corner.
11    pub y: T,
12
13    /// Width of the rectangle.
14    pub width: T,
15
16    /// Height of the rectangle.
17    pub height: T,
18}
19
20impl<T> Rectangle<T>
21where
22    T: Default + Copy,
23{
24    /// Creates a new [`Rectangle`] with its top-left corner at the origin
25    /// and with the provided [`Size`].
26    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    /// Returns the [`Size`] of the [`Rectangle`].
36    pub const fn size(&self) -> Size<T> {
37        Size::new(self.width, self.height)
38    }
39}
40
41impl Rectangle<f32> {
42    /// A rectangle starting at negative infinity and with infinite width and height.
43    pub const INFINITE: Self = Self::new(
44        Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
45        Size::INFINITE,
46    );
47
48    /// Creates a new [`Rectangle`] with its top-left corner in the given
49    /// [`Point`] and with the provided [`Size`].
50    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    /// Creates a new square [`Rectangle`] with the center at the origin and
60    /// with the given radius.
61    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    /// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the
71    /// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`]
72    /// to obtain the desired result.
73    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    /// Returns the [`Point`] at the center of the [`Rectangle`].
114    pub fn center(&self) -> Point {
115        Point::new(self.center_x(), self.center_y())
116    }
117
118    /// Returns the X coordinate of the [`Point`] at the center of the
119    /// [`Rectangle`].
120    pub fn center_x(&self) -> f32 {
121        self.x + self.width / 2.0
122    }
123
124    /// Returns the Y coordinate of the [`Point`] at the center of the
125    /// [`Rectangle`].
126    pub fn center_y(&self) -> f32 {
127        self.y + self.height / 2.0
128    }
129
130    /// Returns the position of the top left corner of the [`Rectangle`].
131    pub fn position(&self) -> Point {
132        Point::new(self.x, self.y)
133    }
134
135    /// Returns the area of the [`Rectangle`].
136    pub fn area(&self) -> f32 {
137        self.width * self.height
138    }
139
140    /// Returns true if the given [`Point`] is contained in the [`Rectangle`].
141    /// Excludes the right and bottom edges.
142    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    /// Returns the minimum distance from the given [`Point`] to any of the edges
150    /// of the [`Rectangle`].
151    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    /// Computes the offset that must be applied to the [`Rectangle`] to be placed
162    /// inside the given `container`.
163    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    /// Returns true if the current [`Rectangle`] is within the given
186    /// `container`. Includes the right and bottom edges.
187    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    /// Computes the intersection with the given [`Rectangle`].
195    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    /// Returns whether the [`Rectangle`] intersects with the given one.
218    pub fn intersects(&self, other: &Self) -> bool {
219        self.intersection(other).is_some()
220    }
221
222    /// Computes the union with the given [`Rectangle`].
223    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    /// Rounds the [`Rectangle`] coordinates.
242    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    /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
255    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    /// Computes the logical [`Rectangle`] that ends up rounded
271    /// after the given `scale_factor` is applied to it.
272    pub fn hint(self, scale_factor: f32) -> Self {
273        (self * scale_factor).round() / scale_factor
274    }
275
276    /// Expands the [`Rectangle`] a given amount.
277    pub fn expand(self, padding: impl Into<Padding>) -> Self {
278        let padding = padding.into();
279
280        Self {
281            x: self.x - padding.left,
282            y: self.y - padding.top,
283            width: self.width + padding.x(),
284            height: self.height + padding.y(),
285        }
286    }
287
288    /// Shrinks the [`Rectangle`] a given amount.
289    pub fn shrink(self, padding: impl Into<Padding>) -> Self {
290        let padding = padding.into();
291
292        Self {
293            x: self.x + padding.left,
294            y: self.y + padding.top,
295            width: self.width - padding.x(),
296            height: self.height - padding.y(),
297        }
298    }
299
300    /// Rotates the [`Rectangle`] and returns the smallest [`Rectangle`]
301    /// containing it.
302    pub fn rotate(self, rotation: Radians) -> Self {
303        let size = self.size().rotate(rotation);
304        let position = Point::new(
305            self.center_x() - size.width / 2.0,
306            self.center_y() - size.height / 2.0,
307        );
308
309        Self::new(position, size)
310    }
311
312    /// Scales the [`Rectangle`] without changing its position, effectively
313    /// "zooming" it.
314    pub fn zoom(self, zoom: f32) -> Self {
315        Self {
316            x: self.x - (self.width * (zoom - 1.0)) / 2.0,
317            y: self.y - (self.height * (zoom - 1.0)) / 2.0,
318            width: self.width * zoom,
319            height: self.height * zoom,
320        }
321    }
322
323    /// Returns the top-left position to render an object of the given [`Size`].
324    /// inside the [`Rectangle`] that is anchored to the edge or corner
325    /// defined by the alignment arguments.
326    pub fn anchor(
327        &self,
328        size: Size,
329        align_x: impl Into<alignment::Horizontal>,
330        align_y: impl Into<alignment::Vertical>,
331    ) -> Point {
332        let x = match align_x.into() {
333            alignment::Horizontal::Left => self.x,
334            alignment::Horizontal::Center => self.x + (self.width - size.width) / 2.0,
335            alignment::Horizontal::Right => self.x + self.width - size.width,
336        };
337
338        let y = match align_y.into() {
339            alignment::Vertical::Top => self.y,
340            alignment::Vertical::Center => self.y + (self.height - size.height) / 2.0,
341            alignment::Vertical::Bottom => self.y + self.height - size.height,
342        };
343
344        Point::new(x, y)
345    }
346
347    /// Returns the two endpoints of a chord through the rectangle's center
348    /// at the given `angle`.
349    ///
350    /// The angle is measured clockwise from the negative y-axis, so `0`
351    /// produces a vertical chord and `π/2` a horizontal one. The returned
352    /// points are `(start, end)`, symmetric about [`Rectangle::center`].
353    pub fn chord(&self, angle: impl Into<Radians>) -> (Point, Point) {
354        use std::f32::consts::FRAC_PI_2;
355
356        let angle = angle.into().0 - FRAC_PI_2;
357        let r = Vector::new(f32::cos(angle), f32::sin(angle));
358
359        let distance_to_rect = f32::max(
360            f32::abs(r.x * self.width / 2.0),
361            f32::abs(r.y * self.height / 2.0),
362        );
363
364        let center = self.center();
365        let start = center - r * distance_to_rect;
366        let end = center + r * distance_to_rect;
367
368        (start, end)
369    }
370}
371
372impl std::ops::Mul<f32> for Rectangle<f32> {
373    type Output = Self;
374
375    fn mul(self, scale: f32) -> Self {
376        Self {
377            x: self.x * scale,
378            y: self.y * scale,
379            width: self.width * scale,
380            height: self.height * scale,
381        }
382    }
383}
384
385impl std::ops::Div<f32> for Rectangle<f32> {
386    type Output = Self;
387
388    fn div(self, scale: f32) -> Self {
389        Self {
390            x: self.x / scale,
391            y: self.y / scale,
392            width: self.width / scale,
393            height: self.height / scale,
394        }
395    }
396}
397
398impl From<Rectangle<u32>> for Rectangle<f32> {
399    fn from(rectangle: Rectangle<u32>) -> Rectangle<f32> {
400        Rectangle {
401            x: rectangle.x as f32,
402            y: rectangle.y as f32,
403            width: rectangle.width as f32,
404            height: rectangle.height as f32,
405        }
406    }
407}
408
409impl<T> From<Rectangle<T>> for Size<T> {
410    fn from(rectangle: Rectangle<T>) -> Size<T> {
411        Size {
412            width: rectangle.width,
413            height: rectangle.height,
414        }
415    }
416}
417
418impl<T> std::ops::Add<Vector<T>> for Rectangle<T>
419where
420    T: std::ops::Add<Output = T>,
421{
422    type Output = Rectangle<T>;
423
424    fn add(self, translation: Vector<T>) -> Self {
425        Rectangle {
426            x: self.x + translation.x,
427            y: self.y + translation.y,
428            ..self
429        }
430    }
431}
432
433impl<T> std::ops::AddAssign<Vector<T>> for Rectangle<T>
434where
435    T: std::ops::AddAssign,
436{
437    fn add_assign(&mut self, translation: Vector<T>) {
438        self.x += translation.x;
439        self.y += translation.y;
440    }
441}
442
443impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
444where
445    T: std::ops::Sub<Output = T>,
446{
447    type Output = Rectangle<T>;
448
449    fn sub(self, translation: Vector<T>) -> Self {
450        Rectangle {
451            x: self.x - translation.x,
452            y: self.y - translation.y,
453            ..self
454        }
455    }
456}
457
458impl<T> std::ops::SubAssign<Vector<T>> for Rectangle<T>
459where
460    T: std::ops::SubAssign,
461{
462    fn sub_assign(&mut self, translation: Vector<T>) {
463        self.x -= translation.x;
464        self.y -= translation.y;
465    }
466}