1#![forbid(unsafe_code)]
6#![warn(missing_docs)]
7
8use glam::Vec2;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12#[derive(Error, Debug, Clone, PartialEq, Eq)]
14pub enum InputError {
15 #[error("Gamepad {0} not connected")]
17 GamepadNotConnected(u32),
18}
19
20pub type Result<T> = core::result::Result<T, InputError>;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum InputDevice {
26 Touch,
28 Mouse,
30 Keyboard,
32 Gamepad(u32),
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
38pub enum ButtonState {
39 #[default]
41 Released,
42 JustPressed,
44 Pressed,
46 JustReleased,
48}
49
50impl ButtonState {
51 #[must_use]
53 pub const fn is_down(self) -> bool {
54 matches!(self, Self::JustPressed | Self::Pressed)
55 }
56
57 #[must_use]
59 pub const fn just_pressed(self) -> bool {
60 matches!(self, Self::JustPressed)
61 }
62
63 #[must_use]
65 pub const fn just_released(self) -> bool {
66 matches!(self, Self::JustReleased)
67 }
68
69 #[must_use]
71 pub const fn advance(self) -> Self {
72 match self {
73 Self::JustPressed => Self::Pressed,
74 Self::JustReleased => Self::Released,
75 other => other,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub enum MouseButton {
83 Left,
85 Right,
87 Middle,
89 Extra(u8),
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct TouchEvent {
96 pub id: u32,
98 pub position: Vec2,
100 pub delta: Vec2,
102 pub phase: TouchPhase,
104 pub pressure: f32,
106}
107
108impl TouchEvent {
109 #[must_use]
111 pub const fn new(position: Vec2) -> Self {
112 Self {
113 id: 0,
114 position,
115 delta: Vec2::ZERO,
116 phase: TouchPhase::Started,
117 pressure: 1.0,
118 }
119 }
120
121 #[must_use]
123 pub const fn with_id(mut self, id: u32) -> Self {
124 self.id = id;
125 self
126 }
127
128 #[must_use]
130 pub const fn with_phase(mut self, phase: TouchPhase) -> Self {
131 self.phase = phase;
132 self
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
138pub enum TouchPhase {
139 Started,
141 Moved,
143 Ended,
145 Cancelled,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
151pub enum KeyCode {
152 Up,
154 Down,
156 Left,
158 Right,
160 Space,
162 Enter,
164 Escape,
166 Letter(char),
168 Number(u8),
170 Function(u8),
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
176pub enum GamepadButton {
177 South,
179 East,
181 West,
183 North,
185 LeftBumper,
187 RightBumper,
189 LeftStick,
191 RightStick,
193 Start,
195 Select,
197 DPadUp,
199 DPadDown,
201 DPadLeft,
203 DPadRight,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
209pub enum GamepadAxis {
210 LeftStickX,
212 LeftStickY,
214 RightStickX,
216 RightStickY,
218 LeftTrigger,
220 RightTrigger,
222}
223
224#[derive(Debug, Clone, Default)]
226pub struct GamepadState {
227 pub connected: bool,
229 pub buttons: [ButtonState; 14],
231 pub axes: [f32; 6],
233}
234
235impl GamepadState {
236 #[must_use]
238 pub const fn button(&self, button: GamepadButton) -> ButtonState {
239 let idx = button as usize;
240 if idx < self.buttons.len() {
241 self.buttons[idx]
242 } else {
243 ButtonState::Released
244 }
245 }
246
247 #[must_use]
249 pub const fn axis(&self, axis: GamepadAxis) -> f32 {
250 let idx = axis as usize;
251 if idx < self.axes.len() {
252 self.axes[idx]
253 } else {
254 0.0
255 }
256 }
257
258 #[must_use]
260 pub const fn left_stick(&self) -> Vec2 {
261 Vec2::new(
262 self.axis(GamepadAxis::LeftStickX),
263 self.axis(GamepadAxis::LeftStickY),
264 )
265 }
266
267 #[must_use]
269 pub const fn right_stick(&self) -> Vec2 {
270 Vec2::new(
271 self.axis(GamepadAxis::RightStickX),
272 self.axis(GamepadAxis::RightStickY),
273 )
274 }
275}
276
277#[derive(Debug, Default)]
279pub struct InputState {
280 pub mouse_position: Vec2,
282 pub mouse_delta: Vec2,
284 pub mouse_buttons: [ButtonState; 5],
286 pub touches: Vec<TouchEvent>,
288 keys: std::collections::HashMap<KeyCode, ButtonState>,
290 pub gamepads: [GamepadState; 4],
292}
293
294impl InputState {
295 #[must_use]
297 pub fn new() -> Self {
298 Self::default()
299 }
300
301 #[must_use]
303 pub const fn mouse_button(&self, button: MouseButton) -> ButtonState {
304 let idx = match button {
305 MouseButton::Left => 0,
306 MouseButton::Right => 1,
307 MouseButton::Middle => 2,
308 MouseButton::Extra(n) => 3 + n as usize,
309 };
310 if idx < self.mouse_buttons.len() {
311 self.mouse_buttons[idx]
312 } else {
313 ButtonState::Released
314 }
315 }
316
317 #[must_use]
319 pub fn key(&self, key: KeyCode) -> ButtonState {
320 self.keys
321 .get(&key)
322 .copied()
323 .unwrap_or(ButtonState::Released)
324 }
325
326 pub fn set_key(&mut self, key: KeyCode, state: ButtonState) {
328 let _ = self.keys.insert(key, state);
329 }
330
331 #[must_use]
333 pub fn primary_pointer(&self) -> Option<Vec2> {
334 self.touches
335 .first()
336 .map(|touch| touch.position)
337 .or_else(|| {
338 if self.mouse_button(MouseButton::Left).is_down() {
339 Some(self.mouse_position)
340 } else {
341 None
342 }
343 })
344 }
345
346 #[must_use]
348 pub fn has_input(&self) -> bool {
349 !self.touches.is_empty()
350 || self.mouse_buttons.iter().any(|b| b.is_down())
351 || self.keys.values().any(|b| b.is_down())
352 || self.gamepads.iter().any(|g| g.connected)
353 }
354
355 pub fn advance_frame(&mut self) {
357 for button in &mut self.mouse_buttons {
358 *button = button.advance();
359 }
360 for state in self.keys.values_mut() {
361 *state = state.advance();
362 }
363 for gamepad in &mut self.gamepads {
364 for button in &mut gamepad.buttons {
365 *button = button.advance();
366 }
367 }
368 self.mouse_delta = Vec2::ZERO;
369 }
370
371 pub fn clear_touches(&mut self) {
373 self.touches.clear();
374 }
375
376 #[must_use]
378 pub fn is_key_pressed(&self, key: KeyCode) -> bool {
379 self.key(key).is_down()
380 }
381
382 pub fn set_key_pressed(&mut self, key: KeyCode, pressed: bool) {
384 let state = if pressed {
385 ButtonState::Pressed
386 } else {
387 ButtonState::Released
388 };
389 self.set_key(key, state);
390 }
391
392 pub fn clear_events(&mut self) {
394 self.touches
396 .retain(|t| !matches!(t.phase, TouchPhase::Ended | TouchPhase::Cancelled));
397
398 self.advance_frame();
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405pub struct InputAction {
406 pub name: String,
408 pub keys: Vec<KeyCode>,
410 pub mouse_buttons: Vec<MouseButton>,
412 pub gamepad_buttons: Vec<GamepadButton>,
414}
415
416impl InputAction {
417 #[must_use]
419 pub fn new(name: impl Into<String>) -> Self {
420 Self {
421 name: name.into(),
422 keys: Vec::new(),
423 mouse_buttons: Vec::new(),
424 gamepad_buttons: Vec::new(),
425 }
426 }
427
428 #[must_use]
430 pub fn with_key(mut self, key: KeyCode) -> Self {
431 self.keys.push(key);
432 self
433 }
434
435 #[must_use]
437 pub fn with_mouse_button(mut self, button: MouseButton) -> Self {
438 self.mouse_buttons.push(button);
439 self
440 }
441
442 #[must_use]
444 pub fn with_gamepad_button(mut self, button: GamepadButton) -> Self {
445 self.gamepad_buttons.push(button);
446 self
447 }
448
449 #[must_use]
451 pub fn is_active(&self, input: &InputState) -> bool {
452 self.keys.iter().any(|k| input.key(*k).is_down())
453 || self
454 .mouse_buttons
455 .iter()
456 .any(|b| input.mouse_button(*b).is_down())
457 || self.gamepad_buttons.iter().any(|b| {
458 input
459 .gamepads
460 .iter()
461 .any(|g| g.connected && g.button(*b).is_down())
462 })
463 }
464}
465
466#[cfg(test)]
467#[allow(clippy::unwrap_used, clippy::expect_used)]
468mod tests {
469 use super::*;
470
471 #[test]
474 fn test_button_state_advance() {
475 assert_eq!(ButtonState::JustPressed.advance(), ButtonState::Pressed);
476 assert_eq!(ButtonState::JustReleased.advance(), ButtonState::Released);
477 assert_eq!(ButtonState::Pressed.advance(), ButtonState::Pressed);
478 assert_eq!(ButtonState::Released.advance(), ButtonState::Released);
479 }
480
481 #[test]
482 fn test_button_state_queries() {
483 assert!(ButtonState::JustPressed.is_down());
484 assert!(ButtonState::Pressed.is_down());
485 assert!(!ButtonState::Released.is_down());
486 assert!(!ButtonState::JustReleased.is_down());
487 assert!(ButtonState::JustPressed.just_pressed());
488 assert!(!ButtonState::Pressed.just_pressed());
489 assert!(ButtonState::JustReleased.just_released());
490 assert!(!ButtonState::Released.just_released());
491 }
492
493 #[test]
494 fn test_button_state_default() {
495 let state = ButtonState::default();
496 assert_eq!(state, ButtonState::Released);
497 }
498
499 #[test]
502 fn test_touch_event() {
503 let touch = TouchEvent::new(Vec2::new(100.0, 200.0))
504 .with_id(1)
505 .with_phase(TouchPhase::Moved);
506
507 assert_eq!(touch.id, 1);
508 assert_eq!(touch.phase, TouchPhase::Moved);
509 assert!((touch.position.x - 100.0).abs() < f32::EPSILON);
510 assert!((touch.position.y - 200.0).abs() < f32::EPSILON);
511 assert!((touch.pressure - 1.0).abs() < f32::EPSILON);
512 assert_eq!(touch.delta, Vec2::ZERO);
513 }
514
515 #[test]
516 fn test_touch_event_defaults() {
517 let touch = TouchEvent::new(Vec2::new(50.0, 50.0));
518 assert_eq!(touch.id, 0);
519 assert_eq!(touch.phase, TouchPhase::Started);
520 }
521
522 #[test]
525 fn test_input_state_new() {
526 let state = InputState::new();
527 assert_eq!(state.mouse_position, Vec2::ZERO);
528 assert_eq!(state.mouse_delta, Vec2::ZERO);
529 assert!(state.touches.is_empty());
530 }
531
532 #[test]
533 fn test_input_state_mouse() {
534 let mut state = InputState::new();
535 state.mouse_position = Vec2::new(100.0, 200.0);
536 state.mouse_buttons[0] = ButtonState::JustPressed;
537
538 assert!(state.mouse_button(MouseButton::Left).is_down());
539 assert!(!state.mouse_button(MouseButton::Right).is_down());
540 assert!(!state.mouse_button(MouseButton::Middle).is_down());
541 }
542
543 #[test]
544 fn test_input_state_mouse_extra_buttons() {
545 let mut state = InputState::new();
546 state.mouse_buttons[3] = ButtonState::Pressed;
547 state.mouse_buttons[4] = ButtonState::Pressed;
548
549 assert!(state.mouse_button(MouseButton::Extra(0)).is_down());
550 assert!(state.mouse_button(MouseButton::Extra(1)).is_down());
551 assert!(!state.mouse_button(MouseButton::Extra(10)).is_down());
553 }
554
555 #[test]
556 fn test_input_state_keyboard() {
557 let mut state = InputState::new();
558 state.set_key(KeyCode::Space, ButtonState::JustPressed);
559
560 assert!(state.key(KeyCode::Space).just_pressed());
561 assert!(!state.key(KeyCode::Enter).is_down());
562 }
563
564 #[test]
565 fn test_input_state_advance() {
566 let mut state = InputState::new();
567 state.mouse_buttons[0] = ButtonState::JustPressed;
568 state.set_key(KeyCode::Space, ButtonState::JustPressed);
569 state.mouse_delta = Vec2::new(10.0, 20.0);
570
571 state.advance_frame();
572
573 assert_eq!(state.mouse_buttons[0], ButtonState::Pressed);
574 assert_eq!(state.key(KeyCode::Space), ButtonState::Pressed);
575 assert_eq!(state.mouse_delta, Vec2::ZERO);
576 }
577
578 #[test]
579 fn test_input_state_advance_gamepad() {
580 let mut state = InputState::new();
581 state.gamepads[0].connected = true;
582 state.gamepads[0].buttons[0] = ButtonState::JustPressed;
583
584 state.advance_frame();
585
586 assert_eq!(state.gamepads[0].buttons[0], ButtonState::Pressed);
587 }
588
589 #[test]
590 fn test_input_state_clear_touches() {
591 let mut state = InputState::new();
592 state.touches.push(TouchEvent::new(Vec2::new(50.0, 50.0)));
593 state.touches.push(TouchEvent::new(Vec2::new(100.0, 100.0)));
594
595 state.clear_touches();
596
597 assert!(state.touches.is_empty());
598 }
599
600 #[test]
601 fn test_input_state_has_input() {
602 let mut state = InputState::new();
603 assert!(!state.has_input());
604
605 state.mouse_buttons[0] = ButtonState::Pressed;
606 assert!(state.has_input());
607
608 state.mouse_buttons[0] = ButtonState::Released;
609 state.touches.push(TouchEvent::new(Vec2::ZERO));
610 assert!(state.has_input());
611 }
612
613 #[test]
614 fn test_input_state_has_input_gamepad() {
615 let mut state = InputState::new();
616 assert!(!state.has_input());
617
618 state.gamepads[0].connected = true;
619 assert!(state.has_input());
620 }
621
622 #[test]
623 fn test_input_state_has_input_keys() {
624 let mut state = InputState::new();
625 assert!(!state.has_input());
626
627 state.set_key(KeyCode::Space, ButtonState::Pressed);
628 assert!(state.has_input());
629 }
630
631 #[test]
634 fn test_primary_pointer_touch() {
635 let mut state = InputState::new();
636 state.touches.push(TouchEvent::new(Vec2::new(50.0, 50.0)));
637
638 let pointer = state.primary_pointer();
639 assert_eq!(pointer, Some(Vec2::new(50.0, 50.0)));
640 }
641
642 #[test]
643 fn test_primary_pointer_mouse() {
644 let mut state = InputState::new();
645 state.mouse_position = Vec2::new(100.0, 100.0);
646 state.mouse_buttons[0] = ButtonState::Pressed;
647
648 let pointer = state.primary_pointer();
649 assert_eq!(pointer, Some(Vec2::new(100.0, 100.0)));
650 }
651
652 #[test]
653 fn test_primary_pointer_none() {
654 let state = InputState::new();
655 let pointer = state.primary_pointer();
656 assert_eq!(pointer, None);
657 }
658
659 #[test]
660 fn test_primary_pointer_touch_priority() {
661 let mut state = InputState::new();
662 state.mouse_position = Vec2::new(100.0, 100.0);
663 state.mouse_buttons[0] = ButtonState::Pressed;
664 state.touches.push(TouchEvent::new(Vec2::new(50.0, 50.0)));
665
666 let pointer = state.primary_pointer();
668 assert_eq!(pointer, Some(Vec2::new(50.0, 50.0)));
669 }
670
671 #[test]
674 fn test_gamepad_state() {
675 let mut gamepad = GamepadState::default();
676 gamepad.axes[0] = 0.5; gamepad.axes[1] = -0.3; let stick = gamepad.left_stick();
680 assert!((stick.x - 0.5).abs() < f32::EPSILON);
681 assert!((stick.y - (-0.3)).abs() < f32::EPSILON);
682 }
683
684 #[test]
685 fn test_gamepad_right_stick() {
686 let mut gamepad = GamepadState::default();
687 gamepad.axes[2] = 0.7; gamepad.axes[3] = 0.8; let stick = gamepad.right_stick();
691 assert!((stick.x - 0.7).abs() < f32::EPSILON);
692 assert!((stick.y - 0.8).abs() < f32::EPSILON);
693 }
694
695 #[test]
696 fn test_gamepad_button() {
697 let mut gamepad = GamepadState::default();
698 gamepad.buttons[0] = ButtonState::Pressed;
699
700 assert!(gamepad.button(GamepadButton::South).is_down());
701 assert!(!gamepad.button(GamepadButton::North).is_down());
702 }
703
704 #[test]
705 fn test_gamepad_axis() {
706 let mut gamepad = GamepadState::default();
707 gamepad.axes[4] = 0.5; gamepad.axes[5] = 0.8; assert!((gamepad.axis(GamepadAxis::LeftTrigger) - 0.5).abs() < f32::EPSILON);
711 assert!((gamepad.axis(GamepadAxis::RightTrigger) - 0.8).abs() < f32::EPSILON);
712 }
713
714 #[test]
717 fn test_input_action() {
718 let action = InputAction::new("jump")
719 .with_key(KeyCode::Space)
720 .with_gamepad_button(GamepadButton::South);
721
722 let mut state = InputState::new();
723 assert!(!action.is_active(&state));
724
725 state.set_key(KeyCode::Space, ButtonState::Pressed);
726 assert!(action.is_active(&state));
727 }
728
729 #[test]
730 fn test_input_action_with_mouse() {
731 let action = InputAction::new("fire").with_mouse_button(MouseButton::Left);
732
733 let mut state = InputState::new();
734 assert!(!action.is_active(&state));
735
736 state.mouse_buttons[0] = ButtonState::Pressed;
737 assert!(action.is_active(&state));
738 }
739
740 #[test]
741 fn test_input_action_with_gamepad() {
742 let action = InputAction::new("jump").with_gamepad_button(GamepadButton::South);
743
744 let mut state = InputState::new();
745 assert!(!action.is_active(&state));
746
747 state.gamepads[0].connected = true;
748 state.gamepads[0].buttons[0] = ButtonState::Pressed;
749 assert!(action.is_active(&state));
750 }
751
752 #[test]
753 fn test_input_action_name() {
754 let action = InputAction::new("test_action");
755 assert_eq!(action.name, "test_action");
756 }
757
758 #[test]
761 fn test_input_device_variants() {
762 assert_eq!(InputDevice::Touch, InputDevice::Touch);
764 assert_eq!(InputDevice::Mouse, InputDevice::Mouse);
765 assert_eq!(InputDevice::Keyboard, InputDevice::Keyboard);
766 assert_eq!(InputDevice::Gamepad(0), InputDevice::Gamepad(0));
767 assert_ne!(InputDevice::Gamepad(0), InputDevice::Gamepad(1));
768 }
769
770 #[test]
773 fn test_input_error_display() {
774 let err = InputError::GamepadNotConnected(2);
775 let msg = format!("{err}");
776 assert!(msg.contains("Gamepad"));
777 assert!(msg.contains('2'));
778 assert!(msg.contains("not connected"));
779 }
780}