Skip to content

Commit

Permalink
feat: add GamepadInfo, expose gamepad names (#6342)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
dataphract committed Oct 24, 2022
1 parent c9ec5c7 commit bcc33f6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 33 deletions.
16 changes: 13 additions & 3 deletions crates/bevy_gilrs/src/gilrs_system.rs
Original file line number Diff line number Diff line change
@@ -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),
));
}
}
Expand All @@ -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 => {
Expand Down
67 changes: 40 additions & 27 deletions crates/bevy_input/src/gamepad.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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,

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/input/gamepad_input_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
4 changes: 2 additions & 2 deletions examples/tools/gamepad_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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");

Expand Down

0 comments on commit bcc33f6

Please sign in to comment.