Skip to content

Commit

Permalink
Merge branch 'main' into fix-egui-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
alice-i-cecile authored Dec 12, 2023
2 parents 6467d5e + 5fdee52 commit 4c1fef7
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 89 deletions.
129 changes: 67 additions & 62 deletions src/input_mocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ pub trait MockInput {
/// Provide the [`Gamepad`] identifier to control which gamepad you are emulating.
fn release_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>);

/// Clears all user input streams, resetting them to their default state
///
/// All buttons are released, and `just_pressed` and `just_released` information on the [`Input`] type are lost.
/// `just_pressed` and `just_released` on the [`ActionState`](crate::action_state::ActionState) will be kept.
///
/// This will clear all [`KeyCode`], [`GamepadButton`] and [`MouseButton`] input streams,
/// as well as any [`Interaction`] components and all input [`Events`].
fn reset_inputs(&mut self);
}

/// Query [`Input`] state directly for testing purposes.
///
/// In game code, you should (almost) always be using [`ActionState`](crate::action_state::ActionState)
/// methods instead.
pub trait QueryInput {
/// Is the provided `user_input` pressed?
///
/// This method is intended as a convenience for testing; check the [`Input`] resource directly,
Expand All @@ -120,26 +135,19 @@ pub trait MockInput {
/// This method is intended as a convenience for testing; check the [`Input`] resource directly,
/// or use an [`InputMap`](crate::input_map::InputMap) in real code.
fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool;
}

/// Clears all user input streams, resetting them to their default state
///
/// All buttons are released, and `just_pressed` and `just_released` information on the [`Input`] type are lost.
/// `just_pressed` and `just_released` on the [`ActionState`](crate::action_state::ActionState) will be kept.
///
/// This will clear all [`KeyCode`], [`GamepadButton`] and [`MouseButton`] input streams,
/// as well as any [`Interaction`] components and all input [`Events`].
fn reset_inputs(&mut self);

/// Send fake UI interaction for testing purposes.
#[cfg(feature = "ui")]
pub trait MockUIInteraction {
/// Presses all `bevy::ui` buttons with the matching `Marker` component
///
/// Changes their [`Interaction`] component to [`Interaction::Pressed`]
#[cfg(feature = "ui")]
fn click_button<Marker: Component>(&mut self);

/// Hovers over all `bevy::ui` buttons with the matching `Marker` component
///
/// Changes their [`Interaction`] component to [`Interaction::Pressed`]
#[cfg(feature = "ui")]
fn hover_button<Marker: Component>(&mut self);
}

Expand Down Expand Up @@ -175,25 +183,25 @@ impl MockInput for MutableInputStreams<'_> {
// Discrete mouse wheel events
for mouse_wheel_direction in raw_inputs.mouse_wheel {
match mouse_wheel_direction {
MouseWheelDirection::Left => self.mouse_wheel.push(MouseWheel {
MouseWheelDirection::Left => self.mouse_wheel.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: -1.0,
y: 0.0,
window: Entity::PLACEHOLDER,
}),
MouseWheelDirection::Right => self.mouse_wheel.push(MouseWheel {
MouseWheelDirection::Right => self.mouse_wheel.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: 1.0,
y: 0.0,
window: Entity::PLACEHOLDER,
}),
MouseWheelDirection::Up => self.mouse_wheel.push(MouseWheel {
MouseWheelDirection::Up => self.mouse_wheel.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: 0.0,
y: 1.0,
window: Entity::PLACEHOLDER,
}),
MouseWheelDirection::Down => self.mouse_wheel.push(MouseWheel {
MouseWheelDirection::Down => self.mouse_wheel.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: 0.0,
y: -1.0,
Expand All @@ -205,16 +213,16 @@ impl MockInput for MutableInputStreams<'_> {
// Discrete mouse motion event
for mouse_motion_direction in raw_inputs.mouse_motion {
match mouse_motion_direction {
MouseMotionDirection::Up => self.mouse_motion.push(MouseMotion {
MouseMotionDirection::Up => self.mouse_motion.send(MouseMotion {
delta: Vec2 { x: 0.0, y: 1.0 },
}),
MouseMotionDirection::Down => self.mouse_motion.push(MouseMotion {
MouseMotionDirection::Down => self.mouse_motion.send(MouseMotion {
delta: Vec2 { x: 0.0, y: -1.0 },
}),
MouseMotionDirection::Right => self.mouse_motion.push(MouseMotion {
MouseMotionDirection::Right => self.mouse_motion.send(MouseMotion {
delta: Vec2 { x: 1.0, y: 0.0 },
}),
MouseMotionDirection::Left => self.mouse_motion.push(MouseMotion {
MouseMotionDirection::Left => self.mouse_motion.send(MouseMotion {
delta: Vec2 { x: -1.0, y: 0.0 },
}),
}
Expand Down Expand Up @@ -249,13 +257,13 @@ impl MockInput for MutableInputStreams<'_> {
AxisType::MouseWheel(axis_type) => {
match axis_type {
// FIXME: MouseScrollUnit is not recorded and is always assumed to be Pixel
MouseWheelAxisType::X => self.mouse_wheel.push(MouseWheel {
MouseWheelAxisType::X => self.mouse_wheel.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: position_data,
y: 0.0,
window: Entity::PLACEHOLDER,
}),
MouseWheelAxisType::Y => self.mouse_wheel.push(MouseWheel {
MouseWheelAxisType::Y => self.mouse_wheel.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: 0.0,
y: position_data,
Expand All @@ -264,13 +272,13 @@ impl MockInput for MutableInputStreams<'_> {
}
}
AxisType::MouseMotion(axis_type) => match axis_type {
MouseMotionAxisType::X => self.mouse_motion.push(MouseMotion {
MouseMotionAxisType::X => self.mouse_motion.send(MouseMotion {
delta: Vec2 {
x: position_data,
y: 0.0,
},
}),
MouseMotionAxisType::Y => self.mouse_motion.push(MouseMotion {
MouseMotionAxisType::Y => self.mouse_motion.send(MouseMotion {
delta: Vec2 {
x: 0.0,
y: position_data,
Expand Down Expand Up @@ -321,18 +329,6 @@ impl MockInput for MutableInputStreams<'_> {
}
}

fn pressed(&self, input: impl Into<UserInput>) -> bool {
let input_streams: InputStreams = self.into();
input_streams.input_pressed(&input.into())
}

fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool {
let mut input_streams: InputStreams = self.into();
input_streams.associated_gamepad = gamepad;

input_streams.input_pressed(&input.into())
}

fn reset_inputs(&mut self) {
// WARNING: this *must* be updated when MutableInputStreams's fields change
// Note that we deliberately are not resetting either Gamepads or associated_gamepad
Expand All @@ -341,18 +337,21 @@ impl MockInput for MutableInputStreams<'_> {
*self.gamepad_axes = Default::default();
*self.keycodes = Default::default();
*self.mouse_buttons = Default::default();
self.mouse_wheel = Default::default();
self.mouse_motion = Default::default();
*self.mouse_wheel = Default::default();
*self.mouse_motion = Default::default();
}
}

#[cfg(feature = "ui")]
fn click_button<Marker: Component>(&mut self) {
panic!("Cannot use bevy_ui input mocking from `MutableInputStreams`, use an `App` or `World` instead.")
impl QueryInput for InputStreams<'_> {
fn pressed(&self, input: impl Into<UserInput>) -> bool {
self.input_pressed(&input.into())
}

#[cfg(feature = "ui")]
fn hover_button<Marker: Component>(&mut self) {
panic!("Cannot use bevy_ui input mocking from `MutableInputStreams`, use an `App` or `World` instead.")
fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool {
let mut input_streams = self.clone();
input_streams.associated_gamepad = gamepad;

input_streams.input_pressed(&input.into())
}
}

Expand Down Expand Up @@ -381,16 +380,6 @@ impl MockInput for World {
mutable_input_streams.release_input_as_gamepad(input, gamepad);
}

fn pressed(&self, input: impl Into<UserInput>) -> bool {
self.pressed_for_gamepad(input, None)
}

fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool {
let input_streams = InputStreams::from_world(self, gamepad);

input_streams.input_pressed(&input.into())
}

fn reset_inputs(&mut self) {
#[cfg(feature = "ui")]
{
Expand Down Expand Up @@ -434,8 +423,22 @@ impl MockInput for World {
self.insert_resource(Touches::default());
self.insert_resource(Events::<TouchInput>::default());
}
}

#[cfg(feature = "ui")]
impl QueryInput for World {
fn pressed(&self, input: impl Into<UserInput>) -> bool {
self.pressed_for_gamepad(input, None)
}

fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool {
let input_streams = InputStreams::from_world(self, gamepad);

input_streams.input_pressed(&input.into())
}
}

#[cfg(feature = "ui")]
impl MockUIInteraction for World {
fn click_button<Marker: Component>(&mut self) {
let mut button_query = self.query_filtered::<&mut Interaction, With<Marker>>();

Expand All @@ -444,7 +447,6 @@ impl MockInput for World {
}
}

#[cfg(feature = "ui")]
fn hover_button<Marker: Component>(&mut self) {
let mut button_query = self.query_filtered::<&mut Interaction, With<Marker>>();

Expand All @@ -471,32 +473,35 @@ impl MockInput for App {
self.world.release_input_as_gamepad(input, gamepad);
}

fn reset_inputs(&mut self) {
self.world.reset_inputs();
}
}

impl QueryInput for App {
fn pressed(&self, input: impl Into<UserInput>) -> bool {
self.world.pressed(input)
}

fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool {
self.world.pressed_for_gamepad(input, gamepad)
}
}

fn reset_inputs(&mut self) {
self.world.reset_inputs();
}

#[cfg(feature = "ui")]
#[cfg(feature = "ui")]
impl MockUIInteraction for App {
fn click_button<Marker: Component>(&mut self) {
self.world.click_button::<Marker>();
}

#[cfg(feature = "ui")]
fn hover_button<Marker: Component>(&mut self) {
self.world.hover_button::<Marker>();
}
}

#[cfg(test)]
mod test {
use crate::input_mocking::MockInput;
use crate::input_mocking::{MockInput, MockUIInteraction, QueryInput};
use bevy::{
input::{
gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo},
Expand Down
Loading

0 comments on commit 4c1fef7

Please sign in to comment.