leftwm_layouts/geometry/
rotation.rs

1use serde::{Deserialize, Serialize};
2
3use super::Rect;
4
5/// Represents the four different possibilities of rotation.
6#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum Rotation {
8    /// A rotation of 0° (ie. no rotation).
9    /// This is the default value.
10    ///
11    /// ```txt
12    ///    North
13    /// +---------+
14    /// |    ^    |
15    /// |         |
16    /// |         |
17    /// +---------+
18    ///      0°
19    /// ```
20    #[default]
21    North,
22
23    /// A rotation of 90° clockwise.
24    ///
25    /// ```txt
26    ///    East
27    /// +---------+
28    /// |         |
29    /// |        >|
30    /// |         |
31    /// +---------+
32    ///     90°
33    /// ```
34    East,
35
36    /// A rotation of 180°.
37    ///
38    /// ```txt
39    ///    South
40    /// +---------+
41    /// |         |
42    /// |         |
43    /// |    v    |
44    /// +---------+
45    ///     180°
46    /// ```
47    South,
48
49    /// A rotation of 270° clockwise.
50    ///
51    /// ```txt
52    ///     West
53    /// +---------+
54    /// |         |
55    /// |<        |
56    /// |         |
57    /// +---------+
58    ///     270°
59    /// ```
60    West,
61}
62
63impl Rotation {
64    /// Returns whether the aspect ratio of the provided
65    /// Rect changes with the given rotation.
66    pub fn aspect_ratio_changes(&self, rect: &Rect) -> bool {
67        // if the rect is not a square, and the rotation is
68        // 90° or 270°, then the aspect ratio changes
69        rect.h != rect.w && matches!(self, Self::West | Self::East)
70    }
71
72    /// Returns the (x, y) coordinate of the point which will be
73    /// the Rect's anchor after it is rotated.
74    ///
75    /// ## Explanation
76    /// The anchor point of a [`Rect`] is usually the top-left (x,y).
77    /// When a [`Rect`] is rotated inside a layout, then another corner
78    /// of the [`Rect`] will become the new anchor point after the rotation.
79    /// This method returns the current position of that corner.
80    pub fn next_anchor(&self, rect: &Rect) -> (i32, i32) {
81        match self {
82            // top-left
83            Self::North => (rect.x, rect.y),
84            // bottom-left
85            Self::East => (rect.x, rect.y + rect.h as i32),
86            // bottom-right
87            Self::South => (rect.x + rect.w as i32, rect.y + rect.h as i32),
88            // top-right
89            Self::West => (rect.x + rect.w as i32, rect.y),
90        }
91    }
92
93    /// Get the next rotation variant when rotating clockwise
94    #[must_use]
95    pub fn clockwise(&self) -> Self {
96        match self {
97            Rotation::North => Rotation::East,
98            Rotation::East => Rotation::South,
99            Rotation::South => Rotation::West,
100            Rotation::West => Rotation::North,
101        }
102    }
103
104    /// Get the next rotation variant when rotating counter clockwise
105    #[must_use]
106    pub fn counter_clockwise(&self) -> Self {
107        match self {
108            Rotation::North => Rotation::West,
109            Rotation::West => Rotation::South,
110            Rotation::South => Rotation::East,
111            Rotation::East => Rotation::North,
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::Rotation;
119    use crate::geometry::Rect;
120
121    const SQUARE: Rect = Rect {
122        x: 0,
123        y: 0,
124        w: 200,
125        h: 200,
126    };
127
128    const RECTANGLE: Rect = Rect {
129        x: 0,
130        y: 0,
131        w: 400,
132        h: 200,
133    };
134
135    #[test]
136    fn square_never_changes_aspect_ratio() {
137        let rotations = vec![
138            Rotation::North,
139            Rotation::East,
140            Rotation::South,
141            Rotation::West,
142        ];
143        for rotation in rotations {
144            assert!(!rotation.aspect_ratio_changes(&SQUARE));
145        }
146    }
147
148    #[test]
149    fn non_square_changes_aspect_ratio_east_west() {
150        assert!(Rotation::East.aspect_ratio_changes(&RECTANGLE));
151        assert!(Rotation::West.aspect_ratio_changes(&RECTANGLE));
152    }
153
154    #[test]
155    fn non_square_doesnt_change_aspect_ratio_north_south() {
156        assert!(!Rotation::North.aspect_ratio_changes(&RECTANGLE));
157        assert!(!Rotation::South.aspect_ratio_changes(&RECTANGLE));
158    }
159
160    #[test]
161    fn calc_anchor_north() {
162        let rect = Rect::new(0, 0, 1920, 1080);
163        let anchor = Rotation::North.next_anchor(&rect);
164        assert_eq!(anchor, (0, 0));
165    }
166
167    #[test]
168    fn calc_anchor_east() {
169        let rect = Rect::new(0, 0, 1920, 1080);
170        let anchor = Rotation::East.next_anchor(&rect);
171        assert_eq!(anchor, (0, 1080));
172    }
173
174    #[test]
175    fn calc_anchor_south() {
176        let rect = Rect::new(0, 0, 1920, 1080);
177        let anchor = Rotation::South.next_anchor(&rect);
178        assert_eq!(anchor, (1920, 1080));
179    }
180
181    #[test]
182    fn calc_anchor_west() {
183        let rect = Rect::new(0, 0, 1920, 1080);
184        let anchor = Rotation::West.next_anchor(&rect);
185        assert_eq!(anchor, (1920, 0));
186    }
187}