Add some missing features from the gamepads-as-entities change that were needed to update leafwing-input-manager. (#15685)

The gamepads-as-entities change caused several regressions. This patch
fixes each of them:

1. This PR introduces two new fields on `GamepadInfo`: `vendor_id`, and
`product_id`, as well as associated methods. These fields are simply
mirrored from the `gilrs` library.

2. That PR removed the methods that allowed iterating over all pressed
and released buttons, as well as the method that allowed iterating over
the axis values. (It was still technically possible to do so by using
reflection to access the private fields of `Gamepad`.)

3. The `Gamepad` component wasn't marked reflectable. This PR fixes that
problem.

These changes allowed me to forward port `leafwing-input-manager`.
This commit is contained in:
Patrick Walton 2024-10-08 05:19:38 -07:00 committed by GitHub
parent 4bf647ff3b
commit 48e2027827
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 5 deletions

View file

@ -28,6 +28,8 @@ pub fn gilrs_event_startup_system(
let info = GamepadInfo { let info = GamepadInfo {
name: gamepad.name().into(), name: gamepad.name().into(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
}; };
events.send(GamepadConnectionEvent { events.send(GamepadConnectionEvent {
@ -62,6 +64,8 @@ pub fn gilrs_event_system(
let info = GamepadInfo { let info = GamepadInfo {
name: pad.name().into(), name: pad.name().into(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
}; };
events.send( events.send(

View file

@ -4,12 +4,16 @@ use bevy_ecs::system::Resource;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use core::hash::Hash; use core::hash::Hash;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// Stores the position data of the input devices of type `T`. /// Stores the position data of the input devices of type `T`.
/// ///
/// The values are stored as `f32`s, using [`Axis::set`]. /// The values are stored as `f32`s, using [`Axis::set`].
/// Use [`Axis::get`] to retrieve the value clamped between [`Axis::MIN`] and [`Axis::MAX`] /// Use [`Axis::get`] to retrieve the value clamped between [`Axis::MIN`] and [`Axis::MAX`]
/// inclusive, or unclamped using [`Axis::get_unclamped`]. /// inclusive, or unclamped using [`Axis::get_unclamped`].
#[derive(Debug, Resource)] #[derive(Debug, Resource)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct Axis<T> { pub struct Axis<T> {
/// The position data of the input devices. /// The position data of the input devices.
axis_data: HashMap<T, f32>, axis_data: HashMap<T, f32>,
@ -70,6 +74,16 @@ where
pub fn remove(&mut self, input_device: T) -> Option<f32> { pub fn remove(&mut self, input_device: T) -> Option<f32> {
self.axis_data.remove(&input_device) self.axis_data.remove(&input_device)
} }
/// Returns an iterator over all axes.
pub fn all_axes(&self) -> impl Iterator<Item = &T> {
self.axis_data.keys()
}
/// Returns an iterator over all axes and their values.
pub fn all_axes_and_values(&self) -> impl Iterator<Item = (&T, f32)> {
self.axis_data.iter().map(|(axis, value)| (axis, *value))
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -364,6 +364,7 @@ pub enum ButtonSettingsError {
/// } /// }
/// ``` /// ```
#[derive(Component, Debug)] #[derive(Component, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
#[require(GamepadSettings)] #[require(GamepadSettings)]
pub struct Gamepad { pub struct Gamepad {
info: GamepadInfo, info: GamepadInfo,
@ -374,7 +375,7 @@ pub struct Gamepad {
} }
impl Gamepad { impl Gamepad {
/// Creates a gamepad with the given metadata /// Creates a gamepad with the given metadata.
fn new(info: GamepadInfo) -> Self { fn new(info: GamepadInfo) -> Self {
let mut analog = Axis::default(); let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() { for button in GamepadButton::all().iter().copied() {
@ -399,6 +400,18 @@ impl Gamepad {
self.info.name.as_str() self.info.name.as_str()
} }
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
pub fn vendor_id(&self) -> Option<u16> {
self.info.vendor_id
}
/// Returns the USB product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub fn product_id(&self) -> Option<u16> {
self.info.product_id
}
/// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`]. /// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
/// ///
/// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]]. /// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]].
@ -505,8 +518,39 @@ impl Gamepad {
.into_iter() .into_iter()
.all(|button_type| self.just_released(button_type)) .all(|button_type| self.just_released(button_type))
} }
/// Returns an iterator over all digital [button]s that are pressed.
///
/// [button]: GamepadButton
pub fn get_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_pressed()
}
/// Returns an iterator over all digital [button]s that were just pressed.
///
/// [button]: GamepadButton
pub fn get_just_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_pressed()
}
/// Returns an iterator over all digital [button]s that were just released.
///
/// [button]: GamepadButton
pub fn get_just_released(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_released()
}
/// Returns an iterator over all analog [axes].
///
/// [axes]: GamepadInput
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
self.analog.all_axes()
}
} }
// Note that we don't expose `gilrs::Gamepad::uuid` due to
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
//
/// Metadata associated with a [`Gamepad`]. /// Metadata associated with a [`Gamepad`].
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -522,6 +566,14 @@ pub struct GamepadInfo {
/// ///
/// For example on Windows the name may be "HID-compliant game controller". /// For example on Windows the name may be "HID-compliant game controller".
pub name: String, pub name: String,
/// The USB vendor ID as assigned by the USB-IF, if available.
pub vendor_id: Option<u16>,
/// The USB product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub product_id: Option<u16>,
} }
/// Represents gamepad input types that are mapped in the range [0.0, 1.0]. /// Represents gamepad input types that are mapped in the range [0.0, 1.0].
@ -665,6 +717,7 @@ impl GamepadAxis {
/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`] /// Encapsulation over [`GamepadAxis`] and [`GamepadButton`]
// This is done so Gamepad can share a single Axis<T> and simplifies the API by having only one get/get_unclamped method // This is done so Gamepad can share a single Axis<T> and simplifies the API by having only one get/get_unclamped method
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
pub enum GamepadInput { pub enum GamepadInput {
/// A [`GamepadAxis`] /// A [`GamepadAxis`]
Axis(GamepadAxis), Axis(GamepadAxis),
@ -1988,6 +2041,8 @@ mod tests {
gamepad, gamepad,
Connected(GamepadInfo { Connected(GamepadInfo {
name: String::from("Gamepad test"), name: String::from("Gamepad test"),
vendor_id: None,
product_id: None,
}), }),
)); ));
gamepad gamepad

View file

@ -51,11 +51,14 @@ use mouse::{
}; };
use touch::{touch_screen_input_system, TouchInput, Touches}; use touch::{touch_screen_input_system, TouchInput, Touches};
#[cfg(feature = "bevy_reflect")]
use gamepad::Gamepad;
use gamepad::{ use gamepad::{
gamepad_connection_system, gamepad_event_processing_system, GamepadAxisChangedEvent, gamepad_connection_system, gamepad_event_processing_system, GamepadAxis,
GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadRumbleRequest, GamepadSettings, GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent,
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
}; };
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))] #[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
@ -134,6 +137,7 @@ impl Plugin for InputPlugin {
.register_type::<RawGamepadEvent>() .register_type::<RawGamepadEvent>()
.register_type::<RawGamepadAxisChangedEvent>() .register_type::<RawGamepadAxisChangedEvent>()
.register_type::<RawGamepadButtonChangedEvent>() .register_type::<RawGamepadButtonChangedEvent>()
.register_type::<Gamepad>()
.register_type::<GamepadConnectionEvent>() .register_type::<GamepadConnectionEvent>()
.register_type::<GamepadButtonChangedEvent>() .register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>() .register_type::<GamepadAxisChangedEvent>()
@ -141,6 +145,9 @@ impl Plugin for InputPlugin {
.register_type::<GamepadInfo>() .register_type::<GamepadInfo>()
.register_type::<GamepadConnection>() .register_type::<GamepadConnection>()
.register_type::<GamepadSettings>() .register_type::<GamepadSettings>()
.register_type::<GamepadAxis>()
.register_type::<GamepadButton>()
.register_type::<GamepadInput>()
.register_type::<AccumulatedMouseMotion>() .register_type::<AccumulatedMouseMotion>()
.register_type::<AccumulatedMouseScroll>(); .register_type::<AccumulatedMouseScroll>();
} }