use std::cell::RefCell;
#[derive(Clone, Default)]
pub struct WasmPad {
pub connected: bool,
pub buttons: [bool; 17],
pub button_values: [f32; 17],
pub axes: [f32; 4],
}
thread_local! {
static PADS: RefCell<Vec<WasmPad>> = const { RefCell::new(Vec::new()) };
}
pub fn poll() -> usize {
let result = try_poll();
result.unwrap_or(0)
}
fn try_poll() -> Option<usize> {
let window = web_sys::window()?;
let navigator = window.navigator();
let raw = navigator.get_gamepads().ok()?;
Some(PADS.with(|cell| {
let mut pads = cell.borrow_mut();
let len = raw.length() as usize;
if pads.len() < len {
pads.resize_with(len, WasmPad::default);
}
let mut connected = 0usize;
for i in 0..len {
let val = raw.get(i as u32);
if val.is_null() || val.is_undefined() {
if i < pads.len() {
pads[i].connected = false;
}
continue;
}
let gp: web_sys::Gamepad = val.into();
if !gp.connected() {
if i < pads.len() {
pads[i].connected = false;
}
continue;
}
if i >= pads.len() {
pads.resize_with(i + 1, WasmPad::default);
}
let pad = &mut pads[i];
pad.connected = true;
connected += 1;
let btns = gp.buttons();
let blen = btns.length().min(17) as usize;
for b in 0..blen {
let btn: web_sys::GamepadButton = btns.get(b as u32).into();
pad.buttons[b] = btn.pressed();
pad.button_values[b] = btn.value() as f32;
}
let axes = gp.axes();
let alen = axes.length().min(4) as usize;
for a in 0..alen {
pad.axes[a] = js_sys::Array::from(&axes).get(a as u32).as_f64().unwrap_or(0.0)
as f32;
}
}
connected
}))
}
pub fn button_index(name: &str) -> Option<usize> {
Some(match name.to_ascii_lowercase().as_str() {
"a" | "south" | "cross" => 0,
"b" | "east" | "circle" => 1,
"x" | "west" | "square" => 2,
"y" | "north" | "triangle" => 3,
"lb" | "l1" | "left_shoulder" => 4,
"rb" | "r1" | "right_shoulder" => 5,
"lt" | "l2" | "left_trigger" => 6,
"rt" | "r2" | "right_trigger" => 7,
"select" | "back" | "share" | "view" => 8,
"start" | "menu" | "options" => 9,
"l3" | "left_stick" => 10,
"r3" | "right_stick" => 11,
"up" | "dpad_up" => 12,
"down" | "dpad_down" => 13,
"left" | "dpad_left" => 14,
"right" | "dpad_right" => 15,
"guide" | "home" => 16,
_ => return None,
})
}
pub fn is_connected(i: usize) -> bool {
PADS.with(|c| c.borrow().get(i).is_some_and(|p| p.connected))
}
pub fn count() -> usize {
PADS.with(|c| c.borrow().iter().filter(|p| p.connected).count())
}
pub fn button_down(pad: usize, name: &str) -> bool {
let idx = match button_index(name) {
Some(i) => i,
None => return false,
};
PADS.with(|c| {
c.borrow()
.get(pad)
.is_some_and(|p| p.connected && p.buttons.get(idx).copied().unwrap_or(false))
})
}
pub fn axis_lx(pad: usize) -> f32 {
PADS.with(|c| c.borrow().get(pad).map_or(0.0, |p| p.axes[0]))
}
pub fn axis_ly(pad: usize) -> f32 {
PADS.with(|c| c.borrow().get(pad).map_or(0.0, |p| p.axes[1]))
}
pub fn axis_rx(pad: usize) -> f32 {
PADS.with(|c| c.borrow().get(pad).map_or(0.0, |p| p.axes[2]))
}
pub fn axis_ry(pad: usize) -> f32 {
PADS.with(|c| c.borrow().get(pad).map_or(0.0, |p| p.axes[3]))
}
pub fn trigger_lt(pad: usize) -> f32 {
PADS.with(|c| c.borrow().get(pad).map_or(0.0, |p| p.button_values[6]))
}
pub fn trigger_rt(pad: usize) -> f32 {
PADS.with(|c| c.borrow().get(pad).map_or(0.0, |p| p.button_values[7]))
}