1use std::sync::atomic::{AtomicU32, Ordering};
102
103use super::{ActionMap, Binding, Device, Pads, TracedInput};
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
112pub struct GamepadId(pub u32);
113
114impl GamepadId {
115 #[must_use]
121 pub fn allocate() -> Self {
122 static NEXT: AtomicU32 = AtomicU32::new(1);
123 let id = NEXT.fetch_add(1, Ordering::Relaxed);
124 assert_ne!(id, 0, "gamepad ids wrapped");
125 Self(id)
126 }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
131pub enum PadButton {
132 South,
134 East,
136 West,
138 North,
140 LeftShoulder,
142 RightShoulder,
144 LeftStick,
146 RightStick,
148 Start,
150 Select,
152 DpadUp,
154 DpadDown,
156 DpadLeft,
158 DpadRight,
160 Guide,
163}
164
165impl PadButton {
166 pub const ALL: [Self; 15] = [
168 Self::South,
169 Self::East,
170 Self::West,
171 Self::North,
172 Self::LeftShoulder,
173 Self::RightShoulder,
174 Self::LeftStick,
175 Self::RightStick,
176 Self::Start,
177 Self::Select,
178 Self::DpadUp,
179 Self::DpadDown,
180 Self::DpadLeft,
181 Self::DpadRight,
182 Self::Guide,
183 ];
184
185 pub(crate) const DPAD: [Self; 4] = [
187 Self::DpadUp,
188 Self::DpadDown,
189 Self::DpadLeft,
190 Self::DpadRight,
191 ];
192
193 const fn bit(self) -> u16 {
194 1 << self as u16
195 }
196}
197
198#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
200pub struct PadButtons(u16);
201
202impl PadButtons {
203 pub const EMPTY: Self = Self(0);
205
206 #[must_use]
208 pub const fn contains(self, button: PadButton) -> bool {
209 self.0 & button.bit() != 0
210 }
211
212 pub const fn insert(&mut self, button: PadButton) {
214 self.0 |= button.bit();
215 }
216
217 pub const fn remove(&mut self, button: PadButton) {
219 self.0 &= !button.bit();
220 }
221
222 #[must_use]
224 pub const fn is_empty(self) -> bool {
225 self.0 == 0
226 }
227
228 pub(crate) const fn union(self, other: Self) -> Self {
229 Self(self.0 | other.0)
230 }
231
232 const fn intersection(self, other: Self) -> Self {
233 Self(self.0 & other.0)
234 }
235
236 const fn difference(self, other: Self) -> Self {
237 Self(self.0 & !other.0)
238 }
239}
240
241impl FromIterator<PadButton> for PadButtons {
242 fn from_iter<I: IntoIterator<Item = PadButton>>(buttons: I) -> Self {
243 let mut set = Self::EMPTY;
244 for button in buttons {
245 set.insert(button);
246 }
247 set
248 }
249}
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
253pub enum PadAxis {
254 LeftX,
256 LeftY,
258 RightX,
260 RightY,
262 LeftTrigger,
264 RightTrigger,
266}
267
268#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
270pub enum Stick {
271 Left,
273 Right,
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
279pub enum Trigger {
280 Left,
282 Right,
284}
285
286#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
289pub enum PadKind {
290 Xbox,
292 PlayStation,
294 Switch,
296 SteamDeck,
298 #[default]
300 Generic,
301}
302
303#[derive(Clone, Copy, Debug, Default, PartialEq)]
307pub struct GamepadSnapshot {
308 pub buttons: PadButtons,
310 pub axes: [f32; 6],
313 pub kind: PadKind,
315}
316
317impl GamepadSnapshot {
318 #[must_use]
320 pub const fn neutral(kind: PadKind) -> Self {
321 Self {
322 buttons: PadButtons::EMPTY,
323 axes: [0.0; 6],
324 kind,
325 }
326 }
327
328 #[must_use]
330 pub const fn axis(&self, axis: PadAxis) -> f32 {
331 self.axes[axis as usize]
332 }
333
334 #[must_use]
336 pub const fn stick(&self, stick: Stick) -> (f32, f32) {
337 match stick {
338 Stick::Left => (self.axis(PadAxis::LeftX), self.axis(PadAxis::LeftY)),
339 Stick::Right => (self.axis(PadAxis::RightX), self.axis(PadAxis::RightY)),
340 }
341 }
342
343 #[must_use]
345 pub const fn trigger(&self, trigger: Trigger) -> f32 {
346 match trigger {
347 Trigger::Left => self.axis(PadAxis::LeftTrigger),
348 Trigger::Right => self.axis(PadAxis::RightTrigger),
349 }
350 }
351
352 #[must_use]
355 pub fn is_finite(&self) -> bool {
356 self.axes.iter().all(|value| value.is_finite())
357 }
358}
359
360#[derive(Clone, Copy, Debug, PartialEq)]
362pub enum GamepadEvent {
363 Connected {
365 id: GamepadId,
367 kind: PadKind,
369 },
370 Disconnected {
372 id: GamepadId,
374 },
375 State {
377 id: GamepadId,
379 snapshot: GamepadSnapshot,
381 },
382}
383
384pub const PAD_ACTIVITY_THRESHOLD: f32 = 0.5;
392
393#[must_use]
403pub fn radial_deadzone((x, y): (f32, f32), deadzone: f32) -> (f32, f32) {
404 let magnitude = x.hypot(y);
405 if !magnitude.is_finite() || magnitude <= deadzone {
406 return (0.0, 0.0);
407 }
408 let scaled = (magnitude.min(1.0) - deadzone) / (1.0 - deadzone);
409 (x / magnitude * scaled, y / magnitude * scaled)
410}
411
412pub(crate) fn trigger_past(value: f32, threshold: f32) -> f32 {
415 let value = value.clamp(0.0, 1.0);
416 if value <= threshold {
417 0.0
418 } else {
419 (value - threshold) / (1.0 - threshold)
420 }
421}
422
423pub(crate) fn valid_deadzone(value: f32) -> bool {
426 (0.0..1.0).contains(&value)
427}
428
429const DEFLECTED_INPUTS: [TracedInput; 4] = [
431 TracedInput::PadStick(Stick::Left),
432 TracedInput::PadStick(Stick::Right),
433 TracedInput::PadTrigger(Trigger::Left),
434 TracedInput::PadTrigger(Trigger::Right),
435];
436
437fn deflected(snapshot: &GamepadSnapshot) -> [bool; 4] {
440 let stick = |stick| {
441 let (x, y) = snapshot.stick(stick);
442 x.hypot(y) > PAD_ACTIVITY_THRESHOLD
443 };
444 [
445 stick(Stick::Left),
446 stick(Stick::Right),
447 snapshot.trigger(Trigger::Left) > PAD_ACTIVITY_THRESHOLD,
448 snapshot.trigger(Trigger::Right) > PAD_ACTIVITY_THRESHOLD,
449 ]
450}
451
452impl ActionMap {
453 pub fn gamepad_event(&mut self, event: &GamepadEvent) {
471 let mut pressed = PadButtons::EMPTY;
473 let mut went_over = [false; 4];
474 match *event {
475 GamepadEvent::Connected { id, kind } => {
476 self.pads.insert(id, GamepadSnapshot::neutral(kind));
477 }
478 GamepadEvent::Disconnected { id } => {
479 self.pads.remove(&id);
480 }
481 GamepadEvent::State { id, snapshot } => {
482 if !snapshot.is_finite() {
483 return;
484 }
485 let before = self
486 .pads
487 .insert(id, snapshot)
488 .unwrap_or(GamepadSnapshot::neutral(snapshot.kind));
489 pressed = snapshot.buttons.difference(before.buttons);
490 for ((over, was), is) in went_over
491 .iter_mut()
492 .zip(deflected(&before))
493 .zip(deflected(&snapshot))
494 {
495 *over = is && !was;
496 }
497 if !pressed.is_empty() || went_over.contains(&true) {
498 self.spoke(Device::Gamepad);
499 self.last_pad_kind = Some(snapshot.kind);
500 }
501 }
502 }
503 self.repad();
504 for button in PadButton::ALL {
505 if pressed.contains(button) {
506 self.traced(|| TracedInput::PadButton(button));
507 }
508 }
509 for (over, input) in went_over.into_iter().zip(DEFLECTED_INPUTS) {
510 if over {
511 self.traced(|| input);
512 }
513 }
514 }
515
516 pub fn release_gamepads(&mut self) {
527 self.suppressed.pad_buttons = self.suppressed.pad_buttons.union(self.held_pad_buttons);
528 for snapshot in self.pads.values_mut() {
529 *snapshot = GamepadSnapshot::neutral(snapshot.kind);
530 }
531 self.held_pad_buttons = PadButtons::EMPTY;
534 self.resolve_matching(Binding::reads_gamepad);
535 }
536
537 pub fn gamepads(&self) -> impl Iterator<Item = (GamepadId, &GamepadSnapshot)> {
539 self.pads.iter().map(|(&id, snapshot)| (id, snapshot))
540 }
541
542 fn repad(&mut self) {
547 let before = self.held_pad_buttons;
548 self.held_pad_buttons = self
549 .pads
550 .values()
551 .fold(PadButtons::EMPTY, |held, pad| held.union(pad.buttons));
552 let held = self.held_pad_buttons;
556 for button in PadButton::ALL {
557 if before.contains(button)
558 && held.contains(button)
559 && self.routes.pad_reader(button, before) != self.routes.pad_reader(button, held)
560 {
561 self.suppressed.pad_buttons.insert(button);
562 }
563 }
564 self.suppressed.pad_buttons = self
565 .suppressed
566 .pad_buttons
567 .intersection(self.held_pad_buttons);
568 for stick in [Stick::Left, Stick::Right] {
569 if self.stick_rests(stick) {
570 self.suppressed.pad_sticks.remove(&stick);
571 }
572 }
573 for trigger in [Trigger::Left, Trigger::Right] {
574 if self.trigger_rests(trigger) {
575 self.suppressed.pad_triggers.remove(&trigger);
576 }
577 }
578 self.resolve_matching(Binding::reads_gamepad);
579 }
580
581 pub(crate) fn stick_rests(&self, stick: Stick) -> bool {
585 let deadzone = self
586 .bindings_all()
587 .filter_map(|binding| match *binding {
588 Binding::PadStick {
589 stick: bound,
590 deadzone,
591 } if bound == stick => Some(deadzone),
592 _ => None,
593 })
594 .reduce(f32::min)
595 .unwrap_or(0.0);
596 self.pads.values().all(|pad| {
597 let (x, y) = pad.stick(stick);
598 x.hypot(y) <= deadzone
599 })
600 }
601
602 pub(crate) fn trigger_rests(&self, trigger: Trigger) -> bool {
606 let threshold = self
607 .bindings_all()
608 .filter_map(|binding| match *binding {
609 Binding::PadTrigger {
610 trigger: bound,
611 threshold,
612 } if bound == trigger => Some(threshold),
613 _ => None,
614 })
615 .reduce(f32::min)
616 .unwrap_or(0.0);
617 self.pads
618 .values()
619 .all(|pad| pad.trigger(trigger) <= threshold)
620 }
621
622 fn bindings_all(&self) -> impl Iterator<Item = &Binding> {
624 self.slots.iter().flat_map(|slot| slot.decl.bindings.iter())
625 }
626}
627
628pub(crate) fn pad_trigger(pads: &Pads, trigger: Trigger) -> f32 {
630 pads.values()
631 .map(|pad| pad.trigger(trigger))
632 .fold(0.0, f32::max)
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use crate::{ActionDecl, ActionKind, ActionMapError};
639 use crcbl_core::input::KeyCode;
640
641 const TICK: f32 = 1.0 / 60.0;
642 const PAD: GamepadId = GamepadId(7);
643
644 fn action(name: &str, kind: ActionKind, bindings: Vec<Binding>) -> ActionDecl {
645 ActionDecl {
646 name: name.to_owned(),
647 kind,
648 bindings,
649 }
650 }
651
652 fn state(id: GamepadId, snapshot: GamepadSnapshot) -> GamepadEvent {
653 GamepadEvent::State { id, snapshot }
654 }
655
656 fn holding(buttons: &[PadButton]) -> GamepadSnapshot {
657 GamepadSnapshot {
658 buttons: buttons.iter().copied().collect(),
659 ..GamepadSnapshot::neutral(PadKind::Xbox)
660 }
661 }
662
663 fn with_axis(mut snapshot: GamepadSnapshot, axis: PadAxis, value: f32) -> GamepadSnapshot {
664 snapshot.axes[axis as usize] = value;
665 snapshot
666 }
667
668 fn jump_map() -> ActionMap {
670 let mut map = ActionMap::new();
671 map.declare(action(
672 "jump",
673 ActionKind::Button,
674 vec![Binding::PadButton(PadButton::South)],
675 ));
676 map.gamepad_event(&GamepadEvent::Connected {
677 id: PAD,
678 kind: PadKind::Xbox,
679 });
680 map
681 }
682
683 fn close(actual: (f32, f32), expected: (f32, f32)) -> bool {
684 (actual.0 - expected.0).abs() < 1e-6 && (actual.1 - expected.1).abs() < 1e-6
685 }
686
687 #[test]
691 fn a_pad_button_presses_holds_and_releases_through_its_binding() {
692 let mut map = jump_map();
693 map.begin_tick(TICK);
694 map.gamepad_event(&state(PAD, holding(&[PadButton::East])));
695 assert!(!map.button_held("jump"), "East is not South");
696
697 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
698 assert!(map.just_pressed("jump"));
699 assert!(map.button_held("jump"));
700
701 map.begin_tick(TICK);
702 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
703 assert!(!map.just_pressed("jump"), "a re-sent level is not a press");
704 assert!(map.button_held("jump"));
705
706 map.begin_tick(TICK);
707 map.gamepad_event(&state(PAD, holding(&[])));
708 assert!(map.just_released("jump"));
709 assert!(!map.button_held("jump"));
710 }
711
712 #[test]
715 fn a_button_held_on_two_pads_is_released_by_the_last_to_let_go() {
716 let mut map = jump_map();
717 let other = GamepadId(8);
718 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
719 map.gamepad_event(&state(other, holding(&[PadButton::South])));
720 map.gamepad_event(&state(PAD, holding(&[])));
721 assert!(map.button_held("jump"), "the other pad still holds it");
722 map.gamepad_event(&state(other, holding(&[])));
723 assert!(!map.button_held("jump"));
724
725 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
727 map.gamepad_event(&state(other, holding(&[PadButton::South])));
728 map.gamepad_event(&state(other, holding(&[])));
729 assert!(map.button_held("jump"), "the first pad still holds it");
730 map.gamepad_event(&state(PAD, holding(&[])));
731 assert!(!map.button_held("jump"));
732 }
733
734 #[test]
737 fn the_radial_deadzone_zeroes_inside_rescales_outside_and_clamps() {
738 assert_eq!(radial_deadzone((0.1, 0.0), 0.2), (0.0, 0.0));
739 assert_eq!(radial_deadzone((0.0, -0.2), 0.2), (0.0, 0.0), "on the edge");
740 assert!(close(radial_deadzone((0.6, 0.0), 0.2), (0.5, 0.0)));
742 assert!(close(radial_deadzone((0.0, -1.0), 0.2), (0.0, -1.0)));
743 assert!(close(radial_deadzone((0.6, 0.8), 0.2), (0.6, 0.8)));
745 assert!(close(radial_deadzone((0.3, 0.4), 0.2), (0.225, 0.3)));
747 assert!(close(radial_deadzone((3.0, 4.0), 0.2), (0.6, 0.8)));
749 assert!(
750 close(radial_deadzone((0.3, 0.4), 0.0), (0.3, 0.4)),
751 "no zone"
752 );
753 }
754
755 #[test]
757 fn the_trigger_threshold_zeroes_below_and_rescales_above() {
758 assert_eq!(trigger_past(0.0, 0.25), 0.0);
759 assert_eq!(trigger_past(0.25, 0.25), 0.0, "at the threshold");
760 assert!((trigger_past(0.5, 0.25) - 1.0 / 3.0).abs() < 1e-6);
762 assert_eq!(trigger_past(1.0, 0.25), 1.0);
763 assert_eq!(trigger_past(2.0, 0.25), 1.0, "clamped to full pull");
764 }
765
766 #[test]
769 fn stick_and_trigger_bindings_resolve_through_their_deadzones() {
770 let mut map = ActionMap::new();
771 map.declare(action(
772 "move",
773 ActionKind::Axis2,
774 vec![Binding::PadStick {
775 stick: Stick::Left,
776 deadzone: 0.2,
777 }],
778 ));
779 map.declare(action(
780 "throttle",
781 ActionKind::Axis1,
782 vec![Binding::PadTrigger {
783 trigger: Trigger::Right,
784 threshold: 0.25,
785 }],
786 ));
787 map.declare(action(
788 "fire",
789 ActionKind::Button,
790 vec![Binding::PadTrigger {
791 trigger: Trigger::Right,
792 threshold: 0.25,
793 }],
794 ));
795
796 let rest = GamepadSnapshot::neutral(PadKind::Xbox);
797 let drift = with_axis(rest, PadAxis::LeftX, 0.15);
798 map.gamepad_event(&state(PAD, drift));
799 assert_eq!(map.axis2("move"), (0.0, 0.0), "drift inside the zone");
800
801 let pushed = with_axis(rest, PadAxis::LeftX, 0.6);
802 let pulled = with_axis(pushed, PadAxis::RightTrigger, 0.5);
803 map.gamepad_event(&state(PAD, pulled));
804 assert!(
805 close(map.axis2("move"), (0.5, 0.0)),
806 "{:?}",
807 map.axis2("move")
808 );
809 assert!((map.axis1("throttle") - 1.0 / 3.0).abs() < 1e-6);
810 assert!(map.just_pressed("fire"), "past the threshold is down");
811
812 map.gamepad_event(&state(PAD, with_axis(rest, PadAxis::RightTrigger, 0.25)));
813 assert!(map.just_released("fire"), "at the threshold is up");
814 assert_eq!(map.axis1("throttle"), 0.0);
815 }
816
817 #[test]
820 fn a_stick_pushed_up_reads_positive_y_like_the_up_key() {
821 let mut map = ActionMap::new();
822 map.declare(action(
823 "move",
824 ActionKind::Axis2,
825 vec![
826 Binding::Wasd {
827 up: KeyCode::KeyW,
828 down: KeyCode::KeyS,
829 left: KeyCode::KeyA,
830 right: KeyCode::KeyD,
831 },
832 Binding::PadStick {
833 stick: Stick::Left,
834 deadzone: 0.0,
835 },
836 ],
837 ));
838 let rest = GamepadSnapshot::neutral(PadKind::Xbox);
839
840 map.key_event(KeyCode::KeyW, true);
841 let by_key = map.axis2("move");
842 map.key_event(KeyCode::KeyW, false);
843 map.gamepad_event(&state(PAD, with_axis(rest, PadAxis::LeftY, 1.0)));
844 assert_eq!(map.axis2("move"), by_key, "stick up is W");
845 assert_eq!(map.axis2("move"), (0.0, 1.0));
846
847 map.gamepad_event(&state(PAD, with_axis(rest, PadAxis::LeftY, -0.5)));
848 assert_eq!(map.axis2("move"), (0.0, -0.5));
849 }
850
851 #[test]
856 fn the_dpad_resolves_to_an_axis2_like_wasd() {
857 use PadButton::{DpadDown, DpadLeft, DpadRight, DpadUp};
858 let mut map = ActionMap::new();
859 map.declare(action("move", ActionKind::Axis2, vec![Binding::PadDpad]));
860 map.declare(action(
861 "walk",
862 ActionKind::Axis2,
863 vec![Binding::Wasd {
864 up: KeyCode::KeyW,
865 down: KeyCode::KeyS,
866 left: KeyCode::KeyA,
867 right: KeyCode::KeyD,
868 }],
869 ));
870 let key = |button| match button {
871 DpadUp => KeyCode::KeyW,
872 DpadDown => KeyCode::KeyS,
873 DpadLeft => KeyCode::KeyA,
874 _ => KeyCode::KeyD,
875 };
876 let half = std::f32::consts::FRAC_1_SQRT_2;
877 let cases: [(&[PadButton], (f32, f32)); 9] = [
878 (&[DpadUp], (0.0, 1.0)),
879 (&[DpadDown], (0.0, -1.0)),
880 (&[DpadLeft], (-1.0, 0.0)),
881 (&[DpadRight], (1.0, 0.0)),
882 (&[DpadUp, DpadRight], (half, half)),
883 (&[DpadUp, DpadLeft], (-half, half)),
884 (&[DpadDown, DpadRight], (half, -half)),
885 (&[DpadDown, DpadLeft], (-half, -half)),
886 (&[DpadUp, DpadDown], (0.0, 0.0)),
887 ];
888 for (buttons, expected) in cases {
889 map.gamepad_event(&state(PAD, holding(buttons)));
890 for &button in buttons {
891 map.key_event(key(button), true);
892 }
893 assert!(
894 close(map.axis2("move"), expected),
895 "{buttons:?}: {:?}",
896 map.axis2("move"),
897 );
898 assert_eq!(map.axis2("move"), map.axis2("walk"), "{buttons:?}");
899 for &button in buttons {
900 map.key_event(key(button), false);
901 }
902 }
903 map.gamepad_event(&state(PAD, holding(&[])));
904 assert_eq!(map.axis2("move"), (0.0, 0.0), "let go");
905 }
906
907 #[test]
910 fn the_dpad_on_a_button_is_down_while_any_direction_is() {
911 let mut map = ActionMap::new();
912 map.declare(action("any", ActionKind::Button, vec![Binding::PadDpad]));
913 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
914 assert!(!map.button_held("any"));
915 for button in PadButton::DPAD {
916 map.gamepad_event(&state(PAD, holding(&[button])));
917 assert!(map.button_held("any"), "{button:?}");
918 map.gamepad_event(&state(PAD, holding(&[])));
919 assert!(!map.button_held("any"));
920 }
921 }
922
923 #[test]
927 fn keys_the_dpad_and_the_stick_sum_into_one_unit_disc() {
928 let mut map = ActionMap::new();
929 map.declare(action(
930 "move",
931 ActionKind::Axis2,
932 vec![
933 Binding::Wasd {
934 up: KeyCode::KeyW,
935 down: KeyCode::KeyS,
936 left: KeyCode::KeyA,
937 right: KeyCode::KeyD,
938 },
939 Binding::PadDpad,
940 Binding::PadStick {
941 stick: Stick::Left,
942 deadzone: 0.0,
943 },
944 ],
945 ));
946 let dpad = |button| holding(&[button]);
947 let half = std::f32::consts::FRAC_1_SQRT_2;
948
949 map.gamepad_event(&state(
951 PAD,
952 with_axis(dpad(PadButton::DpadRight), PadAxis::LeftX, 0.5),
953 ));
954 assert!(
955 close(map.axis2("move"), (1.0, 0.0)),
956 "{:?}",
957 map.axis2("move")
958 );
959
960 map.gamepad_event(&state(
962 PAD,
963 with_axis(dpad(PadButton::DpadRight), PadAxis::LeftY, 1.0),
964 ));
965 assert!(
966 close(map.axis2("move"), (half, half)),
967 "{:?}",
968 map.axis2("move")
969 );
970
971 map.gamepad_event(&state(
973 PAD,
974 with_axis(dpad(PadButton::DpadLeft), PadAxis::LeftX, 0.5),
975 ));
976 assert!(
977 close(map.axis2("move"), (-0.5, 0.0)),
978 "{:?}",
979 map.axis2("move")
980 );
981
982 map.gamepad_event(&state(PAD, dpad(PadButton::DpadRight)));
984 map.key_event(KeyCode::KeyA, true);
985 assert_eq!(map.axis2("move"), (0.0, 0.0));
986 map.key_event(KeyCode::KeyA, false);
987 map.key_event(KeyCode::KeyW, true);
988 map.gamepad_event(&state(
989 PAD,
990 with_axis(dpad(PadButton::DpadUp), PadAxis::LeftY, 1.0),
991 ));
992 assert!(
993 close(map.axis2("move"), (0.0, 1.0)),
994 "{:?}",
995 map.axis2("move")
996 );
997 }
998
999 #[test]
1002 fn a_dpad_press_makes_the_pad_the_last_device() {
1003 let mut map = ActionMap::new();
1004 map.declare(action("move", ActionKind::Axis2, vec![Binding::PadDpad]));
1005 map.key_event(KeyCode::KeyQ, true);
1006 assert_eq!(map.last_device(), Some(Device::Keyboard));
1007 map.gamepad_event(&state(PAD, holding(&[PadButton::DpadLeft])));
1008 assert_eq!(map.last_device(), Some(Device::Gamepad));
1009
1010 map.key_event(KeyCode::KeyQ, false);
1011 map.key_event(KeyCode::KeyQ, true);
1012 map.gamepad_event(&state(PAD, holding(&[])));
1013 assert_eq!(
1014 map.last_device(),
1015 Some(Device::Keyboard),
1016 "letting go of the d-pad is not the pad speaking",
1017 );
1018 }
1019
1020 #[test]
1023 fn a_pushed_dpad_takes_its_four_buttons_from_the_context_beneath() {
1024 let mut map = jump_map();
1025 map.declare(action(
1026 "up",
1027 ActionKind::Button,
1028 vec![Binding::PadButton(PadButton::DpadUp)],
1029 ));
1030 map.declare_in(
1031 "menu",
1032 action("move", ActionKind::Axis2, vec![Binding::PadDpad]),
1033 );
1034 map.push_context("menu").expect("declared");
1035 map.gamepad_event(&state(PAD, holding(&[PadButton::DpadUp, PadButton::South])));
1036 assert_eq!(map.axis2("move"), (0.0, 1.0));
1037 assert!(!map.button_held("up"), "the menu owns DpadUp");
1038 assert!(map.button_held("jump"), "and not South");
1039 }
1040
1041 #[test]
1044 fn the_last_device_becomes_the_pad_on_activity_and_not_on_release() {
1045 let mut map = ActionMap::new();
1046 map.gamepad_event(&GamepadEvent::Connected {
1047 id: PAD,
1048 kind: PadKind::Xbox,
1049 });
1050 assert_eq!(map.last_device(), None, "plugging in is not speaking");
1051
1052 map.gamepad_event(&state(PAD, holding(&[PadButton::North])));
1053 assert_eq!(
1054 map.last_device(),
1055 Some(Device::Gamepad),
1056 "nothing binds North"
1057 );
1058
1059 map.key_event(KeyCode::KeyQ, true);
1060 map.gamepad_event(&state(PAD, holding(&[])));
1061 assert_eq!(
1062 map.last_device(),
1063 Some(Device::Keyboard),
1064 "letting go of a pad button is not the pad speaking",
1065 );
1066
1067 let rest = GamepadSnapshot::neutral(PadKind::Xbox);
1068 let drift = with_axis(rest, PadAxis::RightX, 0.3);
1069 map.gamepad_event(&state(PAD, drift));
1070 assert_eq!(
1071 map.last_device(),
1072 Some(Device::Keyboard),
1073 "drift is not a push"
1074 );
1075
1076 let over = with_axis(rest, PadAxis::RightX, 0.9);
1077 map.gamepad_event(&state(PAD, over));
1078 assert_eq!(map.last_device(), Some(Device::Gamepad), "a push is");
1079
1080 map.key_event(KeyCode::KeyQ, true);
1081 map.gamepad_event(&state(PAD, with_axis(rest, PadAxis::RightX, 0.95)));
1082 assert_eq!(
1083 map.last_device(),
1084 Some(Device::Keyboard),
1085 "a stick held over already spoke",
1086 );
1087 map.gamepad_event(&state(PAD, rest));
1088 assert_eq!(
1089 map.last_device(),
1090 Some(Device::Keyboard),
1091 "nor does centring"
1092 );
1093
1094 map.gamepad_event(&state(PAD, with_axis(rest, PadAxis::LeftTrigger, 0.8)));
1095 assert_eq!(
1096 map.last_device(),
1097 Some(Device::Gamepad),
1098 "a trigger pull is"
1099 );
1100 }
1101
1102 #[test]
1106 fn focus_loss_releases_a_held_pad_button_until_it_is_let_go() {
1107 let mut map = jump_map();
1108 map.begin_tick(TICK);
1109 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1110 assert!(map.button_held("jump"));
1111
1112 map.begin_tick(TICK);
1113 map.release_gamepads();
1114 assert!(map.just_released("jump"));
1115 assert!(!map.button_held("jump"));
1116 assert_eq!(
1117 map.gamepads().collect::<Vec<_>>(),
1118 [(PAD, &GamepadSnapshot::neutral(PadKind::Xbox))],
1119 "still connected, at rest",
1120 );
1121
1122 map.begin_tick(TICK);
1123 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1124 assert!(!map.button_held("jump"), "withheld until released");
1125
1126 map.gamepad_event(&state(PAD, holding(&[])));
1127 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1128 assert!(map.just_pressed("jump"), "a fresh press after the lift");
1129 }
1130
1131 #[test]
1134 fn focus_loss_centres_a_stick_until_the_next_snapshot() {
1135 let mut map = ActionMap::new();
1136 map.declare(action(
1137 "move",
1138 ActionKind::Axis2,
1139 vec![Binding::PadStick {
1140 stick: Stick::Left,
1141 deadzone: 0.0,
1142 }],
1143 ));
1144 let up = with_axis(GamepadSnapshot::neutral(PadKind::Xbox), PadAxis::LeftY, 1.0);
1145 map.gamepad_event(&state(PAD, up));
1146 map.release_gamepads();
1147 assert_eq!(map.axis2("move"), (0.0, 0.0));
1148 map.gamepad_event(&state(PAD, up));
1149 assert_eq!(map.axis2("move"), (0.0, 1.0));
1150 }
1151
1152 #[test]
1155 fn a_disconnected_pad_releases_what_it_held() {
1156 let mut map = jump_map();
1157 map.declare(action(
1158 "move",
1159 ActionKind::Axis2,
1160 vec![Binding::PadStick {
1161 stick: Stick::Left,
1162 deadzone: 0.1,
1163 }],
1164 ));
1165 map.begin_tick(TICK);
1166 let held = with_axis(holding(&[PadButton::South]), PadAxis::LeftX, 1.0);
1167 map.gamepad_event(&state(PAD, held));
1168 assert!(map.button_held("jump"));
1169 assert_eq!(map.axis2("move"), (1.0, 0.0));
1170
1171 map.begin_tick(TICK);
1172 map.gamepad_event(&GamepadEvent::Disconnected { id: PAD });
1173 assert!(map.just_released("jump"));
1174 assert_eq!(map.axis2("move"), (0.0, 0.0));
1175 assert_eq!(map.gamepads().count(), 0);
1176
1177 map.begin_tick(TICK);
1178 assert!(
1179 !map.button_held("jump"),
1180 "and not re-pressed by the next tick"
1181 );
1182 }
1183
1184 #[test]
1186 fn a_non_finite_snapshot_is_dropped() {
1187 let mut map = jump_map();
1188 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1189 let poisoned = with_axis(holding(&[]), PadAxis::LeftX, f32::NAN);
1190 map.gamepad_event(&state(PAD, poisoned));
1191 assert!(map.button_held("jump"));
1192 }
1193
1194 #[test]
1197 fn a_pad_button_is_routed_and_withheld_like_a_key() {
1198 let mut map = jump_map();
1199 map.declare_in(
1200 "menu",
1201 action(
1202 "accept",
1203 ActionKind::Button,
1204 vec![Binding::PadButton(PadButton::South)],
1205 ),
1206 );
1207 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1208 map.push_context("menu").expect("declared");
1209 assert!(map.just_released("jump"), "the menu took South");
1210 assert!(!map.button_held("accept"), "withheld until released");
1211
1212 map.gamepad_event(&state(PAD, holding(&[])));
1213 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1214 assert!(map.just_pressed("accept"));
1215 assert!(!map.button_held("jump"));
1216 }
1217
1218 #[test]
1221 fn an_unusable_deadzone_is_refused() {
1222 let mut map = ActionMap::new();
1223 for deadzone in [1.0, -0.1, f32::NAN] {
1224 assert_eq!(
1225 map.try_declare(action(
1226 "move",
1227 ActionKind::Axis2,
1228 vec![Binding::PadStick {
1229 stick: Stick::Left,
1230 deadzone,
1231 }],
1232 )),
1233 Err(ActionMapError::InvalidDeadzone("move".to_owned())),
1234 );
1235 }
1236 map.declare(action("fire", ActionKind::Button, Vec::new()));
1237 assert_eq!(
1238 map.rebind(
1239 "fire",
1240 vec![Binding::PadTrigger {
1241 trigger: Trigger::Left,
1242 threshold: f32::INFINITY,
1243 }],
1244 ),
1245 Err(ActionMapError::InvalidDeadzone("fire".to_owned())),
1246 );
1247 }
1248
1249 #[test]
1251 fn allocated_ids_are_distinct() {
1252 let a = GamepadId::allocate();
1253 let b = GamepadId::allocate();
1254 assert_ne!(a, b);
1255 }
1256
1257 fn chord_map() -> ActionMap {
1259 let mut map = jump_map();
1260 map.declare(action(
1261 "vault",
1262 ActionKind::Button,
1263 vec![Binding::PadChord {
1264 modifier: PadButton::LeftShoulder,
1265 button: PadButton::South,
1266 }],
1267 ));
1268 map.declare(action(
1269 "lean",
1270 ActionKind::Button,
1271 vec![Binding::PadButton(PadButton::LeftShoulder)],
1272 ));
1273 map
1274 }
1275
1276 #[test]
1280 fn a_pad_chord_takes_its_button_and_leaves_its_modifier_alone() {
1281 let mut map = chord_map();
1282 map.begin_tick(TICK);
1283 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1284 assert!(map.button_held("jump"));
1285 assert!(!map.button_held("vault"));
1286
1287 map.begin_tick(TICK);
1288 map.gamepad_event(&state(PAD, holding(&[])));
1289 map.gamepad_event(&state(
1290 PAD,
1291 holding(&[PadButton::LeftShoulder, PadButton::South]),
1292 ));
1293 assert!(map.button_held("vault"));
1294 assert!(!map.button_held("jump"), "the plain binding fired too");
1295 assert!(map.button_held("lean"), "the modifier was consumed");
1296 }
1297
1298 #[test]
1301 fn a_modifier_pressed_over_a_held_button_hands_the_button_to_the_chord() {
1302 let mut map = chord_map();
1303 map.begin_tick(TICK);
1304 map.gamepad_event(&state(PAD, holding(&[PadButton::South])));
1305 assert!(map.button_held("jump"));
1306
1307 map.begin_tick(TICK);
1308 map.gamepad_event(&state(
1309 PAD,
1310 holding(&[PadButton::South, PadButton::LeftShoulder]),
1311 ));
1312 assert!(map.just_released("jump"));
1313 assert!(map.just_pressed("vault"));
1314 }
1315
1316 #[test]
1318 fn a_pad_chord_drives_an_axis1() {
1319 let mut map = jump_map();
1320 map.declare(action(
1321 "throttle",
1322 ActionKind::Axis1,
1323 vec![Binding::PadChord {
1324 modifier: PadButton::RightShoulder,
1325 button: PadButton::North,
1326 }],
1327 ));
1328 map.gamepad_event(&state(PAD, holding(&[PadButton::North])));
1329 assert!(map.axis1("throttle").abs() < 1e-6, "no modifier, no chord");
1330 map.gamepad_event(&state(
1331 PAD,
1332 holding(&[PadButton::RightShoulder, PadButton::North]),
1333 ));
1334 assert!((map.axis1("throttle") - 1.0).abs() < 1e-6);
1335 }
1336
1337 #[test]
1341 fn a_pad_chord_shadows_only_in_the_context_that_owns_its_button() {
1342 let mut map = jump_map();
1343 map.declare_in(
1344 "vehicle",
1345 action(
1346 "boost",
1347 ActionKind::Button,
1348 vec![Binding::PadChord {
1349 modifier: PadButton::LeftShoulder,
1350 button: PadButton::South,
1351 }],
1352 ),
1353 );
1354 let both = holding(&[PadButton::LeftShoulder, PadButton::South]);
1355 map.gamepad_event(&state(PAD, both));
1356 assert!(
1357 map.button_held("jump"),
1358 "a chord off the stack shadowed South"
1359 );
1360
1361 map.gamepad_event(&state(PAD, holding(&[])));
1362 map.push_context("vehicle").unwrap();
1363 map.gamepad_event(&state(PAD, both));
1364 assert!(map.button_held("boost"));
1365 assert!(!map.button_held("jump"));
1366 }
1367}