Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide functionality to release inputs during mocking #114

Merged
merged 3 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- added geometric primitives (`Direction` and `Rotation`) for working with rotations in 2 dimensions
- added `reasons_pressed` API on `ActionState`, which records the triggering inputs
- you can use this to extract exact input information from analog inputs (like triggers or joysticks)
- added the ability to release user inputs during input mocking

### Usability

Expand All @@ -35,6 +36,7 @@
- added `insert_at` / `remove_at` to insert / remove input at specific index.
- added `remove` remove input for specific mapping.
- use `usize` for sizes as in other Rust containers.
- added `UserInput::raw_inputs`, which breaks down a `UserInput` into the constituent Bevy types (e.g. `KeyCode` and `MouseButton`)

### Bug fixes

Expand Down
118 changes: 87 additions & 31 deletions src/input_mocking.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Helpful utilities for testing input management by sending mock input events

use crate::user_input::{InputButton, InputStreams, MutableInputStreams, UserInput};
use crate::user_input::{InputStreams, MutableInputStreams, UserInput};
use bevy_app::App;
use bevy_ecs::event::Events;
use bevy_ecs::system::{Res, ResMut, SystemState};
Expand Down Expand Up @@ -53,15 +53,30 @@ use bevy_window::CursorMoved;
pub trait MockInput {
/// Send the specified `user_input` directly
///
/// Note that inputs will continue to be pressed until explicitly released or [`MockInput::reset_inputs`] is called.
///
/// Gamepad input will be sent by the first registed controller found.
/// If none are found, gamepad input will be silently skipped.
fn send_input(&mut self, input: impl Into<UserInput>);

/// Send the specified `user_input` directly, using the specified gamepad
///
/// Provide the `Gamepad` identifier to control which gamepad you are emulating inputs from
/// Note that inputs will continue to be pressed until explicitly released or [`MockInput::reset_inputs`] is called.
///
/// Provide the [`Gamepad`] identifier to control which gamepad you are emulating inputs from
fn send_input_to_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>);

/// Releases the specified `user_input` directly
///
/// Gamepad input will be released by the first registed controller found.
/// If none are found, gamepad input will be silently skipped.
fn release_input(&mut self, input: impl Into<UserInput>);

/// Releases the specified `user_input` directly, using the specified gamepad
///
/// Provide the [`Gamepad`] identifier to control which gamepad you are emulating inputs from
fn release_input_for_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>);

/// Is the provided `user_input` pressed?
///
/// This method is intended as a convenience for testing; check the [`Input`] resource directly,
Expand Down Expand Up @@ -106,52 +121,55 @@ impl<'a> MutableInputStreams<'a> {
/// Called by the methods of [`MockInput`].
pub fn send_user_input(&mut self, input: impl Into<UserInput>) {
let input_to_send: UserInput = input.into();
let (gamepad_buttons, keyboard_buttons, mouse_buttons) = input_to_send.raw_inputs();

let mut gamepad_buttons: Vec<GamepadButton> = Vec::default();
if let Some(ref mut gamepad_input) = self.gamepad {
for button in gamepad_buttons {
if let Some(associated_gamepad) = self.associated_gamepad {
let gamepad_button = GamepadButton(associated_gamepad, button);
gamepad_input.press(gamepad_button);
}
}
}

let mut keyboard_buttons: Vec<KeyCode> = Vec::default();
let mut mouse_buttons: Vec<MouseButton> = Vec::default();
if let Some(ref mut keyboard_input) = self.keyboard {
for button in keyboard_buttons {
keyboard_input.press(button);
}
}

match input_to_send {
UserInput::Single(button) => match button {
InputButton::Gamepad(gamepad_buttontype) => {
if let Some(gamepad) = self.associated_gamepad {
gamepad_buttons.push(GamepadButton(gamepad, gamepad_buttontype));
}
}
InputButton::Keyboard(keycode) => keyboard_buttons.push(keycode),
InputButton::Mouse(mouse_button) => mouse_buttons.push(mouse_button),
},
UserInput::Chord(button_set) => {
for button in button_set {
match button {
InputButton::Gamepad(gamepad_buttontype) => {
if let Some(gamepad) = self.associated_gamepad {
gamepad_buttons.push(GamepadButton(gamepad, gamepad_buttontype));
}
}
InputButton::Keyboard(keycode) => keyboard_buttons.push(keycode),
InputButton::Mouse(mouse_button) => mouse_buttons.push(mouse_button),
}
}
if let Some(ref mut mouse_input) = self.mouse {
for button in mouse_buttons {
mouse_input.press(button);
}
};
}
}

/// Releases the specified `user_input` directly, using the specified gamepad
///
/// Called by the methods of [`MockInput`].
pub fn release_user_input(&mut self, input: impl Into<UserInput>) {
let input_to_release: UserInput = input.into();
let (gamepad_buttons, keyboard_buttons, mouse_buttons) = input_to_release.raw_inputs();

if let Some(ref mut gamepad_input) = self.gamepad {
for button in gamepad_buttons {
gamepad_input.press(button);
if let Some(associated_gamepad) = self.associated_gamepad {
let gamepad_button = GamepadButton(associated_gamepad, button);
gamepad_input.release(gamepad_button);
}
}
}

if let Some(ref mut keyboard_input) = self.keyboard {
for button in keyboard_buttons {
keyboard_input.press(button);
keyboard_input.release(button);
}
}

if let Some(ref mut mouse_input) = self.mouse {
for button in mouse_buttons {
mouse_input.press(button);
mouse_input.release(button);
}
}
}
Expand Down Expand Up @@ -190,6 +208,36 @@ impl MockInput for World {
mutable_input_streams.send_user_input(input);
}

fn release_input(&mut self, input: impl Into<UserInput>) {
let gamepad = if let Some(gamepads) = self.get_resource::<Gamepads>() {
gamepads.iter().next().copied()
} else {
None
};

self.release_input_for_gamepad(input, gamepad);
}

fn release_input_for_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
let mut input_system_state: SystemState<(
Option<ResMut<Input<GamepadButton>>>,
Option<ResMut<Input<KeyCode>>>,
Option<ResMut<Input<MouseButton>>>,
)> = SystemState::new(self);

let (mut maybe_gamepad, mut maybe_keyboard, mut maybe_mouse) =
input_system_state.get_mut(self);

let mut mutable_input_streams = MutableInputStreams {
gamepad: maybe_gamepad.as_deref_mut(),
keyboard: maybe_keyboard.as_deref_mut(),
mouse: maybe_mouse.as_deref_mut(),
associated_gamepad: gamepad,
};

mutable_input_streams.release_user_input(input);
}

fn pressed(&mut self, input: impl Into<UserInput>) -> bool {
let gamepad = if let Some(gamepads) = self.get_resource::<Gamepads>() {
gamepads.iter().next().copied()
Expand Down Expand Up @@ -295,6 +343,14 @@ impl MockInput for App {
self.world.send_input_to_gamepad(input, gamepad);
}

fn release_input(&mut self, input: impl Into<UserInput>) {
self.world.release_input(input);
}

fn release_input_for_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
self.world.release_input_for_gamepad(input, gamepad);
}

fn pressed(&mut self, input: impl Into<UserInput>) -> bool {
self.world.pressed(input)
}
Expand Down
26 changes: 26 additions & 0 deletions src/user_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ impl UserInput {
}
}
}

/// Returns the raw inputs that make up this [`UserInput`]
pub fn raw_inputs(&self) -> (Vec<GamepadButtonType>, Vec<KeyCode>, Vec<MouseButton>) {
let mut gamepad_buttons: Vec<GamepadButtonType> = Vec::default();
let mut keyboard_buttons: Vec<KeyCode> = Vec::default();
let mut mouse_buttons: Vec<MouseButton> = Vec::default();

match self {
UserInput::Single(button) => match *button {
InputButton::Gamepad(variant) => gamepad_buttons.push(variant),
InputButton::Keyboard(variant) => keyboard_buttons.push(variant),
InputButton::Mouse(variant) => mouse_buttons.push(variant),
},
UserInput::Chord(button_set) => {
for button in button_set.iter() {
match button {
InputButton::Gamepad(variant) => gamepad_buttons.push(*variant),
InputButton::Keyboard(variant) => keyboard_buttons.push(*variant),
InputButton::Mouse(variant) => mouse_buttons.push(*variant),
}
}
}
};

(gamepad_buttons, keyboard_buttons, mouse_buttons)
}
}

impl From<InputButton> for UserInput {
Expand Down