konnektoren_bevy/input/
device.rs

1use bevy::prelude::*;
2use std::hash::Hash; // Add this import
3
4/// Resource to track available input devices
5#[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        // Always add keyboard schemes if keyboard is available
19        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            // Add some common custom schemes
25            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        // Add gamepad devices
34        for (index, _) in self.gamepads.iter().enumerate() {
35            devices.push(InputDevice::Gamepad(index as u32));
36        }
37
38        // Add mouse if available
39        if self.mouse {
40            devices.push(InputDevice::Mouse);
41        }
42
43        // Add touch if available (typically on mobile/web)
44        if self.touch {
45            devices.push(InputDevice::Touch);
46        }
47
48        devices
49    }
50
51    /// Update device availability based on platform and capabilities
52    pub fn update_availability(&mut self) {
53        // Keyboard is almost always available except on some restricted platforms
54        self.keyboard = true;
55
56        // Mouse availability - true on desktop, false on mobile-only devices
57        #[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            // On web, mouse is usually available
65            self.mouse = true;
66            // Touch might be available on touch devices
67            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            // Default fallback
92            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    /// Get a user-friendly description of the key layout
150    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    /// Get a detailed description of the input device
178    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    /// Get the category of this input device for grouping in UI
201    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/// Categories for grouping input devices in UI
212/// Add Hash and Eq traits for HashMap usage
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
214pub enum InputDeviceCategory {
215    Keyboard,
216    Gamepad,
217    Pointing, // Mouse, trackpad, etc.
218    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    /// Get the display order for consistent UI layout
241    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        // Should have keyboard schemes + gamepads + mouse
267        assert!(available.len() >= 6); // 4 keyboard schemes + 2 gamepads + mouse
268
269        // Check that we have keyboard schemes
270        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        // Check that we have gamepads
281        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        // Check that we have mouse
289        assert!(available.iter().any(|d| matches!(d, InputDevice::Mouse)));
290
291        // Check that we don't have touch (disabled)
292        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}