src/actions.rs 4 K · 133 lines · raw · history

1 use bevy::{input::InputSystems, prelude::*};
2
3 use crate::sim::SimSpeed;
4
5 pub struct ActionsPlugin;
6
7 impl Plugin for ActionsPlugin {
8 fn build(&self, app: &mut App) {
9 app.add_message::<Action>()
10 .add_systems(PreUpdate, keyboard_actions.after(InputSystems));
11 }
12 }
13
14 #[derive(Message, Debug, Clone, Copy, PartialEq, Eq)]
15 pub enum Action {
16 TogglePause,
17 SetSpeed(SimSpeed),
18 ToggleStockpileTool,
19 ToggleFarmTool,
20 CycleBuildTool,
21 Cancel,
22 NewWorld,
23 ToggleUi,
24 ZoomIn,
25 ZoomOut,
26 }
27
28 pub const KEYBINDS: [(KeyCode, Action); 13] = [
29 (KeyCode::Space, Action::TogglePause),
30 (KeyCode::Digit1, Action::SetSpeed(SimSpeed::Normal)),
31 (KeyCode::Digit2, Action::SetSpeed(SimSpeed::Fast)),
32 (KeyCode::Digit3, Action::SetSpeed(SimSpeed::Fastest)),
33 (KeyCode::Digit4, Action::SetSpeed(SimSpeed::Max)),
34 (KeyCode::KeyZ, Action::ToggleStockpileTool),
35 (KeyCode::KeyF, Action::ToggleFarmTool),
36 (KeyCode::KeyB, Action::CycleBuildTool),
37 (KeyCode::Escape, Action::Cancel),
38 (KeyCode::KeyR, Action::NewWorld),
39 (KeyCode::KeyH, Action::ToggleUi),
40 (KeyCode::Equal, Action::ZoomIn),
41 (KeyCode::Minus, Action::ZoomOut),
42 ];
43
44 impl Action {
45 pub fn label(self) -> &'static str {
46 match self {
47 Action::TogglePause => "Pause",
48 Action::SetSpeed(SimSpeed::Normal) => "1x",
49 Action::SetSpeed(SimSpeed::Fast) => "3x",
50 Action::SetSpeed(SimSpeed::Fastest) => "6x",
51 Action::SetSpeed(SimSpeed::Max) => "Max",
52 Action::ToggleStockpileTool => "Stockpile",
53 Action::ToggleFarmTool => "Farm",
54 Action::CycleBuildTool => "Build",
55 Action::Cancel => "Cancel",
56 Action::NewWorld => "New world",
57 Action::ToggleUi => "Hide UI",
58 Action::ZoomIn => "+",
59 Action::ZoomOut => "-",
60 }
61 }
62
63 pub fn key(self) -> Option<KeyCode> {
64 KEYBINDS
65 .iter()
66 .find(|(_, action)| *action == self)
67 .map(|(key, _)| *key)
68 }
69
70 pub fn key_label(self) -> String {
71 match self.key() {
72 Some(KeyCode::Space) => "Space".into(),
73 Some(KeyCode::Escape) => "Esc".into(),
74 Some(KeyCode::Equal) => "=".into(),
75 Some(KeyCode::Minus) => "-".into(),
76 Some(key) => format!("{key:?}")
77 .trim_start_matches("Digit")
78 .trim_start_matches("Key")
79 .to_string(),
80 None => String::new(),
81 }
82 }
83 }
84
85 fn keyboard_actions(keys: Res<ButtonInput<KeyCode>>, mut actions: MessageWriter<Action>) {
86 for (key, action) in KEYBINDS {
87 if keys.just_pressed(key) {
88 actions.write(action);
89 }
90 }
91 }
92
93 #[cfg(test)]
94 mod tests {
95 use super::*;
96
97 #[test]
98 fn pressing_a_bound_key_triggers_its_action() {
99 let mut app = App::new();
100 app.init_resource::<ButtonInput<KeyCode>>()
101 .add_message::<Action>()
102 .add_systems(Update, keyboard_actions);
103 app.world_mut()
104 .resource_mut::<ButtonInput<KeyCode>>()
105 .press(KeyCode::Digit3);
106 app.update();
107 let actions: Vec<Action> = app
108 .world_mut()
109 .resource_mut::<Messages<Action>>()
110 .drain()
111 .collect();
112 assert_eq!(actions, vec![Action::SetSpeed(SimSpeed::Fastest)]);
113 }
114
115 #[test]
116 fn every_action_has_exactly_one_key() {
117 for (_, action) in KEYBINDS {
118 let bound = KEYBINDS.iter().filter(|(_, a)| *a == action).count();
119 assert_eq!(bound, 1, "{action:?}");
120 }
121 let mut keys: Vec<_> = KEYBINDS.iter().map(|(k, _)| format!("{k:?}")).collect();
122 keys.sort();
123 keys.dedup();
124 assert_eq!(keys.len(), KEYBINDS.len());
125 }
126
127 #[test]
128 fn key_labels_are_short() {
129 assert_eq!(Action::SetSpeed(SimSpeed::Fast).key_label(), "2");
130 assert_eq!(Action::ToggleStockpileTool.key_label(), "Z");
131 assert_eq!(Action::Cancel.key_label(), "Esc");
132 }
133 }