feat: add GamepadInfo, expose gamepad names (#6342)

# Objective

Fixes #6339.

## Solution

This PR adds a new type, `GamepadInfo`, which holds metadata associated with a particular `Gamepad`. The `Gamepads` resource now holds a `HashMap<Gamepad, GamepadInfo>`. The `GamepadInfo` is created when the gamepad backend (by default `bevy_gilrs`) emits a "gamepad connected" event.

The `gamepad_viewer` example has been updated to showcase the new functionality.

Before:

![bevy-gamepad-old](https://user-images.githubusercontent.com/86984145/197359427-2130a3c0-bd8a-4683-ae24-2a9eaa98b586.png)

After:

![bevy-gamepad-new](https://user-images.githubusercontent.com/86984145/197359429-f7963163-df26-4906-af7f-6186fe3bd338.png)


---

## Changelog

### Added

- Added `GamepadInfo`.
- Added `Gamepads::name()`, which returns the name of the specified gamepad if it exists.

### Changed

- `GamepadEventType::Connected` is now a tuple variant with a single field of type `GamepadInfo`.
- Since `GamepadInfo` is not `Copy`, `GamepadEventType` is no longer `Copy`. The same is true of `GamepadEvent` and `GamepadEventRaw`.

## Migration Guide

- Pattern matches on `GamepadEventType::Connected` will need to be updated, as the form of the variant has changed.
- Code that requires `GamepadEvent`, `GamepadEventRaw` or `GamepadEventType` to be `Copy` will need to be updated.
This commit is contained in:
dataphract 2022-10-24 14:33:50 +00:00
parent c9ec5c771a
commit bcc33f6757
4 changed files with 56 additions and 33 deletions

View file

@ -1,14 +1,19 @@
use crate::converter::{convert_axis, convert_button, convert_gamepad_id};
use bevy_ecs::event::EventWriter;
use bevy_ecs::system::{NonSend, NonSendMut};
use bevy_input::gamepad::GamepadInfo;
use bevy_input::{gamepad::GamepadEventRaw, prelude::*};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs};
pub fn gilrs_event_startup_system(gilrs: NonSend<Gilrs>, mut events: EventWriter<GamepadEventRaw>) {
for (id, _) in gilrs.gamepads() {
for (id, gamepad) in gilrs.gamepads() {
let info = GamepadInfo {
name: gamepad.name().into(),
};
events.send(GamepadEventRaw::new(
convert_gamepad_id(id),
GamepadEventType::Connected,
GamepadEventType::Connected(info),
));
}
}
@ -22,9 +27,14 @@ pub fn gilrs_event_system(mut gilrs: NonSendMut<Gilrs>, mut events: EventWriter<
match gilrs_event.event {
EventType::Connected => {
let pad = gilrs.gamepad(gilrs_event.id);
let info = GamepadInfo {
name: pad.name().into(),
};
events.send(GamepadEventRaw::new(
convert_gamepad_id(gilrs_event.id),
GamepadEventType::Connected,
GamepadEventType::Connected(info),
));
}
EventType::Disconnected => {

View file

@ -1,7 +1,7 @@
use crate::{Axis, Input};
use bevy_ecs::event::{EventReader, EventWriter};
use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_utils::{tracing::info, HashMap, HashSet};
use bevy_utils::{tracing::info, HashMap};
use thiserror::Error;
/// Errors that occur when setting axis settings for gamepad input.
@ -78,6 +78,13 @@ impl Gamepad {
}
}
/// Metadata associated with a `Gamepad`.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct GamepadInfo {
pub name: String,
}
/// A collection of connected [`Gamepad`]s.
///
/// ## Usage
@ -92,23 +99,27 @@ impl Gamepad {
#[derive(Resource, Default, Debug)]
pub struct Gamepads {
/// The collection of the connected [`Gamepad`]s.
gamepads: HashSet<Gamepad>,
gamepads: HashMap<Gamepad, GamepadInfo>,
}
impl Gamepads {
/// Returns `true` if the `gamepad` is connected.
pub fn contains(&self, gamepad: Gamepad) -> bool {
self.gamepads.contains(&gamepad)
self.gamepads.contains_key(&gamepad)
}
/// Returns an iterator over registered [`Gamepad`]s in an arbitrary order.
pub fn iter(&self) -> impl Iterator<Item = Gamepad> + '_ {
self.gamepads.iter().copied()
self.gamepads.keys().copied()
}
pub fn name(&self, gamepad: Gamepad) -> Option<&str> {
self.gamepads.get(&gamepad).map(|g| g.name.as_str())
}
/// Registers the `gamepad`, marking it as connected.
fn register(&mut self, gamepad: Gamepad) {
self.gamepads.insert(gamepad);
fn register(&mut self, gamepad: Gamepad, info: GamepadInfo) {
self.gamepads.insert(gamepad, info);
}
/// Deregisters the `gamepad`, marking it as disconnected.
@ -118,11 +129,11 @@ impl Gamepads {
}
/// The data contained in a [`GamepadEvent`] or [`GamepadEventRaw`].
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub enum GamepadEventType {
/// A [`Gamepad`] has been connected.
Connected,
Connected(GamepadInfo),
/// A [`Gamepad`] has been disconnected.
Disconnected,
@ -151,7 +162,7 @@ pub enum GamepadEventType {
/// [`Axis<GamepadAxis>`], and [`Axis<GamepadButton>`] resources won't be updated correctly.
///
/// An example for gamepad input mocking can be seen in the documentation of the [`GamepadEventRaw`].
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct GamepadEvent {
/// The gamepad this event corresponds to.
@ -191,7 +202,7 @@ impl GamepadEvent {
/// ```
/// # use bevy_input::prelude::*;
/// # use bevy_input::InputPlugin;
/// # use bevy_input::gamepad::GamepadEventRaw;
/// # use bevy_input::gamepad::{GamepadEventRaw, GamepadInfo};
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #[derive(Resource)]
@ -223,7 +234,8 @@ impl GamepadEvent {
///
/// // Send the gamepad connected event to mark our gamepad as connected.
/// // This updates the `Gamepads` resource accordingly.
/// app.world.send_event(GamepadEventRaw::new(gamepad, GamepadEventType::Connected));
/// let info = GamepadInfo { name: "Mock Gamepad".into() };
/// app.world.send_event(GamepadEventRaw::new(gamepad, GamepadEventType::Connected(info)));
///
/// // Send the gamepad input event to mark the `South` gamepad button as pressed.
/// // This updates the `Input<GamepadButton>` resource accordingly.
@ -254,7 +266,7 @@ impl GamepadEvent {
/// #
/// # bevy_ecs::system::assert_is_system(change_resource_on_gamepad_button_press);
/// ```
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct GamepadEventRaw {
/// The gamepad this event corresponds to.
@ -1062,11 +1074,12 @@ pub fn gamepad_connection_system(
mut gamepad_event: EventReader<GamepadEvent>,
) {
for event in gamepad_event.iter() {
match event.event_type {
GamepadEventType::Connected => {
gamepads.register(event.gamepad);
match &event.event_type {
GamepadEventType::Connected(info) => {
gamepads.register(event.gamepad, info.clone());
info!("{:?} Connected", event.gamepad);
}
GamepadEventType::Disconnected => {
gamepads.deregister(event.gamepad);
info!("{:?} Disconnected", event.gamepad);
@ -1096,9 +1109,9 @@ pub fn gamepad_event_system(
) {
button_input.clear();
for event in raw_events.iter() {
match event.event_type {
GamepadEventType::Connected => {
events.send(GamepadEvent::new(event.gamepad, event.event_type));
match &event.event_type {
GamepadEventType::Connected(_) => {
events.send(GamepadEvent::new(event.gamepad, event.event_type.clone()));
for button_type in &ALL_BUTTON_TYPES {
let gamepad_button = GamepadButton::new(event.gamepad, *button_type);
button_input.reset(gamepad_button);
@ -1109,7 +1122,7 @@ pub fn gamepad_event_system(
}
}
GamepadEventType::Disconnected => {
events.send(GamepadEvent::new(event.gamepad, event.event_type));
events.send(GamepadEvent::new(event.gamepad, event.event_type.clone()));
for button_type in &ALL_BUTTON_TYPES {
let gamepad_button = GamepadButton::new(event.gamepad, *button_type);
button_input.reset(gamepad_button);
@ -1120,37 +1133,37 @@ pub fn gamepad_event_system(
}
}
GamepadEventType::AxisChanged(axis_type, value) => {
let gamepad_axis = GamepadAxis::new(event.gamepad, axis_type);
let gamepad_axis = GamepadAxis::new(event.gamepad, *axis_type);
if let Some(filtered_value) = settings
.get_axis_settings(gamepad_axis)
.filter(value, axis.get(gamepad_axis))
.filter(*value, axis.get(gamepad_axis))
{
axis.set(gamepad_axis, filtered_value);
events.send(GamepadEvent::new(
event.gamepad,
GamepadEventType::AxisChanged(axis_type, filtered_value),
GamepadEventType::AxisChanged(*axis_type, filtered_value),
));
}
}
GamepadEventType::ButtonChanged(button_type, value) => {
let gamepad_button = GamepadButton::new(event.gamepad, button_type);
let gamepad_button = GamepadButton::new(event.gamepad, *button_type);
if let Some(filtered_value) = settings
.get_button_axis_settings(gamepad_button)
.filter(value, button_axis.get(gamepad_button))
.filter(*value, button_axis.get(gamepad_button))
{
button_axis.set(gamepad_button, filtered_value);
events.send(GamepadEvent::new(
event.gamepad,
GamepadEventType::ButtonChanged(button_type, filtered_value),
GamepadEventType::ButtonChanged(*button_type, filtered_value),
));
}
let button_property = settings.get_button_settings(gamepad_button);
if button_input.pressed(gamepad_button) {
if button_property.is_released(value) {
if button_property.is_released(*value) {
button_input.release(gamepad_button);
}
} else if button_property.is_pressed(value) {
} else if button_property.is_pressed(*value) {
button_input.press(gamepad_button);
}
}

View file

@ -15,7 +15,7 @@ fn main() {
fn gamepad_events(mut gamepad_event: EventReader<GamepadEvent>) {
for event in gamepad_event.iter() {
match event.event_type {
GamepadEventType::Connected => {
GamepadEventType::Connected(_) => {
info!("{:?} Connected", event.gamepad);
}
GamepadEventType::Disconnected => {

View file

@ -437,7 +437,7 @@ fn setup_connected(mut commands: Commands, font: Res<FontHandle>) {
commands.spawn((
TextBundle::from_sections([
TextSection {
value: "Connected Gamepads\n".to_string(),
value: "Connected Gamepads:\n".to_string(),
style: style.clone(),
},
TextSection {
@ -521,7 +521,7 @@ fn update_connected(
let formatted = gamepads
.iter()
.map(|g| format!("{:?}", g))
.map(|g| format!("- {}", gamepads.name(g).unwrap()))
.collect::<Vec<_>>()
.join("\n");