Add gamepad support to the desktop build
Co-Authored-By: deepseek-v4.1-flash (opencode-go) via CodeCraft
This commit is contained in:
1 parent
8d51d6ec6a
commit
e44aab6f5c
7 files changed
+260
-31
No files matched your search
+134
-12
@@ -65,6 +65,32 @@ pub fn kind_for(setting: u8, detected: i32) -> Kind {
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop: the pad's vendor id names the type (Microsoft, Sony, Nintendo)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn kind_from_vendor(vendor: u16) -> i32 {
|
||||
match vendor {
|
||||
0x045e => 1,
|
||||
0x054c => 2,
|
||||
0x057e => 3,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop: a pad without a known vendor, its name usually says what it is
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn kind_from_name(name: &str) -> i32 {
|
||||
let n = name.to_lowercase();
|
||||
if n.contains("xbox") || n.contains("xinput") || n.contains("microsoft") {
|
||||
1
|
||||
} else if n.contains("nintendo") || n.contains("switch") || n.contains("joy-con") || n.contains("joycon") || n.contains("pro controller") {
|
||||
3
|
||||
} else if n.contains("playstation") || n.contains("dualshock") || n.contains("dualsense") || n.contains("sony") || n.contains("wireless controller") {
|
||||
2
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static KIND: Cell<Kind> = const { Cell::new(Kind::Xbox) };
|
||||
}
|
||||
@@ -84,28 +110,113 @@ pub struct Pad {
|
||||
axes: [f32; 4],
|
||||
pointer: (f32, f32), // device pixels of the interface
|
||||
pointer_ready: bool,
|
||||
// desktop builds read the pads themselves (miniquad does not poll them); gilrs opens the
|
||||
// system's input devices, None when that is impossible or there are none
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
gilrs: Option<gilrs::Gilrs>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tried: bool,
|
||||
}
|
||||
|
||||
impl Pad {
|
||||
pub fn new() -> Pad {
|
||||
Pad { connected: false, buttons: 0, prev: 0, axes: [0.0; 4], pointer: (0.0, 0.0), pointer_ready: false }
|
||||
Pad {
|
||||
connected: false,
|
||||
buttons: 0,
|
||||
prev: 0,
|
||||
axes: [0.0; 4],
|
||||
pointer: (0.0, 0.0),
|
||||
pointer_ready: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
gilrs: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tried: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Read the pad for this frame; kind_setting = Settings::gamepad.
|
||||
pub fn poll(&mut self, kind_setting: u8) {
|
||||
self.prev = if self.connected { self.buttons } else { 0 };
|
||||
let slot = crate::platform::gamepad_slot();
|
||||
self.connected = slot >= 0;
|
||||
let detected = if self.connected { crate::platform::gamepad_kind(slot) } else { 0 };
|
||||
KIND.with(|c| c.set(kind_for(kind_setting, detected)));
|
||||
if !self.connected {
|
||||
self.buttons = 0;
|
||||
self.axes = [0.0; 4];
|
||||
return;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let slot = crate::platform::gamepad_slot();
|
||||
self.connected = slot >= 0;
|
||||
let detected = if self.connected { crate::platform::gamepad_kind(slot) } else { 0 };
|
||||
KIND.with(|c| c.set(kind_for(kind_setting, detected)));
|
||||
if !self.connected {
|
||||
self.buttons = 0;
|
||||
self.axes = [0.0; 4];
|
||||
return;
|
||||
}
|
||||
self.buttons = crate::platform::gamepad_buttons(slot);
|
||||
for (i, a) in self.axes.iter_mut().enumerate() {
|
||||
*a = crate::platform::gamepad_axis(slot, i as i32);
|
||||
}
|
||||
}
|
||||
self.buttons = crate::platform::gamepad_buttons(slot);
|
||||
for (i, a) in self.axes.iter_mut().enumerate() {
|
||||
*a = crate::platform::gamepad_axis(slot, i as i32);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.poll_desktop(kind_setting);
|
||||
}
|
||||
|
||||
// Desktop: gilrs reports the pad's buttons and sticks. Its Y axes point up when positive,
|
||||
// the browser's Gamepad API points down, so they are flipped to the same meaning here.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn poll_desktop(&mut self, kind_setting: u8) {
|
||||
use gilrs::{Axis, Button as GBtn};
|
||||
if self.gilrs.is_none() && !self.tried {
|
||||
self.tried = true;
|
||||
match gilrs::Gilrs::new() {
|
||||
Ok(g) => self.gilrs = Some(g),
|
||||
Err(e) => eprintln!("DungeonCraft: no gamepad support: {e}"),
|
||||
}
|
||||
}
|
||||
let state = match self.gilrs.as_mut() {
|
||||
None => None,
|
||||
Some(g) => {
|
||||
// drain the queue, so hot-plugged pads show up and the state stays current
|
||||
while g.next_event().is_some() {}
|
||||
g.gamepads().next().map(|(_, p)| {
|
||||
let mut m = 0u32;
|
||||
for (gb, b) in [
|
||||
(GBtn::South, Btn::South),
|
||||
(GBtn::East, Btn::East),
|
||||
(GBtn::West, Btn::West),
|
||||
(GBtn::North, Btn::North),
|
||||
(GBtn::LeftTrigger, Btn::Lb),
|
||||
(GBtn::RightTrigger, Btn::Rb),
|
||||
(GBtn::LeftTrigger2, Btn::Lt),
|
||||
(GBtn::RightTrigger2, Btn::Rt),
|
||||
(GBtn::Select, Btn::Select),
|
||||
(GBtn::Start, Btn::Start),
|
||||
(GBtn::LeftThumb, Btn::LStick),
|
||||
(GBtn::RightThumb, Btn::RStick),
|
||||
(GBtn::DPadUp, Btn::Up),
|
||||
(GBtn::DPadDown, Btn::Down),
|
||||
(GBtn::DPadLeft, Btn::Left),
|
||||
(GBtn::DPadRight, Btn::Right),
|
||||
] {
|
||||
if p.is_pressed(gb) {
|
||||
m |= b.bit();
|
||||
}
|
||||
}
|
||||
let axes = [p.value(Axis::LeftStickX), -p.value(Axis::LeftStickY), p.value(Axis::RightStickX), -p.value(Axis::RightStickY)];
|
||||
let detected = p.vendor_id().map(kind_from_vendor).unwrap_or_else(|| kind_from_name(&p.name()));
|
||||
(detected, m, axes)
|
||||
})
|
||||
}
|
||||
};
|
||||
match state {
|
||||
Some((detected, buttons, axes)) => {
|
||||
self.connected = true;
|
||||
self.buttons = buttons;
|
||||
self.axes = axes;
|
||||
KIND.with(|c| c.set(kind_for(kind_setting, detected)));
|
||||
}
|
||||
None => {
|
||||
self.connected = false;
|
||||
self.buttons = 0;
|
||||
self.axes = [0.0; 4];
|
||||
KIND.with(|c| c.set(kind_for(kind_setting, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +323,17 @@ mod tests {
|
||||
assert!(!p.pressed(Btn::South));
|
||||
}
|
||||
|
||||
// the desktop path opens the system's input devices; without a pad (or without access to
|
||||
// them) it simply reports none, and it must never panic
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[test]
|
||||
fn polling_the_desktop_pad_is_safe() {
|
||||
let mut p = Pad::new();
|
||||
p.poll(0);
|
||||
p.poll(2);
|
||||
assert_eq!(p.connected(), p.gilrs.as_ref().map_or(false, |g| g.gamepads().next().is_some()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_pointer_follows_the_mouse_and_the_stick() {
|
||||
let mut p = Pad::new();
|
||||
|
||||
+2
-14
@@ -388,20 +388,8 @@ mod imp {
|
||||
}
|
||||
}
|
||||
|
||||
// The Gamepad API only exists in browsers. The desktop build has no gamepad input yet
|
||||
// (miniquad does not poll gamepads; that would need a crate like gilrs).
|
||||
pub fn gamepad_slot() -> i32 {
|
||||
-1
|
||||
}
|
||||
pub fn gamepad_buttons(_slot: i32) -> u32 {
|
||||
0
|
||||
}
|
||||
pub fn gamepad_axis(_slot: i32, _axis: i32) -> f32 {
|
||||
0.0
|
||||
}
|
||||
pub fn gamepad_kind(_slot: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
// Gamepads are polled by pad.rs with gilrs; the browser's Gamepad API lives in the
|
||||
// dc_gamepad_* imports of the JS plugin (see the wasm imp above).
|
||||
}
|
||||
|
||||
pub use imp::*;
|
||||
|
||||
Reference in new issue
Block a user