1use super::{ActionMap, Binding, PadKind};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub enum Device {
18 Keyboard,
20 Pointer,
23 Touch,
26 Gamepad,
30}
31
32impl ActionMap {
33 #[must_use]
35 pub fn last_device(&self) -> Option<Device> {
36 self.devices.first().copied()
37 }
38
39 #[must_use]
50 pub const fn last_device_changed(&self) -> bool {
51 self.device_changed
52 }
53
54 #[must_use]
61 pub fn last_pad_kind(&self) -> Option<PadKind> {
62 self.last_pad_kind
63 }
64
65 pub(crate) fn spoke(&mut self, device: Device) {
68 if self.devices.first() != Some(&device) {
69 self.device_changed = true;
70 }
71 if let Some(index) = self.devices.iter().position(|&seen| seen == device) {
72 self.devices.remove(index);
73 }
74 self.devices.insert(0, device);
75 }
76}
77
78impl Binding {
79 #[must_use]
89 pub const fn device(&self) -> Device {
90 match self {
91 Self::Key(_) | Self::Chord { .. } | Self::KeyAxis { .. } | Self::Wasd { .. } => {
92 Device::Keyboard
93 }
94 Self::MouseButton(_)
95 | Self::ButtonChord { .. }
96 | Self::MouseMotion
97 | Self::MouseScroll
98 | Self::ScrollChord { .. }
99 | Self::PointerPosition { .. } => Device::Pointer,
100 Self::Virtual(_) => Device::Touch,
101 Self::PadButton(_)
102 | Self::PadChord { .. }
103 | Self::PadDpad
104 | Self::PadStick { .. }
105 | Self::PadTrigger { .. } => Device::Gamepad,
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::{PadButton, PointerAxis};
114 use crcbl_core::input::{KeyCode, PointerButton};
115
116 #[test]
119 fn a_binding_belongs_to_the_device_its_value_comes_from() {
120 for (binding, device) in [
121 (
122 Binding::Chord {
123 modifier: crate::Modifier::Shift,
124 key: KeyCode::KeyR,
125 },
126 Device::Keyboard,
127 ),
128 (
129 Binding::ScrollChord {
130 held: KeyCode::ControlLeft,
131 },
132 Device::Pointer,
133 ),
134 (
135 Binding::ButtonChord {
136 modifier: crate::Modifier::Alt,
137 button: PointerButton::Right,
138 },
139 Device::Pointer,
140 ),
141 (
142 Binding::PointerPosition {
143 axis: PointerAxis::Y,
144 },
145 Device::Pointer,
146 ),
147 (Binding::Virtual("stick".to_owned()), Device::Touch),
148 (
149 Binding::PadChord {
150 modifier: PadButton::LeftShoulder,
151 button: PadButton::South,
152 },
153 Device::Gamepad,
154 ),
155 ] {
156 assert_eq!(binding.device(), device, "{binding}");
157 }
158 }
159
160 #[test]
165 fn the_last_device_changes_on_a_real_press_and_not_on_drift() {
166 use crate::{GamepadEvent, GamepadId, GamepadSnapshot, PAD_ACTIVITY_THRESHOLD, PadAxis};
167
168 let pad = |map: &mut ActionMap, edit: &dyn Fn(&mut GamepadSnapshot)| {
169 let mut snapshot = GamepadSnapshot::neutral(PadKind::Xbox);
170 edit(&mut snapshot);
171 map.gamepad_event(&GamepadEvent::State {
172 id: GamepadId(4),
173 snapshot,
174 });
175 };
176 let mut map = ActionMap::new();
177 assert!(!map.last_device_changed());
178
179 map.key_event(KeyCode::KeyQ, true);
180 assert!(map.last_device_changed(), "the first device to speak");
181 map.begin_tick(0.0);
182 assert!(!map.last_device_changed(), "begin_tick clears the edge");
183
184 let drift = PAD_ACTIVITY_THRESHOLD * 0.9;
185 pad(&mut map, &|pad| pad.axes[PadAxis::LeftX as usize] = drift);
186 pad(&mut map, &|pad| pad.axes[PadAxis::LeftY as usize] = -drift);
187 assert_eq!(
188 map.last_device(),
189 Some(Device::Keyboard),
190 "drift is not a push"
191 );
192 assert!(!map.last_device_changed(), "and raises nothing");
193
194 map.key_event(KeyCode::KeyE, true);
195 assert!(
196 !map.last_device_changed(),
197 "the keyboard speaking again changes nothing"
198 );
199
200 pad(&mut map, &|pad| pad.buttons.insert(PadButton::South));
201 assert_eq!(map.last_device(), Some(Device::Gamepad));
202 assert!(map.last_device_changed(), "a pad press takes over");
203 }
204
205 #[test]
208 fn the_last_device_follows_activity_and_not_releases() {
209 let mut map = ActionMap::new();
210 assert_eq!(map.last_device(), None);
211
212 map.key_event(KeyCode::KeyQ, true);
213 assert_eq!(map.last_device(), Some(Device::Keyboard), "nothing binds Q");
214
215 map.mouse_motion(0.0, 0.0);
216 map.mouse_scroll(0.0, 0.0);
217 assert_eq!(
218 map.last_device(),
219 Some(Device::Keyboard),
220 "a zero delta is not movement"
221 );
222
223 map.mouse_motion(3.0, 0.0);
224 assert_eq!(map.last_device(), Some(Device::Pointer));
225
226 map.key_event(KeyCode::KeyQ, false);
227 assert_eq!(
228 map.last_device(),
229 Some(Device::Pointer),
230 "letting go of a key is not the keyboard speaking",
231 );
232
233 map.virtual_stick("stick", 0.0, 0.0);
234 assert_eq!(
235 map.last_device(),
236 Some(Device::Pointer),
237 "a centred stick said nothing"
238 );
239 map.virtual_stick("stick", 0.5, 0.0);
240 assert_eq!(map.last_device(), Some(Device::Touch));
241
242 map.pointer_position(0.2, 0.3);
243 assert_eq!(map.last_device(), Some(Device::Pointer));
244 map.key_event(KeyCode::Enter, true);
245 map.pointer_position(0.2, 0.3);
246 assert_eq!(
247 map.last_device(),
248 Some(Device::Keyboard),
249 "a pointer re-reporting where it already is has not moved",
250 );
251
252 map.virtual_button("btn", true);
253 assert_eq!(map.last_device(), Some(Device::Touch));
254 map.mouse_button(PointerButton::Left, true);
255 assert_eq!(map.last_device(), Some(Device::Pointer));
256 map.mouse_scroll(0.0, 1.0);
257 map.key_event(KeyCode::Enter, true);
258 assert_eq!(map.last_device(), Some(Device::Keyboard));
259 map.mouse_scroll(0.0, 1.0);
260 assert_eq!(map.last_device(), Some(Device::Pointer));
261 }
262}