shuyi_assets/keymap/
action_set.rs1use core::fmt::Debug;
9use core::hash::Hash;
10
11use super::{Chord, Key};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub enum UiAction {
18 Up,
20 Down,
22 Left,
24 Right,
26 Confirm,
28 Cancel,
30 Debug,
32}
33
34pub trait ActionSet: Copy + Ord + Eq + Hash + Debug + 'static {
39 type Group: Copy + Ord + Eq + Debug;
43
44 const ALL: &'static [Self];
47
48 fn name(self) -> &'static str;
50
51 fn from_name(name: &str) -> Option<Self> {
53 Self::ALL.iter().copied().find(|a| a.name() == name)
54 }
55
56 fn is_repeatable(self) -> bool;
58
59 fn is_required(self) -> bool;
62
63 fn is_rebindable(self) -> bool;
66
67 fn group(self) -> Self::Group;
69
70 fn ui(self) -> Option<UiAction>;
72
73 fn fixed_keys() -> &'static [(Chord, Self)];
78
79 fn for_ui(ui: UiAction) -> Option<Self> {
81 Self::ALL.iter().copied().find(|a| a.ui() == Some(ui))
82 }
83}
84
85const UI_FIXED_KEYS: [(Chord, UiAction); 1] = [(Chord::plain(Key::Escape), UiAction::Cancel)];
88
89impl ActionSet for UiAction {
96 type Group = ();
98
99 const ALL: &'static [Self] = &[
100 Self::Up,
101 Self::Down,
102 Self::Left,
103 Self::Right,
104 Self::Confirm,
105 Self::Cancel,
106 ];
107
108 fn name(self) -> &'static str {
109 match self {
110 Self::Up => "Up",
111 Self::Down => "Down",
112 Self::Left => "Left",
113 Self::Right => "Right",
114 Self::Confirm => "Confirm",
115 Self::Cancel => "Cancel",
116 Self::Debug => "Debug",
117 }
118 }
119
120 fn is_repeatable(self) -> bool {
122 matches!(self, Self::Up | Self::Down | Self::Left | Self::Right)
123 }
124
125 fn is_required(self) -> bool {
127 self != Self::Debug
128 }
129
130 fn is_rebindable(self) -> bool {
131 self != Self::Debug
132 }
133
134 fn group(self) {}
135
136 fn ui(self) -> Option<UiAction> {
137 Some(self)
138 }
139
140 fn fixed_keys() -> &'static [(Chord, Self)] {
141 &UI_FIXED_KEYS
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::keymap::KeymapDef;
149
150 fn source(extra: &str) -> String {
153 format!(
154 r#"(
155 layouts: [(
156 id: "Arrows",
157 name: "layout.arrows",
158 keys: {{
159 "Up": ["Up"], "Down": ["Down"], "Left": ["Left"], "Right": ["Right"],
160 "Confirm": ["Enter"], "Cancel": ["Backspace"],{extra}
161 }},
162 )],
163 layout_picker: {{ "Up": ["Up"], "Down": ["Down"], "Confirm": ["Enter"] }},
164 pad: {{
165 "Up": ["DpadUp"], "Down": ["DpadDown"], "Left": ["DpadLeft"],
166 "Right": ["DpadRight"], "Confirm": ["South"], "Cancel": ["East"],
167 }},
168 targets: {{}},
169 repeat: (delay_ms: 300, interval_ms: 70),
170 stick: (press_percent: 50, release_percent: 35),
171)"#
172 )
173 }
174
175 #[test]
176 fn the_six_moves_are_the_set_and_debug_is_not() {
177 assert_eq!(
178 UiAction::ALL,
179 [
180 UiAction::Up,
181 UiAction::Down,
182 UiAction::Left,
183 UiAction::Right,
184 UiAction::Confirm,
185 UiAction::Cancel,
186 ]
187 );
188 for &action in UiAction::ALL {
189 assert_eq!(UiAction::from_name(action.name()), Some(action));
190 assert_eq!(action.ui(), Some(action));
191 assert_eq!(UiAction::for_ui(action), Some(action));
192 assert!(action.is_required() && action.is_rebindable());
193 }
194 let repeating: Vec<UiAction> = UiAction::ALL
195 .iter()
196 .copied()
197 .filter(|a| a.is_repeatable())
198 .collect();
199 assert_eq!(
200 repeating,
201 [
202 UiAction::Up,
203 UiAction::Down,
204 UiAction::Left,
205 UiAction::Right
206 ]
207 );
208 assert_eq!(UiAction::Debug.name(), "Debug");
209 assert_eq!(UiAction::from_name("Debug"), None);
210 assert_eq!(UiAction::for_ui(UiAction::Debug), None);
211 assert!(!UiAction::Debug.is_required() && !UiAction::Debug.is_rebindable());
212 assert!(!UiAction::Debug.is_repeatable());
213 assert_eq!(
214 UiAction::fixed_keys(),
215 [(Chord::plain(Key::Escape), UiAction::Cancel)]
216 );
217 }
218
219 #[test]
220 fn a_keymap_file_of_the_six_moves_loads_and_one_naming_debug_is_refused() {
221 let keymap = KeymapDef::<UiAction>::from_source("keymap.ron", &source(""))
222 .unwrap_or_else(|e| panic!("{e:?}"));
223 assert_eq!(
224 keymap
225 .first_layout()
226 .map(|l| l.name().to_owned())
227 .as_deref(),
228 Some("Arrows")
229 );
230 let errors =
231 KeymapDef::<UiAction>::from_source("keymap.ron", &source(r#" "Debug": ["F3"],"#))
232 .unwrap_err();
233 assert!(
234 errors
235 .iter()
236 .any(|e| e.to_string().contains("unknown action \"Debug\"")),
237 "{errors:?}"
238 );
239 }
240}