leafwing-input-manager 0.20.0

A powerful, flexible and ergonomic way to manage action-input keybindings for the Bevy game engine.
Documentation
#![cfg(all(feature = "gamepad", feature = "keyboard", feature = "mouse"))]

use bevy::{
    input::{
        InputPlugin,
        gamepad::{
            GamepadConnection, GamepadConnectionEvent, RawGamepadButtonChangedEvent,
            RawGamepadEvent,
        },
    },
    prelude::*,
};
use leafwing_input_manager::prelude::*;
use updating::CentralInputStore;

#[derive(Actionlike, Debug, PartialEq, Eq, Clone, Copy, Hash, Reflect)]
enum TestAction {
    Throttle,
}

struct TestContext {
    pub app: App,
}

impl TestContext {
    pub fn new() -> Self {
        let mut app = App::new();
        app.add_plugins((
            MinimalPlugins,
            InputPlugin,
            InputManagerPlugin::<TestAction>::default(),
        ));

        let mut input_map = InputMap::<TestAction>::default()
            .with(TestAction::Throttle, GamepadButton::South)
            .with(TestAction::Throttle, GamepadButton::RightTrigger);
        input_map
            .insert(TestAction::Throttle, KeyCode::Space)
            .insert(TestAction::Throttle, MouseButton::Left);

        app.insert_resource(input_map)
            .init_resource::<ActionState<TestAction>>();

        app.update();
        app.update();

        Self { app }
    }

    pub fn send_gamepad_connection_event(&mut self, gamepad: Option<Entity>) -> Entity {
        let gamepad = gamepad.unwrap_or_else(|| self.app.world_mut().spawn_empty().id());
        self.app
            .world_mut()
            .resource_mut::<Messages<GamepadConnectionEvent>>()
            .write(GamepadConnectionEvent::new(
                gamepad,
                GamepadConnection::Connected {
                    name: "TestController".to_string(),
                    vendor_id: None,
                    product_id: None,
                },
            ));

        self.app.update();
        gamepad
    }

    pub fn update(&mut self) {
        self.app.update();
    }

    pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
        self.app
            .world_mut()
            .resource_mut::<Messages<RawGamepadEvent>>()
            .write(event);
    }
}

#[test]
fn set_gamepad_updates_central_input_store() {
    let mut ctx = TestContext::new();
    let gamepad = ctx.send_gamepad_connection_event(None);
    let gamepad_trigger = GamepadButton::RightTrigger;
    ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
        gamepad,
        button: gamepad_trigger,
        value: 0.8,
    }));
    ctx.update();

    let central_input_store = ctx.app.world().resource::<CentralInputStore>();
    assert_eq!(central_input_store.button_value(&gamepad_trigger), 0.8);
    assert!(central_input_store.pressed(&gamepad_trigger).unwrap());
}

#[test]
fn set_keyboard_updates_central_input_store() {
    let mut app = TestContext::new().app;

    let keycode = KeyCode::Space;
    keycode.set_value(app.world_mut(), 1.0);

    app.update();

    let central_input_store = app.world().resource::<CentralInputStore>();

    assert_eq!(central_input_store.button_value(&keycode), 1.0);
    assert!(central_input_store.pressed(&keycode).unwrap());
}

#[test]
fn gamepad_button_value() {
    let mut ctx = TestContext::new();
    let gamepad = ctx.send_gamepad_connection_event(None);
    let mut app = ctx.app;

    let gamepad_button: GamepadButton = GamepadButton::South;
    gamepad_button.set_value_as_gamepad(app.world_mut(), 1.0, Some(gamepad));
    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 1.0);
}

#[test]
fn mouse_button_value() {
    let mut app = TestContext::new().app;

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 0.0);

    let relevant_button = MouseButton::Left;
    relevant_button.press(app.world_mut());

    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 1.0);
}

#[test]
fn keyboard_button_value() {
    let mut app = TestContext::new().app;

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 0.0);

    let relevant_button = KeyCode::Space;
    relevant_button.press(app.world_mut());

    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 1.0);
}

#[test]
fn gamepad_trigger() {
    let mut ctx = TestContext::new();
    let gamepad = ctx.send_gamepad_connection_event(None);
    let mut app = ctx.app;

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 0.0);

    let gamepad_trigger = GamepadButton::RightTrigger;
    gamepad_trigger.set_value_as_gamepad(app.world_mut(), 0.7, Some(gamepad));
    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    let button_value = action_state.button_value(&TestAction::Throttle);
    assert_eq!(button_value, 0.7);
}

#[test]
fn buttonlike_actions_can_be_pressed_and_released_when_pressed() {
    let mut app = TestContext::new().app;

    let relevant_button = MouseButton::Left;

    relevant_button.press(app.world_mut());
    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    assert!(action_state.just_pressed(&TestAction::Throttle));
    assert!(action_state.pressed(&TestAction::Throttle));
    assert!(!action_state.released(&TestAction::Throttle));
    assert!(!action_state.just_released(&TestAction::Throttle));

    relevant_button.release(app.world_mut());
    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    assert!(!action_state.just_pressed(&TestAction::Throttle));
    assert!(!action_state.pressed(&TestAction::Throttle));
    assert!(action_state.just_released(&TestAction::Throttle));
    assert!(action_state.released(&TestAction::Throttle));
}

#[test]
fn buttonlike_actions_can_be_pressed_and_released_when_button_value_set() {
    let mut ctx = TestContext::new();
    let gamepad = ctx.send_gamepad_connection_event(None);
    let mut app = ctx.app;

    let gamepad_trigger = GamepadButton::RightTrigger;
    gamepad_trigger.set_value_as_gamepad(app.world_mut(), 1.0, Some(gamepad));
    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    assert!(action_state.just_pressed(&TestAction::Throttle));
    assert!(action_state.pressed(&TestAction::Throttle));
    assert!(!action_state.released(&TestAction::Throttle));
    assert!(!action_state.just_released(&TestAction::Throttle));

    let gamepad_trigger = GamepadButton::RightTrigger;
    gamepad_trigger.set_value_as_gamepad(app.world_mut(), 0.0, Some(gamepad));
    app.update();

    let action_state = app.world().resource::<ActionState<TestAction>>();
    assert!(!action_state.just_pressed(&TestAction::Throttle));
    assert!(!action_state.pressed(&TestAction::Throttle));
    assert!(action_state.just_released(&TestAction::Throttle));
    assert!(action_state.released(&TestAction::Throttle));
}