use glam::Vec2;
#[cfg(feature = "window")]
use winit::keyboard::KeyCode;
macro_rules! define_keys {
($($(#[$meta:meta])* $name:ident => $code:ident),* $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
#[allow(missing_docs)] pub enum Key {
$($(#[$meta])* $name),*
}
impl Key {
pub(crate) const COUNT: usize = [$(Key::$name),*].len();
#[cfg(feature = "window")]
pub(crate) fn from_winit(code: KeyCode) -> Option<Key> {
match code {
$(KeyCode::$code => Some(Key::$name),)*
_ => None,
}
}
}
};
}
define_keys! {
A => KeyA, B => KeyB, C => KeyC, D => KeyD, E => KeyE, F => KeyF, G => KeyG,
H => KeyH, I => KeyI, J => KeyJ, K => KeyK, L => KeyL, M => KeyM, N => KeyN,
O => KeyO, P => KeyP, Q => KeyQ, R => KeyR, S => KeyS, T => KeyT, U => KeyU,
V => KeyV, W => KeyW, X => KeyX, Y => KeyY, Z => KeyZ,
Num0 => Digit0, Num1 => Digit1, Num2 => Digit2, Num3 => Digit3, Num4 => Digit4,
Num5 => Digit5, Num6 => Digit6, Num7 => Digit7, Num8 => Digit8, Num9 => Digit9,
F1 => F1, F2 => F2, F3 => F3, F4 => F4, F5 => F5, F6 => F6,
F7 => F7, F8 => F8, F9 => F9, F10 => F10, F11 => F11, F12 => F12,
Up => ArrowUp, Down => ArrowDown, Left => ArrowLeft, Right => ArrowRight,
Space => Space, Enter => Enter, Escape => Escape, Tab => Tab,
Backspace => Backspace, CapsLock => CapsLock,
Insert => Insert, Delete => Delete, Home => Home, End => End,
PageUp => PageUp, PageDown => PageDown,
LeftShift => ShiftLeft, RightShift => ShiftRight,
LeftCtrl => ControlLeft, RightCtrl => ControlRight,
LeftAlt => AltLeft, RightAlt => AltRight,
LeftSuper => SuperLeft,
RightSuper => SuperRight,
Minus => Minus, Equal => Equal,
LeftBracket => BracketLeft, RightBracket => BracketRight, Backslash => Backslash,
Semicolon => Semicolon, Apostrophe => Quote, Backquote => Backquote,
Comma => Comma, Period => Period, Slash => Slash,
Numpad0 => Numpad0, Numpad1 => Numpad1, Numpad2 => Numpad2, Numpad3 => Numpad3,
Numpad4 => Numpad4, Numpad5 => Numpad5, Numpad6 => Numpad6, Numpad7 => Numpad7,
Numpad8 => Numpad8, Numpad9 => Numpad9,
NumpadAdd => NumpadAdd, NumpadSubtract => NumpadSubtract,
NumpadMultiply => NumpadMultiply, NumpadDivide => NumpadDivide,
NumpadDecimal => NumpadDecimal, NumpadEnter => NumpadEnter,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum MouseButton {
Left,
Right,
Middle,
Back,
Forward,
}
impl MouseButton {
pub(crate) const COUNT: usize = 5;
#[cfg(feature = "window")]
pub(crate) fn from_winit(button: winit::event::MouseButton) -> Option<MouseButton> {
use winit::event::MouseButton as W;
match button {
W::Left => Some(MouseButton::Left),
W::Right => Some(MouseButton::Right),
W::Middle => Some(MouseButton::Middle),
W::Back => Some(MouseButton::Back),
W::Forward => Some(MouseButton::Forward),
W::Other(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum GamepadButton {
South,
East,
North,
West,
LeftBumper,
RightBumper,
LeftTrigger,
RightTrigger,
Select,
Start,
LeftStick,
RightStick,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
}
impl GamepadButton {
pub(crate) const COUNT: usize = 16;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum GamepadAxis {
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
LeftTrigger,
RightTrigger,
}
impl GamepadAxis {
pub(crate) const COUNT: usize = 6;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum GamepadStick {
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(try_from = "u8", into = "u8")]
pub struct Gamepad(pub(crate) u8);
impl TryFrom<u8> for Gamepad {
type Error = &'static str;
fn try_from(slot: u8) -> Result<Self, Self::Error> {
Self::from_slot(usize::from(slot)).ok_or("gamepad slot out of range")
}
}
impl From<Gamepad> for u8 {
fn from(pad: Gamepad) -> u8 {
pad.0
}
}
impl Gamepad {
#[must_use]
pub fn slot(self) -> usize {
usize::from(self.0)
}
#[must_use]
pub fn from_slot(slot: usize) -> Option<Gamepad> {
(slot < MAX_GAMEPADS).then_some(Gamepad(slot as u8))
}
}
pub const MAX_GAMEPADS: usize = 8;
#[derive(Debug, Clone)]
pub(super) struct PadState {
pub(super) buttons: ButtonStates<{ GamepadButton::COUNT }>,
pub(super) axes: [f32; GamepadAxis::COUNT],
}
impl PadState {
const fn new() -> Self {
Self {
buttons: ButtonStates::new(),
axes: [0.0; GamepadAxis::COUNT],
}
}
}
#[derive(Debug, Clone)]
pub(super) struct ButtonStates<const N: usize> {
pub(super) down: [bool; N],
pressed: [bool; N],
released: [bool; N],
}
impl<const N: usize> ButtonStates<N> {
const fn new() -> Self {
Self {
down: [false; N],
pressed: [false; N],
released: [false; N],
}
}
fn set(&mut self, i: usize, is_down: bool) {
match (self.down[i], is_down) {
(false, true) => self.pressed[i] = true,
(true, false) => self.released[i] = true,
_ => {}
}
self.down[i] = is_down;
}
fn release_all(&mut self) {
for i in 0..N {
self.set(i, false);
}
}
fn end_frame(&mut self) {
self.pressed = [false; N];
self.released = [false; N];
}
}
#[derive(Debug, Clone)]
pub(crate) struct Input {
pub(super) keys: ButtonStates<{ Key::COUNT }>,
pub(super) mouse: ButtonStates<{ MouseButton::COUNT }>,
mouse_pos: Vec2,
mouse_pos_at_frame_start: Vec2,
mouse_wheel: Vec2,
typed_text: String,
mouse_motion: Vec2,
pub(super) pads: [PadState; MAX_GAMEPADS],
connected: Vec<Gamepad>,
pub(crate) actions: crate::ActionMap,
}
impl Input {
pub fn new() -> Self {
Self {
keys: ButtonStates::new(),
mouse: ButtonStates::new(),
mouse_pos: Vec2::ZERO,
mouse_pos_at_frame_start: Vec2::ZERO,
mouse_wheel: Vec2::ZERO,
typed_text: String::new(),
mouse_motion: Vec2::ZERO,
pads: [const { PadState::new() }; MAX_GAMEPADS],
connected: Vec::with_capacity(MAX_GAMEPADS),
actions: crate::ActionMap::default(),
}
}
pub fn set_key(&mut self, key: Key, is_down: bool) {
self.keys.set(key as usize, is_down);
self.refresh_actions();
}
pub fn set_mouse_button(&mut self, button: MouseButton, is_down: bool) {
self.mouse.set(button as usize, is_down);
self.refresh_actions();
}
pub fn set_mouse_pos(&mut self, pos: Vec2) {
self.mouse_pos = pos;
}
pub fn add_text(&mut self, text: &str) {
self.typed_text.push_str(text);
}
pub fn typed_text(&self) -> &str {
&self.typed_text
}
pub fn add_mouse_wheel(&mut self, delta: Vec2) {
self.mouse_wheel += delta;
}
pub fn add_mouse_motion(&mut self, delta: Vec2) {
self.mouse_motion += delta;
}
pub fn connect_gamepad(&mut self) -> Option<Gamepad> {
let slot = (0..MAX_GAMEPADS as u8).find(|&s| !self.connected.contains(&Gamepad(s)))?;
let pad = Gamepad(slot);
self.pads[usize::from(slot)] = PadState::new();
let at = self.connected.partition_point(|p| p.0 < slot);
self.connected.insert(at, pad);
self.refresh_actions();
Some(pad)
}
pub fn disconnect_gamepad(&mut self, pad: Gamepad) {
self.connected.retain(|&p| p != pad);
let state = &mut self.pads[pad.slot()];
state.buttons.release_all();
state.axes = [0.0; GamepadAxis::COUNT];
self.refresh_actions();
}
pub fn set_gamepad_button(&mut self, pad: Gamepad, button: GamepadButton, is_down: bool) {
self.pads[pad.slot()].buttons.set(button as usize, is_down);
self.refresh_actions();
}
pub fn set_gamepad_axis(&mut self, pad: Gamepad, axis: GamepadAxis, value: f32) {
self.pads[pad.slot()].axes[axis as usize] = value;
self.refresh_actions();
}
pub fn release_all(&mut self) {
self.keys.release_all();
self.mouse.release_all();
self.refresh_actions();
}
pub fn end_frame(&mut self) {
self.actions.end_frame();
self.keys.end_frame();
self.mouse.end_frame();
for pad in &mut self.pads {
pad.buttons.end_frame();
}
self.typed_text.clear();
self.mouse_wheel = Vec2::ZERO;
self.mouse_motion = Vec2::ZERO;
self.mouse_pos_at_frame_start = self.mouse_pos;
}
pub(crate) fn refresh_actions(&mut self) {
self.actions.refresh(&self.keys, &self.mouse, &self.pads);
}
pub fn key_down(&self, key: Key) -> bool {
self.keys.down[key as usize]
}
pub fn key_pressed(&self, key: Key) -> bool {
self.keys.pressed[key as usize]
}
pub fn key_released(&self, key: Key) -> bool {
self.keys.released[key as usize]
}
pub fn mouse_down(&self, button: MouseButton) -> bool {
self.mouse.down[button as usize]
}
pub fn mouse_pressed(&self, button: MouseButton) -> bool {
self.mouse.pressed[button as usize]
}
pub fn mouse_released(&self, button: MouseButton) -> bool {
self.mouse.released[button as usize]
}
pub fn mouse_pos(&self) -> Vec2 {
self.mouse_pos
}
pub fn mouse_delta(&self) -> Vec2 {
self.mouse_pos - self.mouse_pos_at_frame_start
}
pub fn mouse_wheel(&self) -> Vec2 {
self.mouse_wheel
}
pub fn mouse_motion(&self) -> Vec2 {
self.mouse_motion
}
pub fn gamepads(&self) -> &[Gamepad] {
&self.connected
}
pub fn gamepad_down(&self, pad: Gamepad, button: GamepadButton) -> bool {
self.pads[pad.slot()].buttons.down[button as usize]
}
pub fn gamepad_pressed(&self, pad: Gamepad, button: GamepadButton) -> bool {
self.pads[pad.slot()].buttons.pressed[button as usize]
}
pub fn gamepad_released(&self, pad: Gamepad, button: GamepadButton) -> bool {
self.pads[pad.slot()].buttons.released[button as usize]
}
pub fn gamepad_axis(&self, pad: Gamepad, axis: GamepadAxis) -> f32 {
self.pads[pad.slot()].axes[axis as usize]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn press_hold_release_cycle() {
let mut input = Input::new();
input.set_key(Key::Space, true);
assert!(input.key_down(Key::Space));
assert!(input.key_pressed(Key::Space));
assert!(!input.key_released(Key::Space));
input.end_frame();
assert!(input.key_down(Key::Space));
assert!(!input.key_pressed(Key::Space));
input.end_frame();
input.set_key(Key::Space, false);
assert!(!input.key_down(Key::Space));
assert!(input.key_released(Key::Space));
input.end_frame();
assert!(!input.key_released(Key::Space));
}
#[test]
fn tap_within_one_frame_is_not_lost() {
let mut input = Input::new();
input.set_key(Key::A, true);
input.set_key(Key::A, false);
assert!(input.key_pressed(Key::A));
assert!(input.key_released(Key::A));
assert!(!input.key_down(Key::A));
}
#[test]
fn repeated_down_events_do_not_retrigger_pressed() {
let mut input = Input::new();
input.set_key(Key::W, true);
input.end_frame();
input.set_key(Key::W, true);
assert!(!input.key_pressed(Key::W));
}
#[test]
fn release_all_clears_held_keys_and_buttons() {
let mut input = Input::new();
input.set_key(Key::Left, true);
input.set_mouse_button(MouseButton::Right, true);
input.end_frame();
input.release_all();
assert!(!input.key_down(Key::Left));
assert!(input.key_released(Key::Left));
assert!(!input.mouse_down(MouseButton::Right));
assert!(input.mouse_released(MouseButton::Right));
assert!(!input.key_released(Key::Right));
}
#[test]
fn mouse_delta_and_wheel_reset_each_frame() {
let mut input = Input::new();
input.set_mouse_pos(Vec2::new(10.0, 20.0));
input.end_frame();
input.set_mouse_pos(Vec2::new(15.0, 18.0));
input.add_mouse_wheel(Vec2::new(0.0, 1.0));
input.add_mouse_wheel(Vec2::new(0.0, 2.0));
assert_eq!(input.mouse_delta(), Vec2::new(5.0, -2.0));
assert_eq!(input.mouse_wheel(), Vec2::new(0.0, 3.0));
input.end_frame();
assert_eq!(input.mouse_delta(), Vec2::ZERO);
assert_eq!(input.mouse_wheel(), Vec2::ZERO);
assert_eq!(input.mouse_pos(), Vec2::new(15.0, 18.0));
}
#[test]
fn raw_mouse_motion_adds_up_and_resets_each_frame() {
let mut input = Input::new();
input.add_mouse_motion(Vec2::new(3.0, -1.0));
input.add_mouse_motion(Vec2::new(2.0, 4.0));
assert_eq!(input.mouse_delta(), Vec2::ZERO);
assert_eq!(input.mouse_motion(), Vec2::new(5.0, 3.0));
input.end_frame();
assert_eq!(input.mouse_motion(), Vec2::ZERO);
}
#[test]
fn gamepads_take_the_lowest_free_slot() {
let mut input = Input::new();
let a = input.connect_gamepad().unwrap();
let b = input.connect_gamepad().unwrap();
assert_eq!((a.slot(), b.slot()), (0, 1));
input.disconnect_gamepad(a);
assert_eq!(input.gamepads(), [b]);
let c = input.connect_gamepad().unwrap();
assert_eq!(c.slot(), 0, "a new pad fills the gap");
assert_eq!(input.gamepads(), [c, b], "sorted by slot");
for _ in 2..MAX_GAMEPADS {
input.connect_gamepad().unwrap();
}
assert_eq!(input.connect_gamepad(), None, "all slots taken");
}
#[test]
fn gamepad_buttons_and_axes() {
let mut input = Input::new();
let pad = input.connect_gamepad().unwrap();
input.set_gamepad_button(pad, GamepadButton::South, true);
input.set_gamepad_axis(pad, GamepadAxis::LeftStickX, -0.5);
assert!(input.gamepad_pressed(pad, GamepadButton::South));
assert!(input.gamepad_down(pad, GamepadButton::South));
assert_eq!(input.gamepad_axis(pad, GamepadAxis::LeftStickX), -0.5);
input.end_frame();
assert!(!input.gamepad_pressed(pad, GamepadButton::South));
assert!(input.gamepad_down(pad, GamepadButton::South));
assert_eq!(input.gamepad_axis(pad, GamepadAxis::LeftStickX), -0.5, "axes hold");
}
#[test]
fn unplugging_releases_buttons_and_centers_sticks() {
let mut input = Input::new();
let pad = input.connect_gamepad().unwrap();
input.set_gamepad_button(pad, GamepadButton::Start, true);
input.set_gamepad_axis(pad, GamepadAxis::RightTrigger, 1.0);
input.end_frame();
input.disconnect_gamepad(pad);
assert!(input.gamepad_released(pad, GamepadButton::Start));
assert!(!input.gamepad_down(pad, GamepadButton::Start));
assert_eq!(input.gamepad_axis(pad, GamepadAxis::RightTrigger), 0.0);
assert!(input.gamepads().is_empty());
}
#[test]
#[cfg(feature = "window")]
fn winit_mapping() {
assert_eq!(Key::from_winit(KeyCode::KeyW), Some(Key::W));
assert_eq!(Key::from_winit(KeyCode::ArrowLeft), Some(Key::Left));
assert_eq!(Key::from_winit(KeyCode::Digit5), Some(Key::Num5));
assert_eq!(Key::from_winit(KeyCode::MediaPlayPause), None);
assert_eq!(
MouseButton::from_winit(winit::event::MouseButton::Middle),
Some(MouseButton::Middle)
);
}
}