ratatui_core/layout/rect/
ops.rs

1use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
2
3use super::{Offset, Rect};
4
5impl Neg for Offset {
6    type Output = Self;
7
8    /// Negates the offset.
9    ///
10    /// # Panics
11    ///
12    /// Panics if the negated value overflows (i.e. `x` or `y` is `i32::MIN`).
13    fn neg(self) -> Self {
14        Self {
15            x: self.x.neg(),
16            y: self.y.neg(),
17        }
18    }
19}
20
21impl Add<Offset> for Rect {
22    type Output = Self;
23
24    /// Moves the rect by an offset without changing its size.
25    ///
26    /// If the offset would move the any of the rect's edges outside the bounds of `u16`, the
27    /// rect's position is clamped to the nearest edge.
28    fn add(self, offset: Offset) -> Self {
29        let max_x = i32::from(u16::MAX - self.width);
30        let max_y = i32::from(u16::MAX - self.height);
31        let x = i32::from(self.x).saturating_add(offset.x).clamp(0, max_x) as u16;
32        let y = i32::from(self.y).saturating_add(offset.y).clamp(0, max_y) as u16;
33        Self { x, y, ..self }
34    }
35}
36
37impl Add<Rect> for Offset {
38    type Output = Rect;
39
40    /// Moves the rect by an offset without changing its size.
41    ///
42    /// If the offset would move the any of the rect's edges outside the bounds of `u16`, the
43    /// rect's position is clamped to the nearest edge.
44    fn add(self, rect: Rect) -> Rect {
45        rect + self
46    }
47}
48
49impl Sub<Offset> for Rect {
50    type Output = Self;
51
52    /// Subtracts an offset from the rect without changing its size.
53    ///
54    /// If the offset would move the any of the rect's edges outside the bounds of `u16`, the
55    /// rect's position is clamped to the nearest
56    fn sub(self, offset: Offset) -> Self {
57        // Note this cannot be simplified to `self + -offset` because `Offset::MIN` would overflow
58        let max_x = i32::from(u16::MAX - self.width);
59        let max_y = i32::from(u16::MAX - self.height);
60        let x = i32::from(self.x).saturating_sub(offset.x).clamp(0, max_x) as u16;
61        let y = i32::from(self.y).saturating_sub(offset.y).clamp(0, max_y) as u16;
62        Self { x, y, ..self }
63    }
64}
65
66impl AddAssign<Offset> for Rect {
67    /// Moves the rect by an offset in place without changing its size.
68    ///
69    /// If the offset would move the any of the rect's edges outside the bounds of `u16`, the
70    /// rect's position is clamped to the nearest edge.
71    fn add_assign(&mut self, offset: Offset) {
72        *self = *self + offset;
73    }
74}
75
76impl SubAssign<Offset> for Rect {
77    /// Moves the rect by an offset in place without changing its size.
78    ///
79    /// If the offset would move the any of the rect's edges outside the bounds of `u16`, the
80    /// rect's position is clamped to the nearest edge.
81    fn sub_assign(&mut self, offset: Offset) {
82        *self = *self - offset;
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use rstest::rstest;
89
90    use super::*;
91
92    #[rstest]
93    #[case::zero(Rect::new(3, 4, 5, 6), Offset::ZERO, Rect::new(3, 4, 5, 6))]
94    #[case::positive(Rect::new(3, 4, 5, 6), Offset::new(1, 2), Rect::new(4, 6, 5, 6))]
95    #[case::negative(Rect::new(3, 4, 5, 6), Offset::new(-1, -2), Rect::new(2, 2, 5, 6))]
96    #[case::saturate_negative(Rect::new(3, 4, 5, 6), Offset::MIN, Rect::new(0, 0, 5, 6))]
97    #[case::saturate_positive(Rect::new(3, 4, 5, 6), Offset::MAX, Rect::new(u16::MAX- 5, u16::MAX - 6, 5, 6))]
98    fn add_offset(#[case] rect: Rect, #[case] offset: Offset, #[case] expected: Rect) {
99        assert_eq!(rect + offset, expected);
100        assert_eq!(offset + rect, expected);
101    }
102
103    #[rstest]
104    #[case::zero(Rect::new(3, 4, 5, 6), Offset::ZERO, Rect::new(3, 4, 5, 6))]
105    #[case::positive(Rect::new(3, 4, 5, 6), Offset::new(1, 2), Rect::new(2, 2, 5, 6))]
106    #[case::negative(Rect::new(3, 4, 5, 6), Offset::new(-1, -2), Rect::new(4, 6, 5, 6))]
107    #[case::saturate_negative(Rect::new(3, 4, 5, 6), Offset::MAX, Rect::new(0, 0, 5, 6))]
108    #[case::saturate_positive(Rect::new(3, 4, 5, 6), -Offset::MAX, Rect::new(u16::MAX - 5, u16::MAX - 6, 5, 6))]
109    fn sub_offset(#[case] rect: Rect, #[case] offset: Offset, #[case] expected: Rect) {
110        assert_eq!(rect - offset, expected);
111    }
112
113    #[rstest]
114    #[case::zero(Rect::new(3, 4, 5, 6), Offset::ZERO, Rect::new(3, 4, 5, 6))]
115    #[case::positive(Rect::new(3, 4, 5, 6), Offset::new(1, 2), Rect::new(4, 6, 5, 6))]
116    #[case::negative(Rect::new(3, 4, 5, 6), Offset::new(-1, -2), Rect::new(2, 2, 5, 6))]
117    #[case::saturate_negative(Rect::new(3, 4, 5, 6), Offset::MIN, Rect::new(0, 0, 5, 6))]
118    #[case::saturate_positive(Rect::new(3, 4, 5, 6), Offset::MAX, Rect::new(u16::MAX - 5, u16::MAX - 6, 5, 6))]
119    fn add_assign_offset(#[case] rect: Rect, #[case] offset: Offset, #[case] expected: Rect) {
120        let mut rect = rect;
121        rect += offset;
122        assert_eq!(rect, expected);
123    }
124
125    #[rstest]
126    #[case::zero(Rect::new(3, 4, 5, 6), Offset::ZERO, Rect::new(3, 4, 5, 6))]
127    #[case::positive(Rect::new(3, 4, 5, 6), Offset::new(1, 2), Rect::new(2, 2, 5, 6))]
128    #[case::negative(Rect::new(3, 4, 5, 6), Offset::new(-1, -2), Rect::new(4, 6, 5, 6))]
129    #[case::saturate_negative(Rect::new(3, 4, 5, 6), Offset::MAX, Rect::new(0, 0, 5, 6))]
130    #[case::saturate_positive(Rect::new(3, 4, 5, 6), -Offset::MAX, Rect::new(u16::MAX - 5, u16::MAX - 6, 5, 6))]
131    fn sub_assign_offset(#[case] rect: Rect, #[case] offset: Offset, #[case] expected: Rect) {
132        let mut rect = rect;
133        rect -= offset;
134        assert_eq!(rect, expected);
135    }
136}