//! This module helps us deal with controls and events. use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use crate::{log, Result}; /// The different actions that a user can do. #[derive(Debug, Copy, Clone)] pub enum Event { /// A button has been pressed. ButtonPressed(Button), /// A button has been released. ButtonReleased(Button), } /// The different actions that a user can do. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Button { /// The user asks to go to the left. Left, /// The user asks to go to the right. Right, /// The user asks to go up. Up, /// The user asks to go down. Down, /// The main action or validatation button. Button0, /// The user asks to do the secondary action. Button1, /// The user asks to do the tertiary action. Button2, /// The user asks to do the 4th action or cancel. Button3, } /// This structure holds what button are pressed or released, and stores the events. pub struct InputManager { /// All the connected gamepads. gamepads: Vec, /// The events that have occur. events: VecDeque, } impl InputManager { /// Creates a new input manager. pub fn new() -> InputManager { InputManager { gamepads: vec![], events: VecDeque::new(), } } /// Adds a gamepad to the input manager. pub fn add_gamepad(&mut self, gamepad: web_sys::Gamepad) { log!("Gamepad added {}", gamepad.id()); self.gamepads.push(Gamepad::new(gamepad)); } /// Updates the gamepad states. pub fn update(&mut self) { for gamepad in &mut self.gamepads { gamepad.update(&mut self.events); } } } /// Holds the gamepad information. #[derive(Clone)] pub struct Gamepad { /// The javascript gamepad object. inner: web_sys::Gamepad, /// What buttons are pressed or released. state: HashMap, } impl Gamepad { /// Creates a new gamepad from its javascript gamepad. pub fn new(inner: web_sys::Gamepad) -> Gamepad { Gamepad { inner, state: HashMap::new(), } } /// Updates the state of the gamepad and adds the corresponding events to the deque. pub fn update(&mut self, events: &mut VecDeque) { const BUTTONS: [Button; 4] = [ Button::Button0, Button::Button1, Button::Button2, Button::Button3, ]; for button in BUTTONS { // Finds the real state of the button let is_pressed = self.is_js_pressed(button); // Updates the map and returns the old state of the button let was_pressed = self.state.insert(button, is_pressed).unwrap_or(false); if was_pressed && !is_pressed { // Button was released events.push_back(Event::ButtonReleased(button)); } if is_pressed && !was_pressed { // Button was pressed events.push_back(Event::ButtonPressed(button)); } } } /// Checks if a button is pressed. pub fn is_pressed(&self, button: Button) -> bool { *self.state.get(&button).unwrap_or(&false) } /// Utility function to really check the state of the button. fn is_js_pressed(&self, button: Button) -> bool { let buttons = self.inner.buttons(); match button { Button::Left => false, Button::Right => false, Button::Up => false, Button::Down => false, Button::Button0 => Into::::into(buttons.get(0)).pressed(), Button::Button1 => Into::::into(buttons.get(1)).pressed(), Button::Button2 => Into::::into(buttons.get(2)).pressed(), Button::Button3 => Into::::into(buttons.get(3)).pressed(), } } } /// A helper to easily deal with inputs. #[derive(Clone)] pub struct Inputs(Rc>); impl Inputs { /// Creates a new inputs object. pub fn new(window: &web_sys::Window) -> Result { let inputs = Inputs(Rc::new(RefCell::new(InputManager::new()))); let clone = inputs.clone(); let cb = Closure::::new(move |event: web_sys::GamepadEvent| { let mut inner = clone.0.borrow_mut(); inner.add_gamepad(event.gamepad().unwrap()); }); window.add_event_listener_with_callback("gamepadconnected", cb.as_ref().unchecked_ref())?; cb.forget(); Ok(inputs) } /// Updates the inputs. /// /// This function needs to be called at each frame. pub fn update(&mut self) { let mut inner = self.0.borrow_mut(); inner.update(); } /// Returns the next event. pub fn next(&mut self) -> Option { let mut inner = self.0.borrow_mut(); inner.events.pop_front() } }