1use core::fmt;
5
6use winit::keyboard::KeyCode;
7
8use crate::math::Vec2;
9
10const MOST_DEADZONE: f32 = 0.95;
13
14macro_rules! controls {
17 (
18 $(#[$meta:meta])*
19 $name:ident, $noun:literal { $($variant:ident $text:literal),* $(,)? }
20 ) => {
21 $(#[$meta])*
22 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
23 #[repr(u8)]
24 pub enum $name {
25 $(
26 #[doc = concat!("The `", stringify!($variant), "` ", $noun, ".")]
27 $variant,
28 )*
29 }
30
31 impl $name {
32 pub(crate) fn token(self) -> &'static str {
33 match self {
34 $(Self::$variant => stringify!($variant),)*
35 }
36 }
37
38 pub(crate) fn from_token(token: &str) -> Option<Self> {
39 match token {
40 $(stringify!($variant) => Some(Self::$variant),)*
41 _ => None,
42 }
43 }
44 }
45
46 impl fmt::Display for $name {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(match self {
49 $(Self::$variant => $text,)*
50 })
51 }
52 }
53 };
54}
55
56macro_rules! listed {
58 (
59 $(#[$meta:meta])*
60 $name:ident, $noun:literal { $($variant:ident $text:literal),* $(,)? }
61 ) => {
62 controls! { $(#[$meta])* $name, $noun { $($variant $text),* } }
63
64 impl $name {
65 pub(crate) const ALL: &'static [Self] = &[$(Self::$variant),*];
66 }
67 };
68}
69
70macro_rules! indexed {
72 (
73 $(#[$meta:meta])*
74 $name:ident, $noun:literal { $($variant:ident $text:literal),* $(,)? }
75 ) => {
76 listed! { $(#[$meta])* $name, $noun { $($variant $text),* } }
77
78 impl $name {
79 pub(crate) const COUNT: usize = Self::ALL.len();
80
81 pub(crate) fn index(self) -> usize {
82 self as usize
83 }
84 }
85 };
86}
87
88macro_rules! keys {
90 ($($variant:ident $text:literal $code:ident),* $(,)?) => {
91 indexed! {
92 Key, "key position" { $($variant $text),* }
99 }
100
101 impl Key {
102 pub(crate) fn from_code(code: KeyCode) -> Option<Self> {
103 match code {
104 $(KeyCode::$code => Some(Self::$variant),)*
105 _ => None,
106 }
107 }
108 }
109 };
110}
111
112keys! {
113 A "A" KeyA, B "B" KeyB, C "C" KeyC, D "D" KeyD, E "E" KeyE, F "F" KeyF,
114 G "G" KeyG, H "H" KeyH, I "I" KeyI, J "J" KeyJ, K "K" KeyK, L "L" KeyL,
115 M "M" KeyM, N "N" KeyN, O "O" KeyO, P "P" KeyP, Q "Q" KeyQ, R "R" KeyR,
116 S "S" KeyS, T "T" KeyT, U "U" KeyU, V "V" KeyV, W "W" KeyW, X "X" KeyX,
117 Y "Y" KeyY, Z "Z" KeyZ,
118 Digit0 "0" Digit0, Digit1 "1" Digit1, Digit2 "2" Digit2, Digit3 "3" Digit3,
119 Digit4 "4" Digit4, Digit5 "5" Digit5, Digit6 "6" Digit6, Digit7 "7" Digit7,
120 Digit8 "8" Digit8, Digit9 "9" Digit9,
121 Left "Left Arrow" ArrowLeft, Right "Right Arrow" ArrowRight,
122 Up "Up Arrow" ArrowUp, Down "Down Arrow" ArrowDown,
123 Space "Space" Space, Enter "Enter" Enter, Escape "Escape" Escape,
124 Tab "Tab" Tab, Backspace "Backspace" Backspace,
125 LeftShift "Left Shift" ShiftLeft, RightShift "Right Shift" ShiftRight,
126 LeftControl "Left Control" ControlLeft, RightControl "Right Control" ControlRight,
127 LeftAlt "Left Alt" AltLeft, RightAlt "Right Alt" AltRight,
128 F1 "F1" F1, F2 "F2" F2, F3 "F3" F3, F4 "F4" F4, F5 "F5" F5, F6 "F6" F6,
129 F7 "F7" F7, F8 "F8" F8, F9 "F9" F9, F10 "F10" F10, F11 "F11" F11,
130 F12 "F12" F12,
131 Minus "-" Minus, Equal "=" Equal,
132 BracketLeft "[" BracketLeft, BracketRight "]" BracketRight,
133 Semicolon ";" Semicolon, Quote "'" Quote, Backquote "`" Backquote,
134 Backslash "\\" Backslash, Comma "," Comma, Period "." Period,
135 Slash "/" Slash,
136 Home "Home" Home, End "End" End,
137 PageUp "Page Up" PageUp, PageDown "Page Down" PageDown,
138 Insert "Insert" Insert, Delete "Delete" Delete,
139 CapsLock "Caps Lock" CapsLock,
140 Numpad0 "Numpad 0" Numpad0, Numpad1 "Numpad 1" Numpad1,
141 Numpad2 "Numpad 2" Numpad2, Numpad3 "Numpad 3" Numpad3,
142 Numpad4 "Numpad 4" Numpad4, Numpad5 "Numpad 5" Numpad5,
143 Numpad6 "Numpad 6" Numpad6, Numpad7 "Numpad 7" Numpad7,
144 Numpad8 "Numpad 8" Numpad8, Numpad9 "Numpad 9" Numpad9,
145 NumpadAdd "Numpad +" NumpadAdd,
146 NumpadSubtract "Numpad -" NumpadSubtract,
147 NumpadMultiply "Numpad *" NumpadMultiply,
148 NumpadDivide "Numpad /" NumpadDivide,
149 NumpadDecimal "Numpad ." NumpadDecimal,
150 NumpadEnter "Numpad Enter" NumpadEnter,
151 NumLock "Num Lock" NumLock,
152}
153
154indexed! {
155 MouseButton, "mouse button" {
158 Left "Left Mouse",
159 Right "Right Mouse",
160 Middle "Middle Mouse",
161 }
162}
163
164indexed! {
165 Pad, "pad button" {
168 South "Pad South",
169 East "Pad East",
170 West "Pad West",
171 North "Pad North",
172 LeftBumper "Left Bumper",
173 RightBumper "Right Bumper",
174 LeftTrigger "Left Trigger",
175 RightTrigger "Right Trigger",
176 Select "Select",
177 Start "Start",
178 Guide "Guide",
179 LeftStick "Left Stick Press",
180 RightStick "Right Stick Press",
181 DPadUp "D-Pad Up",
182 DPadDown "D-Pad Down",
183 DPadLeft "D-Pad Left",
184 DPadRight "D-Pad Right",
185 }
186}
187
188indexed! {
189 PadAxis, "pad axis" {
192 LeftX "Left Stick Sideways",
193 LeftY "Left Stick Up",
194 RightX "Right Stick Sideways",
195 RightY "Right Stick Up",
196 LeftTrigger "Left Trigger",
197 RightTrigger "Right Trigger",
198 }
199}
200
201listed! {
202 Stick, "stick" {
205 Left "Left Stick",
206 Right "Right Stick",
207 }
208}
209
210controls! {
211 PointerDelta, "pointer lane" {
215 Sideways "Pointer Sideways",
216 Up "Pointer Up",
217 }
218}
219
220impl PointerDelta {
221 #[cfg(feature = "offscreen")]
224 pub(crate) fn moving(self, pixels: f32) -> Vec2 {
225 match self {
226 Self::Sideways => Vec2::new(pixels, 0.0),
227 Self::Up => Vec2::new(0.0, pixels),
228 }
229 }
230}
231
232controls! {
233 WheelDelta, "wheel lane" {
242 Sideways "Wheel Sideways",
243 Up "Wheel Up",
244 }
245}
246
247impl WheelDelta {
248 #[cfg(feature = "offscreen")]
251 pub(crate) fn turning(self, notches: f32) -> Vec2 {
252 match self {
253 Self::Sideways => Vec2::new(notches, 0.0),
254 Self::Up => Vec2::new(0.0, notches),
255 }
256 }
257}
258
259impl Key {
260 #[cfg(all(feature = "ui", feature = "offscreen"))]
264 pub(crate) fn ui_key(self) -> Option<egui::Key> {
265 use egui::Key as Ui;
266
267 Some(match self {
268 Self::A => Ui::A,
269 Self::B => Ui::B,
270 Self::C => Ui::C,
271 Self::D => Ui::D,
272 Self::E => Ui::E,
273 Self::F => Ui::F,
274 Self::G => Ui::G,
275 Self::H => Ui::H,
276 Self::I => Ui::I,
277 Self::J => Ui::J,
278 Self::K => Ui::K,
279 Self::L => Ui::L,
280 Self::M => Ui::M,
281 Self::N => Ui::N,
282 Self::O => Ui::O,
283 Self::P => Ui::P,
284 Self::Q => Ui::Q,
285 Self::R => Ui::R,
286 Self::S => Ui::S,
287 Self::T => Ui::T,
288 Self::U => Ui::U,
289 Self::V => Ui::V,
290 Self::W => Ui::W,
291 Self::X => Ui::X,
292 Self::Y => Ui::Y,
293 Self::Z => Ui::Z,
294 Self::Digit0 | Self::Numpad0 => Ui::Num0,
295 Self::Digit1 | Self::Numpad1 => Ui::Num1,
296 Self::Digit2 | Self::Numpad2 => Ui::Num2,
297 Self::Digit3 | Self::Numpad3 => Ui::Num3,
298 Self::Digit4 | Self::Numpad4 => Ui::Num4,
299 Self::Digit5 | Self::Numpad5 => Ui::Num5,
300 Self::Digit6 | Self::Numpad6 => Ui::Num6,
301 Self::Digit7 | Self::Numpad7 => Ui::Num7,
302 Self::Digit8 | Self::Numpad8 => Ui::Num8,
303 Self::Digit9 | Self::Numpad9 => Ui::Num9,
304 Self::Left => Ui::ArrowLeft,
305 Self::Right => Ui::ArrowRight,
306 Self::Up => Ui::ArrowUp,
307 Self::Down => Ui::ArrowDown,
308 Self::Space => Ui::Space,
309 Self::Enter | Self::NumpadEnter => Ui::Enter,
310 Self::Escape => Ui::Escape,
311 Self::Tab => Ui::Tab,
312 Self::Backspace => Ui::Backspace,
313 Self::F1 => Ui::F1,
314 Self::F2 => Ui::F2,
315 Self::F3 => Ui::F3,
316 Self::F4 => Ui::F4,
317 Self::F5 => Ui::F5,
318 Self::F6 => Ui::F6,
319 Self::F7 => Ui::F7,
320 Self::F8 => Ui::F8,
321 Self::F9 => Ui::F9,
322 Self::F10 => Ui::F10,
323 Self::F11 => Ui::F11,
324 Self::F12 => Ui::F12,
325 Self::Minus | Self::NumpadSubtract => Ui::Minus,
326 Self::Equal => Ui::Equals,
327 Self::NumpadAdd => Ui::Plus,
328 Self::BracketLeft => Ui::OpenBracket,
329 Self::BracketRight => Ui::CloseBracket,
330 Self::Semicolon => Ui::Semicolon,
331 Self::Quote => Ui::Quote,
332 Self::Backquote => Ui::Backtick,
333 Self::Backslash => Ui::Backslash,
334 Self::Comma => Ui::Comma,
335 Self::Period | Self::NumpadDecimal => Ui::Period,
336 Self::Slash | Self::NumpadDivide => Ui::Slash,
337 Self::Home => Ui::Home,
338 Self::End => Ui::End,
339 Self::PageUp => Ui::PageUp,
340 Self::PageDown => Ui::PageDown,
341 Self::Insert => Ui::Insert,
342 Self::Delete => Ui::Delete,
343 Self::LeftShift
344 | Self::RightShift
345 | Self::LeftControl
346 | Self::RightControl
347 | Self::LeftAlt
348 | Self::RightAlt
349 | Self::CapsLock
350 | Self::NumLock
351 | Self::NumpadMultiply => return None,
352 })
353 }
354}
355
356impl MouseButton {
357 #[cfg(all(feature = "ui", feature = "offscreen"))]
360 pub(crate) fn ui_button(self) -> egui::PointerButton {
361 match self {
362 Self::Left => egui::PointerButton::Primary,
363 Self::Right => egui::PointerButton::Secondary,
364 Self::Middle => egui::PointerButton::Middle,
365 }
366 }
367
368 pub(crate) fn from_winit(button: winit::event::MouseButton) -> Option<Self> {
369 match button {
370 winit::event::MouseButton::Left => Some(Self::Left),
371 winit::event::MouseButton::Right => Some(Self::Right),
372 winit::event::MouseButton::Middle => Some(Self::Middle),
373 _ => None,
374 }
375 }
376}
377
378impl Stick {
379 pub(crate) fn lanes(self) -> (PadAxis, PadAxis) {
381 match self {
382 Self::Left => (PadAxis::LeftX, PadAxis::LeftY),
383 Self::Right => (PadAxis::RightX, PadAxis::RightY),
384 }
385 }
386}
387
388#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
390pub enum ButtonBinding {
391 Key(Key),
393 Mouse(MouseButton),
395 Pad(Pad),
397 Joystick(JoystickControl),
400}
401
402impl From<Key> for ButtonBinding {
403 fn from(key: Key) -> Self {
404 Self::Key(key)
405 }
406}
407
408impl From<MouseButton> for ButtonBinding {
409 fn from(button: MouseButton) -> Self {
410 Self::Mouse(button)
411 }
412}
413
414impl From<Pad> for ButtonBinding {
415 fn from(button: Pad) -> Self {
416 Self::Pad(button)
417 }
418}
419
420impl fmt::Display for ButtonBinding {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 match self {
423 Self::Key(key) => key.fmt(f),
424 Self::Mouse(button) => button.fmt(f),
425 Self::Pad(button) => button.fmt(f),
426 Self::Joystick(control) => write!(f, "Joystick {control}"),
427 }
428 }
429}
430
431#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
435pub struct JoystickControl(u32);
436
437impl JoystickControl {
438 pub(crate) const fn new(control: u32) -> Self {
439 Self(control)
440 }
441}
442
443impl fmt::Display for JoystickControl {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 self.0.fmt(f)
446 }
447}
448
449#[derive(Clone, Copy, Debug, PartialEq)]
455pub struct AxisBinding {
456 pub(crate) source: AxisSource,
457 pub(crate) knobs: Knobs,
458}
459
460impl AxisBinding {
461 pub const DEFAULT_DEADZONE: f32 = 0.15;
463
464 pub fn pad(axis: PadAxis) -> Self {
466 Self::of(AxisSource::Pad(axis))
467 }
468
469 pub fn joystick(control: JoystickControl) -> Self {
472 Self::of(AxisSource::Joystick(control))
473 }
474
475 pub fn pointer_delta(lane: PointerDelta) -> Self {
478 Self::of(AxisSource::Pointer(lane))
479 }
480
481 pub fn wheel(lane: WheelDelta) -> Self {
484 Self::of(AxisSource::Wheel(lane))
485 }
486
487 pub(crate) fn resolve(&self, raw: f32) -> f32 {
490 self.knobs.applied(raw, self.source.reach())
491 }
492
493 fn of(source: AxisSource) -> Self {
494 Self {
495 source,
496 knobs: source.knobs(),
497 }
498 }
499}
500
501impl From<PadAxis> for AxisBinding {
502 fn from(axis: PadAxis) -> Self {
503 Self::pad(axis)
504 }
505}
506
507impl From<PointerDelta> for AxisBinding {
508 fn from(lane: PointerDelta) -> Self {
509 Self::pointer_delta(lane)
510 }
511}
512
513impl From<WheelDelta> for AxisBinding {
514 fn from(lane: WheelDelta) -> Self {
515 Self::wheel(lane)
516 }
517}
518
519impl fmt::Display for AxisBinding {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 match &self.source {
522 AxisSource::Pad(axis) => axis.fmt(f),
523 AxisSource::Joystick(control) => write!(f, "Joystick Axis {control}"),
524 AxisSource::Pointer(lane) => lane.fmt(f),
525 AxisSource::Wheel(lane) => lane.fmt(f),
526 AxisSource::Buttons { negative, positive } => write!(f, "{negative} / {positive}"),
527 }
528 }
529}
530
531#[derive(Clone, Copy, Debug, PartialEq)]
538pub struct Axis2Binding {
539 pub(crate) source: Axis2Source,
540 pub(crate) knobs: Knobs,
541}
542
543impl Axis2Binding {
544 pub fn stick(stick: Stick) -> Self {
546 Self::of(Axis2Source::Stick(stick))
547 }
548
549 pub fn pointer() -> Self {
552 Self::of(Axis2Source::Pointer)
553 }
554
555 pub fn wheel() -> Self {
559 Self::of(Axis2Source::Wheel)
560 }
561
562 pub(crate) fn resolve(&self, raw: Vec2) -> Vec2 {
565 self.knobs.applied2(raw, self.source.reach())
566 }
567
568 fn of(source: Axis2Source) -> Self {
569 Self {
570 source,
571 knobs: source.knobs(),
572 }
573 }
574}
575
576impl<L, R, D, U> From<ButtonAxis2<L, R, D, U>> for Axis2Binding
577where
578 L: Into<ButtonBinding>,
579 R: Into<ButtonBinding>,
580 D: Into<ButtonBinding>,
581 U: Into<ButtonBinding>,
582{
583 fn from(quad: ButtonAxis2<L, R, D, U>) -> Self {
588 Self::of(Axis2Source::Buttons {
589 left: quad.left.into(),
590 right: quad.right.into(),
591 down: quad.down.into(),
592 up: quad.up.into(),
593 })
594 }
595}
596
597impl From<Stick> for Axis2Binding {
598 fn from(stick: Stick) -> Self {
599 Self::stick(stick)
600 }
601}
602
603impl fmt::Display for Axis2Binding {
604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605 match &self.source {
606 Axis2Source::Stick(stick) => stick.fmt(f),
607 Axis2Source::Pointer => f.write_str("Pointer"),
608 Axis2Source::Wheel => f.write_str("Wheel"),
609 Axis2Source::Buttons {
610 left,
611 right,
612 down,
613 up,
614 } => write!(f, "{left} / {right} / {down} / {up}"),
615 }
616 }
617}
618
619#[derive(Clone, Copy, Debug, PartialEq)]
621pub(crate) enum AxisSource {
622 Pad(PadAxis),
623 Joystick(JoystickControl),
624 Pointer(PointerDelta),
625 Wheel(WheelDelta),
626 Buttons {
627 negative: ButtonBinding,
628 positive: ButtonBinding,
629 },
630}
631
632impl AxisSource {
633 fn reach(self) -> Reach {
635 match self {
636 Self::Pointer(_) | Self::Wheel(_) => Reach::Open,
637 Self::Pad(_) | Self::Joystick(_) | Self::Buttons { .. } => Reach::Unit,
638 }
639 }
640
641 fn knobs(self) -> Knobs {
643 match self {
644 Self::Pad(_) | Self::Joystick(_) => Knobs::at(AxisBinding::DEFAULT_DEADZONE),
645 Self::Pointer(_) | Self::Wheel(_) | Self::Buttons { .. } => Knobs::at(0.0),
646 }
647 }
648}
649
650#[derive(Clone, Copy, Debug, PartialEq)]
652pub(crate) enum Axis2Source {
653 Stick(Stick),
654 Pointer,
655 Wheel,
656 Buttons {
657 left: ButtonBinding,
658 right: ButtonBinding,
659 down: ButtonBinding,
660 up: ButtonBinding,
661 },
662}
663
664impl Axis2Source {
665 fn reach(self) -> Reach {
667 match self {
668 Self::Pointer | Self::Wheel => Reach::Open,
669 Self::Stick(_) | Self::Buttons { .. } => Reach::Unit,
670 }
671 }
672
673 fn knobs(self) -> Knobs {
675 match self {
676 Self::Stick(_) => Knobs::at(AxisBinding::DEFAULT_DEADZONE),
677 Self::Pointer | Self::Wheel | Self::Buttons { .. } => Knobs::at(0.0),
678 }
679 }
680}
681
682#[derive(Clone, Copy, Debug, PartialEq)]
687enum Reach {
688 Unit,
690 Open,
692}
693
694impl Reach {
695 fn hold(self, value: f32) -> f32 {
698 match (self, value.is_finite()) {
699 (_, false) => 0.0,
700 (Self::Unit, true) => value.clamp(-1.0, 1.0),
701 (Self::Open, true) => value,
702 }
703 }
704}
705
706#[derive(Clone, Copy, Debug)]
709pub struct ButtonAxis<
710 N: Into<ButtonBinding> = ButtonBinding,
711 P: Into<ButtonBinding> = ButtonBinding,
712> {
713 pub negative: N,
715 pub positive: P,
717}
718
719#[derive(Clone, Copy, Debug)]
721pub struct ButtonAxis2<
722 L: Into<ButtonBinding> = ButtonBinding,
723 R: Into<ButtonBinding> = ButtonBinding,
724 D: Into<ButtonBinding> = ButtonBinding,
725 U: Into<ButtonBinding> = ButtonBinding,
726> {
727 pub left: L,
729 pub right: R,
731 pub down: D,
733 pub up: U,
735}
736
737#[derive(Clone, Copy, Debug, PartialEq)]
740pub(crate) struct Deadzone(f32);
741
742impl Deadzone {
743 pub(crate) fn new(deadzone: f32) -> Self {
746 Self(MOST_DEADZONE.min(deadzone.max(0.0)))
747 }
748
749 fn past(self, magnitude: f32) -> f32 {
753 ((magnitude - self.0) / (1.0 - self.0)).max(0.0)
754 }
755
756 pub(crate) fn get(self) -> f32 {
758 self.0
759 }
760}
761
762#[derive(Clone, Copy, Debug, PartialEq)]
765pub(crate) struct Knobs {
766 pub(crate) deadzone: Deadzone,
767 pub(crate) scale: f32,
768 pub(crate) inverted: bool,
769}
770
771impl Knobs {
772 fn at(deadzone: f32) -> Self {
775 Self {
776 deadzone: Deadzone::new(deadzone),
777 scale: 1.0,
778 inverted: false,
779 }
780 }
781
782 pub(crate) fn stored(deadzone: Deadzone, scale: f32, inverted: bool) -> Self {
785 Self {
786 deadzone,
787 scale,
788 inverted,
789 }
790 }
791
792 fn applied(self, raw: f32, reach: Reach) -> f32 {
795 reach.hold(self.deadzone.past(raw.abs()).copysign(raw) * self.scale * self.turned())
796 }
797
798 fn applied2(self, raw: Vec2, reach: Reach) -> Vec2 {
801 let raw = Vec2::new(raw.x, raw.y * self.turned());
802 let length = raw.length();
803 if !length.is_finite() || length <= f32::EPSILON {
804 return Vec2::ZERO;
805 }
806 raw / length * reach.hold(self.deadzone.past(length) * self.scale)
807 }
808
809 fn turned(self) -> f32 {
810 match self.inverted {
811 true => -1.0,
812 false => 1.0,
813 }
814 }
815}
816
817macro_rules! knobs {
819 ($name:ident, $lane:literal) => {
820 impl $name {
821 #[must_use]
829 pub fn deadzone(mut self, deadzone: f32) -> Self {
830 self.knobs.deadzone = Deadzone::new(deadzone);
831 self
832 }
833
834 #[must_use]
842 pub fn scale(mut self, scale: f32) -> Self {
843 self.knobs.scale = scale;
844 self
845 }
846
847 #[doc = concat!("Flips ", $lane, ", for a control that reads the other way round.")]
848 #[must_use]
849 pub fn invert(mut self) -> Self {
850 self.knobs.inverted = true;
851 self
852 }
853 }
854 };
855}
856
857knobs!(AxisBinding, "which way the control counts");
858knobs!(Axis2Binding, "the upward lane");
859
860impl<N: Into<ButtonBinding>, P: Into<ButtonBinding>> From<ButtonAxis<N, P>> for AxisBinding {
861 fn from(pair: ButtonAxis<N, P>) -> Self {
864 Self::of(AxisSource::Buttons {
865 negative: pair.negative.into(),
866 positive: pair.positive.into(),
867 })
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874
875 #[test]
876 fn physical_positions_map_to_keys() {
877 assert_eq!(Key::from_code(KeyCode::KeyW), Some(Key::W));
878 assert_eq!(Key::from_code(KeyCode::ArrowLeft), Some(Key::Left));
879 assert_eq!(Key::from_code(KeyCode::ShiftLeft), Some(Key::LeftShift));
880 assert_eq!(
881 Key::from_code(KeyCode::F13),
882 None,
883 "keys we skip are ignored"
884 );
885 }
886
887 #[test]
888 fn the_punctuation_and_pad_positions_read_and_show_like_the_rest() {
889 assert_eq!(Key::from_code(KeyCode::BracketLeft), Some(Key::BracketLeft));
890 assert_eq!(Key::BracketLeft.to_string(), "[");
891 assert_eq!(Key::from_code(KeyCode::NumpadAdd), Some(Key::NumpadAdd));
892 assert_eq!(Key::NumpadAdd.to_string(), "Numpad +");
893 }
894
895 #[test]
896 fn a_control_answers_to_its_own_name_and_shows_a_readable_one() {
897 assert_eq!(Key::from_token("LeftShift"), Some(Key::LeftShift));
898 assert_eq!(Key::LeftShift.token(), "LeftShift");
899 assert_eq!(Key::LeftShift.to_string(), "Left Shift");
900 assert_eq!(PadAxis::from_token("Left Trigger"), None, "names are exact");
901 assert_eq!(PadAxis::RightTrigger.to_string(), "Right Trigger");
902 }
903
904 #[test]
905 fn a_deadzone_starts_the_range_where_it_ends() {
906 let stick = AxisBinding::pad(PadAxis::LeftX).deadzone(0.5);
907
908 assert_eq!(stick.resolve(0.5), 0.0, "the edge reads as nothing");
909 assert_eq!(stick.resolve(0.25), 0.0, "and so does anything under");
910 assert_eq!(stick.resolve(1.0), 1.0, "the far end still reaches");
911 assert_eq!(stick.resolve(-0.75), -0.5, "in both directions");
912 }
913
914 #[test]
915 fn a_control_with_a_range_of_its_own_keeps_to_it_however_the_knobs_are_set() {
916 let lane = AxisBinding::pad(PadAxis::LeftX).deadzone(0.0).scale(100.0);
917 assert_eq!(lane.resolve(1.0), 1.0);
918 assert_eq!(lane.resolve(-1.0), -1.0);
919
920 let quad = Axis2Binding::from(ButtonAxis2 {
921 left: Key::A,
922 right: Key::D,
923 down: Key::S,
924 up: Key::W,
925 })
926 .scale(100.0);
927 assert_eq!(quad.resolve(Vec2::X), Vec2::X);
928
929 let broken = AxisBinding::pad(PadAxis::LeftX).scale(f32::NAN);
930 assert_eq!(broken.resolve(1.0), 0.0, "and stays a number");
931
932 let deep = AxisBinding::pad(PadAxis::LeftX).deadzone(4.0);
933 assert_eq!(deep.knobs.deadzone.get(), MOST_DEADZONE);
934 }
935
936 #[test]
937 fn a_delta_binding_reads_the_whole_distance_its_scale_makes_of_it() {
938 let look = AxisBinding::pointer_delta(PointerDelta::Sideways).scale(0.01);
939 assert_eq!(look.resolve(500.0), 5.0);
940 assert_eq!(look.resolve(-500.0), -5.0);
941
942 let wheel = AxisBinding::wheel(WheelDelta::Up).scale(100.0);
943 assert_eq!(wheel.resolve(4.0), 400.0);
944
945 let pointer = Axis2Binding::pointer().scale(0.01);
946 assert_eq!(pointer.resolve(Vec2::new(500.0, 0.0)), Vec2::new(5.0, 0.0));
947
948 let broken = AxisBinding::pointer_delta(PointerDelta::Up).scale(f32::INFINITY);
949 assert_eq!(broken.resolve(500.0), 0.0, "and stays a number");
950 }
951
952 #[test]
953 fn inverting_turns_an_axis_round_and_a_vector_upside_down() {
954 let axis = AxisBinding::pad(PadAxis::LeftY).deadzone(0.0).invert();
955 assert_eq!(axis.resolve(0.5), -0.5);
956
957 let stick = Axis2Binding::stick(Stick::Left).deadzone(0.0).invert();
958 assert_eq!(stick.resolve(Vec2::new(1.0, 0.0)), Vec2::X);
959 assert_eq!(stick.resolve(Vec2::new(0.0, 1.0)), Vec2::NEG_Y);
960 }
961
962 #[test]
963 fn a_vector_reaches_no_further_than_one_however_far_it_is_pushed() {
964 let quad = Axis2Binding::from(ButtonAxis2 {
965 left: Key::A,
966 right: Key::D,
967 down: Key::S,
968 up: Key::W,
969 });
970 let diagonal = quad.resolve(Vec2::ONE);
971
972 assert!(
973 (diagonal.length() - 1.0).abs() < 1e-6,
974 "{diagonal} is one long"
975 );
976 assert!((diagonal.x - diagonal.y).abs() < 1e-6, "and still diagonal");
977 assert_eq!(quad.resolve(Vec2::X), Vec2::X, "a straight one is whole");
978 assert_eq!(quad.resolve(Vec2::ZERO), Vec2::ZERO);
979 }
980
981 #[test]
982 fn a_binding_shows_the_control_a_menu_would_name() {
983 assert_eq!(ButtonBinding::from(Key::Space).to_string(), "Space");
984 assert_eq!(ButtonBinding::from(Pad::South).to_string(), "Pad South");
985 assert_eq!(
986 ButtonBinding::Joystick(JoystickControl::new(7)).to_string(),
987 "Joystick 7"
988 );
989 assert_eq!(
990 AxisBinding::from(ButtonAxis {
991 negative: Key::A,
992 positive: Key::D
993 })
994 .to_string(),
995 "A / D"
996 );
997 assert_eq!(
998 AxisBinding::from(PointerDelta::Sideways).to_string(),
999 "Pointer Sideways"
1000 );
1001 assert_eq!(AxisBinding::from(WheelDelta::Up).to_string(), "Wheel Up");
1002 assert_eq!(
1003 AxisBinding::from(WheelDelta::Sideways).to_string(),
1004 "Wheel Sideways"
1005 );
1006 assert_eq!(Axis2Binding::from(Stick::Left).to_string(), "Left Stick");
1007 assert_eq!(
1008 Axis2Binding::from(ButtonAxis2 {
1009 left: Key::A,
1010 right: Key::D,
1011 down: Key::S,
1012 up: Key::W
1013 })
1014 .to_string(),
1015 "A / D / S / W"
1016 );
1017 }
1018}