use crate::axislike::{AxisType, MouseMotionAxisType, MouseWheelAxisType};
use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection};
use crate::input_streams::{InputStreams, MutableInputStreams};
use crate::user_input::{RawInputs, UserInput};
use bevy::app::App;
use bevy::ecs::event::Events;
use bevy::ecs::system::{ResMut, SystemState};
use bevy::ecs::world::World;
#[cfg(feature = "ui")]
use bevy::ecs::{component::Component, query::With, system::Query};
use bevy::input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent};
use bevy::input::keyboard::{Key, NativeKey};
use bevy::input::mouse::MouseScrollUnit;
use bevy::input::ButtonState;
use bevy::input::{
gamepad::{Gamepad, GamepadButton, GamepadEvent},
keyboard::{KeyCode, KeyboardInput},
mouse::{MouseButton, MouseButtonInput, MouseMotion, MouseWheel},
touch::{TouchInput, Touches},
ButtonInput,
};
use bevy::math::Vec2;
use bevy::prelude::Entity;
#[cfg(feature = "ui")]
use bevy::ui::Interaction;
use bevy::window::CursorMoved;
pub trait MockInput {
fn send_input(&mut self, input: impl Into<UserInput>);
fn send_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>);
fn release_input(&mut self, input: impl Into<UserInput>);
fn release_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>);
fn reset_inputs(&mut self);
}
pub trait QueryInput {
fn pressed(&self, input: impl Into<UserInput>) -> bool;
fn pressed_for_gamepad(&self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) -> bool;
}
#[cfg(feature = "ui")]
pub trait MockUIInteraction {
fn click_button<Marker: Component>(&mut self);
fn hover_button<Marker: Component>(&mut self);
}
impl MockInput for MutableInputStreams<'_> {
fn send_input(&mut self, input: impl Into<UserInput>) {
self.send_input_as_gamepad(input, self.guess_gamepad());
}
fn send_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
let input_to_send: UserInput = input.into();
let raw_inputs = input_to_send.raw_inputs();
self.send_keyboard_input(ButtonState::Pressed, &raw_inputs);
for button in raw_inputs.mouse_buttons.iter() {
self.mouse_button_events.send(MouseButtonInput {
button: *button,
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
});
}
for mouse_wheel_direction in raw_inputs.mouse_wheel.iter() {
match *mouse_wheel_direction {
MouseWheelDirection::Left => self.send_mouse_wheel(-1.0, 0.0),
MouseWheelDirection::Right => self.send_mouse_wheel(1.0, 0.0),
MouseWheelDirection::Up => self.send_mouse_wheel(0.0, 1.0),
MouseWheelDirection::Down => self.send_mouse_wheel(0.0, -1.0),
};
}
for mouse_motion_direction in raw_inputs.mouse_motion.iter() {
match *mouse_motion_direction {
MouseMotionDirection::Up => self.send_mouse_motion(0.0, 1.0),
MouseMotionDirection::Down => self.send_mouse_motion(0.0, -1.0),
MouseMotionDirection::Right => self.send_mouse_motion(1.0, 0.0),
MouseMotionDirection::Left => self.send_mouse_motion(-1.0, 0.0),
};
}
self.send_gamepad_button_changed(gamepad, &raw_inputs);
for (outer_axis_type, maybe_position_data) in raw_inputs.axis_data.iter() {
if let Some(position_data) = *maybe_position_data {
match outer_axis_type {
AxisType::Gamepad(axis_type) => {
if let Some(gamepad) = gamepad {
self.gamepad_events
.send(GamepadEvent::Axis(GamepadAxisChangedEvent {
gamepad,
axis_type: *axis_type,
value: position_data,
}));
}
}
AxisType::MouseWheel(axis_type) => match *axis_type {
MouseWheelAxisType::X => self.send_mouse_wheel(position_data, 0.0),
MouseWheelAxisType::Y => self.send_mouse_wheel(0.0, position_data),
},
AxisType::MouseMotion(axis_type) => match *axis_type {
MouseMotionAxisType::X => self.send_mouse_motion(position_data, 0.0),
MouseMotionAxisType::Y => self.send_mouse_motion(0.0, position_data),
},
}
}
}
}
fn release_input(&mut self, input: impl Into<UserInput>) {
self.release_input_as_gamepad(input, self.guess_gamepad())
}
fn release_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
let input_to_release: UserInput = input.into();
let raw_inputs = input_to_release.raw_inputs();
self.send_gamepad_button_changed(gamepad, &raw_inputs);
self.send_keyboard_input(ButtonState::Released, &raw_inputs);
for button in raw_inputs.mouse_buttons {
self.mouse_button_events.send(MouseButtonInput {
button,
state: ButtonState::Released,
window: Entity::PLACEHOLDER,
});
}
}
fn reset_inputs(&mut self) {
*self.gamepad_buttons = Default::default();
*self.gamepad_axes = Default::default();
*self.keycodes = Default::default();
*self.mouse_buttons = Default::default();
*self.mouse_wheel = Default::default();
*self.mouse_motion = Default::default();
}
}
impl MutableInputStreams<'_> {
fn send_keyboard_input(&mut self, button_state: ButtonState, raw_inputs: &RawInputs) {
for key_code in raw_inputs.keycodes.iter() {
self.keyboard_events.send(KeyboardInput {
logical_key: Key::Unidentified(NativeKey::Unidentified),
key_code: *key_code,
state: button_state,
window: Entity::PLACEHOLDER,
});
}
}
fn send_mouse_wheel(&mut self, x: f32, y: f32) {
let unit = MouseScrollUnit::Pixel;
let window = Entity::PLACEHOLDER;
self.mouse_wheel.send(MouseWheel { unit, x, y, window });
}
fn send_mouse_motion(&mut self, x: f32, y: f32) {
let delta = Vec2::new(x, y);
self.mouse_motion.send(MouseMotion { delta });
}
fn send_gamepad_button_changed(&mut self, gamepad: Option<Gamepad>, raw_inputs: &RawInputs) {
if let Some(gamepad) = gamepad {
for button_type in raw_inputs.gamepad_buttons.iter() {
self.gamepad_events
.send(GamepadEvent::Button(GamepadButtonChangedEvent {
gamepad,
button_type: *button_type,
value: 1.0,
}));
}
}
}
}
impl QueryInput for InputStreams<'_> {
fn pressed(&self, input: impl Into<UserInput>) -> bool {
self.input_pressed(&input.into())
}
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())
}
}
impl MockInput for World {
fn send_input(&mut self, input: impl Into<UserInput>) {
let mut mutable_input_streams = MutableInputStreams::from_world(self, None);
mutable_input_streams.send_input(input);
}
fn send_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
let mut mutable_input_streams = MutableInputStreams::from_world(self, gamepad);
mutable_input_streams.send_input_as_gamepad(input, gamepad);
}
fn release_input(&mut self, input: impl Into<UserInput>) {
let mut mutable_input_streams = MutableInputStreams::from_world(self, None);
mutable_input_streams.release_input(input);
}
fn release_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
let mut mutable_input_streams = MutableInputStreams::from_world(self, gamepad);
mutable_input_streams.release_input_as_gamepad(input, gamepad);
}
fn reset_inputs(&mut self) {
#[cfg(feature = "ui")]
{
let mut interaction_system_state: SystemState<Query<&mut Interaction>> =
SystemState::new(self);
let mut interaction_query = interaction_system_state.get_mut(self);
for mut interaction in interaction_query.iter_mut() {
*interaction = Interaction::None;
}
}
let mut input_system_state: SystemState<(
Option<ResMut<ButtonInput<GamepadButton>>>,
Option<ResMut<ButtonInput<KeyCode>>>,
Option<ResMut<ButtonInput<MouseButton>>>,
)> = SystemState::new(self);
let (maybe_gamepad, maybe_keyboard, maybe_mouse) = input_system_state.get_mut(self);
if let Some(mut gamepad) = maybe_gamepad {
*gamepad = Default::default();
}
if let Some(mut keyboard) = maybe_keyboard {
*keyboard = Default::default();
}
if let Some(mut mouse) = maybe_mouse {
*mouse = Default::default();
}
self.insert_resource(Events::<GamepadEvent>::default());
self.insert_resource(Events::<KeyboardInput>::default());
self.insert_resource(Events::<MouseButtonInput>::default());
self.insert_resource(Events::<MouseWheel>::default());
self.insert_resource(Events::<CursorMoved>::default());
self.insert_resource(Touches::default());
self.insert_resource(Events::<TouchInput>::default());
}
}
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>>();
for mut interaction in button_query.iter_mut(self) {
*interaction = Interaction::Pressed;
}
}
fn hover_button<Marker: Component>(&mut self) {
let mut button_query = self.query_filtered::<&mut Interaction, With<Marker>>();
for mut interaction in button_query.iter_mut(self) {
*interaction = Interaction::Hovered;
}
}
}
impl MockInput for App {
fn send_input(&mut self, input: impl Into<UserInput>) {
self.world.send_input(input);
}
fn send_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
self.world.send_input_as_gamepad(input, gamepad);
}
fn release_input(&mut self, input: impl Into<UserInput>) {
self.world.release_input(input);
}
fn release_input_as_gamepad(&mut self, input: impl Into<UserInput>, gamepad: Option<Gamepad>) {
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)
}
}
#[cfg(feature = "ui")]
impl MockUIInteraction for App {
fn click_button<Marker: Component>(&mut self) {
self.world.click_button::<Marker>();
}
fn hover_button<Marker: Component>(&mut self) {
self.world.hover_button::<Marker>();
}
}
#[cfg(test)]
mod test {
use crate::input_mocking::{MockInput, QueryInput};
use bevy::{
input::{
gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo},
InputPlugin,
},
prelude::*,
};
#[test]
fn ordinary_button_inputs() {
let mut app = App::new();
app.add_plugins(InputPlugin);
assert!(!app.pressed(KeyCode::Space));
assert!(!app.pressed(MouseButton::Right));
app.send_input(KeyCode::Space);
app.send_input(MouseButton::Right);
app.update();
let keyboard_input: &ButtonInput<KeyCode> = app.world.resource();
assert!(keyboard_input.pressed(KeyCode::Space));
assert!(app.pressed(KeyCode::Space));
assert!(app.pressed(MouseButton::Right));
app.reset_inputs();
app.update();
assert!(!app.pressed(KeyCode::Space));
assert!(!app.pressed(MouseButton::Right));
}
#[test]
fn explicit_gamepad_button_inputs() {
let mut app = App::new();
app.add_plugins(InputPlugin);
let gamepad = Gamepad { id: 0 };
let mut gamepad_events = app.world.resource_mut::<Events<GamepadEvent>>();
gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent {
gamepad,
connection: GamepadConnection::Connected(GamepadInfo {
name: "TestController".into(),
}),
}));
app.update();
assert!(!app.pressed_for_gamepad(GamepadButtonType::North, Some(gamepad)));
app.send_input_as_gamepad(GamepadButtonType::North, Some(gamepad));
app.update();
let gamepad_input = app.world.resource::<ButtonInput<GamepadButton>>();
assert!(gamepad_input.pressed(GamepadButton {
gamepad,
button_type: GamepadButtonType::North,
}));
assert!(app.pressed_for_gamepad(GamepadButtonType::North, Some(gamepad)));
app.reset_inputs();
app.update();
assert!(!app.pressed_for_gamepad(GamepadButtonType::North, Some(gamepad)));
}
#[test]
fn implicit_gamepad_button_inputs() {
let mut app = App::new();
app.add_plugins(InputPlugin);
let gamepad = Gamepad { id: 0 };
let mut gamepad_events = app.world.resource_mut::<Events<GamepadEvent>>();
gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent {
gamepad,
connection: GamepadConnection::Connected(GamepadInfo {
name: "TestController".into(),
}),
}));
app.update();
assert!(!app.pressed(GamepadButtonType::North));
app.send_input(GamepadButtonType::North);
app.update();
assert!(app.pressed(GamepadButtonType::North));
app.reset_inputs();
app.update();
assert!(!app.pressed(GamepadButtonType::North));
}
#[test]
#[cfg(feature = "ui")]
fn ui_inputs() {
use crate::input_mocking::MockUIInteraction;
use bevy::ecs::prelude::*;
use bevy::ui::Interaction;
#[derive(Component)]
struct ButtonMarker;
let mut app = App::new();
app.add_plugins(InputPlugin);
app.world.spawn((Interaction::None, ButtonMarker));
app.world.spawn(Interaction::None);
app.world.click_button::<ButtonMarker>();
app.update();
let mut interaction_query = app.world.query::<(&Interaction, Option<&ButtonMarker>)>();
for (interaction, maybe_marker) in interaction_query.iter(&app.world) {
match maybe_marker {
Some(_) => assert_eq!(*interaction, Interaction::Pressed),
None => assert_eq!(*interaction, Interaction::None),
}
}
app.world.reset_inputs();
let mut interaction_query = app.world.query::<&Interaction>();
for interaction in interaction_query.iter(&app.world) {
assert_eq!(*interaction, Interaction::None)
}
app.hover_button::<ButtonMarker>();
app.update();
let mut interaction_query = app.world.query::<(&Interaction, Option<&ButtonMarker>)>();
for (interaction, maybe_marker) in interaction_query.iter(&app.world) {
match maybe_marker {
Some(_) => assert_eq!(*interaction, Interaction::Hovered),
None => assert_eq!(*interaction, Interaction::None),
}
}
app.world.reset_inputs();
let mut interaction_query = app.world.query::<&Interaction>();
for interaction in interaction_query.iter(&app.world) {
assert_eq!(*interaction, Interaction::None)
}
}
}