tfc/
enum.rs

1use std::{iter::Iterator, marker::PhantomData, fmt::{Display, Debug}};
2
3/// An iterator for the variants of an [`Enum`].
4///
5/// # Examples
6///
7/// ```
8/// use tfc::{CommandCode, Enum};
9///
10/// for var in CommandCode::iter() {
11///    println!("{}", var.display_name());
12/// }
13/// ```
14pub struct EnumIterator<E: Enum> {
15    index: u8,
16    phantom: PhantomData<E>,
17}
18
19impl<E: Enum> Iterator for EnumIterator<E> {
20    type Item = E;
21
22    fn next(&mut self) -> Option<Self::Item> {
23        let ret = E::from_u8(self.index);
24        if ret.is_some() {
25            self.index += 1;
26        }
27        ret
28    }
29
30    fn size_hint(&self) -> (usize, Option<usize>) {
31        let size = (E::COUNT - self.index) as usize;
32        (size, Some(size))
33    }
34}
35
36/// An enum with limited reflection capabilities.
37///
38/// The name of the enum itself and its variants is available. An iterator over
39/// the variants is also provided. The three enums that implement this trait
40/// are:
41///  - [`CommandCode`](crate::CommandCode)
42///  - [`Key`](crate::Key)
43///  - [`MouseButton`](crate::MouseButton)
44///
45/// # Examples
46///
47/// ```
48/// use tfc::{Key, Enum};
49///
50/// assert_eq!(Key::NAME, "Key");
51/// assert_eq!(Key::PlayPause.identifier_name(), "PlayPause");
52/// assert_eq!(Key::PlayPause.display_name(), "Play/Pause");
53/// ```
54pub trait Enum: Copy + Clone + Eq + PartialEq + Display + Debug {
55    /// The name of the enum.
56    const NAME: &'static str;
57
58    /// The number of variants in the enum.
59    const COUNT: u8;
60
61    /// The display name of this enum variant.
62    ///
63    /// This is the name that is appropriate for showing to end users. It may
64    /// contain spaces or other symbols and is in Title Case. It is used by the
65    /// [`Display`] implementation.
66    fn display_name(&self) -> &'static str;
67
68    /// The identifier name of this enum variant.
69    ///
70    /// This is the raw identifier name of the enum variant in PascalCase. It is
71    /// used by the [`Debug`] implementation.
72    fn identifier_name(&self) -> &'static str;
73
74    /// Create an instance of the enum from a `u8`.
75    ///
76    /// `None` is returned if the given byte is out of range (i.e. `>= COUNT`).
77    fn from_u8(byte: u8) -> Option<Self>;
78
79    /// Convert this enum variant to a `u8`.
80    ///
81    /// This is useful when casting a generic `T: Enum` to a `u8`.
82    fn into_u8(self) -> u8;
83
84    /// Get an iterator over the variants of the enum.
85    fn iter() -> EnumIterator<Self> {
86        EnumIterator::<Self> {
87            index: 0, phantom: PhantomData
88        }
89    }
90}
91
92macro_rules! count {
93    () => { 0 };
94    ($first:tt $($rest:tt)*) => { 1 + count!($($rest)*) };
95}
96
97macro_rules! enumeration {
98    (
99        $name:ident,
100        $description:literal,
101        [$(($identifier_name:ident, $display_name:literal)),+$(,)?]
102    ) => {
103        use crate::Enum;
104
105        #[doc = $description]
106        ///
107        /// This implements the [`Enum`] trait.
108        #[repr(u8)]
109        #[derive(Copy, Clone, Eq, PartialEq)]
110        pub enum $name {
111            $($identifier_name),*
112        }
113
114        impl $name {
115            const DISPLAY_NAMES: [&'static str; Self::COUNT as usize] = [
116                $($display_name),*
117            ];
118
119            const IDENTIFIER_NAMES: [&'static str; Self::COUNT as usize] = [
120                $(stringify!($identifier_name)),*
121            ];
122        }
123
124        impl Enum for $name {
125            const NAME: &'static str = stringify!($name);
126            const COUNT: u8 = count!($($identifier_name)*);
127
128            fn display_name(&self) -> &'static str {
129                Self::DISPLAY_NAMES[*self as u8 as usize]
130            }
131
132            fn identifier_name(&self) -> &'static str {
133                Self::IDENTIFIER_NAMES[*self as u8 as usize]
134            }
135
136            fn from_u8(byte: u8) -> Option<Self> {
137                match byte {
138                    $(b if b == Self::$identifier_name as u8 => Some(Self::$identifier_name)),*,
139                    _ => None,
140                }
141            }
142
143            fn into_u8(self) -> u8 {
144                self as u8
145            }
146        }
147
148        impl std::fmt::Display for $name {
149            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150                f.write_str(self.display_name())
151            }
152        }
153
154        // derive(Debug) is very inefficient (not that it really matters)
155        impl std::fmt::Debug for $name {
156            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157                f.write_str(self.identifier_name())
158            }
159        }
160    }
161}