concinnity_core/components/
gamepad_map.rs1use crate::components::GamepadButton;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum GamepadAction {
11 Sprint,
13 Jump,
15 Interact,
17}
18
19impl GamepadAction {
20 pub const ALL: [GamepadAction; 3] = [
22 GamepadAction::Sprint,
23 GamepadAction::Jump,
24 GamepadAction::Interact,
25 ];
26
27 pub fn setting_key(self) -> &'static str {
31 match self {
32 GamepadAction::Sprint => "pad_sprint",
33 GamepadAction::Jump => "pad_jump",
34 GamepadAction::Interact => "pad_interact",
35 }
36 }
37
38 pub fn from_setting_key(key: &str) -> Option<GamepadAction> {
41 GamepadAction::ALL
42 .into_iter()
43 .find(|a| a.setting_key() == key)
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
53pub struct GamepadMap {
54 #[serde(default = "def_sprint")]
56 pub sprint: GamepadButton,
57 #[serde(default = "def_jump")]
59 pub jump: GamepadButton,
60 #[serde(default = "def_interact")]
62 pub interact: GamepadButton,
63}
64
65impl GamepadMap {
66 pub const DEFAULT: GamepadMap = GamepadMap {
69 sprint: GamepadButton::LeftStick,
70 jump: GamepadButton::South,
71 interact: GamepadButton::West,
72 };
73
74 pub fn get(self, action: GamepadAction) -> GamepadButton {
76 match action {
77 GamepadAction::Sprint => self.sprint,
78 GamepadAction::Jump => self.jump,
79 GamepadAction::Interact => self.interact,
80 }
81 }
82
83 pub fn set(&mut self, action: GamepadAction, button: GamepadButton) {
85 match action {
86 GamepadAction::Sprint => self.sprint = button,
87 GamepadAction::Jump => self.jump = button,
88 GamepadAction::Interact => self.interact = button,
89 }
90 }
91
92 pub fn action_for_button(self, button: GamepadButton) -> Option<GamepadAction> {
96 GamepadAction::ALL
97 .into_iter()
98 .find(|&a| self.get(a) == button)
99 }
100
101 pub fn rebind(&mut self, action: GamepadAction, new_button: GamepadButton) {
105 let old_button = self.get(action);
106 if old_button == new_button {
107 return;
108 }
109 if let Some(other) = self.action_for_button(new_button)
110 && other != action
111 {
112 self.set(other, old_button);
113 }
114 self.set(action, new_button);
115 }
116}
117
118impl Default for GamepadMap {
119 fn default() -> Self {
120 Self::DEFAULT
121 }
122}
123
124fn def_sprint() -> GamepadButton {
125 GamepadMap::DEFAULT.sprint
126}
127fn def_jump() -> GamepadButton {
128 GamepadMap::DEFAULT.jump
129}
130fn def_interact() -> GamepadButton {
131 GamepadMap::DEFAULT.interact
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn setting_key_round_trips() {
140 for a in GamepadAction::ALL {
141 assert_eq!(GamepadAction::from_setting_key(a.setting_key()), Some(a));
142 }
143 assert_eq!(GamepadAction::from_setting_key("key_jump"), None);
144 assert_eq!(GamepadAction::from_setting_key("pad_nope"), None);
145 }
146
147 #[test]
148 fn get_set_cover_every_action_arm() {
149 let buttons = [
150 GamepadButton::North,
151 GamepadButton::East,
152 GamepadButton::RightShoulder,
153 ];
154 let mut m = GamepadMap::default();
155 for (a, b) in GamepadAction::ALL.into_iter().zip(buttons) {
156 m.set(a, b);
157 }
158 for (a, b) in GamepadAction::ALL.into_iter().zip(buttons) {
159 assert_eq!(m.get(a), b);
160 }
161 }
162
163 #[test]
164 fn rebind_to_free_button_just_sets_it() {
165 let mut m = GamepadMap::default();
166 m.rebind(GamepadAction::Jump, GamepadButton::North);
167 assert_eq!(m.jump, GamepadButton::North);
168 assert_eq!(m.interact, GamepadMap::DEFAULT.interact);
169 }
170
171 #[test]
172 fn rebind_to_own_button_is_a_noop() {
173 let mut m = GamepadMap::default();
174 m.rebind(GamepadAction::Jump, GamepadMap::DEFAULT.jump);
175 assert_eq!(m, GamepadMap::default());
176 }
177
178 #[test]
179 fn rebind_to_occupied_button_swaps() {
180 let mut m = GamepadMap::default();
183 m.rebind(GamepadAction::Jump, GamepadButton::West);
184 assert_eq!(m.jump, GamepadButton::West);
185 assert_eq!(m.interact, GamepadMap::DEFAULT.jump);
186 for a in GamepadAction::ALL {
187 assert_eq!(m.action_for_button(m.get(a)), Some(a));
188 }
189 }
190
191 #[test]
192 fn missing_field_falls_back_to_default() {
193 let partial: GamepadMap = serde_json::from_str(r#"{"jump":"East"}"#).unwrap();
196 assert_eq!(partial.jump, GamepadButton::East);
197 assert_eq!(partial.sprint, GamepadMap::DEFAULT.sprint);
198 assert_eq!(partial.interact, GamepadMap::DEFAULT.interact);
199 }
200}