From edc457c8d58c28cf76cc1f45f8d3d83f961ac515 Mon Sep 17 00:00:00 2001 From: Wesley Clements Date: Tue, 12 Dec 2023 13:38:02 -0800 Subject: [PATCH] Split MockInput trait (#425) * split MockInput trait * renamed MockUIInput to MockUIInteraction --- src/input_mocking.rs | 101 +++++++++++++++++++++++-------------------- src/input_streams.rs | 12 ++--- src/lib.rs | 4 +- 3 files changed, 62 insertions(+), 55 deletions(-) diff --git a/src/input_mocking.rs b/src/input_mocking.rs index dcb494ad..35ff080d 100644 --- a/src/input_mocking.rs +++ b/src/input_mocking.rs @@ -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, gamepad: Option); + /// 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, @@ -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, gamepad: Option) -> 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(&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(&mut self); } @@ -321,18 +329,6 @@ impl MockInput for MutableInputStreams<'_> { } } - fn pressed(&self, input: impl Into) -> bool { - let input_streams: InputStreams = self.into(); - input_streams.input_pressed(&input.into()) - } - - fn pressed_for_gamepad(&self, input: impl Into, gamepad: Option) -> 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 @@ -344,15 +340,18 @@ impl MockInput for MutableInputStreams<'_> { self.mouse_wheel = Default::default(); self.mouse_motion = Default::default(); } +} - #[cfg(feature = "ui")] - fn click_button(&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) -> bool { + self.input_pressed(&input.into()) } - #[cfg(feature = "ui")] - fn hover_button(&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, gamepad: Option) -> bool { + let mut input_streams = self.clone(); + input_streams.associated_gamepad = gamepad; + + input_streams.input_pressed(&input.into()) } } @@ -381,16 +380,6 @@ impl MockInput for World { mutable_input_streams.release_input_as_gamepad(input, gamepad); } - fn pressed(&self, input: impl Into) -> bool { - self.pressed_for_gamepad(input, None) - } - - fn pressed_for_gamepad(&self, input: impl Into, gamepad: Option) -> bool { - let input_streams = InputStreams::from_world(self, gamepad); - - input_streams.input_pressed(&input.into()) - } - fn reset_inputs(&mut self) { #[cfg(feature = "ui")] { @@ -434,8 +423,22 @@ impl MockInput for World { self.insert_resource(Touches::default()); self.insert_resource(Events::::default()); } +} - #[cfg(feature = "ui")] +impl QueryInput for World { + fn pressed(&self, input: impl Into) -> bool { + self.pressed_for_gamepad(input, None) + } + + fn pressed_for_gamepad(&self, input: impl Into, gamepad: Option) -> 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(&mut self) { let mut button_query = self.query_filtered::<&mut Interaction, With>(); @@ -444,7 +447,6 @@ impl MockInput for World { } } - #[cfg(feature = "ui")] fn hover_button(&mut self) { let mut button_query = self.query_filtered::<&mut Interaction, With>(); @@ -471,6 +473,12 @@ 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) -> bool { self.world.pressed(input) } @@ -478,17 +486,14 @@ impl MockInput for App { fn pressed_for_gamepad(&self, input: impl Into, gamepad: Option) -> 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(&mut self) { self.world.click_button::(); } - #[cfg(feature = "ui")] fn hover_button(&mut self) { self.world.hover_button::(); } @@ -496,7 +501,7 @@ impl MockInput for App { #[cfg(test)] mod test { - use crate::input_mocking::MockInput; + use crate::input_mocking::{MockInput, MockUIInteraction, QueryInput}; use bevy::{ input::{ gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo}, diff --git a/src/input_streams.rs b/src/input_streams.rs index 3f934318..368ad03c 100644 --- a/src/input_streams.rs +++ b/src/input_streams.rs @@ -615,8 +615,8 @@ impl<'a> From<&'a MutableInputStreams<'a>> for InputStreams<'a> { #[cfg(test)] mod tests { - use super::MutableInputStreams; - use crate::prelude::MockInput; + use super::{InputStreams, MutableInputStreams}; + use crate::prelude::{MockInput, QueryInput}; use bevy::input::InputPlugin; use bevy::prelude::*; @@ -627,24 +627,24 @@ mod tests { app.add_plugins(InputPlugin); let mut input_streams = MutableInputStreams::from_world(&mut app.world, None); - assert!(!input_streams.pressed(Modifier::Control)); + assert!(!InputStreams::from(&input_streams).pressed(Modifier::Control)); input_streams.send_input(KeyCode::ControlLeft); app.update(); let mut input_streams = MutableInputStreams::from_world(&mut app.world, None); - assert!(input_streams.pressed(Modifier::Control)); + assert!(InputStreams::from(&input_streams).pressed(Modifier::Control)); input_streams.reset_inputs(); app.update(); let mut input_streams = MutableInputStreams::from_world(&mut app.world, None); - assert!(!input_streams.pressed(Modifier::Control)); + assert!(!InputStreams::from(&input_streams).pressed(Modifier::Control)); input_streams.send_input(KeyCode::ControlRight); app.update(); let input_streams = MutableInputStreams::from_world(&mut app.world, None); - assert!(input_streams.pressed(Modifier::Control)); + assert!(InputStreams::from(&input_streams).pressed(Modifier::Control)); } } diff --git a/src/lib.rs b/src/lib.rs index 2338e048..8a08acd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,9 @@ pub mod prelude { pub use crate::buttonlike::MouseWheelDirection; pub use crate::clashing_inputs::ClashStrategy; pub use crate::input_map::InputMap; - pub use crate::input_mocking::MockInput; + #[cfg(feature = "ui")] + pub use crate::input_mocking::MockUIInteraction; + pub use crate::input_mocking::{MockInput, QueryInput}; pub use crate::scan_codes::QwertyScanCode; pub use crate::user_input::{Modifier, UserInput};