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,
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
36impl Rectangle<f32> {
37    /// A rectangle starting at negative infinity and with infinite width and height.
38    pub const INFINITE: Self = Self::new(
39        Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
40        Size::INFINITE,
41    );
42
43    /// Creates a new [`Rectangle`] with its top-left corner in the given
44    /// [`Point`] and with the provided [`Size`].
45    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    /// Creates a new square [`Rectangle`] with the center at the origin and
55    /// with the given radius.
56    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    /// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the
66    /// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`]
67    /// to obtain the desired result.
68    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    /// Returns the [`Point`] at the center of the [`Rectangle`].
111    pub fn center(&self) -> Point {
112        Point::new(self.center_x(), self.center_y())
113    }
114
115    /// Returns the X coordinate of the [`Point`] at the center of the
116    /// [`Rectangle`].
117    pub fn center_x(&self) -> f32 {
118        self.x + self.width / 2.0
119    }
120
121    /// Returns the Y coordinate of the [`Point`] at the center of the
122    /// [`Rectangle`].
123    pub fn center_y(&self) -> f32 {
124        self.y + self.height / 2.0
125    }
126
127    /// Returns the position of the top left corner of the [`Rectangle`].
128    pub fn position(&self) -> Point {
129        Point::new(self.x, self.y)
130    }
131
132    /// Returns the [`Size`] of the [`Rectangle`].
133    pub fn size(&self) -> Size {
134        Size::new(self.width, self.height)
135    }
136
137    /// Returns the area of the [`Rectangle`].
138    pub fn area(&self) -> f32 {
139        self.width * self.height
140    }
141
142    /// Returns true if the given [`Point`] is contained in the [`Rectangle`].
143    /// Excludes the right and bottom edges.
144    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    /// Returns the minimum distance from the given [`Point`] to any of the edges
152    /// of the [`Rectangle`].
153    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    /// Computes the offset that must be applied to the [`Rectangle`] to be placed
166    /// inside the given `container`.
167    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    /// Returns true if the current [`Rectangle`] is within the given
190    /// `container`. Includes the right and bottom edges.
191    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    /// Computes the intersection with the given [`Rectangle`].
199    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    /// Returns whether the [`Rectangle`] intersects with the given one.
225    pub fn intersects(&self, other: &Self) -> bool {
226        self.intersection(other).is_some()
227    }
228
229    /// Computes the union with the given [`Rectangle`].
230    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    /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
249    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    /// Expands the [`Rectangle`] a given amount.
269    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.horizontal(),
276            height: self.height + padding.vertical(),
277        }
278    }
279
280    /// Shrinks the [`Rectangle`] a given amount.
281    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.horizontal(),
288            height: self.height - padding.vertical(),
289        }
290    }
291
292    /// Rotates the [`Rectangle`] and returns the smallest [`Rectangle`]
293    /// containing it.
294    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    /// Scales the [`Rectangle`] without changing its position, effectively
305    /// "zooming" it.
306    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    /// Returns the top-left position to render an object of the given [`Size`].
316    /// inside the [`Rectangle`] that is anchored to the edge or corner
317    /// defined by the alignment arguments.
318    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}
397
398impl<T> std::ops::Mul<Vector<T>> for Rectangle<T>
399where
400    T: std::ops::Mul<Output = T> + Copy,
401{
402    type Output = Rectangle<T>;
403
404    fn mul(self, scale: Vector<T>) -> Self {
405        Rectangle {
406            x: self.x * scale.x,
407            y: self.y * scale.y,
408            width: self.width * scale.x,
409            height: self.height * scale.y,
410        }
411    }
412}