winit-input-map 0.3.0

Input Map for Winit
Documentation
use winit::{
    dpi::PhysicalPosition,
    event::*,
    keyboard::{KeyCode, PhysicalKey}
};
/// you can use anything that implements the `Into<usize>` trait as an action, but it's recommended 
/// to use an action enum which derives `ToUsize`. `input_map!` macro reduces the boilerplate of
/// this function
/// ```
/// #[derive(ToUsize)] // could also manualy implement Into<usize>
/// enum Actions{
///     Debug,
///     Left,
///     Right,
///     Click
/// }
/// use winit::{event::*, keyboard::KeyCode, event_loop::*, window::Window};
/// use winit_input_map::*;
/// use Actions::*;
///
/// let mut input = InputMap::new([ // doesnt have to be in the same order as the enum
///     (Debug, vec![Input::keycode(KeyCode::Space)]),
///     (Click, vec![Input::Mouse(MouseButton::Left)]),
///     (Left,  vec![Input::keycode(KeyCode::ArrowLeft), Input::keycode(KeyCode::KeyA)]),
///     (Right, vec![Input::keycode(KeyCode::ArrowRight), Input::keycode(KeyCode::KeyD)]),
/// ]);
/// 
/// let event_loop = EventLoop::new().unwrap();
/// event_loop.set_control_flow(ControlFlow::Poll);
/// let _window = Window::new(&event_loop).unwrap();
///
/// event_loop.run(|event, target|{
///     input.update(&event);
///     match &event {
///         Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { target.exit() },
///         Event::AboutToWait => {
///             if input.pressed(Debug) { println!("pressed {:?}", input.binds(Debug)) }
///             if input.pressing(Right) || input.pressing(Left) { 
///                 println!("axis: {}", input.axis(Right, Left)) 
///             }
///             if input.mouse_move != (0.0, 0.0) {
///                 println!("mouse moved: {:?} and is now at {:?}", input.mouse_move, input.mouse_pos)
///             }
///             if input.released(Click) { println!("released {:?}", input.binds(Click)) }
///             
///             std::thread::sleep(std::time::Duration::from_millis(100));
///             //put at end of loop because were done with inputs this loop.
///             input.init();
///         }
///         _ => ()
///     }
/// }).unwrap();
/// ```
pub struct InputMap<const BINDS: usize> {
    pub binds: [Vec<Input>; BINDS],
    pub pressing: [bool; BINDS],
    pub pressed:  [bool; BINDS],
    pub released: [bool; BINDS],
    /// the amount the scroll wheel has changed
    pub mouse_scroll: f32,
    pub mouse_move: Vec2,
    pub mouse_pos: Vec2,
    /// last input even if it isnt in the binds. useful for rebinding.
    pub other_pressed: Option<Input>,
    /// the text typed this loop. useful for typing
    pub text_typed: Option<String>
}
impl<const BINDS: usize> InputMap<BINDS> {
    /// create new input system. recommended to use an action enum which implements the 
    /// `Into<usize>` trait. the `input_map!` macro reduces boilerplate.
    /// ```
    /// use Action::*;
    /// use input::*;
    /// use winit::keyboard::KeyCode;
    /// #[derive(ToUsize)]
    /// enum Action {
    ///     Forward,
    ///     Back,
    ///     Left,
    ///     Right
    /// }
    /// //doesnt have to be the same ordered as the enum.
    /// let mut input = Input::new([
    ///     (vec![Input::keycode(KeyCode::KeyW)], Forward),
    ///     (vec![Input::keycode(KeyCode::KeyA)], Left),
    ///     (vec![Input::keycode(KeyCode::KeyS)], Back),
    ///     (vec![Input::keycode(KeyCode::KeyD)], Right)
    /// ]);
    /// ```
    pub fn new(binds: [(impl Into<usize>, Vec<Input>); BINDS]) -> Self {
        const NONE: Vec<Input> = Vec::new();
        let mut temp_binds = [NONE; BINDS];
        for (i, binds) in binds {
            let i = i.into();
            if binds.is_empty() {
                println!("no binds for {i:?}")
            }
            if i >= BINDS {
                panic!("input action is larger than bounds of array.")
            }
            temp_binds[i] = binds;
        }
        Self {
            binds: temp_binds,
            pressing: [false; BINDS],
            pressed:  [false; BINDS],
            released: [false; BINDS],
            mouse_scroll: 0.0,
            mouse_move: v(0.0, 0.0),
            mouse_pos:  v(0.0, 0.0),
            other_pressed: None,
            text_typed:    None
        }
    }
    /// use if you dont want to have any action. will still have access to everythin else
    pub fn empty() -> InputMap<0> {
        InputMap {
            mouse_scroll: 0.0,
            mouse_move: v(0.0, 0.0),
            mouse_pos:  v(0.0, 0.0),
            other_pressed: None,
            text_typed:    None,
            binds:    [],
            pressing: [],
            pressed:  [],
            released: []
        }
    }
    /// updates the input using a winit event. make sure to call `input.init()` when your done with
    /// the input this loop.
    /// ```no_run
    /// use winit::{event::*, window::Window, event_loop::EventLoop};
    /// use winit_input_map::InputMap;
    ///
    /// let mut event_loop = EventLoop::new().unwrap();
    /// event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
    /// let _window = Window::new(&event_loop).unwrap();
    ///
    /// let mut input = input_map!();
    ///
    /// event_loop.run(|event, target|{
    ///     input.update(&event);
    ///     match &event{
    ///         Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => target.exit(),
    ///         Event::AboutToWait => input.init(),
    ///         _ => ()
    ///     }
    /// });
    /// ```
    pub fn update(&mut self, event: &Event<()>) {
        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::CursorMoved { position, .. } => {
                    self.update_mouse(*position);
                }
                WindowEvent::MouseInput { state, button, .. } => {
                    self.update_buttons(state, *button)
                }
                WindowEvent::KeyboardInput { event, .. } => self.update_keys(event),
                _ => (),
            },
            Event::DeviceEvent { event, .. } => match event {
                DeviceEvent::MouseMotion { delta } => self.update_mouse_move(*delta),
                DeviceEvent::MouseWheel { delta } => self.mouse_scroll += match delta {
                    MouseScrollDelta::LineDelta(_, change) => *change,
                    MouseScrollDelta::PixelDelta(PhysicalPosition { y: change, .. }) => *change as f32
                },
                _ => (),
            },
            _ => (),
        }
    }
    /// initialise input. use when your done with input this loop. required to be called for 
    /// everything except `pressing` and `mouse_pos` to work properly.
    /// ```no_run
    /// use winit::{event::*, window::Window, event_loop::EventLoop};
    /// use winit_input_map::InputMap;
    ///
    /// let mut event_loop = EventLoop::new().unwrap();
    /// event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
    /// let _window = Window::new(&event_loop).unwrap();
    ///
    /// let mut input = input_map!();
    ///
    /// event_loop.run(|event, target| {
    ///     input.update(&event);
    ///     match &event{
    ///         Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => target.exit(),
    ///         Event::AboutToWait => input.init(),
    ///         _ => ()
    ///     }
    /// });
    /// ```
    pub fn init(&mut self) {
        self.mouse_move = v(0.0, 0.0);
        self.pressed = [false; BINDS];
        self.released = [false; BINDS];
        self.mouse_scroll  = 0.0;
        self.text_typed    = None;
        self.other_pressed = None;
    }
    /// you should use `self.update()` instead
    fn update_mouse(&mut self, position: PhysicalPosition<f64>) {
        self.mouse_pos = v(position.x as f32, position.y as f32);
    }

    fn update_mouse_move(&mut self, delta: (f64, f64)) {
        self.mouse_move = v(delta.0 as f32, delta.1 as f32);
    }
    /// you should use `self.update()` instead
    fn update_keys(&mut self, event: &KeyEvent) {
        let input_code = Input::Key(event.physical_key);
        self.other_pressed = Some(input_code);

        if let (Some(string), Some(new)) = (&mut self.text_typed, &event.text) {
            string.push_str(new);
        } else { self.text_typed = event.text.as_ref().map(|i| i.to_string()) }

        for (i, key) in self.binds.iter().enumerate() {
            if key.contains(&input_code) {
                self.pressed[i] = event.state.is_pressed() && !self.pressing[i];
                self.released[i] = !event.state.is_pressed() && self.pressing[i];
                self.pressing[i] = event.state.is_pressed();
            }
        }
    }
    /// you should use `self.update()` instead
    fn update_buttons(&mut self, state: &ElementState, button: MouseButton) {
        let input_code = Input::Mouse(button);
        self.other_pressed = Some(input_code);
        self.update_val(input_code, state.is_pressed());
    }
    /// updates based off of provided input code and bool, where true means just pressed and false
    /// means *just* released
    fn update_val(&mut self, input_code: Input, pressed: bool) {
        for (i, key) in self.binds.iter().enumerate() {
            if key.contains(&input_code) {
                self.pressed[i] = pressed && !self.pressing[i];
                self.released[i] = !pressed && self.pressing[i];
                self.pressing[i] = pressed;
            }
        }
    }
    #[cfg(feature = "gamepad")]
    fn update_gamepad(&mut self, event: gilrs::Event) {
        let gilrs::Event { id, event, .. } = event;
        let id = SpecifyGamepad::Id(id);

        use gilrs::ev::EventType;
        let mut pressed = false;
        let mut released = false;
        match event {
            EventType::ButtonPressed(b, _) => {
                let input = GamepadInput::Button(b);
                self.update_val(Input::Gamepad { id, input }, true);
                self.update_val(Input::Gamepad { id: SpecifyGamepad::Any, input }, true);
            },
            EventType::ButtonReleased(b, _) => {
                let input = GamepadInput::Button(b);
                self.update_val(Input::Gamepad { id, input }, false);
                self.update_val(Input::Gamepad { id: SpecifyGamepad::Any, input }, false);
            },
            EventType::AxisChanged(b, v, _) => {
                use GamepadInput::Axis;
                use Direction::*;
                let dir_right = v.is_sign_negative();
                let dir_left = !dir_right && v != 0.0;
                self.update_val(Input::Gamepad { id, input: Axis(b, Left) }, dir_left);
                self.update_val(Input::Gamepad { id: SpecifyGamepad::Any, input: Axis(b, Left) }, dir_left);

                self.update_val(Input::Gamepad { id, input: Axis(b, Right) }, dir_right);
                self.update_val(Input::Gamepad { id: SpecifyGamepad::Any, input: Axis(b, Right) }, dir_right);
            }
            _ => ()
        }
    }
    /// get binds of action. same as `self.binds[action.into()]`
    pub fn binds(&mut self, action: impl Into<usize>) -> &mut Vec<Input> {
        &mut self.binds[action.into()]
    }
    /// checks if action is being pressed currently. same as `self.pressing[action.into()]`
    pub fn pressing(&self, action: impl Into<usize>) -> bool {
        self.pressing[action.into()]
    }
    /// checks if action was just pressed. same as `self.pressed[action.into()]`
    pub fn pressed(&self, action: impl Into<usize>) -> bool {
        self.pressed[action.into()]
    }
    /// checks if action was just released. same as `self.released[action.into()]`
    pub fn released(&self, action: impl Into<usize>) -> bool {
        self.released[action.into()]
    }
    /// returns 1.0 if pos is pressed, -1.0 if neg is pressed or 0.0 if either pos and neg
    /// or nothing is pressed. usefull for movement controls.
    /// ```
    /// let move_dir = (input.axis(Right, Left), input.axis(Up, Down));
    /// ```
    pub fn axis(&self, pos: impl Into<usize>, neg: impl Into<usize>) -> f32 {
        (self.pressing(pos) as i8 - self.pressing(neg) as i8) as f32
    }
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Input {
    Key(PhysicalKey),
    Mouse(MouseButton),
    #[cfg(feature = "gamepad")]
    Gamepad { id: SpecifyGamepad, input: GamepadInput  }
}
impl Input {
    pub const fn keycode(key: KeyCode) -> Self {
        Self::Key(PhysicalKey::Code(key))
    }
}
impl From<KeyCode> for Input {
    fn from(value: KeyCode) -> Input {
        Self::keycode(value)
    }
}
impl From<PhysicalKey> for Input {
    fn from(value: PhysicalKey) -> Input {
        Self::Key(value)
    }
}
impl From<MouseButton> for Input {
    fn from(value: MouseButton) -> Input {
        Self::Mouse(value)
    }
}

#[cfg(feature = "gamepad")] 
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum GamepadInput {
    Button(gilrs::ev::Button),
    Axis(gilrs::ev::Axis, Direction)
}
#[cfg(feature = "gamepad")]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Direction {
    Left,
    Right
}
/// specify gamepad
#[cfg(feature = "gamepad")]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum SpecifyGamepad {
    /// cant be set at compile time. use `Any` as default and then let the user select a specific
    /// gamepad
    Id(gilrs::GamepadId),
    /// use as default
    #[default]
    Any
}
#[cfg(not(feature = "glium-types"))]
type Vec2 = (f32, f32);
#[cfg(feature = "glium-types")]
type Vec2 = glium_types::vectors::Vec2;
fn v(a: f32, b: f32) -> Vec2 {
    #[cfg(not(feature = "glium-types"))]
    { (a, b) }
    #[cfg(feature = "glium-types")]
    { Vec2::new(a, b) }
}