use crate::input::state::Reading;
#[cfg(not(target_arch = "wasm32"))]
use library as sys;
#[cfg(target_arch = "wasm32")]
use page as sys;
pub(crate) struct Pads(Option<sys::Pads>);
impl Pads {
pub(crate) fn open() -> Self {
match sys::Pads::open() {
Some(pads) => Self(Some(pads)),
None => Self::silent(),
}
}
pub(crate) fn silent() -> Self {
Self(None)
}
pub(crate) fn poll(&mut self, reading: &mut Reading) {
if let Some(pads) = &mut self.0 {
pads.poll(reading);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
mod library {
use gilrs::ev::state::ButtonData;
use gilrs::{Axis, Button, Gamepad, Gilrs};
use crate::input::binding::{JoystickControl, Pad, PadAxis};
use crate::input::state::Reading;
pub(crate) struct Pads(Gilrs);
impl Pads {
pub(crate) fn open() -> Option<Self> {
match Gilrs::new() {
Ok(pads) => Some(Self(pads)),
Err(error) => {
log::debug!("mirage-engine reads no gamepads on this machine: {error}");
None
}
}
}
pub(crate) fn poll(&mut self, reading: &mut Reading) {
while self.0.next_event().is_some() {}
for (_, pad) in self.0.gamepads() {
for &button in Pad::ALL {
if pad.is_pressed(mapped(button)) {
reading.press_pad(button);
}
}
for &axis in PadAxis::ALL {
reading.push_pad(axis, lane(&pad, axis));
}
let state = pad.state();
for (control, data) in state.buttons() {
if data.is_pressed() {
reading.press_joystick(JoystickControl::new(control.into_u32()));
}
}
for (control, data) in state.axes() {
reading.push_joystick(JoystickControl::new(control.into_u32()), data.value());
}
}
}
}
fn mapped(button: Pad) -> Button {
match button {
Pad::South => Button::South,
Pad::East => Button::East,
Pad::West => Button::West,
Pad::North => Button::North,
Pad::LeftBumper => Button::LeftTrigger,
Pad::RightBumper => Button::RightTrigger,
Pad::LeftTrigger => Button::LeftTrigger2,
Pad::RightTrigger => Button::RightTrigger2,
Pad::Select => Button::Select,
Pad::Start => Button::Start,
Pad::Guide => Button::Mode,
Pad::LeftStick => Button::LeftThumb,
Pad::RightStick => Button::RightThumb,
Pad::DPadUp => Button::DPadUp,
Pad::DPadDown => Button::DPadDown,
Pad::DPadLeft => Button::DPadLeft,
Pad::DPadRight => Button::DPadRight,
}
}
fn lane(pad: &Gamepad<'_>, axis: PadAxis) -> f32 {
let trigger = |button| pad.button_data(button).map_or(0.0, ButtonData::value);
match axis {
PadAxis::LeftX => pad.value(Axis::LeftStickX),
PadAxis::LeftY => pad.value(Axis::LeftStickY),
PadAxis::RightX => pad.value(Axis::RightStickX),
PadAxis::RightY => pad.value(Axis::RightStickY),
PadAxis::LeftTrigger => trigger(Button::LeftTrigger2),
PadAxis::RightTrigger => trigger(Button::RightTrigger2),
}
}
}
#[cfg(target_arch = "wasm32")]
mod page {
use wasm_bindgen::JsCast;
use web_sys::{Gamepad, GamepadButton, GamepadMappingType};
use crate::input::binding::{JoystickControl, Pad, PadAxis};
use crate::input::state::Reading;
const MAPPED: [Pad; 17] = [
Pad::South,
Pad::East,
Pad::West,
Pad::North,
Pad::LeftBumper,
Pad::RightBumper,
Pad::LeftTrigger,
Pad::RightTrigger,
Pad::Select,
Pad::Start,
Pad::LeftStick,
Pad::RightStick,
Pad::DPadUp,
Pad::DPadDown,
Pad::DPadLeft,
Pad::DPadRight,
Pad::Guide,
];
const LANES: [(PadAxis, f32); 4] = [
(PadAxis::LeftX, 1.0),
(PadAxis::LeftY, -1.0),
(PadAxis::RightX, 1.0),
(PadAxis::RightY, -1.0),
];
const TRIGGERS: [(usize, PadAxis); 2] = [(6, PadAxis::LeftTrigger), (7, PadAxis::RightTrigger)];
pub(crate) struct Pads;
impl Pads {
pub(crate) fn open() -> Option<Self> {
web_sys::window().map(|_| Self)
}
pub(crate) fn poll(&mut self, reading: &mut Reading) {
let Some(window) = web_sys::window() else {
return;
};
let Ok(pads) = window.navigator().get_gamepads() else {
return;
};
for value in pads.iter() {
let Ok(pad) = value.dyn_into::<Gamepad>() else {
continue;
};
if !pad.connected() {
continue;
}
read(&pad, reading);
}
}
}
fn read(pad: &Gamepad, reading: &mut Reading) {
let buttons: Vec<(bool, f32)> = pad
.buttons()
.iter()
.map(|value| match value.dyn_into::<GamepadButton>() {
Ok(button) => (button.pressed(), button.value() as f32),
Err(_) => (false, 0.0),
})
.collect();
let axes: Vec<f32> = pad
.axes()
.iter()
.map(|value| value.as_f64().unwrap_or_default() as f32)
.collect();
for (control, &(pressed, _)) in buttons.iter().enumerate() {
if pressed {
reading.press_joystick(JoystickControl::new(control as u32));
}
}
for (control, &value) in axes.iter().enumerate() {
reading.push_joystick(JoystickControl::new(control as u32), value);
}
if pad.mapping() != GamepadMappingType::Standard {
return;
}
for (&button, &(pressed, _)) in MAPPED.iter().zip(buttons.iter()) {
if pressed {
reading.press_pad(button);
}
}
for (&(axis, sign), &value) in LANES.iter().zip(axes.iter()) {
reading.push_pad(axis, value * sign);
}
for (at, axis) in TRIGGERS {
let pulled = buttons.get(at).map_or(0.0, |&(_, value)| value);
reading.push_pad(axis, pulled);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn opening_the_platforms_pads_reads_whatever_is_plugged_in_and_no_more() {
let mut reading = Reading::default();
Pads::open().poll(&mut reading);
Pads::silent().poll(&mut reading);
}
}