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