#[cfg(all(target_os = "android", feature = "android-winit"))]
mod backend_android_winit;
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
mod backend_gilrs;
#[cfg(all(target_family = "wasm", feature = "wasm-bindgen"))]
mod backend_web_bindgen;
#[cfg(all(target_family = "wasm", not(feature = "wasm-bindgen")))]
mod backend_web_direct;
const MAX_GAMEPADS: usize = 8;
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct Gamepad {
id: GamepadId,
connected: bool,
button_count: u8,
axes_count: u8,
pressed_bits: u32,
axes: [f32; 6],
#[cfg(target_family = "wasm")]
last_pressed_bits: u32,
#[cfg(not(target_family = "wasm"))]
just_pressed_bits: u32,
}
#[cfg(target_family = "wasm")]
const _: () = [(); 1][(core::mem::size_of::<Gamepad>() == 36) as usize ^ 1];
impl Gamepad {
pub const fn id(&self) -> GamepadId {
self.id
}
pub const fn button_count(&self) -> usize {
self.button_count as usize
}
pub const fn axes_count(&self) -> usize {
self.axes_count as usize
}
pub const fn left_stick(&self) -> (f32, f32) {
(self.axes[0], self.axes[1])
}
pub const fn left_stick_x(&self) -> f32 {
self.axes[0]
}
pub const fn left_stick_y(&self) -> f32 {
self.axes[1]
}
pub const fn right_stick(&self) -> (f32, f32) {
(self.axes[2], self.axes[3])
}
pub const fn right_stick_x(&self) -> f32 {
self.axes[2]
}
pub const fn right_stick_y(&self) -> f32 {
self.axes[3]
}
pub const fn left_trigger(&self) -> f32 {
self.axes[4]
}
pub const fn right_trigger(&self) -> f32 {
self.axes[5]
}
pub fn all_currently_pressed(&self) -> impl Iterator<Item = Button> + '_ {
Button::all().filter(|&t| self.is_currently_pressed(t))
}
pub fn all_just_pressed(&self) -> impl Iterator<Item = Button> + '_ {
Button::all().filter(|&t| self.is_just_pressed(t))
}
pub const fn is_just_pressed(&self, button: Button) -> bool {
let queried_bit = 1 << (button as u32);
#[cfg(target_family = "wasm")]
{
(self.pressed_bits & queried_bit) != 0 && (self.last_pressed_bits & queried_bit) == 0
}
#[cfg(not(target_family = "wasm"))]
{
(self.just_pressed_bits & queried_bit) != 0
}
}
pub const fn is_currently_pressed(&self, button: Button) -> bool {
let queried_bit = 1 << (button as u32);
(self.pressed_bits & queried_bit) != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct GamepadId(u8);
impl GamepadId {
pub const fn value(self) -> u8 {
self.0
}
}
pub struct Gamepads {
gamepads: [Gamepad; MAX_GAMEPADS],
#[cfg(all(target_os = "android", feature = "android-winit"))]
android_winit_gamepad_ids: [winit::event::DeviceId; MAX_GAMEPADS],
#[cfg(all(target_os = "android", feature = "android-winit"))]
num_connected_pads: u8,
#[cfg(all(target_os = "android", feature = "android-winit"))]
just_polled: bool,
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
gilrs_gamepad_ids: [usize; MAX_GAMEPADS],
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
gilrs_instance: gilrs::Gilrs,
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
num_connected_pads: u8,
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
deadzones: [[f32; 4]; MAX_GAMEPADS],
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
playing_ff_effects: Vec<(gilrs::ff::Effect, u128)>,
}
impl Gamepads {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
#[cfg(target_os = "android")]
android_logger::Config::default().with_max_level(log::LevelFilter::Warn);
let mut gamepads = Self {
gamepads: std::array::from_fn(|idx| Gamepad {
id: GamepadId(idx as u8),
connected: false,
button_count: 0,
axes_count: 0,
pressed_bits: 0,
axes: [0.; 6],
#[cfg(target_family = "wasm")]
last_pressed_bits: 0,
#[cfg(not(target_family = "wasm"))]
just_pressed_bits: 0,
}),
#[cfg(all(target_os = "android", feature = "android-winit"))]
android_winit_gamepad_ids: [winit::event::DeviceId::dummy(); MAX_GAMEPADS],
#[cfg(all(target_os = "android", feature = "android-winit"))]
num_connected_pads: 0,
#[cfg(all(target_os = "android", feature = "android-winit"))]
just_polled: false,
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
gilrs_gamepad_ids: [usize::MAX; MAX_GAMEPADS],
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
gilrs_instance: gilrs::Gilrs::new().unwrap(),
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
num_connected_pads: 0,
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
deadzones: [[0.; 4]; MAX_GAMEPADS],
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
playing_ff_effects: Vec::new(),
};
gamepads.poll();
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
gamepads.setup_initially_connected_gilrs();
gamepads
}
pub fn get(&self, gamepad_id: GamepadId) -> Option<Gamepad> {
let pad = self.gamepads[gamepad_id.0 as usize];
pad.connected.then_some(pad)
}
pub fn all(&self) -> impl Iterator<Item = Gamepad> {
self.gamepads.into_iter().filter(|p| p.connected)
}
pub fn rumble(
&mut self,
gamepad_id: GamepadId,
duration_ms: u32,
start_delay_ms: u32,
strong_magnitude: f32,
weak_magnitude: f32,
) {
#[cfg(target_family = "wasm")]
{
#[cfg(not(feature = "wasm-bindgen"))]
unsafe {
backend_web_direct::playEffect(
gamepad_id.0,
duration_ms,
start_delay_ms,
strong_magnitude,
weak_magnitude,
);
}
#[cfg(feature = "wasm-bindgen")]
backend_web_bindgen::play_effect(
gamepad_id.0,
duration_ms,
start_delay_ms,
strong_magnitude,
weak_magnitude,
);
}
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
{
self.rumble_gilrs(
gamepad_id,
duration_ms,
start_delay_ms,
strong_magnitude,
weak_magnitude,
);
}
#[cfg(all(target_os = "android", feature = "android-winit"))]
{
self.rumble_android(
gamepad_id,
duration_ms,
start_delay_ms,
strong_magnitude,
weak_magnitude,
);
}
}
pub fn poll(&mut self) {
#[cfg(target_os = "android")]
{
self.poll_android_winit();
}
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
{
self.poll_gilrs();
}
#[cfg(target_family = "wasm")]
{
for gamepad in self.gamepads.iter_mut() {
gamepad.last_pressed_bits = gamepad.pressed_bits;
}
#[cfg(not(feature = "wasm-bindgen"))]
{
let pointer = self.gamepads.as_ptr();
unsafe { backend_web_direct::getGamepads(pointer) }
}
#[cfg(feature = "wasm-bindgen")]
{
backend_web_bindgen::poll(self);
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Button {
ActionDown,
ActionRight,
ActionLeft,
ActionUp,
FrontLeftUpper,
FrontRightUpper,
FrontLeftLower,
FrontRightLower,
LeftCenterCluster,
RightCenterCluster,
LeftStick,
RightStick,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
Mode,
}
impl Button {
pub fn all() -> impl Iterator<Item = Self> {
[
Self::ActionDown,
Self::ActionRight,
Self::ActionLeft,
Self::ActionUp,
Self::FrontLeftUpper,
Self::FrontRightUpper,
Self::FrontLeftLower,
Self::FrontRightLower,
Self::LeftCenterCluster,
Self::RightCenterCluster,
Self::LeftStick,
Self::RightStick,
Self::DPadUp,
Self::DPadDown,
Self::DPadLeft,
Self::DPadRight,
Self::Mode,
]
.into_iter()
}
}