1use bevy::prelude::*;
2use std::hash::Hash; #[derive(Resource, Reflect, Default, Clone)]
6#[reflect(Resource)]
7pub struct AvailableInputDevices {
8 pub gamepads: Vec<Entity>,
9 pub mouse: bool,
10 pub touch: bool,
11 pub keyboard: bool,
12}
13
14impl AvailableInputDevices {
15 pub fn get_available_devices(&self) -> Vec<InputDevice> {
16 let mut devices = Vec::new();
17
18 if self.keyboard {
20 devices.push(InputDevice::Keyboard(KeyboardScheme::WASD));
21 devices.push(InputDevice::Keyboard(KeyboardScheme::Arrows));
22 devices.push(InputDevice::Keyboard(KeyboardScheme::IJKL));
23
24 devices.push(InputDevice::Keyboard(KeyboardScheme::Custom {
26 up: KeyCode::KeyT,
27 down: KeyCode::KeyG,
28 left: KeyCode::KeyF,
29 right: KeyCode::KeyH,
30 }));
31 }
32
33 for (index, _) in self.gamepads.iter().enumerate() {
35 devices.push(InputDevice::Gamepad(index as u32));
36 }
37
38 if self.mouse {
40 devices.push(InputDevice::Mouse);
41 }
42
43 if self.touch {
45 devices.push(InputDevice::Touch);
46 }
47
48 devices
49 }
50
51 pub fn update_availability(&mut self) {
53 self.keyboard = true;
55
56 #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
58 {
59 self.mouse = true;
60 }
61
62 #[cfg(target_family = "wasm")]
63 {
64 self.mouse = true;
66 self.touch = true;
68 }
69
70 #[cfg(target_os = "android")]
71 {
72 self.mouse = false;
73 self.touch = true;
74 }
75
76 #[cfg(target_os = "ios")]
77 {
78 self.mouse = false;
79 self.touch = true;
80 }
81
82 #[cfg(not(any(
83 target_os = "windows",
84 target_os = "macos",
85 target_os = "linux",
86 target_family = "wasm",
87 target_os = "android",
88 target_os = "ios"
89 )))]
90 {
91 self.mouse = true;
93 self.touch = false;
94 }
95 }
96}
97
98#[derive(Reflect, Clone, Debug, PartialEq)]
99pub enum InputDevice {
100 Keyboard(KeyboardScheme),
101 Gamepad(u32),
102 Mouse,
103 Touch,
104}
105
106#[allow(clippy::upper_case_acronyms)]
107#[derive(Reflect, Clone, Debug, PartialEq)]
108pub enum KeyboardScheme {
109 WASD,
110 Arrows,
111 IJKL,
112 Custom {
113 up: KeyCode,
114 down: KeyCode,
115 left: KeyCode,
116 right: KeyCode,
117 },
118}
119
120impl KeyboardScheme {
121 pub fn get_keys(&self) -> (KeyCode, KeyCode, KeyCode, KeyCode) {
122 match self {
123 KeyboardScheme::WASD => (KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD),
124 KeyboardScheme::Arrows => (
125 KeyCode::ArrowUp,
126 KeyCode::ArrowDown,
127 KeyCode::ArrowLeft,
128 KeyCode::ArrowRight,
129 ),
130 KeyboardScheme::IJKL => (KeyCode::KeyI, KeyCode::KeyK, KeyCode::KeyJ, KeyCode::KeyL),
131 KeyboardScheme::Custom {
132 up,
133 down,
134 left,
135 right,
136 } => (*up, *down, *left, *right),
137 }
138 }
139
140 pub fn name(&self) -> &'static str {
141 match self {
142 KeyboardScheme::WASD => "WASD",
143 KeyboardScheme::Arrows => "Arrow Keys",
144 KeyboardScheme::IJKL => "IJKL",
145 KeyboardScheme::Custom { .. } => "Custom Keys",
146 }
147 }
148
149 pub fn description(&self) -> String {
151 match self {
152 KeyboardScheme::WASD => "W/A/S/D keys".to_string(),
153 KeyboardScheme::Arrows => "Arrow keys".to_string(),
154 KeyboardScheme::IJKL => "I/J/K/L keys".to_string(),
155 KeyboardScheme::Custom {
156 up,
157 down,
158 left,
159 right,
160 } => {
161 format!("{:?}/{:?}/{:?}/{:?}", up, down, left, right)
162 }
163 }
164 }
165}
166
167impl InputDevice {
168 pub fn name(&self) -> String {
169 match self {
170 InputDevice::Keyboard(scheme) => format!("Keyboard ({})", scheme.name()),
171 InputDevice::Gamepad(id) => format!("Gamepad {}", id + 1),
172 InputDevice::Mouse => "Mouse".to_string(),
173 InputDevice::Touch => "Touch".to_string(),
174 }
175 }
176
177 pub fn description(&self) -> String {
179 match self {
180 InputDevice::Keyboard(scheme) => {
181 format!("Keyboard - {}", scheme.description())
182 }
183 InputDevice::Gamepad(id) => {
184 format!("Gamepad {} (D-Pad + Analog Stick)", id + 1)
185 }
186 InputDevice::Mouse => "Mouse (Click and drag for movement)".to_string(),
187 InputDevice::Touch => "Touch (Tap and swipe gestures)".to_string(),
188 }
189 }
190
191 pub fn is_available(&self, available_devices: &AvailableInputDevices) -> bool {
192 match self {
193 InputDevice::Keyboard(_) => available_devices.keyboard,
194 InputDevice::Gamepad(id) => (*id as usize) < available_devices.gamepads.len(),
195 InputDevice::Mouse => available_devices.mouse,
196 InputDevice::Touch => available_devices.touch,
197 }
198 }
199
200 pub fn category(&self) -> InputDeviceCategory {
202 match self {
203 InputDevice::Keyboard(_) => InputDeviceCategory::Keyboard,
204 InputDevice::Gamepad(_) => InputDeviceCategory::Gamepad,
205 InputDevice::Mouse => InputDeviceCategory::Pointing,
206 InputDevice::Touch => InputDeviceCategory::Touch,
207 }
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
214pub enum InputDeviceCategory {
215 Keyboard,
216 Gamepad,
217 Pointing, Touch,
219}
220
221impl InputDeviceCategory {
222 pub fn name(&self) -> &'static str {
223 match self {
224 InputDeviceCategory::Keyboard => "Keyboard",
225 InputDeviceCategory::Gamepad => "Gamepad",
226 InputDeviceCategory::Pointing => "Mouse",
227 InputDeviceCategory::Touch => "Touch",
228 }
229 }
230
231 pub fn icon(&self) -> &'static str {
232 match self {
233 InputDeviceCategory::Keyboard => "⌨️",
234 InputDeviceCategory::Gamepad => "🎮",
235 InputDeviceCategory::Pointing => "🖱️",
236 InputDeviceCategory::Touch => "👆",
237 }
238 }
239
240 pub fn order(&self) -> u8 {
242 match self {
243 InputDeviceCategory::Keyboard => 0,
244 InputDeviceCategory::Gamepad => 1,
245 InputDeviceCategory::Pointing => 2,
246 InputDeviceCategory::Touch => 3,
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn test_available_input_devices() {
257 let devices = AvailableInputDevices {
258 gamepads: vec![Entity::from_raw(1), Entity::from_raw(2)],
259 mouse: true,
260 touch: false,
261 keyboard: true,
262 };
263
264 let available = devices.get_available_devices();
265
266 assert!(available.len() >= 6); assert!(available
271 .iter()
272 .any(|d| matches!(d, InputDevice::Keyboard(KeyboardScheme::WASD))));
273 assert!(available
274 .iter()
275 .any(|d| matches!(d, InputDevice::Keyboard(KeyboardScheme::Arrows))));
276 assert!(available
277 .iter()
278 .any(|d| matches!(d, InputDevice::Keyboard(KeyboardScheme::IJKL))));
279
280 assert!(available
282 .iter()
283 .any(|d| matches!(d, InputDevice::Gamepad(0))));
284 assert!(available
285 .iter()
286 .any(|d| matches!(d, InputDevice::Gamepad(1))));
287
288 assert!(available.iter().any(|d| matches!(d, InputDevice::Mouse)));
290
291 assert!(!available.iter().any(|d| matches!(d, InputDevice::Touch)));
293 }
294
295 #[test]
296 fn test_keyboard_schemes() {
297 let wasd = KeyboardScheme::WASD;
298 let arrows = KeyboardScheme::Arrows;
299 let custom = KeyboardScheme::Custom {
300 up: KeyCode::KeyT,
301 down: KeyCode::KeyG,
302 left: KeyCode::KeyF,
303 right: KeyCode::KeyH,
304 };
305
306 assert_eq!(wasd.name(), "WASD");
307 assert_eq!(arrows.name(), "Arrow Keys");
308 assert_eq!(custom.name(), "Custom Keys");
309
310 let (up, down, left, right) = wasd.get_keys();
311 assert_eq!(
312 (up, down, left, right),
313 (KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD)
314 );
315 }
316
317 #[test]
318 fn test_device_categories() {
319 let keyboard_device = InputDevice::Keyboard(KeyboardScheme::WASD);
320 let gamepad_device = InputDevice::Gamepad(0);
321 let mouse_device = InputDevice::Mouse;
322 let touch_device = InputDevice::Touch;
323
324 assert_eq!(keyboard_device.category(), InputDeviceCategory::Keyboard);
325 assert_eq!(gamepad_device.category(), InputDeviceCategory::Gamepad);
326 assert_eq!(mouse_device.category(), InputDeviceCategory::Pointing);
327 assert_eq!(touch_device.category(), InputDeviceCategory::Touch);
328 }
329
330 #[test]
331 fn test_category_hash() {
332 use std::collections::HashMap;
333
334 let mut map = HashMap::new();
335 map.insert(InputDeviceCategory::Keyboard, "keyboard");
336 map.insert(InputDeviceCategory::Gamepad, "gamepad");
337
338 assert_eq!(map.get(&InputDeviceCategory::Keyboard), Some(&"keyboard"));
339 assert_eq!(map.get(&InputDeviceCategory::Gamepad), Some(&"gamepad"));
340 }
341}