use core::fmt;
use winit::keyboard::KeyCode;
use crate::math::Vec2;
const MOST_DEADZONE: f32 = 0.95;
macro_rules! controls {
(
$(#[$meta:meta])*
$name:ident, $noun:literal { $($variant:ident $text:literal),* $(,)? }
) => {
$(#[$meta])*
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(u8)]
pub enum $name {
$(
#[doc = concat!("The `", stringify!($variant), "` ", $noun, ".")]
$variant,
)*
}
impl $name {
pub(crate) fn token(self) -> &'static str {
match self {
$(Self::$variant => stringify!($variant),)*
}
}
pub(crate) fn from_token(token: &str) -> Option<Self> {
match token {
$(stringify!($variant) => Some(Self::$variant),)*
_ => None,
}
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
$(Self::$variant => $text,)*
})
}
}
};
}
macro_rules! listed {
(
$(#[$meta:meta])*
$name:ident, $noun:literal { $($variant:ident $text:literal),* $(,)? }
) => {
controls! { $(#[$meta])* $name, $noun { $($variant $text),* } }
impl $name {
pub(crate) const ALL: &'static [Self] = &[$(Self::$variant),*];
}
};
}
macro_rules! indexed {
(
$(#[$meta:meta])*
$name:ident, $noun:literal { $($variant:ident $text:literal),* $(,)? }
) => {
listed! { $(#[$meta])* $name, $noun { $($variant $text),* } }
impl $name {
pub(crate) const COUNT: usize = Self::ALL.len();
pub(crate) fn index(self) -> usize {
self as usize
}
}
};
}
macro_rules! keys {
($($variant:ident $text:literal $code:ident),* $(,)?) => {
indexed! {
Key, "key position" { $($variant $text),* }
}
impl Key {
pub(crate) fn from_code(code: KeyCode) -> Option<Self> {
match code {
$(KeyCode::$code => Some(Self::$variant),)*
_ => None,
}
}
}
};
}
keys! {
A "A" KeyA, B "B" KeyB, C "C" KeyC, D "D" KeyD, E "E" KeyE, F "F" KeyF,
G "G" KeyG, H "H" KeyH, I "I" KeyI, J "J" KeyJ, K "K" KeyK, L "L" KeyL,
M "M" KeyM, N "N" KeyN, O "O" KeyO, P "P" KeyP, Q "Q" KeyQ, R "R" KeyR,
S "S" KeyS, T "T" KeyT, U "U" KeyU, V "V" KeyV, W "W" KeyW, X "X" KeyX,
Y "Y" KeyY, Z "Z" KeyZ,
Digit0 "0" Digit0, Digit1 "1" Digit1, Digit2 "2" Digit2, Digit3 "3" Digit3,
Digit4 "4" Digit4, Digit5 "5" Digit5, Digit6 "6" Digit6, Digit7 "7" Digit7,
Digit8 "8" Digit8, Digit9 "9" Digit9,
Left "Left Arrow" ArrowLeft, Right "Right Arrow" ArrowRight,
Up "Up Arrow" ArrowUp, Down "Down Arrow" ArrowDown,
Space "Space" Space, Enter "Enter" Enter, Escape "Escape" Escape,
Tab "Tab" Tab, Backspace "Backspace" Backspace,
LeftShift "Left Shift" ShiftLeft, RightShift "Right Shift" ShiftRight,
LeftControl "Left Control" ControlLeft, RightControl "Right Control" ControlRight,
LeftAlt "Left Alt" AltLeft, RightAlt "Right Alt" AltRight,
F1 "F1" F1, F2 "F2" F2, F3 "F3" F3, F4 "F4" F4, F5 "F5" F5, F6 "F6" F6,
F7 "F7" F7, F8 "F8" F8, F9 "F9" F9, F10 "F10" F10, F11 "F11" F11,
F12 "F12" F12,
Minus "-" Minus, Equal "=" Equal,
BracketLeft "[" BracketLeft, BracketRight "]" BracketRight,
Semicolon ";" Semicolon, Quote "'" Quote, Backquote "`" Backquote,
Backslash "\\" Backslash, Comma "," Comma, Period "." Period,
Slash "/" Slash,
Home "Home" Home, End "End" End,
PageUp "Page Up" PageUp, PageDown "Page Down" PageDown,
Insert "Insert" Insert, Delete "Delete" Delete,
CapsLock "Caps Lock" CapsLock,
Numpad0 "Numpad 0" Numpad0, Numpad1 "Numpad 1" Numpad1,
Numpad2 "Numpad 2" Numpad2, Numpad3 "Numpad 3" Numpad3,
Numpad4 "Numpad 4" Numpad4, Numpad5 "Numpad 5" Numpad5,
Numpad6 "Numpad 6" Numpad6, Numpad7 "Numpad 7" Numpad7,
Numpad8 "Numpad 8" Numpad8, Numpad9 "Numpad 9" Numpad9,
NumpadAdd "Numpad +" NumpadAdd,
NumpadSubtract "Numpad -" NumpadSubtract,
NumpadMultiply "Numpad *" NumpadMultiply,
NumpadDivide "Numpad /" NumpadDivide,
NumpadDecimal "Numpad ." NumpadDecimal,
NumpadEnter "Numpad Enter" NumpadEnter,
NumLock "Num Lock" NumLock,
}
indexed! {
MouseButton, "mouse button" {
Left "Left Mouse",
Right "Right Mouse",
Middle "Middle Mouse",
}
}
indexed! {
Pad, "pad button" {
South "Pad South",
East "Pad East",
West "Pad West",
North "Pad North",
LeftBumper "Left Bumper",
RightBumper "Right Bumper",
LeftTrigger "Left Trigger",
RightTrigger "Right Trigger",
Select "Select",
Start "Start",
Guide "Guide",
LeftStick "Left Stick Press",
RightStick "Right Stick Press",
DPadUp "D-Pad Up",
DPadDown "D-Pad Down",
DPadLeft "D-Pad Left",
DPadRight "D-Pad Right",
}
}
indexed! {
PadAxis, "pad axis" {
LeftX "Left Stick Sideways",
LeftY "Left Stick Up",
RightX "Right Stick Sideways",
RightY "Right Stick Up",
LeftTrigger "Left Trigger",
RightTrigger "Right Trigger",
}
}
listed! {
Stick, "stick" {
Left "Left Stick",
Right "Right Stick",
}
}
controls! {
PointerDelta, "pointer lane" {
Sideways "Pointer Sideways",
Up "Pointer Up",
}
}
impl PointerDelta {
#[cfg(feature = "offscreen")]
pub(crate) fn moving(self, pixels: f32) -> Vec2 {
match self {
Self::Sideways => Vec2::new(pixels, 0.0),
Self::Up => Vec2::new(0.0, pixels),
}
}
}
controls! {
WheelDelta, "wheel lane" {
Sideways "Wheel Sideways",
Up "Wheel Up",
}
}
impl WheelDelta {
#[cfg(feature = "offscreen")]
pub(crate) fn turning(self, notches: f32) -> Vec2 {
match self {
Self::Sideways => Vec2::new(notches, 0.0),
Self::Up => Vec2::new(0.0, notches),
}
}
}
impl Key {
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn ui_key(self) -> Option<egui::Key> {
use egui::Key as Ui;
Some(match self {
Self::A => Ui::A,
Self::B => Ui::B,
Self::C => Ui::C,
Self::D => Ui::D,
Self::E => Ui::E,
Self::F => Ui::F,
Self::G => Ui::G,
Self::H => Ui::H,
Self::I => Ui::I,
Self::J => Ui::J,
Self::K => Ui::K,
Self::L => Ui::L,
Self::M => Ui::M,
Self::N => Ui::N,
Self::O => Ui::O,
Self::P => Ui::P,
Self::Q => Ui::Q,
Self::R => Ui::R,
Self::S => Ui::S,
Self::T => Ui::T,
Self::U => Ui::U,
Self::V => Ui::V,
Self::W => Ui::W,
Self::X => Ui::X,
Self::Y => Ui::Y,
Self::Z => Ui::Z,
Self::Digit0 | Self::Numpad0 => Ui::Num0,
Self::Digit1 | Self::Numpad1 => Ui::Num1,
Self::Digit2 | Self::Numpad2 => Ui::Num2,
Self::Digit3 | Self::Numpad3 => Ui::Num3,
Self::Digit4 | Self::Numpad4 => Ui::Num4,
Self::Digit5 | Self::Numpad5 => Ui::Num5,
Self::Digit6 | Self::Numpad6 => Ui::Num6,
Self::Digit7 | Self::Numpad7 => Ui::Num7,
Self::Digit8 | Self::Numpad8 => Ui::Num8,
Self::Digit9 | Self::Numpad9 => Ui::Num9,
Self::Left => Ui::ArrowLeft,
Self::Right => Ui::ArrowRight,
Self::Up => Ui::ArrowUp,
Self::Down => Ui::ArrowDown,
Self::Space => Ui::Space,
Self::Enter | Self::NumpadEnter => Ui::Enter,
Self::Escape => Ui::Escape,
Self::Tab => Ui::Tab,
Self::Backspace => Ui::Backspace,
Self::F1 => Ui::F1,
Self::F2 => Ui::F2,
Self::F3 => Ui::F3,
Self::F4 => Ui::F4,
Self::F5 => Ui::F5,
Self::F6 => Ui::F6,
Self::F7 => Ui::F7,
Self::F8 => Ui::F8,
Self::F9 => Ui::F9,
Self::F10 => Ui::F10,
Self::F11 => Ui::F11,
Self::F12 => Ui::F12,
Self::Minus | Self::NumpadSubtract => Ui::Minus,
Self::Equal => Ui::Equals,
Self::NumpadAdd => Ui::Plus,
Self::BracketLeft => Ui::OpenBracket,
Self::BracketRight => Ui::CloseBracket,
Self::Semicolon => Ui::Semicolon,
Self::Quote => Ui::Quote,
Self::Backquote => Ui::Backtick,
Self::Backslash => Ui::Backslash,
Self::Comma => Ui::Comma,
Self::Period | Self::NumpadDecimal => Ui::Period,
Self::Slash | Self::NumpadDivide => Ui::Slash,
Self::Home => Ui::Home,
Self::End => Ui::End,
Self::PageUp => Ui::PageUp,
Self::PageDown => Ui::PageDown,
Self::Insert => Ui::Insert,
Self::Delete => Ui::Delete,
Self::LeftShift
| Self::RightShift
| Self::LeftControl
| Self::RightControl
| Self::LeftAlt
| Self::RightAlt
| Self::CapsLock
| Self::NumLock
| Self::NumpadMultiply => return None,
})
}
}
impl MouseButton {
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn ui_button(self) -> egui::PointerButton {
match self {
Self::Left => egui::PointerButton::Primary,
Self::Right => egui::PointerButton::Secondary,
Self::Middle => egui::PointerButton::Middle,
}
}
pub(crate) fn from_winit(button: winit::event::MouseButton) -> Option<Self> {
match button {
winit::event::MouseButton::Left => Some(Self::Left),
winit::event::MouseButton::Right => Some(Self::Right),
winit::event::MouseButton::Middle => Some(Self::Middle),
_ => None,
}
}
}
impl Stick {
pub(crate) fn lanes(self) -> (PadAxis, PadAxis) {
match self {
Self::Left => (PadAxis::LeftX, PadAxis::LeftY),
Self::Right => (PadAxis::RightX, PadAxis::RightY),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ButtonBinding {
Key(Key),
Mouse(MouseButton),
Pad(Pad),
Joystick(JoystickControl),
}
impl From<Key> for ButtonBinding {
fn from(key: Key) -> Self {
Self::Key(key)
}
}
impl From<MouseButton> for ButtonBinding {
fn from(button: MouseButton) -> Self {
Self::Mouse(button)
}
}
impl From<Pad> for ButtonBinding {
fn from(button: Pad) -> Self {
Self::Pad(button)
}
}
impl fmt::Display for ButtonBinding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Key(key) => key.fmt(f),
Self::Mouse(button) => button.fmt(f),
Self::Pad(button) => button.fmt(f),
Self::Joystick(control) => write!(f, "Joystick {control}"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct JoystickControl(u32);
impl JoystickControl {
pub(crate) const fn new(control: u32) -> Self {
Self(control)
}
}
impl fmt::Display for JoystickControl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AxisBinding {
pub(crate) source: AxisSource,
pub(crate) knobs: Knobs,
}
impl AxisBinding {
pub const DEFAULT_DEADZONE: f32 = 0.15;
pub fn pad(axis: PadAxis) -> Self {
Self::of(AxisSource::Pad(axis))
}
pub fn joystick(control: JoystickControl) -> Self {
Self::of(AxisSource::Joystick(control))
}
pub fn pointer_delta(lane: PointerDelta) -> Self {
Self::of(AxisSource::Pointer(lane))
}
pub fn wheel(lane: WheelDelta) -> Self {
Self::of(AxisSource::Wheel(lane))
}
pub(crate) fn resolve(&self, raw: f32) -> f32 {
self.knobs.applied(raw, self.source.reach())
}
fn of(source: AxisSource) -> Self {
Self {
source,
knobs: source.knobs(),
}
}
}
impl From<PadAxis> for AxisBinding {
fn from(axis: PadAxis) -> Self {
Self::pad(axis)
}
}
impl From<PointerDelta> for AxisBinding {
fn from(lane: PointerDelta) -> Self {
Self::pointer_delta(lane)
}
}
impl From<WheelDelta> for AxisBinding {
fn from(lane: WheelDelta) -> Self {
Self::wheel(lane)
}
}
impl fmt::Display for AxisBinding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.source {
AxisSource::Pad(axis) => axis.fmt(f),
AxisSource::Joystick(control) => write!(f, "Joystick Axis {control}"),
AxisSource::Pointer(lane) => lane.fmt(f),
AxisSource::Wheel(lane) => lane.fmt(f),
AxisSource::Buttons { negative, positive } => write!(f, "{negative} / {positive}"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Axis2Binding {
pub(crate) source: Axis2Source,
pub(crate) knobs: Knobs,
}
impl Axis2Binding {
pub fn stick(stick: Stick) -> Self {
Self::of(Axis2Source::Stick(stick))
}
pub fn pointer() -> Self {
Self::of(Axis2Source::Pointer)
}
pub fn wheel() -> Self {
Self::of(Axis2Source::Wheel)
}
pub(crate) fn resolve(&self, raw: Vec2) -> Vec2 {
self.knobs.applied2(raw, self.source.reach())
}
fn of(source: Axis2Source) -> Self {
Self {
source,
knobs: source.knobs(),
}
}
}
impl<L, R, D, U> From<ButtonAxis2<L, R, D, U>> for Axis2Binding
where
L: Into<ButtonBinding>,
R: Into<ButtonBinding>,
D: Into<ButtonBinding>,
U: Into<ButtonBinding>,
{
fn from(quad: ButtonAxis2<L, R, D, U>) -> Self {
Self::of(Axis2Source::Buttons {
left: quad.left.into(),
right: quad.right.into(),
down: quad.down.into(),
up: quad.up.into(),
})
}
}
impl From<Stick> for Axis2Binding {
fn from(stick: Stick) -> Self {
Self::stick(stick)
}
}
impl fmt::Display for Axis2Binding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.source {
Axis2Source::Stick(stick) => stick.fmt(f),
Axis2Source::Pointer => f.write_str("Pointer"),
Axis2Source::Wheel => f.write_str("Wheel"),
Axis2Source::Buttons {
left,
right,
down,
up,
} => write!(f, "{left} / {right} / {down} / {up}"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum AxisSource {
Pad(PadAxis),
Joystick(JoystickControl),
Pointer(PointerDelta),
Wheel(WheelDelta),
Buttons {
negative: ButtonBinding,
positive: ButtonBinding,
},
}
impl AxisSource {
fn reach(self) -> Reach {
match self {
Self::Pointer(_) | Self::Wheel(_) => Reach::Open,
Self::Pad(_) | Self::Joystick(_) | Self::Buttons { .. } => Reach::Unit,
}
}
fn knobs(self) -> Knobs {
match self {
Self::Pad(_) | Self::Joystick(_) => Knobs::at(AxisBinding::DEFAULT_DEADZONE),
Self::Pointer(_) | Self::Wheel(_) | Self::Buttons { .. } => Knobs::at(0.0),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Axis2Source {
Stick(Stick),
Pointer,
Wheel,
Buttons {
left: ButtonBinding,
right: ButtonBinding,
down: ButtonBinding,
up: ButtonBinding,
},
}
impl Axis2Source {
fn reach(self) -> Reach {
match self {
Self::Pointer | Self::Wheel => Reach::Open,
Self::Stick(_) | Self::Buttons { .. } => Reach::Unit,
}
}
fn knobs(self) -> Knobs {
match self {
Self::Stick(_) => Knobs::at(AxisBinding::DEFAULT_DEADZONE),
Self::Pointer | Self::Wheel | Self::Buttons { .. } => Knobs::at(0.0),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Reach {
Unit,
Open,
}
impl Reach {
fn hold(self, value: f32) -> f32 {
match (self, value.is_finite()) {
(_, false) => 0.0,
(Self::Unit, true) => value.clamp(-1.0, 1.0),
(Self::Open, true) => value,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct ButtonAxis<
N: Into<ButtonBinding> = ButtonBinding,
P: Into<ButtonBinding> = ButtonBinding,
> {
pub negative: N,
pub positive: P,
}
#[derive(Clone, Copy, Debug)]
pub struct ButtonAxis2<
L: Into<ButtonBinding> = ButtonBinding,
R: Into<ButtonBinding> = ButtonBinding,
D: Into<ButtonBinding> = ButtonBinding,
U: Into<ButtonBinding> = ButtonBinding,
> {
pub left: L,
pub right: R,
pub down: D,
pub up: U,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Deadzone(f32);
impl Deadzone {
pub(crate) fn new(deadzone: f32) -> Self {
Self(MOST_DEADZONE.min(deadzone.max(0.0)))
}
fn past(self, magnitude: f32) -> f32 {
((magnitude - self.0) / (1.0 - self.0)).max(0.0)
}
pub(crate) fn get(self) -> f32 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Knobs {
pub(crate) deadzone: Deadzone,
pub(crate) scale: f32,
pub(crate) inverted: bool,
}
impl Knobs {
fn at(deadzone: f32) -> Self {
Self {
deadzone: Deadzone::new(deadzone),
scale: 1.0,
inverted: false,
}
}
pub(crate) fn stored(deadzone: Deadzone, scale: f32, inverted: bool) -> Self {
Self {
deadzone,
scale,
inverted,
}
}
fn applied(self, raw: f32, reach: Reach) -> f32 {
reach.hold(self.deadzone.past(raw.abs()).copysign(raw) * self.scale * self.turned())
}
fn applied2(self, raw: Vec2, reach: Reach) -> Vec2 {
let raw = Vec2::new(raw.x, raw.y * self.turned());
let length = raw.length();
if !length.is_finite() || length <= f32::EPSILON {
return Vec2::ZERO;
}
raw / length * reach.hold(self.deadzone.past(length) * self.scale)
}
fn turned(self) -> f32 {
match self.inverted {
true => -1.0,
false => 1.0,
}
}
}
macro_rules! knobs {
($name:ident, $lane:literal) => {
impl $name {
#[must_use]
pub fn deadzone(mut self, deadzone: f32) -> Self {
self.knobs.deadzone = Deadzone::new(deadzone);
self
}
#[must_use]
pub fn scale(mut self, scale: f32) -> Self {
self.knobs.scale = scale;
self
}
#[doc = concat!("Flips ", $lane, ", for a control that reads the other way round.")]
#[must_use]
pub fn invert(mut self) -> Self {
self.knobs.inverted = true;
self
}
}
};
}
knobs!(AxisBinding, "which way the control counts");
knobs!(Axis2Binding, "the upward lane");
impl<N: Into<ButtonBinding>, P: Into<ButtonBinding>> From<ButtonAxis<N, P>> for AxisBinding {
fn from(pair: ButtonAxis<N, P>) -> Self {
Self::of(AxisSource::Buttons {
negative: pair.negative.into(),
positive: pair.positive.into(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn physical_positions_map_to_keys() {
assert_eq!(Key::from_code(KeyCode::KeyW), Some(Key::W));
assert_eq!(Key::from_code(KeyCode::ArrowLeft), Some(Key::Left));
assert_eq!(Key::from_code(KeyCode::ShiftLeft), Some(Key::LeftShift));
assert_eq!(
Key::from_code(KeyCode::F13),
None,
"keys we skip are ignored"
);
}
#[test]
fn the_punctuation_and_pad_positions_read_and_show_like_the_rest() {
assert_eq!(Key::from_code(KeyCode::BracketLeft), Some(Key::BracketLeft));
assert_eq!(Key::BracketLeft.to_string(), "[");
assert_eq!(Key::from_code(KeyCode::NumpadAdd), Some(Key::NumpadAdd));
assert_eq!(Key::NumpadAdd.to_string(), "Numpad +");
}
#[test]
fn a_control_answers_to_its_own_name_and_shows_a_readable_one() {
assert_eq!(Key::from_token("LeftShift"), Some(Key::LeftShift));
assert_eq!(Key::LeftShift.token(), "LeftShift");
assert_eq!(Key::LeftShift.to_string(), "Left Shift");
assert_eq!(PadAxis::from_token("Left Trigger"), None, "names are exact");
assert_eq!(PadAxis::RightTrigger.to_string(), "Right Trigger");
}
#[test]
fn a_deadzone_starts_the_range_where_it_ends() {
let stick = AxisBinding::pad(PadAxis::LeftX).deadzone(0.5);
assert_eq!(stick.resolve(0.5), 0.0, "the edge reads as nothing");
assert_eq!(stick.resolve(0.25), 0.0, "and so does anything under");
assert_eq!(stick.resolve(1.0), 1.0, "the far end still reaches");
assert_eq!(stick.resolve(-0.75), -0.5, "in both directions");
}
#[test]
fn a_control_with_a_range_of_its_own_keeps_to_it_however_the_knobs_are_set() {
let lane = AxisBinding::pad(PadAxis::LeftX).deadzone(0.0).scale(100.0);
assert_eq!(lane.resolve(1.0), 1.0);
assert_eq!(lane.resolve(-1.0), -1.0);
let quad = Axis2Binding::from(ButtonAxis2 {
left: Key::A,
right: Key::D,
down: Key::S,
up: Key::W,
})
.scale(100.0);
assert_eq!(quad.resolve(Vec2::X), Vec2::X);
let broken = AxisBinding::pad(PadAxis::LeftX).scale(f32::NAN);
assert_eq!(broken.resolve(1.0), 0.0, "and stays a number");
let deep = AxisBinding::pad(PadAxis::LeftX).deadzone(4.0);
assert_eq!(deep.knobs.deadzone.get(), MOST_DEADZONE);
}
#[test]
fn a_delta_binding_reads_the_whole_distance_its_scale_makes_of_it() {
let look = AxisBinding::pointer_delta(PointerDelta::Sideways).scale(0.01);
assert_eq!(look.resolve(500.0), 5.0);
assert_eq!(look.resolve(-500.0), -5.0);
let wheel = AxisBinding::wheel(WheelDelta::Up).scale(100.0);
assert_eq!(wheel.resolve(4.0), 400.0);
let pointer = Axis2Binding::pointer().scale(0.01);
assert_eq!(pointer.resolve(Vec2::new(500.0, 0.0)), Vec2::new(5.0, 0.0));
let broken = AxisBinding::pointer_delta(PointerDelta::Up).scale(f32::INFINITY);
assert_eq!(broken.resolve(500.0), 0.0, "and stays a number");
}
#[test]
fn inverting_turns_an_axis_round_and_a_vector_upside_down() {
let axis = AxisBinding::pad(PadAxis::LeftY).deadzone(0.0).invert();
assert_eq!(axis.resolve(0.5), -0.5);
let stick = Axis2Binding::stick(Stick::Left).deadzone(0.0).invert();
assert_eq!(stick.resolve(Vec2::new(1.0, 0.0)), Vec2::X);
assert_eq!(stick.resolve(Vec2::new(0.0, 1.0)), Vec2::NEG_Y);
}
#[test]
fn a_vector_reaches_no_further_than_one_however_far_it_is_pushed() {
let quad = Axis2Binding::from(ButtonAxis2 {
left: Key::A,
right: Key::D,
down: Key::S,
up: Key::W,
});
let diagonal = quad.resolve(Vec2::ONE);
assert!(
(diagonal.length() - 1.0).abs() < 1e-6,
"{diagonal} is one long"
);
assert!((diagonal.x - diagonal.y).abs() < 1e-6, "and still diagonal");
assert_eq!(quad.resolve(Vec2::X), Vec2::X, "a straight one is whole");
assert_eq!(quad.resolve(Vec2::ZERO), Vec2::ZERO);
}
#[test]
fn a_binding_shows_the_control_a_menu_would_name() {
assert_eq!(ButtonBinding::from(Key::Space).to_string(), "Space");
assert_eq!(ButtonBinding::from(Pad::South).to_string(), "Pad South");
assert_eq!(
ButtonBinding::Joystick(JoystickControl::new(7)).to_string(),
"Joystick 7"
);
assert_eq!(
AxisBinding::from(ButtonAxis {
negative: Key::A,
positive: Key::D
})
.to_string(),
"A / D"
);
assert_eq!(
AxisBinding::from(PointerDelta::Sideways).to_string(),
"Pointer Sideways"
);
assert_eq!(AxisBinding::from(WheelDelta::Up).to_string(), "Wheel Up");
assert_eq!(
AxisBinding::from(WheelDelta::Sideways).to_string(),
"Wheel Sideways"
);
assert_eq!(Axis2Binding::from(Stick::Left).to_string(), "Left Stick");
assert_eq!(
Axis2Binding::from(ButtonAxis2 {
left: Key::A,
right: Key::D,
down: Key::S,
up: Key::W
})
.to_string(),
"A / D / S / W"
);
}
}