Skip to main content

mirage_engine/input/
binding.rs

1//! Device controls, and the typed bindings that turn them into a game's
2//! actions.
3
4use core::fmt;
5
6use winit::keyboard::KeyCode;
7
8use crate::math::Vec2;
9
10/// The deepest a deadzone may be, so that a control keeps a range to
11/// read in.
12const MOST_DEADZONE: f32 = 0.95;
13
14/// One device's controls: the values, the text a controls menu shows, and
15/// the names the store writes.
16macro_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
56/// The same, plus the list capture walks looking for what the player moved.
57macro_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
70/// The same, plus a place of its own in a reading for each control.
71macro_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
88// One list keeps the key vocabulary and its winit mapping from drifting apart.
89macro_rules! keys {
90    ($($variant:ident $text:literal $code:ident),* $(,)?) => {
91        indexed! {
92            /// A key by physical position, independent of the layout in use:
93            /// [`Key::W`] is the key at the `W` position on a US keyboard.
94            ///
95            /// Text entry belongs to the UI layer, not here. A key is also a
96            /// button vocabulary of its own, which is what a prototype binds
97            /// through before it declares its actions.
98            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    /// A mouse button. The first touch of a touch screen presses
156    /// [`MouseButton::Left`], wherever it lands.
157    MouseButton, "mouse button" {
158        Left "Left Mouse",
159        Right "Right Mouse",
160        Middle "Middle Mouse",
161    }
162}
163
164indexed! {
165    /// A button of the standard gamepad layout, by its position rather than
166    /// by what a maker prints on it.
167    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    /// One lane of the standard gamepad layout: positive is right and up,
190    /// and a trigger reads `0..=1` of the range.
191    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    /// A stick of the standard gamepad layout, read as both its lanes at
203    /// once.
204    Stick, "stick" {
205        Left "Left Stick",
206        Right "Right Stick",
207    }
208}
209
210controls! {
211    /// One lane of the pointer, reporting how far it moved since the last
212    /// frame rather than where it is, so its range is open and
213    /// [`AxisBinding::scale`] is what bounds it.
214    PointerDelta, "pointer lane" {
215        Sideways "Pointer Sideways",
216        Up "Pointer Up",
217    }
218}
219
220impl PointerDelta {
221    /// `pixels` along this lane as a movement of the whole pointer,
222    /// counted right and up.
223    #[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    /// One lane of the wheel, reporting the notches it turned since the
234    /// last frame, so its range is open and [`AxisBinding::scale`] is what
235    /// bounds it.
236    ///
237    /// One notch of a mouse wheel is `1` on every target. [`WheelDelta::Up`]
238    /// counts a roll away from the player and [`WheelDelta::Sideways`] a
239    /// tilt to the right: right and away, as [`PointerDelta`] counts right
240    /// and up.
241    WheelDelta, "wheel lane" {
242        Sideways "Wheel Sideways",
243        Up "Wheel Up",
244    }
245}
246
247impl WheelDelta {
248    /// `notches` along this lane as a turn of the whole wheel, counted
249    /// right and away.
250    #[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    /// This key as the UI names it, `None` where the UI names no key in
261    /// this position. Only a session passes a key to the UI: a window's
262    /// keys reach it through `egui-winit` instead.
263    #[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    /// This button as the UI names it, for the session that passes it; a
358    /// window's buttons reach the UI through `egui-winit` instead.
359    #[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    /// The two lanes this stick reads through, sideways then up.
380    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/// One control that reads back `true` while it is held.
389#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
390pub enum ButtonBinding {
391    /// A key, by physical position.
392    Key(Key),
393    /// A mouse button, which the first touch of a touch screen also presses.
394    Mouse(MouseButton),
395    /// A button of the standard gamepad layout.
396    Pad(Pad),
397    /// A button of a device with no standard layout, by the number its
398    /// platform reports for it; capture one rather than writing one.
399    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/// A control of a device with no standard layout, by the number its
432/// platform reports for it: captured rather than written, and usable only on
433/// the machine that captured it.
434#[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/// One control that reads back a number, and the knobs it reads through.
450///
451/// A pad lane, a trigger, a joystick axis and a button composite read in
452/// `-1..=1`. A [`PointerDelta`] lane and a [`WheelDelta`] lane read how far
453/// they moved through [`scale`](Self::scale), which nothing clamps.
454#[derive(Clone, Copy, Debug, PartialEq)]
455pub struct AxisBinding {
456    pub(crate) source: AxisSource,
457    pub(crate) knobs: Knobs,
458}
459
460impl AxisBinding {
461    /// Distance a pad or joystick control moves before it reads at all.
462    pub const DEFAULT_DEADZONE: f32 = 0.15;
463
464    /// One lane of the standard gamepad layout.
465    pub fn pad(axis: PadAxis) -> Self {
466        Self::of(AxisSource::Pad(axis))
467    }
468
469    /// One axis of a device with no standard layout, by the number its
470    /// platform reports for it; capture one rather than writing one.
471    pub fn joystick(control: JoystickControl) -> Self {
472        Self::of(AxisSource::Joystick(control))
473    }
474
475    /// Pixels one lane of the pointer moved this frame, read through
476    /// [`scale`](Self::scale) alone: nothing clamps what it reads.
477    pub fn pointer_delta(lane: PointerDelta) -> Self {
478        Self::of(AxisSource::Pointer(lane))
479    }
480
481    /// Notches `lane` of the wheel turned this frame, read through
482    /// [`scale`](Self::scale) alone: nothing clamps what it reads.
483    pub fn wheel(lane: WheelDelta) -> Self {
484        Self::of(AxisSource::Wheel(lane))
485    }
486
487    /// `raw` as this binding reads it: the knobs applied, then the range
488    /// the source reads in.
489    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/// A pair of controls that read back a vector, and the knobs they read
532/// through.
533///
534/// A stick and a button composite read no longer than `1`. The pointer and
535/// the wheel read how far they moved through [`scale`](Self::scale), which
536/// nothing clamps.
537#[derive(Clone, Copy, Debug, PartialEq)]
538pub struct Axis2Binding {
539    pub(crate) source: Axis2Source,
540    pub(crate) knobs: Knobs,
541}
542
543impl Axis2Binding {
544    /// Both lanes of one stick of the standard gamepad layout.
545    pub fn stick(stick: Stick) -> Self {
546        Self::of(Axis2Source::Stick(stick))
547    }
548
549    /// Pixels the pointer moved this frame, read through
550    /// [`scale`](Self::scale) alone: nothing clamps what it reads.
551    pub fn pointer() -> Self {
552        Self::of(Axis2Source::Pointer)
553    }
554
555    /// Notches the wheel turned this frame, both lanes as one vector:
556    /// [`WheelDelta::Sideways`] is `x` and [`WheelDelta::Up`] is `y`. Read
557    /// through [`scale`](Self::scale) alone, so nothing clamps what it reads.
558    pub fn wheel() -> Self {
559        Self::of(Axis2Source::Wheel)
560    }
561
562    /// `raw` as this binding reads it: the knobs applied, then the range
563    /// the source reads in.
564    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    /// Four buttons around a center, the way `WASD` is laid out; a diagonal
584    /// reads as long as a straight direction, never longer. The vector is
585    /// `x` right minus left, `y` up minus down; what that means in the
586    /// world is the game's call.
587    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/// Source one axis takes its number from.
620#[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    /// Range a binding over this source reads in.
634    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    /// The knobs a binding over this source starts at.
642    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/// Source one vector takes its two numbers from.
651#[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    /// Range a binding over this source reads in.
666    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    /// The knobs a binding over this source starts at.
674    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/// Range a source's readings lie in, which the source itself decides: a pad
683/// or joystick control, a stick and a button composite each have a range of
684/// their own, and a pointer lane or a wheel lane reports a distance with
685/// none.
686#[derive(Clone, Copy, Debug, PartialEq)]
687enum Reach {
688    /// The whole of a control's own range, `-1..=1`.
689    Unit,
690    /// However far the control moved, which its scale multiplies.
691    Open,
692}
693
694impl Reach {
695    /// `value` held to this range. A value that is not finite reads as
696    /// nothing, which is what keeps every query total.
697    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/// Two buttons that make an axis: `negative` counts down, `positive` counts
707/// up.
708#[derive(Clone, Copy, Debug)]
709pub struct ButtonAxis<
710    N: Into<ButtonBinding> = ButtonBinding,
711    P: Into<ButtonBinding> = ButtonBinding,
712> {
713    /// The button counting down.
714    pub negative: N,
715    /// The button counting up.
716    pub positive: P,
717}
718
719/// Four buttons that make a vector, the way `WASD` is laid out.
720#[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    /// The button counting left.
728    pub left: L,
729    /// The button counting right.
730    pub right: R,
731    /// The button counting down.
732    pub down: D,
733    /// The button counting up.
734    pub up: U,
735}
736
737/// Distance a control must move before it reads at all, held to the range a
738/// control keeps to read in.
739#[derive(Clone, Copy, Debug, PartialEq)]
740pub(crate) struct Deadzone(f32);
741
742impl Deadzone {
743    /// `deadzone`, held to the range a control keeps to read in; one that is
744    /// not a number opens the range whole, which `clamp` would not.
745    pub(crate) fn new(deadzone: f32) -> Self {
746        Self(MOST_DEADZONE.min(deadzone.max(0.0)))
747    }
748
749    /// The amount of `magnitude` that lies past this deadzone, stretched so
750    /// that a control leaves it at nothing and covers the whole range at
751    /// one.
752    fn past(self, magnitude: f32) -> f32 {
753        ((magnitude - self.0) / (1.0 - self.0)).max(0.0)
754    }
755
756    /// The deadzone as the number a game set, or the store reads back.
757    pub(crate) fn get(self) -> f32 {
758        self.0
759    }
760}
761
762/// Distance a control must move before it reads at all, how much of it
763/// the action reads, and which way round.
764#[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    /// The knobs a binding starts at, reading nothing until the control has
773    /// moved `deadzone` of its way and the whole of it after that.
774    fn at(deadzone: f32) -> Self {
775        Self {
776            deadzone: Deadzone::new(deadzone),
777            scale: 1.0,
778            inverted: false,
779        }
780    }
781
782    /// The knobs one stored line reads back as, clamped like the ones a
783    /// game sets by hand.
784    pub(crate) fn stored(deadzone: Deadzone, scale: f32, inverted: bool) -> Self {
785        Self {
786            deadzone,
787            scale,
788            inverted,
789        }
790    }
791
792    /// `raw` once the deadzone, the scale and the direction are applied,
793    /// held to `reach`.
794    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    /// The same for a vector, whose deadzone is over its length rather
799    /// than over either lane.
800    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
817/// The knobs every analog binding holds.
818macro_rules! knobs {
819    ($name:ident, $lane:literal) => {
820        impl $name {
821            /// Reads nothing until the control has moved `deadzone` of its
822            /// way, then stretches what is left over the whole range;
823            /// [`AxisBinding::DEFAULT_DEADZONE`] for a pad or joystick
824            /// control, and nothing for the rest.
825            ///
826            /// Clamped into `0.0..=0.95`, a fraction of that way, so a
827            /// control always keeps a range to read in.
828            #[must_use]
829            pub fn deadzone(mut self, deadzone: f32) -> Self {
830                self.knobs.deadzone = Deadzone::new(deadzone);
831                self
832            }
833
834            /// Multiplies what the control reads, as a fraction of it;
835            /// `1.0` until set.
836            ///
837            /// A control with a range of its own keeps to it whatever
838            /// this is set to. A pointer lane or a wheel lane has none, so
839            /// it reads its own distance times this: pixels for a pointer
840            /// lane, notches for a wheel lane.
841            #[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    /// A pair, `negative` counting down and `positive` counting up; a
862    /// diagonal never reads longer than a straight direction.
863    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}