From edc457c8d58c28cf76cc1f45f8d3d83f961ac515 Mon Sep 17 00:00:00 2001 From: Wesley Clements Date: Tue, 12 Dec 2023 13:38:02 -0800 Subject: [PATCH 1/2] 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}; From 5fdee52b7b230b50f8af8842e1f551f4e4fea371 Mon Sep 17 00:00:00 2001 From: Wesley Clements Date: Tue, 12 Dec 2023 13:38:43 -0800 Subject: [PATCH 2/2] reverted MutableInputStreams to use Events from mouse_wheel and mouse_motion (#424) --- src/input_mocking.rs | 28 ++++++++++++------------ src/input_streams.rs | 52 +++++++++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/input_mocking.rs b/src/input_mocking.rs index 35ff080d..fc397ca2 100644 --- a/src/input_mocking.rs +++ b/src/input_mocking.rs @@ -183,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, @@ -213,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 }, }), } @@ -257,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, @@ -272,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, @@ -337,8 +337,8 @@ 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(); } } diff --git a/src/input_streams.rs b/src/input_streams.rs index 368ad03c..c6167e92 100644 --- a/src/input_streams.rs +++ b/src/input_streams.rs @@ -497,9 +497,9 @@ pub struct MutableInputStreams<'a> { /// Events used for mocking [`MouseButton`] inputs pub mouse_button_events: &'a mut Events, /// A [`MouseWheel`] event stream - pub mouse_wheel: Vec, + pub mouse_wheel: &'a mut Events, /// A [`MouseMotion`] event stream - pub mouse_motion: Vec, + pub mouse_motion: &'a mut Events, /// The [`Gamepad`] that this struct will detect inputs from pub associated_gamepad: Option, @@ -538,18 +538,6 @@ impl<'a> MutableInputStreams<'a> { mouse_motion, ) = input_system_state.get_mut(world); - let mouse_wheel: Vec = mouse_wheel - .get_reader() - .read(&mouse_wheel) - .cloned() - .collect(); - - let mouse_motion: Vec = mouse_motion - .get_reader() - .read(&mouse_motion) - .cloned() - .collect(); - MutableInputStreams { gamepad_buttons: gamepad_buttons.into_inner(), gamepad_button_axes: gamepad_button_axes.into_inner(), @@ -561,8 +549,8 @@ impl<'a> MutableInputStreams<'a> { keyboard_events: keyboard_events.into_inner(), mouse_buttons: mouse_buttons.into_inner(), mouse_button_events: mouse_button_events.into_inner(), - mouse_wheel, - mouse_motion, + mouse_wheel: mouse_wheel.into_inner(), + mouse_motion: mouse_motion.into_inner(), associated_gamepad: gamepad, } } @@ -589,8 +577,20 @@ impl<'a> From> for InputStreams<'a> { keycodes: Some(mutable_streams.keycodes), scan_codes: Some(mutable_streams.scan_codes), mouse_buttons: Some(mutable_streams.mouse_buttons), - mouse_wheel: Some(mutable_streams.mouse_wheel), - mouse_motion: mutable_streams.mouse_motion, + mouse_wheel: Some( + mutable_streams + .mouse_wheel + .get_reader() + .read(mutable_streams.mouse_wheel) + .cloned() + .collect(), + ), + mouse_motion: mutable_streams + .mouse_motion + .get_reader() + .read(mutable_streams.mouse_motion) + .cloned() + .collect(), associated_gamepad: mutable_streams.associated_gamepad, } } @@ -606,8 +606,20 @@ impl<'a> From<&'a MutableInputStreams<'a>> for InputStreams<'a> { keycodes: Some(mutable_streams.keycodes), scan_codes: Some(mutable_streams.scan_codes), mouse_buttons: Some(mutable_streams.mouse_buttons), - mouse_wheel: Some(mutable_streams.mouse_wheel.clone()), - mouse_motion: mutable_streams.mouse_motion.clone(), + mouse_wheel: Some( + mutable_streams + .mouse_wheel + .get_reader() + .read(mutable_streams.mouse_wheel) + .cloned() + .collect(), + ), + mouse_motion: mutable_streams + .mouse_motion + .get_reader() + .read(mutable_streams.mouse_motion) + .cloned() + .collect(), associated_gamepad: mutable_streams.associated_gamepad, } }