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}