use bevy::prelude::*;
use std::collections::HashSet;
#[derive(Resource, Debug, Default)]
pub struct MockInput {
pressed_keys: HashSet<KeyCode>,
just_pressed_keys: HashSet<KeyCode>,
just_released_keys: HashSet<KeyCode>,
pressed_mouse_buttons: HashSet<MouseButton>,
just_pressed_mouse_buttons: HashSet<MouseButton>,
just_released_mouse_buttons: HashSet<MouseButton>,
axis_values: Vec<f32>,
pressed_gamepad_buttons: HashSet<GamepadButton>,
just_pressed_gamepad_buttons: HashSet<GamepadButton>,
just_released_gamepad_buttons: HashSet<GamepadButton>,
mouse_position: Vec2,
mouse_delta: Vec2,
scroll_delta: Vec2,
}
impl MockInput {
#[must_use]
pub fn new() -> Self {
Self {
axis_values: vec![0.0; 6], ..Default::default()
}
}
pub fn clear_just_states(&mut self) {
self.just_pressed_keys.clear();
self.just_released_keys.clear();
self.just_pressed_mouse_buttons.clear();
self.just_released_mouse_buttons.clear();
self.just_pressed_gamepad_buttons.clear();
self.just_released_gamepad_buttons.clear();
self.mouse_delta = Vec2::ZERO;
self.scroll_delta = Vec2::ZERO;
}
pub fn reset(&mut self) {
*self = Self::new();
}
pub fn press_key(&mut self, key: KeyCode) {
if self.pressed_keys.insert(key) {
self.just_pressed_keys.insert(key);
}
}
pub fn release_key(&mut self, key: KeyCode) {
if self.pressed_keys.remove(&key) {
self.just_released_keys.insert(key);
}
}
#[must_use]
pub fn is_key_pressed(&self, key: KeyCode) -> bool {
self.pressed_keys.contains(&key)
}
#[must_use]
pub fn is_key_just_pressed(&self, key: KeyCode) -> bool {
self.just_pressed_keys.contains(&key)
}
#[must_use]
pub fn is_key_just_released(&self, key: KeyCode) -> bool {
self.just_released_keys.contains(&key)
}
pub fn press_mouse(&mut self, button: MouseButton) {
if self.pressed_mouse_buttons.insert(button) {
self.just_pressed_mouse_buttons.insert(button);
}
}
pub fn release_mouse(&mut self, button: MouseButton) {
if self.pressed_mouse_buttons.remove(&button) {
self.just_released_mouse_buttons.insert(button);
}
}
#[must_use]
pub fn is_mouse_pressed(&self, button: MouseButton) -> bool {
self.pressed_mouse_buttons.contains(&button)
}
#[must_use]
pub fn is_mouse_just_pressed(&self, button: MouseButton) -> bool {
self.just_pressed_mouse_buttons.contains(&button)
}
pub fn set_mouse_position(&mut self, pos: Vec2) {
self.mouse_delta = pos - self.mouse_position;
self.mouse_position = pos;
}
#[must_use]
pub fn mouse_position(&self) -> Vec2 {
self.mouse_position
}
#[must_use]
pub fn mouse_delta(&self) -> Vec2 {
self.mouse_delta
}
pub fn set_scroll(&mut self, delta: Vec2) {
self.scroll_delta = delta;
}
#[must_use]
pub fn scroll_delta(&self) -> Vec2 {
self.scroll_delta
}
pub fn press_gamepad(&mut self, button: GamepadButton) {
if self.pressed_gamepad_buttons.insert(button) {
self.just_pressed_gamepad_buttons.insert(button);
}
}
pub fn release_gamepad(&mut self, button: GamepadButton) {
if self.pressed_gamepad_buttons.remove(&button) {
self.just_released_gamepad_buttons.insert(button);
}
}
#[must_use]
pub fn is_gamepad_pressed(&self, button: GamepadButton) -> bool {
self.pressed_gamepad_buttons.contains(&button)
}
#[must_use]
pub fn is_gamepad_just_pressed(&self, button: GamepadButton) -> bool {
self.just_pressed_gamepad_buttons.contains(&button)
}
pub fn set_axis(&mut self, axis: GamepadAxis, value: f32) {
let index = axis_to_index(axis);
if index < self.axis_values.len() {
self.axis_values[index] = value.clamp(-1.0, 1.0);
}
}
#[must_use]
pub fn get_axis(&self, axis: GamepadAxis) -> f32 {
let index = axis_to_index(axis);
self.axis_values.get(index).copied().unwrap_or(0.0)
}
pub fn set_left_stick(&mut self, x: f32, y: f32) {
self.set_axis(GamepadAxis::LeftStickX, x);
self.set_axis(GamepadAxis::LeftStickY, y);
}
pub fn set_right_stick(&mut self, x: f32, y: f32) {
self.set_axis(GamepadAxis::RightStickX, x);
self.set_axis(GamepadAxis::RightStickY, y);
}
#[must_use]
pub fn left_stick(&self) -> Vec2 {
Vec2::new(
self.get_axis(GamepadAxis::LeftStickX),
self.get_axis(GamepadAxis::LeftStickY),
)
}
#[must_use]
pub fn right_stick(&self) -> Vec2 {
Vec2::new(
self.get_axis(GamepadAxis::RightStickX),
self.get_axis(GamepadAxis::RightStickY),
)
}
}
fn axis_to_index(axis: GamepadAxis) -> usize {
match axis {
GamepadAxis::LeftStickX => 0,
GamepadAxis::LeftStickY => 1,
GamepadAxis::RightStickX => 2,
GamepadAxis::RightStickY => 3,
GamepadAxis::LeftZ => 4,
GamepadAxis::RightZ => 5,
GamepadAxis::Other(_) => 0,
}
}
pub struct MockInputPlugin;
impl Plugin for MockInputPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<MockInput>()
.add_systems(PreUpdate, sync_mock_input_to_bevy);
}
}
fn sync_mock_input_to_bevy(
mock: Res<MockInput>,
mut keyboard: ResMut<ButtonInput<KeyCode>>,
mut mouse_buttons: ResMut<ButtonInput<MouseButton>>,
) {
for key in &mock.just_pressed_keys {
keyboard.press(*key);
}
for key in &mock.just_released_keys {
keyboard.release(*key);
}
for button in &mock.just_pressed_mouse_buttons {
mouse_buttons.press(*button);
}
for button in &mock.just_released_mouse_buttons {
mouse_buttons.release(*button);
}
}
#[derive(Debug, Default)]
pub struct MockInputSequence {
frames: Vec<MockInputFrame>,
}
#[derive(Debug, Default)]
pub struct MockInputFrame {
pub press_keys: Vec<KeyCode>,
pub release_keys: Vec<KeyCode>,
pub press_gamepad: Vec<GamepadButton>,
pub release_gamepad: Vec<GamepadButton>,
pub axis_values: Vec<(GamepadAxis, f32)>,
pub press_mouse: Vec<MouseButton>,
pub release_mouse: Vec<MouseButton>,
pub mouse_position: Option<Vec2>,
}
impl MockInputSequence {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn frame(mut self, frame: MockInputFrame) -> Self {
self.frames.push(frame);
self
}
#[must_use]
pub fn press_key(mut self, key: KeyCode) -> Self {
self.frames.push(MockInputFrame {
press_keys: vec![key],
..Default::default()
});
self
}
#[must_use]
pub fn release_key(mut self, key: KeyCode) -> Self {
self.frames.push(MockInputFrame {
release_keys: vec![key],
..Default::default()
});
self
}
#[must_use]
pub fn wait(mut self) -> Self {
self.frames.push(MockInputFrame::default());
self
}
#[must_use]
pub fn wait_frames(mut self, count: usize) -> Self {
for _ in 0..count {
self.frames.push(MockInputFrame::default());
}
self
}
#[must_use]
pub fn len(&self) -> usize {
self.frames.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
#[must_use]
pub fn frames(&self) -> &[MockInputFrame] {
&self.frames
}
}
pub struct MockInputRunner<'a> {
mock: &'a mut MockInput,
sequence: &'a MockInputSequence,
current_frame: usize,
}
impl<'a> MockInputRunner<'a> {
pub fn new(mock: &'a mut MockInput, sequence: &'a MockInputSequence) -> Self {
Self {
mock,
sequence,
current_frame: 0,
}
}
pub fn with_index(
mock: &'a mut MockInput,
sequence: &'a MockInputSequence,
index: usize,
) -> Self {
Self {
mock,
sequence,
current_frame: index,
}
}
pub fn next_frame(&mut self) -> bool {
if self.current_frame >= self.sequence.len() {
return false;
}
self.mock.clear_just_states();
let frame = &self.sequence.frames[self.current_frame];
for key in &frame.press_keys {
self.mock.press_key(*key);
}
for key in &frame.release_keys {
self.mock.release_key(*key);
}
for button in &frame.press_gamepad {
self.mock.press_gamepad(*button);
}
for button in &frame.release_gamepad {
self.mock.release_gamepad(*button);
}
for (axis, value) in &frame.axis_values {
self.mock.set_axis(*axis, *value);
}
for button in &frame.press_mouse {
self.mock.press_mouse(*button);
}
for button in &frame.release_mouse {
self.mock.release_mouse(*button);
}
if let Some(pos) = frame.mouse_position {
self.mock.set_mouse_position(pos);
}
self.current_frame += 1;
true
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.current_frame >= self.sequence.len()
}
pub fn reset(&mut self) {
self.current_frame = 0;
self.mock.reset();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_input_keyboard() {
let mut mock = MockInput::new();
mock.press_key(KeyCode::Space);
assert!(mock.is_key_pressed(KeyCode::Space));
assert!(mock.is_key_just_pressed(KeyCode::Space));
mock.clear_just_states();
assert!(mock.is_key_pressed(KeyCode::Space));
assert!(!mock.is_key_just_pressed(KeyCode::Space));
mock.release_key(KeyCode::Space);
assert!(!mock.is_key_pressed(KeyCode::Space));
assert!(mock.is_key_just_released(KeyCode::Space));
}
#[test]
fn test_mock_input_gamepad_axis() {
let mut mock = MockInput::new();
mock.set_left_stick(0.5, -0.7);
let stick = mock.left_stick();
assert!((stick.x - 0.5).abs() < 0.001);
assert!((stick.y - (-0.7)).abs() < 0.001);
}
#[test]
fn test_mock_input_sequence() {
let sequence = MockInputSequence::new()
.press_key(KeyCode::Space)
.wait()
.release_key(KeyCode::Space);
assert_eq!(sequence.len(), 3);
let mut mock = MockInput::new();
{
let mut runner = MockInputRunner::new(&mut mock, &sequence);
assert!(runner.next_frame());
}
assert!(mock.is_key_pressed(KeyCode::Space));
{
let mut runner = MockInputRunner::with_index(&mut mock, &sequence, 1);
assert!(runner.next_frame());
}
assert!(mock.is_key_pressed(KeyCode::Space));
{
let mut runner = MockInputRunner::with_index(&mut mock, &sequence, 2);
assert!(runner.next_frame());
}
assert!(!mock.is_key_pressed(KeyCode::Space));
{
let mut runner = MockInputRunner::with_index(&mut mock, &sequence, 3);
assert!(!runner.next_frame());
assert!(runner.is_complete());
}
}
#[test]
fn test_mock_input_mouse() {
let mut mock = MockInput::new();
mock.set_mouse_position(Vec2::new(100.0, 200.0));
assert_eq!(mock.mouse_position(), Vec2::new(100.0, 200.0));
mock.set_mouse_position(Vec2::new(150.0, 250.0));
assert_eq!(mock.mouse_delta(), Vec2::new(50.0, 50.0));
}
}