pix_engine/shape/
rect.rs

1//! A shape type representing squares and rectangles used for drawing.
2//!
3//! # Examples
4//!
5//! You can create a [Rect] or square using [`Rect::new`] or [`Rect::square`]:
6//!
7//! ```
8//! use pix_engine::prelude::*;
9//!
10//! let r = Rect::new(10, 20, 100, 200);
11//! let s = Rect::square(10, 20, 100);
12//! ```
13//!
14//! ...or by using the [rect!] or [square!] macros:
15//!
16//! ```
17//! use pix_engine::prelude::*;
18//!
19//! let r = rect!(10, 20, 100, 200);
20//! let s = square!(10, 20, 100);
21//!
22//! // using a point
23//! let r = rect!([10, 20], 100, 200);
24//! let r = rect!(point![10, 20], 100, 200);
25//! let s = square!([10, 20], 100);
26//! let s = square!(point![10, 20], 100);
27//! ```
28
29use crate::{error::Result, prelude::*};
30use num_traits::{AsPrimitive, Bounded, NumCast};
31#[cfg(feature = "serde")]
32use serde::{Deserialize, Serialize};
33use std::ops::{Add, Sub};
34
35/// A `Rectangle` positioned at `(x, y)` with `width` and `height`. A square is a `Rectangle` where
36/// `width` and `height` are equal.
37///
38/// Please see the [module-level documentation] for examples.
39///
40/// [module-level documentation]: crate::shape::rect
41#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
42#[repr(transparent)]
43#[must_use]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45pub struct Rect<T = i32>(pub(crate) [T; 4]);
46
47/// Constructs a [Rect] at position `(x, y)` with `width` and `height`.
48///
49/// ```
50/// # use pix_engine::prelude::*;
51/// let p = point!(10, 20);
52/// let r = rect!(p, 100, 200);
53/// assert_eq!(r.x(), 10);
54/// assert_eq!(r.y(), 20);
55/// assert_eq!(r.width(), 100);
56/// assert_eq!(r.height(), 200);
57///
58/// let r = rect!(10, 20, 100, 200);
59/// assert_eq!(r.x(), 10);
60/// assert_eq!(r.y(), 20);
61/// assert_eq!(r.width(), 100);
62/// assert_eq!(r.height(), 200);
63/// ```
64#[macro_export]
65macro_rules! rect {
66    ($p1:expr, $p2:expr$(,)?) => {
67        $crate::prelude::Rect::with_points($p1, $p2)
68    };
69    ($p:expr, $width:expr, $height:expr$(,)?) => {
70        $crate::prelude::Rect::with_position($p, $width, $height)
71    };
72    ($x:expr, $y:expr, $width:expr, $height:expr$(,)?) => {
73        $crate::prelude::Rect::new($x, $y, $width, $height)
74    };
75}
76
77/// Constructs a square [Rect] at position `(x, y)` with the same `width` and `height`.
78///
79/// ```
80/// # use pix_engine::prelude::*;
81/// let p = point!(10, 20);
82/// let s = square!(p, 100);
83/// assert_eq!(s.x(), 10);
84/// assert_eq!(s.y(), 20);
85/// assert_eq!(s.width(), 100);
86/// assert_eq!(s.height(), 100);
87///
88/// let s = square!(10, 20, 100);
89/// assert_eq!(s.x(), 10);
90/// assert_eq!(s.y(), 20);
91/// assert_eq!(s.width(), 100);
92/// assert_eq!(s.height(), 100);
93/// ```
94#[macro_export]
95macro_rules! square {
96    ($p:expr, $size:expr$(,)?) => {{
97        $crate::prelude::Rect::square_with_position($p, $size)
98    }};
99    ($x:expr, $y:expr, $size:expr$(,)?) => {
100        $crate::prelude::Rect::square($x, $y, $size)
101    };
102}
103
104impl<T> Rect<T> {
105    /// Constructs a `Rect` at position `(x, y)` with `width` and `height`.
106    pub const fn new(x: T, y: T, width: T, height: T) -> Self {
107        Self([x, y, width, height])
108    }
109
110    /// Constructs a square `Rect` at position `(x, y)` with `size`.
111    pub fn square(x: T, y: T, size: T) -> Self
112    where
113        T: Copy,
114    {
115        Self::new(x, y, size, size)
116    }
117}
118
119impl<T: Copy> Rect<T> {
120    /// Returns `Rect` coordinates as `[x, y, width, height]`.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// # use pix_engine::prelude::*;
126    /// let r = rect!(5, 10, 100, 100);
127    /// assert_eq!(r.coords(), [5, 10, 100, 100]);
128    /// ```
129    #[inline]
130    pub fn coords(&self) -> [T; 4] {
131        self.0
132    }
133
134    /// Returns `Rect` coordinates as a mutable slice `&mut [x, y, width, height]`.
135    ///
136    /// # Example
137    ///
138    /// ```
139    /// # use pix_engine::prelude::*;
140    /// let mut r = rect!(5, 10, 100, 100);
141    /// for p in r.coords_mut() {
142    ///     *p += 5;
143    /// }
144    /// assert_eq!(r.coords(), [10, 15, 105, 105]);
145    /// ```
146    #[inline]
147    pub fn coords_mut(&mut self) -> &mut [T; 4] {
148        &mut self.0
149    }
150
151    /// Returns the `x-coordinate` of the rectangle.
152    #[inline]
153    pub fn x(&self) -> T {
154        self.0[0]
155    }
156
157    /// Sets the `x-coordinate` of the rectangle.
158    #[inline]
159    pub fn set_x(&mut self, x: T) {
160        self.0[0] = x;
161    }
162
163    /// Returns the `y-coordinate` of the rectangle.
164    #[inline]
165    pub fn y(&self) -> T {
166        self.0[1]
167    }
168
169    /// Sets the `y-coordinate` of the rectangle.
170    #[inline]
171    pub fn set_y(&mut self, y: T) {
172        self.0[1] = y;
173    }
174
175    /// Returns the `width` of the rectangle.
176    #[inline]
177    pub fn width(&self) -> T {
178        self.0[2]
179    }
180
181    /// Sets the `width` of the rectangle.
182    #[inline]
183    pub fn set_width(&mut self, width: T) {
184        self.0[2] = width;
185    }
186
187    /// Returns the `height` of the rectangle.
188    #[inline]
189    pub fn height(&self) -> T {
190        self.0[3]
191    }
192
193    /// Sets the `height` of the rectangle.
194    #[inline]
195    pub fn set_height(&mut self, height: T) {
196        self.0[3] = height;
197    }
198}
199
200impl<T: Num> Rect<T> {
201    /// Constructs a `Rect` at position [Point] with `width` and `height`.
202    pub fn with_position<P: Into<Point<T>>>(p: P, width: T, height: T) -> Self {
203        let p = p.into();
204        Self::new(p.x(), p.y(), width, height)
205    }
206
207    /// Constructs a square `Rect` at position [Point] with `size`.
208    pub fn square_with_position<P: Into<Point<T>>>(p: P, size: T) -> Self {
209        Self::with_position(p, size, size)
210    }
211
212    /// Constructs a `Rect` by providing top-left and bottom-right [Point]s.
213    ///
214    /// # Panics
215    ///
216    /// Panics if `p2 <= p1`.
217    ///
218    /// # Example
219    ///
220    /// ```
221    /// # use pix_engine::prelude::*;
222    /// let r = Rect::with_points([50, 50], [150, 150]);
223    /// assert_eq!(r.coords(), [50, 50, 100, 100]);
224    /// ```
225    pub fn with_points<P: Into<Point<T>>>(p1: P, p2: P) -> Self {
226        let p1 = p1.into();
227        let p2 = p2.into();
228        assert!(p2 > p1, "bottom-right point must be greater than top-right",);
229        let width = p2.x() - p1.x();
230        let height = p2.y() - p1.y();
231        Self::new(p1.x(), p1.y(), width, height)
232    }
233
234    /// Constructs a `Rect` centered at position `(x, y)` with `width` and `height`.
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// # use pix_engine::prelude::*;
240    /// let r = Rect::from_center([50, 50], 100, 100);
241    /// assert_eq!(r.coords(), [0, 0, 100, 100]);
242    /// ```
243    pub fn from_center<P: Into<Point<T>>>(p: P, width: T, height: T) -> Self {
244        let p = p.into();
245        let two = T::one() + T::one();
246        Self::new(p.x() - width / two, p.y() - height / two, width, height)
247    }
248
249    /// Constructs a square `Rect` centered at position `(x, y)` with `size`.
250    ///
251    /// # Example
252    ///
253    /// ```
254    /// # use pix_engine::prelude::*;
255    /// let s = Rect::square_from_center([50, 50], 100);
256    /// assert_eq!(s.coords(), [0, 0, 100, 100]);
257    /// ```
258    pub fn square_from_center<P: Into<Point<T>>>(p: P, size: T) -> Self {
259        let p = p.into();
260        let two = T::one() + T::one();
261        let offset = size / two;
262        Self::new(p.x() - offset, p.y() - offset, size, size)
263    }
264
265    /// Returns the `size` of the rectangle as a `Point`.
266    #[inline]
267    pub fn size(&self) -> Point<T> {
268        point!(self.width(), self.height())
269    }
270
271    /// Reposition the the rectangle.
272    #[inline]
273    pub fn reposition(&self, x: T, y: T) -> Self {
274        Self::new(x, y, self.width(), self.height())
275    }
276
277    /// Resize the the rectangle.
278    #[inline]
279    pub fn resize(&self, width: T, height: T) -> Self {
280        Self::new(self.x(), self.y(), width, height)
281    }
282
283    /// Offsets a rectangle by shifting coordinates by given amount.
284    #[inline]
285    pub fn offset<P>(&self, offsets: P) -> Self
286    where
287        P: Into<Point<T>>,
288    {
289        let offsets = offsets.into();
290        let mut rect = *self;
291        for i in 0..=1 {
292            rect[i] += offsets[i];
293        }
294        rect
295    }
296
297    /// Offsets a rectangle's size by shifting coordinates by given amount.
298    #[inline]
299    pub fn offset_size<P>(&self, offsets: P) -> Self
300    where
301        P: Into<Point<T>>,
302    {
303        let offsets = offsets.into();
304        let mut rect = *self;
305        for i in 2..=3 {
306            rect[i] += offsets[i - 2];
307        }
308        rect
309    }
310
311    /// Grows a rectangle by a given size.
312    #[inline]
313    pub fn grow<P>(&self, offsets: P) -> Self
314    where
315        P: Into<Point<T>>,
316    {
317        let offsets = offsets.into();
318        let mut rect = *self;
319        for i in 0..=1 {
320            rect[i] -= offsets[i];
321        }
322        for i in 2..=3 {
323            rect[i] += (T::one() + T::one()) * offsets[i - 2];
324        }
325        rect
326    }
327
328    /// Shrinks a rectangle by a given size.
329    #[inline]
330    pub fn shrink<P>(&self, offsets: P) -> Self
331    where
332        P: Into<Point<T>>,
333    {
334        let offsets = offsets.into();
335        let mut rect = *self;
336        for i in 0..=1 {
337            rect[i] += offsets[i];
338        }
339        for i in 2..=3 {
340            rect[i] -= (T::one() + T::one()) * offsets[i - 2];
341        }
342        rect
343    }
344
345    /// Returns `Rect` as a [Vec].
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// # use pix_engine::prelude::*;
351    /// let r = rect!(5, 10, 100, 100);
352    /// assert_eq!(r.to_vec(), vec![5, 10, 100, 100]);
353    /// ```
354    #[inline]
355    pub fn to_vec(self) -> Vec<T> {
356        self.0.to_vec()
357    }
358
359    /// Returns the horizontal position of the left edge.
360    #[inline]
361    pub fn left(&self) -> T {
362        self.x()
363    }
364
365    /// Set the horizontal position of the left edge.
366    #[inline]
367    pub fn set_left(&mut self, left: T) {
368        self.set_x(left);
369    }
370
371    /// Returns the horizontal position of the right edge.
372    #[inline]
373    pub fn right(&self) -> T {
374        self.x() + self.width()
375    }
376
377    /// Set the horizontal position of the right edge.
378    #[inline]
379    pub fn set_right(&mut self, right: T) {
380        self.set_x(right - self.width());
381    }
382
383    /// Returns the horizontal position of the top edge.
384    #[inline]
385    pub fn top(&self) -> T {
386        self.y()
387    }
388
389    /// Set the vertical position of the top edge.
390    #[inline]
391    pub fn set_top(&mut self, top: T) {
392        self.set_y(top);
393    }
394
395    /// Returns the vertical position of the bottom edge.
396    #[inline]
397    pub fn bottom(&self) -> T {
398        self.y() + self.height()
399    }
400
401    /// Set the vertical position of the bottom edge.
402    #[inline]
403    pub fn set_bottom(&mut self, bottom: T) {
404        self.set_y(bottom - self.height());
405    }
406
407    /// Returns the center position as [Point].
408    #[inline]
409    pub fn center(&self) -> Point<T> {
410        let two = T::one() + T::one();
411        point!(
412            self.x() + self.width() / two,
413            self.y() + self.height() / two
414        )
415    }
416
417    /// Returns the top-left position as [Point].
418    #[inline]
419    pub fn top_left(&self) -> Point<T> {
420        point!(self.left(), self.top())
421    }
422
423    /// Returns the top-right position as [Point].
424    #[inline]
425    pub fn top_right(&self) -> Point<T> {
426        point!(self.right(), self.top())
427    }
428
429    /// Returns the bottom-left position as [Point].
430    #[inline]
431    pub fn bottom_left(&self) -> Point<T> {
432        point!(self.left(), self.bottom())
433    }
434
435    /// Returns the bottom-right position as [Point].
436    #[inline]
437    pub fn bottom_right(&self) -> Point<T> {
438        point!(self.right(), self.bottom())
439    }
440
441    /// Returns the four [Point]s that compose this `Rect` as `[top_left, top_right, bottom_right,
442    /// bottom_left]`.
443    #[inline]
444    pub fn points(&self) -> [Point<T>; 4] {
445        [
446            self.top_left(),
447            self.top_right(),
448            self.bottom_right(),
449            self.bottom_left(),
450        ]
451    }
452
453    /// Set position centered on a [Point].
454    #[inline]
455    pub fn center_on<P: Into<Point<T>>>(&mut self, p: P) {
456        let p = p.into();
457        let two = T::one() + T::one();
458        self.set_x(p.x() - self.width() / two);
459        self.set_y(p.y() - self.height() / two);
460    }
461
462    /// Returns the bounding box for a given rectangle rotated about a `center` by a given
463    /// `angle`. Passing `None` for `center` rotates about the top-left point of the rectangle.
464    #[inline]
465    pub fn rotated(&self, angle: f64, center: Option<Point<T>>) -> Self
466    where
467        T: Ord + Bounded + AsPrimitive<f64> + NumCast,
468    {
469        if angle == 0.0 {
470            return *self;
471        }
472
473        let sin_cos = angle.sin_cos();
474        // Determine rotated bounding box
475        let [cx, cy]: [f64; 2] = center.unwrap_or_else(|| self.center()).as_().coords();
476        let (sin, cos) = sin_cos;
477        let transformed_points = self.points().map(|p| {
478            let [x, y]: [f64; 2] = p.as_().coords();
479            point![
480                NumCast::from(((x - cx).mul_add(cos, cx) - (y - cy) * sin).round())
481                    .unwrap_or_else(T::zero),
482                NumCast::from(((y - cy).mul_add(cos, (x - cx).mul_add(sin, cy))).round())
483                    .unwrap_or_else(T::zero),
484            ]
485        });
486        let (min_x, min_y) = transformed_points
487            .iter()
488            .fold((T::max_value(), T::max_value()), |(min_x, min_y), point| {
489                (min_x.min(point.x()), min_y.min(point.y()))
490            });
491        let (max_x, max_y) = transformed_points
492            .iter()
493            .fold((T::min_value(), T::min_value()), |(max_x, max_y), point| {
494                (max_x.max(point.x()), max_y.max(point.y()))
495            });
496        Self::with_points([min_x, min_y], [max_x, max_y])
497    }
498}
499
500impl<T: Num> Contains<Point<T>> for Rect<T> {
501    /// Returns whether this rectangle contains a given [Point].
502    fn contains(&self, p: Point<T>) -> bool {
503        p.x() >= self.left() && p.x() < self.right() && p.y() >= self.top() && p.y() < self.bottom()
504    }
505}
506
507impl<T: Num> Contains<Rect<T>> for Rect<T> {
508    /// Returns whether this rectangle completely contains another rectangle.
509    fn contains(&self, rect: Rect<T>) -> bool {
510        rect.left() >= self.left()
511            && rect.right() < self.right()
512            && rect.top() >= self.top()
513            && rect.bottom() < self.bottom()
514    }
515}
516
517impl<T: Float> Intersects<Line<T>> for Rect<T> {
518    type Result = (Point<T>, T);
519
520    /// Returns the closest intersection point with a given line and distance along the line or
521    /// `None` if there is no intersection.
522    fn intersects(&self, line: Line<T>) -> Option<Self::Result> {
523        let left = line.intersects(line_![self.top_left(), self.bottom_left()]);
524        let right = line.intersects(line_![self.top_right(), self.bottom_right()]);
525        let top = line.intersects(line_![self.top_left(), self.top_right()]);
526        let bottom = line.intersects(line_![self.bottom_left(), self.bottom_right()]);
527        [left, right, top, bottom]
528            .iter()
529            .filter_map(|&p| p)
530            .fold(None, |closest, intersection| {
531                let closest_t = closest.map_or_else(T::infinity, |c| c.1);
532                let t = intersection.1;
533                if t < closest_t {
534                    Some(intersection)
535                } else {
536                    closest
537                }
538            })
539    }
540}
541
542impl<T: Num> Intersects<Rect<T>> for Rect<T> {
543    // FIXME: Provide a better intersection result
544    type Result = ();
545
546    /// Returns whether this rectangle intersects with another rectangle.
547    fn intersects(&self, rect: Rect<T>) -> Option<Self::Result> {
548        let tl = self.top_left();
549        let br = self.bottom_right();
550        let otl = rect.top_left();
551        let obr = rect.bottom_right();
552        // Both rectangle corner x and y values overlap ranges
553        if tl.x() < obr.x() && br.x() > otl.x() && tl.y() < otl.y() && br.y() > obr.y() {
554            Some(())
555        } else {
556            None
557        }
558    }
559}
560
561impl Draw for Rect<i32> {
562    /// Draw `Rect` to the current [`PixState`] canvas.
563    fn draw(&self, s: &mut PixState) -> Result<()> {
564        s.rect(*self)
565    }
566}
567
568impl<T: Copy> From<[T; 3]> for Rect<T> {
569    /// Converts `[T; 3]` into `Rect<T>`.
570    #[inline]
571    fn from([x, y, s]: [T; 3]) -> Self {
572        Self([x, y, s, s])
573    }
574}
575
576impl<T: Copy> From<&[T; 3]> for Rect<T> {
577    /// Converts `&[T; 3]` into `Rect<T>`.
578    #[inline]
579    fn from(&[x, y, s]: &[T; 3]) -> Self {
580        Self([x, y, s, s])
581    }
582}
583
584impl Add<Point<i32>> for Rect {
585    type Output = Self;
586    fn add(self, p: Point<i32>) -> Self::Output {
587        self.offset(p)
588    }
589}
590
591impl Sub<Point<i32>> for Rect {
592    type Output = Self;
593    fn sub(self, p: Point<i32>) -> Self::Output {
594        self.offset(-p)
595    }
596}