lcd_async/options/
orientation.rs

1/// Display rotation.
2#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3pub enum Rotation {
4    /// No rotation.
5    Deg0,
6    /// 90° clockwise rotation.
7    Deg90,
8    /// 180° clockwise rotation.
9    Deg180,
10    /// 270° clockwise rotation.
11    Deg270,
12}
13
14impl Rotation {
15    /// Returns the rotation in degrees.
16    pub const fn degree(self) -> i32 {
17        match self {
18            Self::Deg0 => 0,
19            Self::Deg90 => 90,
20            Self::Deg180 => 180,
21            Self::Deg270 => 270,
22        }
23    }
24
25    /// Converts an angle into a rotation.
26    ///
27    /// Returns an error if the angle isn't an integer multiple of 90°.
28    pub const fn try_from_degree(mut angle: i32) -> Result<Self, InvalidAngleError> {
29        if angle < 0 || angle > 270 {
30            angle = angle.rem_euclid(360)
31        }
32
33        Ok(match angle {
34            0 => Self::Deg0,
35            90 => Self::Deg90,
36            180 => Self::Deg180,
37            270 => Self::Deg270,
38            _ => return Err(InvalidAngleError),
39        })
40    }
41
42    /// Rotates one rotation by another rotation.
43    #[must_use]
44    pub const fn rotate(self, other: Rotation) -> Self {
45        match Self::try_from_degree(self.degree() + other.degree()) {
46            Ok(r) => r,
47            Err(_) => unreachable!(),
48        }
49    }
50
51    /// Returns `true` if the rotation is horizontal (0° or 180°).
52    pub const fn is_horizontal(self) -> bool {
53        matches!(self, Self::Deg0 | Self::Deg180)
54    }
55
56    /// Returns `true` if the rotation is vertical (90° or 270°).
57    pub const fn is_vertical(self) -> bool {
58        matches!(self, Self::Deg90 | Self::Deg270)
59    }
60}
61
62/// Invalid angle error.
63///
64/// The error type returned by [`Rotation::try_from_degree`].
65#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub struct InvalidAngleError;
67
68/// Display orientation.
69///
70/// A display orientation describes how the display content is oriented relative
71/// to the default orientation of the display.
72///
73/// # Examples
74///
75/// ```
76/// use lcd_async::options::{Orientation, Rotation};
77///
78/// // Rotate display content by 90 degree clockwise.
79/// let rotated = Orientation::new().rotate(Rotation::Deg90);
80///
81/// // Flip display content horizontally.
82/// let flipped = Orientation::new().flip_horizontal();
83/// ```
84///
85/// Multiple transformations can be combined to build more complex orientations:
86///
87/// ```
88/// use lcd_async::options::{Orientation, Rotation};
89///
90/// let orientation = Orientation::new().rotate(Rotation::Deg270).flip_vertical();
91///
92/// // Note that the order of operations is important:
93/// assert_ne!(orientation, Orientation::new().flip_vertical().rotate(Rotation::Deg270));
94/// assert_eq!(orientation, Orientation::new().flip_vertical().rotate(Rotation::Deg90));
95/// ```
96///
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98pub struct Orientation {
99    /// Rotation.
100    pub rotation: Rotation,
101    /// Mirrored.
102    pub mirrored: bool,
103}
104
105impl Orientation {
106    /// Creates a default orientation.
107    pub const fn new() -> Self {
108        Self {
109            rotation: Rotation::Deg0,
110            mirrored: false,
111        }
112    }
113
114    /// Rotates the orientation.
115    #[must_use]
116    pub const fn rotate(self, rotation: Rotation) -> Self {
117        Self {
118            rotation: self.rotation.rotate(rotation),
119            mirrored: self.mirrored,
120        }
121    }
122
123    /// Flips the orientation across the horizontal display axis.
124    #[must_use]
125    const fn flip_horizontal_absolute(self) -> Self {
126        Self {
127            rotation: self.rotation,
128            mirrored: !self.mirrored,
129        }
130    }
131
132    /// Flips the orientation across the vertical display axis.
133    #[must_use]
134    const fn flip_vertical_absolute(self) -> Self {
135        Self {
136            rotation: self.rotation.rotate(Rotation::Deg180),
137            mirrored: !self.mirrored,
138        }
139    }
140
141    /// Flips the orientation across the horizontal axis.
142    #[must_use]
143    pub const fn flip_horizontal(self) -> Self {
144        if self.rotation.is_vertical() {
145            self.flip_vertical_absolute()
146        } else {
147            self.flip_horizontal_absolute()
148        }
149    }
150
151    /// Flips the orientation across the vertical axis.
152    #[must_use]
153    pub const fn flip_vertical(self) -> Self {
154        if self.rotation.is_vertical() {
155            self.flip_horizontal_absolute()
156        } else {
157            self.flip_vertical_absolute()
158        }
159    }
160}
161
162impl Default for Orientation {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168/// Memory mapping.
169///
170/// A memory mapping describes how a framebuffer is mapped to the physical
171/// row and columns of a display.
172#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
173pub struct MemoryMapping {
174    /// Rows and columns are swapped.
175    pub swap_rows_and_columns: bool,
176
177    /// Rows are reversed.
178    pub reverse_rows: bool,
179    /// Columns are reversed.
180    pub reverse_columns: bool,
181}
182
183impl MemoryMapping {
184    /// `const` variant of `From<Orientation>` impl.
185    pub const fn from_orientation(orientation: Orientation) -> Self {
186        let (reverse_rows, reverse_columns) = match orientation.rotation {
187            Rotation::Deg0 => (false, false),
188            Rotation::Deg90 => (false, true),
189            Rotation::Deg180 => (true, true),
190            Rotation::Deg270 => (true, false),
191        };
192
193        Self {
194            reverse_rows,
195            reverse_columns: reverse_columns ^ orientation.mirrored,
196            swap_rows_and_columns: orientation.rotation.is_vertical(),
197        }
198    }
199}
200
201impl From<Orientation> for MemoryMapping {
202    fn from(orientation: Orientation) -> Self {
203        Self::from_orientation(orientation)
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn try_from_degree() {
213        let mut expected = [
214            Rotation::Deg0,
215            Rotation::Deg90,
216            Rotation::Deg180,
217            Rotation::Deg270,
218        ]
219        .iter()
220        .copied()
221        .cycle();
222
223        for angle in (-720..=720).step_by(90) {
224            assert_eq!(
225                Rotation::try_from_degree(angle).unwrap(),
226                expected.next().unwrap(),
227                "{angle}"
228            );
229        }
230    }
231
232    #[test]
233    fn try_from_degree_error() {
234        assert_eq!(Rotation::try_from_degree(1), Err(InvalidAngleError));
235        assert_eq!(Rotation::try_from_degree(-1), Err(InvalidAngleError));
236        assert_eq!(Rotation::try_from_degree(i32::MIN), Err(InvalidAngleError));
237        assert_eq!(Rotation::try_from_degree(i32::MAX), Err(InvalidAngleError));
238    }
239
240    /// Abbreviated constructor for orientations.
241    const fn orientation(rotation: Rotation, mirrored: bool) -> Orientation {
242        Orientation { rotation, mirrored }
243    }
244
245    #[test]
246    fn flip_horizontal() {
247        use Rotation::*;
248
249        for ((rotation, mirrored), (expected_rotation, expected_mirrored)) in [
250            ((Deg0, false), (Deg0, true)),
251            ((Deg90, false), (Deg270, true)),
252            ((Deg180, false), (Deg180, true)),
253            ((Deg270, false), (Deg90, true)),
254            ((Deg0, true), (Deg0, false)),
255            ((Deg90, true), (Deg270, false)),
256            ((Deg180, true), (Deg180, false)),
257            ((Deg270, true), (Deg90, false)),
258        ]
259        .iter()
260        .copied()
261        {
262            assert_eq!(
263                orientation(rotation, mirrored).flip_horizontal(),
264                orientation(expected_rotation, expected_mirrored)
265            );
266        }
267    }
268
269    #[test]
270    fn flip_vertical() {
271        use Rotation::*;
272
273        for ((rotation, mirrored), (expected_rotation, expected_mirrored)) in [
274            ((Deg0, false), (Deg180, true)),
275            ((Deg90, false), (Deg90, true)),
276            ((Deg180, false), (Deg0, true)),
277            ((Deg270, false), (Deg270, true)),
278            ((Deg0, true), (Deg180, false)),
279            ((Deg90, true), (Deg90, false)),
280            ((Deg180, true), (Deg0, false)),
281            ((Deg270, true), (Deg270, false)),
282        ]
283        .iter()
284        .copied()
285        {
286            assert_eq!(
287                orientation(rotation, mirrored).flip_vertical(),
288                orientation(expected_rotation, expected_mirrored)
289            );
290        }
291    }
292
293    fn draw_memory_mapping(order: MemoryMapping) -> [[u8; 3]; 3] {
294        let mut buffer = [[0u8; 3]; 3];
295
296        let (max_x, max_y) = if order.swap_rows_and_columns {
297            (1, 2)
298        } else {
299            (2, 1)
300        };
301
302        let mut i = 1..;
303        for y in 0..2 {
304            for x in 0..3 {
305                let (x, y) = if order.swap_rows_and_columns {
306                    (y, x)
307                } else {
308                    (x, y)
309                };
310                let x = if order.reverse_columns { max_x - x } else { x };
311                let y = if order.reverse_rows { max_y - y } else { y };
312
313                buffer[y as usize][x as usize] = i.next().unwrap();
314            }
315        }
316
317        buffer
318    }
319
320    #[test]
321    fn test_draw_memory_mapping() {
322        assert_eq!(
323            &draw_memory_mapping(MemoryMapping {
324                reverse_rows: false,
325                reverse_columns: false,
326                swap_rows_and_columns: false,
327            }),
328            &[
329                [1, 2, 3], //
330                [4, 5, 6], //
331                [0, 0, 0], //
332            ]
333        );
334
335        assert_eq!(
336            &draw_memory_mapping(MemoryMapping {
337                reverse_rows: true,
338                reverse_columns: false,
339                swap_rows_and_columns: false,
340            }),
341            &[
342                [4, 5, 6], //
343                [1, 2, 3], //
344                [0, 0, 0], //
345            ]
346        );
347
348        assert_eq!(
349            &draw_memory_mapping(MemoryMapping {
350                reverse_rows: false,
351                reverse_columns: true,
352                swap_rows_and_columns: false,
353            }),
354            &[
355                [3, 2, 1], //
356                [6, 5, 4], //
357                [0, 0, 0], //
358            ]
359        );
360
361        assert_eq!(
362            &draw_memory_mapping(MemoryMapping {
363                reverse_rows: true,
364                reverse_columns: true,
365                swap_rows_and_columns: false,
366            }),
367            &[
368                [6, 5, 4], //
369                [3, 2, 1], //
370                [0, 0, 0], //
371            ]
372        );
373
374        assert_eq!(
375            &draw_memory_mapping(MemoryMapping {
376                reverse_rows: false,
377                reverse_columns: false,
378                swap_rows_and_columns: true,
379            }),
380            &[
381                [1, 4, 0], //
382                [2, 5, 0], //
383                [3, 6, 0], //
384            ]
385        );
386
387        assert_eq!(
388            &draw_memory_mapping(MemoryMapping {
389                reverse_rows: true,
390                reverse_columns: false,
391                swap_rows_and_columns: true,
392            }),
393            &[
394                [3, 6, 0], //
395                [2, 5, 0], //
396                [1, 4, 0], //
397            ]
398        );
399
400        assert_eq!(
401            &draw_memory_mapping(MemoryMapping {
402                reverse_rows: false,
403                reverse_columns: true,
404                swap_rows_and_columns: true,
405            }),
406            &[
407                [4, 1, 0], //
408                [5, 2, 0], //
409                [6, 3, 0], //
410            ]
411        );
412
413        assert_eq!(
414            &draw_memory_mapping(MemoryMapping {
415                reverse_rows: true,
416                reverse_columns: true,
417                swap_rows_and_columns: true,
418            }),
419            &[
420                [6, 3, 0], //
421                [5, 2, 0], //
422                [4, 1, 0], //
423            ]
424        );
425    }
426
427    #[test]
428    fn into_memory_order_not_mirrored() {
429        assert_eq!(
430            &draw_memory_mapping(orientation(Rotation::Deg0, false).into()),
431            &[
432                [1, 2, 3], //
433                [4, 5, 6], //
434                [0, 0, 0], //
435            ]
436        );
437
438        assert_eq!(
439            &draw_memory_mapping(orientation(Rotation::Deg90, false).into()),
440            &[
441                [4, 1, 0], //
442                [5, 2, 0], //
443                [6, 3, 0], //
444            ]
445        );
446
447        assert_eq!(
448            &draw_memory_mapping(orientation(Rotation::Deg180, false).into()),
449            &[
450                [6, 5, 4], //
451                [3, 2, 1], //
452                [0, 0, 0], //
453            ]
454        );
455
456        assert_eq!(
457            &draw_memory_mapping(orientation(Rotation::Deg270, false).into()),
458            &[
459                [3, 6, 0], //
460                [2, 5, 0], //
461                [1, 4, 0], //
462            ]
463        );
464    }
465
466    #[test]
467    fn into_memory_order_mirrored() {
468        assert_eq!(
469            &draw_memory_mapping(orientation(Rotation::Deg0, true).into()),
470            &[
471                [3, 2, 1], //
472                [6, 5, 4], //
473                [0, 0, 0], //
474            ]
475        );
476
477        assert_eq!(
478            &draw_memory_mapping(orientation(Rotation::Deg90, true).into()),
479            &[
480                [1, 4, 0], //
481                [2, 5, 0], //
482                [3, 6, 0], //
483            ]
484        );
485
486        assert_eq!(
487            &draw_memory_mapping(orientation(Rotation::Deg180, true).into()),
488            &[
489                [4, 5, 6], //
490                [1, 2, 3], //
491                [0, 0, 0], //
492            ]
493        );
494
495        assert_eq!(
496            &draw_memory_mapping(orientation(Rotation::Deg270, true).into()),
497            &[
498                [6, 3, 0], //
499                [5, 2, 0], //
500                [4, 1, 0], //
501            ]
502        );
503    }
504
505    #[test]
506    fn equivalent_orientations() {
507        let o1 = Orientation::new().rotate(Rotation::Deg270).flip_vertical();
508        let o2 = Orientation::new().rotate(Rotation::Deg90).flip_horizontal();
509        let o3 = Orientation::new()
510            .flip_horizontal()
511            .rotate(Rotation::Deg270);
512        let o4 = Orientation::new().flip_vertical().rotate(Rotation::Deg90);
513
514        assert_eq!(o1, o2);
515        assert_eq!(o1, o3);
516        assert_eq!(o1, o4);
517    }
518}