1use repose_core::input::{GamepadAxis, GamepadButton, PhysicalKey, PointerButton};
4use repose_core::shortcuts::KeyChord;
5
6#[derive(Clone, Debug)]
9pub enum Binding {
10 Key(KeyChord),
13 Physical(PhysicalKey),
17 Mouse(PointerButton),
19 Pad(GamepadButton),
21 Axis { axis: GamepadAxis, threshold: f32 },
24}
25
26impl PartialEq for Binding {
29 fn eq(&self, other: &Self) -> bool {
30 match (self, other) {
31 (Binding::Key(a), Binding::Key(b)) => a == b,
32 (Binding::Physical(a), Binding::Physical(b)) => a == b,
33 (Binding::Mouse(a), Binding::Mouse(b)) => {
34 std::mem::discriminant(a) == std::mem::discriminant(b)
35 }
36 (Binding::Pad(a), Binding::Pad(b)) => a == b,
37 (
38 Binding::Axis {
39 axis: a,
40 threshold: t,
41 },
42 Binding::Axis {
43 axis: b,
44 threshold: u,
45 },
46 ) => a == b && t == u,
47 _ => false,
48 }
49 }
50}
51
52impl Eq for Binding {}
53
54impl std::hash::Hash for Binding {
55 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
56 std::mem::discriminant(self).hash(state);
57 match self {
58 Binding::Key(chord) => chord.hash(state),
59 Binding::Physical(key) => key.hash(state),
60 Binding::Mouse(b) => std::mem::discriminant(b).hash(state),
61 Binding::Pad(b) => b.hash(state),
62 Binding::Axis { axis, threshold } => {
63 axis.hash(state);
64 let t = if *threshold == 0.0 { 0.0 } else { *threshold };
65 t.to_bits().hash(state);
66 }
67 }
68 }
69}
70
71impl Binding {
72 pub fn from_entry(entry: &super::keymap::KeymapEntry) -> Option<Self> {
73 use super::keymap::KeymapEntry;
74 Some(match entry {
75 KeymapEntry::None => return None,
76 KeymapEntry::Key(chord) => Binding::Key(chord.clone()),
77 KeymapEntry::Physical(key) => Binding::Physical(*key),
78 KeymapEntry::Mouse(button) => Binding::Mouse(*button),
79 KeymapEntry::Pad(button) => Binding::Pad(*button),
80 KeymapEntry::Axis { axis, threshold } => Binding::Axis {
81 axis: *axis,
82 threshold: *threshold,
83 },
84 })
85 }
86
87 pub fn axis_active(threshold: f32, value: f32) -> bool {
90 if !value.is_finite() {
91 return false;
92 }
93 if threshold == 0.0 {
94 value != 0.0
95 } else if threshold > 0.0 {
96 value >= threshold
97 } else {
98 value <= threshold
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn axis_threshold_sign_selects_side() {
109 assert!(Binding::axis_active(0.5, 0.7));
110 assert!(!Binding::axis_active(0.5, 0.3));
111 assert!(Binding::axis_active(-0.5, -0.7));
112 assert!(!Binding::axis_active(-0.5, -0.3));
113 assert!(!Binding::axis_active(0.0, 0.0));
114 assert!(Binding::axis_active(0.0, 0.1));
115 assert!(Binding::axis_active(0.0, -0.1));
116 assert!(!Binding::axis_active(0.0, f32::NAN));
117 assert!(!Binding::axis_active(0.5, f32::NAN));
118 assert!(!Binding::axis_active(-0.5, f32::NAN));
119 }
120
121 #[test]
122 fn bindings_hash_for_sets() {
123 use repose_core::input::{GamepadAxis, GamepadButton, Key, Modifiers, PointerButton};
124 use repose_core::shortcuts::KeyChord;
125 use std::collections::HashSet;
126 let mut set = HashSet::new();
127 set.insert(Binding::Pad(GamepadButton::South));
128 assert!(set.contains(&Binding::Pad(GamepadButton::South)));
129 set.insert(Binding::Mouse(PointerButton::Primary));
130 assert!(set.contains(&Binding::Mouse(PointerButton::Primary)));
131 assert!(!set.contains(&Binding::Mouse(PointerButton::Secondary)));
132 set.insert(Binding::Axis {
133 axis: GamepadAxis::LeftStickX,
134 threshold: 0.5,
135 });
136 assert!(set.contains(&Binding::Axis {
137 axis: GamepadAxis::LeftStickX,
138 threshold: 0.5,
139 }));
140 assert!(!set.contains(&Binding::Axis {
141 axis: GamepadAxis::LeftStickX,
142 threshold: 0.6,
143 }));
144 let _ = KeyChord::new(Key::Space, Modifiers::default());
145 }
146}