src/actions.rs 4.4 K · 145 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 ToggleDesignateTool,
22 Cancel,
23 NewWorld,
24 ToggleUi,
25 ZoomIn,
26 ZoomOut,
27 Save,
28 Load,
29 ToggleDemolishTool,
30 }
31
32 pub const KEYBINDS: [(KeyCode, Action); 17] = [
33 (KeyCode::Space, Action::TogglePause),
34 (KeyCode::Digit1, Action::SetSpeed(SimSpeed::Normal)),
35 (KeyCode::Digit2, Action::SetSpeed(SimSpeed::Fast)),
36 (KeyCode::Digit3, Action::SetSpeed(SimSpeed::Fastest)),
37 (KeyCode::Digit4, Action::SetSpeed(SimSpeed::Max)),
38 (KeyCode::KeyZ, Action::ToggleStockpileTool),
39 (KeyCode::KeyF, Action::ToggleFarmTool),
40 (KeyCode::KeyB, Action::CycleBuildTool),
41 (KeyCode::KeyX, Action::ToggleDesignateTool),
42 (KeyCode::KeyQ, Action::ToggleDemolishTool),
43 (KeyCode::Escape, Action::Cancel),
44 (KeyCode::KeyR, Action::NewWorld),
45 (KeyCode::KeyH, Action::ToggleUi),
46 (KeyCode::Equal, Action::ZoomIn),
47 (KeyCode::Minus, Action::ZoomOut),
48 (KeyCode::F5, Action::Save),
49 (KeyCode::F9, Action::Load),
50 ];
51
52 impl Action {
53 pub fn label(self) -> &'static str {
54 match self {
55 Action::TogglePause => "Pause",
56 Action::SetSpeed(SimSpeed::Normal) => "1x",
57 Action::SetSpeed(SimSpeed::Fast) => "3x",
58 Action::SetSpeed(SimSpeed::Fastest) => "6x",
59 Action::SetSpeed(SimSpeed::Max) => "Max",
60 Action::ToggleStockpileTool => "Stockpile",
61 Action::ToggleFarmTool => "Farm",
62 Action::CycleBuildTool => "Build",
63 Action::ToggleDesignateTool => "Mark",
64 Action::ToggleDemolishTool => "Demolish",
65 Action::Cancel => "Cancel",
66 Action::NewWorld => "New map",
67 Action::ToggleUi => "Hide",
68 Action::ZoomIn => "+",
69 Action::ZoomOut => "-",
70 Action::Save => "Save",
71 Action::Load => "Load",
72 }
73 }
74
75 pub fn key(self) -> Option<KeyCode> {
76 KEYBINDS
77 .iter()
78 .find(|(_, action)| *action == self)
79 .map(|(key, _)| *key)
80 }
81
82 pub fn key_label(self) -> String {
83 match self.key() {
84 Some(KeyCode::Space) => "Space".into(),
85 Some(KeyCode::Escape) => "Esc".into(),
86 Some(KeyCode::Equal) => "=".into(),
87 Some(KeyCode::Minus) => "-".into(),
88 Some(key) => format!("{key:?}")
89 .trim_start_matches("Digit")
90 .trim_start_matches("Key")
91 .to_string(),
92 None => String::new(),
93 }
94 }
95 }
96
97 fn keyboard_actions(keys: Res<ButtonInput<KeyCode>>, mut actions: MessageWriter<Action>) {
98 for (key, action) in KEYBINDS {
99 if keys.just_pressed(key) {
100 actions.write(action);
101 }
102 }
103 }
104
105 #[cfg(test)]
106 mod tests {
107 use super::*;
108
109 #[test]
110 fn pressing_a_bound_key_triggers_its_action() {
111 let mut app = App::new();
112 app.init_resource::<ButtonInput<KeyCode>>()
113 .add_message::<Action>()
114 .add_systems(Update, keyboard_actions);
115 app.world_mut()
116 .resource_mut::<ButtonInput<KeyCode>>()
117 .press(KeyCode::Digit3);
118 app.update();
119 let actions: Vec<Action> = app
120 .world_mut()
121 .resource_mut::<Messages<Action>>()
122 .drain()
123 .collect();
124 assert_eq!(actions, vec![Action::SetSpeed(SimSpeed::Fastest)]);
125 }
126
127 #[test]
128 fn every_action_has_exactly_one_key() {
129 for (_, action) in KEYBINDS {
130 let bound = KEYBINDS.iter().filter(|(_, a)| *a == action).count();
131 assert_eq!(bound, 1, "{action:?}");
132 }
133 let mut keys: Vec<_> = KEYBINDS.iter().map(|(k, _)| format!("{k:?}")).collect();
134 keys.sort();
135 keys.dedup();
136 assert_eq!(keys.len(), KEYBINDS.len());
137 }
138
139 #[test]
140 fn key_labels_are_short() {
141 assert_eq!(Action::SetSpeed(SimSpeed::Fast).key_label(), "2");
142 assert_eq!(Action::ToggleStockpileTool.key_label(), "Z");
143 assert_eq!(Action::Cancel.key_label(), "Esc");
144 }
145 }