From 4c5ad291ef5899f91116245480e633ec26bdabf9 Mon Sep 17 00:00:00 2001 From: Shute052 Date: Fri, 6 Sep 2024 04:23:06 +0800 Subject: [PATCH 1/3] Merge virtual axes --- RELEASES.md | 9 + examples/action_state_resource.rs | 2 +- examples/default_controls.rs | 2 +- examples/input_processing.rs | 2 +- examples/twin_stick_controller.rs | 4 +- examples/virtual_dpad.rs | 10 +- src/clashing_inputs.rs | 17 +- src/input_map.rs | 4 +- src/plugin.rs | 14 +- src/user_input/gamepad.rs | 394 ----------------- src/user_input/keyboard.rs | 523 +---------------------- src/user_input/mod.rs | 32 +- src/user_input/trait_serde.rs | 26 +- src/user_input/virtual_axial.rs | 679 ++++++++++++++++++++++++++++++ tests/gamepad_axis.rs | 2 +- 15 files changed, 762 insertions(+), 958 deletions(-) create mode 100644 src/user_input/virtual_axial.rs diff --git a/RELEASES.md b/RELEASES.md index 1579882b..902fed12 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,14 @@ # Release Notes +## Version 0.16.0 + +### Usability (0.16) + +- made virtual axial controls more flexible, accepting any kind of `Buttonlike` + - removed `KeyboardVirtualAxis` and `GamepadVirtualAxis` in favor of `VirtualAxis` + - removed `KeyboardVirtualDPad` and `GamepadVirtualDPad` in favor of `VirtualDPad` + - removed `KeyboardVirtualDPad3D` in favor of `VirtualDPad3D` + ## Version 0.15.1 ### Enhancements (0.15.1) diff --git a/examples/action_state_resource.rs b/examples/action_state_resource.rs index 00687f12..cd0f9c25 100644 --- a/examples/action_state_resource.rs +++ b/examples/action_state_resource.rs @@ -30,7 +30,7 @@ pub enum PlayerAction { impl PlayerAction { fn mkb_input_map() -> InputMap { InputMap::new([(Self::Jump, KeyCode::Space)]) - .with_dual_axis(Self::Move, KeyboardVirtualDPad::WASD) + .with_dual_axis(Self::Move, VirtualDPad::wasd()) } } diff --git a/examples/default_controls.rs b/examples/default_controls.rs index f9da18db..14953838 100644 --- a/examples/default_controls.rs +++ b/examples/default_controls.rs @@ -31,7 +31,7 @@ impl PlayerAction { input_map.insert(Self::UseItem, GamepadButtonType::RightTrigger2); // Default kbm input bindings - input_map.insert_dual_axis(Self::Run, KeyboardVirtualDPad::WASD); + input_map.insert_dual_axis(Self::Run, VirtualDPad::wasd()); input_map.insert(Self::Jump, KeyCode::Space); input_map.insert(Self::UseItem, MouseButton::Left); diff --git a/examples/input_processing.rs b/examples/input_processing.rs index 336dc1a4..8ed7b448 100644 --- a/examples/input_processing.rs +++ b/examples/input_processing.rs @@ -24,7 +24,7 @@ fn spawn_player(mut commands: Commands) { let input_map = InputMap::default() .with_dual_axis( Action::Move, - KeyboardVirtualDPad::WASD + VirtualDPad::wasd() // You can configure a processing pipeline to handle axis-like user inputs. // // This step adds a circular deadzone that normalizes input values diff --git a/examples/twin_stick_controller.rs b/examples/twin_stick_controller.rs index 743f7410..3b5d9e61 100644 --- a/examples/twin_stick_controller.rs +++ b/examples/twin_stick_controller.rs @@ -51,8 +51,8 @@ impl PlayerAction { input_map.insert(Self::Shoot, GamepadButtonType::RightTrigger); // Default kbm input bindings - input_map.insert_dual_axis(Self::Move, KeyboardVirtualDPad::WASD); - input_map.insert_dual_axis(Self::Look, KeyboardVirtualDPad::ARROW_KEYS); + input_map.insert_dual_axis(Self::Move, VirtualDPad::wasd()); + input_map.insert_dual_axis(Self::Look, VirtualDPad::arrow_keys()); input_map.insert(Self::Shoot, MouseButton::Left); input_map diff --git a/examples/virtual_dpad.rs b/examples/virtual_dpad.rs index dce5c03c..a4938c54 100644 --- a/examples/virtual_dpad.rs +++ b/examples/virtual_dpad.rs @@ -27,9 +27,13 @@ fn spawn_player(mut commands: Commands) { // Stores "which actions are currently activated" let input_map = InputMap::default().with_dual_axis( Action::Move, - // Define a virtual D-pad using four arbitrary keys. - // You can also use GamepadVirtualDPad to create similar ones using gamepad buttons. - KeyboardVirtualDPad::new(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD), + // Define a virtual D-pad using four arbitrary buttons. + VirtualDPad::new( + KeyCode::KeyW, + KeyCode::KeyS, + GamepadButtonType::DPadLeft, + GamepadButtonType::DPadRight, + ), ); commands .spawn(InputManagerBundle::with_map(input_map)) diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index 55f69cdc..96840bfd 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -417,7 +417,7 @@ mod tests { use bevy::prelude::Reflect; use super::*; - use crate::prelude::{KeyboardVirtualDPad, UserInput}; + use crate::prelude::{UserInput, VirtualDPad}; use crate::user_input::ButtonlikeChord; use crate as leafwing_input_manager; @@ -456,7 +456,7 @@ mod tests { CtrlAltOne, ButtonlikeChord::new([ControlLeft, AltLeft, Digit1]), ); - input_map.insert_dual_axis(MoveDPad, KeyboardVirtualDPad::ARROW_KEYS); + input_map.insert_dual_axis(MoveDPad, VirtualDPad::arrow_keys()); input_map.insert(CtrlUp, ButtonlikeChord::new([ControlLeft, ArrowUp])); input_map @@ -473,16 +473,15 @@ mod tests { } mod basic_functionality { + use super::*; use crate::{ input_map::UpdatedValue, plugin::{AccumulatorPlugin, CentralInputStorePlugin}, - prelude::{AccumulatedMouseMovement, AccumulatedMouseScroll, ModifierKey}, + prelude::{AccumulatedMouseMovement, AccumulatedMouseScroll, ModifierKey, VirtualDPad}, }; use bevy::{input::InputPlugin, prelude::*}; use Action::*; - use super::*; - #[test] #[ignore = "Figuring out how to handle the length of chords with group inputs is out of scope."] fn input_types_have_right_length() { @@ -501,7 +500,7 @@ mod tests { let modified_chord = ButtonlikeChord::modified(ModifierKey::Control, KeyA).decompose(); assert_eq!(modified_chord.len(), 2); - let group = KeyboardVirtualDPad::WASD.decompose(); + let group = VirtualDPad::wasd().decompose(); assert_eq!(group.len(), 1); } @@ -513,11 +512,11 @@ mod tests { let ab = ButtonlikeChord::new([KeyA, KeyB]); let bc = ButtonlikeChord::new([KeyB, KeyC]); let abc = ButtonlikeChord::new([KeyA, KeyB, KeyC]); - let axyz_dpad = KeyboardVirtualDPad::new(KeyA, KeyX, KeyY, KeyZ); - let abcd_dpad = KeyboardVirtualDPad::WASD; + let axyz_dpad = VirtualDPad::new(KeyA, KeyX, KeyY, KeyZ); + let abcd_dpad = VirtualDPad::wasd(); let ctrl_up = ButtonlikeChord::new([ArrowUp, ControlLeft]); - let directions_dpad = KeyboardVirtualDPad::ARROW_KEYS; + let directions_dpad = VirtualDPad::arrow_keys(); assert!(!inputs_clash(a, b)); assert!(inputs_clash(a, ab.clone())); diff --git a/src/input_map.rs b/src/input_map.rs index 9f16841d..ee19be96 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -87,7 +87,7 @@ fn find_gamepad(_gamepads: &Gamepads) -> Gamepad { /// (Action::Jump, KeyCode::Space), /// ]) /// // Associate actions with other input types. -/// .with_dual_axis(Action::Move, KeyboardVirtualDPad::WASD) +/// .with_dual_axis(Action::Move, VirtualDPad::wasd()) /// .with_dual_axis(Action::Move, GamepadStick::LEFT) /// // Associate an action with multiple inputs at once. /// .with_one_to_many(Action::Jump, [KeyCode::KeyJ, KeyCode::KeyU]); @@ -966,7 +966,7 @@ mod tests { Axis, #[actionlike(DualAxis)] DualAxis, - #[actionlike(Axis)] + #[actionlike(TripleAxis)] TripleAxis, } diff --git a/src/plugin.rs b/src/plugin.rs index 47e51133..c4191145 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -210,18 +210,18 @@ impl Plugin #[cfg(feature = "keyboard")] app.register_buttonlike_input::() - .register_buttonlike_input::() - .register_axislike_input::() - .register_dual_axislike_input::() - .register_triple_axislike_input::(); + .register_buttonlike_input::(); #[cfg(feature = "gamepad")] app.register_buttonlike_input::() .register_axislike_input::() .register_dual_axislike_input::() - .register_buttonlike_input::() - .register_axislike_input::() - .register_dual_axislike_input::(); + .register_buttonlike_input::(); + + // Virtual Axes + app.register_axislike_input::() + .register_dual_axislike_input::() + .register_triple_axislike_input::(); // Chords app.register_buttonlike_input::() diff --git a/src/user_input/gamepad.rs b/src/user_input/gamepad.rs index 3be5d901..e34ef08e 100644 --- a/src/user_input/gamepad.rs +++ b/src/user_input/gamepad.rs @@ -491,20 +491,6 @@ fn button_pressed( input_store.pressed(&button) } -/// Retrieves the current value of the given [`GamepadButtonType`]. -#[must_use] -#[inline] -fn button_value( - input_store: &CentralInputStore, - gamepad: Gamepad, - button: GamepadButtonType, -) -> f32 { - // TODO: consider providing more accurate data from trigger-like buttons - // This is part of https://github.com/Leafwing-Studios/leafwing-input-manager/issues/551 - - f32::from(button_pressed(input_store, gamepad, button)) -} - impl UpdatableInput for GamepadButton { type SourceData = ButtonInput; @@ -611,339 +597,6 @@ impl Buttonlike for GamepadButtonType { } } -/// A virtual single-axis control constructed by combining two [`GamepadButtonType`]s. -/// One button represents the negative direction (left for the X-axis, down for the Y-axis), -/// while the other represents the positive direction (right for the X-axis, up for the Y-axis). -/// -/// By default, it reads from **any connected gamepad**. -/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones. -/// -/// # Value Processing -/// -/// You can customize how the values are processed using a pipeline of processors. -/// See [`WithAxisProcessingPipelineExt`] for details. -/// -/// The raw value is determined based on the state of the associated buttons: -/// - `-1.0` if only the negative button is currently pressed. -/// - `1.0` if only the positive button is currently pressed. -/// - `0.0` if neither button is pressed, or both are pressed simultaneously. -/// -/// ```rust,ignore -/// use bevy::prelude::*; -/// use bevy::input::InputPlugin; -/// use leafwing_input_manager::prelude::*; -/// -/// let mut app = App::new(); -/// app.add_plugins(InputPlugin); -/// -/// // Define a virtual Y-axis using D-pad "up" and "down" buttons -/// let axis = GamepadVirtualAxis::DPAD_Y; -/// -/// // Pressing either button activates the input -/// GamepadButtonType::DPadUp.press(app.world_mut()); -/// app.update(); -/// assert_eq!(app.read_axis_values(axis), [1.0]); -/// -/// // You can configure a processing pipeline (e.g., doubling the value) -/// let doubled = GamepadVirtualAxis::DPAD_Y.sensitivity(2.0); -/// assert_eq!(app.read_axis_values(doubled), [2.0]); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] -#[must_use] -pub struct GamepadVirtualAxis { - /// The button that represents the negative direction. - pub(crate) negative: GamepadButtonType, - - /// The button that represents the positive direction. - pub(crate) positive: GamepadButtonType, - - /// A processing pipeline that handles input values. - pub(crate) processors: Vec, -} - -impl GamepadVirtualAxis { - /// Creates a new [`GamepadVirtualAxis`] with two given [`GamepadButtonType`]s. - /// No processing is applied to raw data from the gamepad. - #[inline] - pub const fn new(negative: GamepadButtonType, positive: GamepadButtonType) -> Self { - Self { - negative, - positive, - processors: Vec::new(), - } - } - - /// The [`GamepadVirtualAxis`] using the horizontal D-Pad button mappings. - /// No processing is applied to raw data from the gamepad. - /// - /// - [`GamepadButtonType::DPadLeft`] for negative direction. - /// - [`GamepadButtonType::DPadRight`] for positive direction. - pub const DPAD_X: Self = Self::new(GamepadButtonType::DPadLeft, GamepadButtonType::DPadRight); - - /// The [`GamepadVirtualAxis`] using the vertical D-Pad button mappings. - /// No processing is applied to raw data from the gamepad. - /// - /// - [`GamepadButtonType::DPadDown`] for negative direction. - /// - [`GamepadButtonType::DPadUp`] for positive direction. - pub const DPAD_Y: Self = Self::new(GamepadButtonType::DPadDown, GamepadButtonType::DPadUp); - - /// The [`GamepadVirtualAxis`] using the horizontal action pad button mappings. - /// No processing is applied to raw data from the gamepad. - /// - /// - [`GamepadButtonType::West`] for negative direction. - /// - [`GamepadButtonType::East`] for positive direction. - pub const ACTION_PAD_X: Self = Self::new(GamepadButtonType::West, GamepadButtonType::East); - - /// The [`GamepadVirtualAxis`] using the vertical action pad button mappings. - /// No processing is applied to raw data from the gamepad. - /// - /// - [`GamepadButtonType::South`] for negative direction. - /// - [`GamepadButtonType::North`] for positive direction. - pub const ACTION_PAD_Y: Self = Self::new(GamepadButtonType::South, GamepadButtonType::North); -} - -impl UserInput for GamepadVirtualAxis { - /// [`GamepadVirtualAxis`] acts as an axis input. - #[inline] - fn kind(&self) -> InputControlKind { - InputControlKind::Axis - } - - /// Returns the two [`GamepadButtonType`]s used by this axis. - #[inline] - fn decompose(&self) -> BasicInputs { - BasicInputs::Composite(vec![Box::new(self.negative), Box::new(self.positive)]) - } -} - -#[serde_typetag] -impl Axislike for GamepadVirtualAxis { - /// Retrieves the current value of this axis after processing by the associated processors. - #[must_use] - #[inline] - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { - let negative = button_value(input_store, gamepad, self.negative); - let positive = button_value(input_store, gamepad, self.positive); - let value = positive - negative; - self.processors - .iter() - .fold(value, |value, processor| processor.process(value)) - } - - /// Sends a [`GamepadEvent::Button`] event on the provided [`Gamepad`]. - /// - /// If the value is negative, the negative button is pressed. - /// If the value is positive, the positive button is pressed. - /// If the value is zero, neither button is pressed. - fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { - if value < 0.0 { - self.negative.press_as_gamepad(world, gamepad); - } else if value > 0.0 { - self.positive.press_as_gamepad(world, gamepad); - } - } -} - -impl WithAxisProcessingPipelineExt for GamepadVirtualAxis { - #[inline] - fn reset_processing_pipeline(mut self) -> Self { - self.processors.clear(); - self - } - - #[inline] - fn replace_processing_pipeline( - mut self, - processors: impl IntoIterator, - ) -> Self { - self.processors = processors.into_iter().collect(); - self - } - - #[inline] - fn with_processor(mut self, processor: impl Into) -> Self { - self.processors.push(processor.into()); - self - } -} - -/// A virtual dual-axis control constructed from four [`GamepadButtonType`]s. -/// Each button represents a specific direction (up, down, left, right), -/// functioning similarly to a directional pad (D-pad) on both X and Y axes, -/// and offering intermediate diagonals by means of two-button combinations. -/// -/// By default, it reads from **any connected gamepad**. -/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones. -/// -/// # Value Processing -/// -/// You can customize how the values are processed using a pipeline of processors. -/// See [`WithDualAxisProcessingPipelineExt`] for details. -/// -/// The raw axis values are determined based on the state of the associated buttons: -/// - `-1.0` if only the negative button is currently pressed (Down/Left). -/// - `1.0` if only the positive button is currently pressed (Up/Right). -/// - `0.0` if neither button is pressed, or both are pressed simultaneously. -/// -/// ```rust,ignore -/// use bevy::prelude::*; -/// use bevy::input::InputPlugin; -/// use leafwing_input_manager::prelude::*; -/// -/// let mut app = App::new(); -/// app.add_plugins(InputPlugin); -/// -/// // Define a virtual D-pad using the physical D-pad buttons -/// let input = GamepadVirtualDPad::DPAD; -/// -/// // Pressing a D-pad button activates the corresponding axis -/// GamepadButtonType::DPadUp.press(app.world_mut()); -/// app.update(); -/// assert_eq!(app.read_axis_values(input), [0.0, 1.0]); -/// -/// // You can configure a processing pipeline (e.g., doubling the Y value) -/// let doubled = GamepadVirtualDPad::DPAD.sensitivity_y(2.0); -/// assert_eq!(app.read_axis_values(doubled), [0.0, 2.0]); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] -#[must_use] -pub struct GamepadVirtualDPad { - /// The button for the upward direction. - pub(crate) up: GamepadButtonType, - - /// The button for the downward direction. - pub(crate) down: GamepadButtonType, - - /// The button for the leftward direction. - pub(crate) left: GamepadButtonType, - - /// The button for the rightward direction. - pub(crate) right: GamepadButtonType, - - /// A processing pipeline that handles input values. - pub(crate) processors: Vec, -} - -impl GamepadVirtualDPad { - /// Creates a new [`GamepadVirtualDPad`] with four given [`GamepadButtonType`]s. - /// Each button represents a specific direction (up, down, left, right). - #[inline] - pub const fn new( - up: GamepadButtonType, - down: GamepadButtonType, - left: GamepadButtonType, - right: GamepadButtonType, - ) -> Self { - Self { - up, - down, - left, - right, - processors: Vec::new(), - } - } - - /// Creates a new [`GamepadVirtualDPad`] using the common D-Pad button mappings. - /// - /// - [`GamepadButtonType::DPadUp`] for upward direction. - /// - [`GamepadButtonType::DPadDown`] for downward direction. - /// - [`GamepadButtonType::DPadLeft`] for leftward direction. - /// - [`GamepadButtonType::DPadRight`] for rightward direction. - pub const DPAD: Self = Self::new( - GamepadButtonType::DPadUp, - GamepadButtonType::DPadDown, - GamepadButtonType::DPadLeft, - GamepadButtonType::DPadRight, - ); - - /// Creates a new [`GamepadVirtualDPad`] using the common action pad button mappings. - /// - /// - [`GamepadButtonType::North`] for upward direction. - /// - [`GamepadButtonType::South`] for downward direction. - /// - [`GamepadButtonType::West`] for leftward direction. - /// - [`GamepadButtonType::East`] for rightward direction. - pub const ACTION_PAD: Self = Self::new( - GamepadButtonType::North, - GamepadButtonType::South, - GamepadButtonType::West, - GamepadButtonType::East, - ); -} - -impl UserInput for GamepadVirtualDPad { - /// [`GamepadVirtualDPad`] acts as a dual-axis input. - #[inline] - fn kind(&self) -> InputControlKind { - InputControlKind::DualAxis - } - - /// Returns the four [`GamepadButtonType`]s used by this D-pad. - #[inline] - fn decompose(&self) -> BasicInputs { - BasicInputs::Composite(vec![ - Box::new(self.up), - Box::new(self.down), - Box::new(self.left), - Box::new(self.right), - ]) - } -} - -#[serde_typetag] -impl DualAxislike for GamepadVirtualDPad { - /// Retrieves the current X and Y values of this D-pad after processing by the associated processors. - #[must_use] - #[inline] - fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 { - let up = button_value(input_store, gamepad, self.up); - let down = button_value(input_store, gamepad, self.down); - let left = button_value(input_store, gamepad, self.left); - let right = button_value(input_store, gamepad, self.right); - let value = Vec2::new(right - left, up - down); - self.processors - .iter() - .fold(value, |value, processor| processor.process(value)) - } - - /// Presses the corresponding buttons on the provided [`Gamepad`] based on the quadrant of the given value. - fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option) { - if value.x < 0.0 { - self.left.press_as_gamepad(world, gamepad); - } else if value.x > 0.0 { - self.right.press_as_gamepad(world, gamepad); - } - - if value.y < 0.0 { - self.down.press_as_gamepad(world, gamepad); - } else if value.y > 0.0 { - self.up.press_as_gamepad(world, gamepad); - } - } -} - -impl WithDualAxisProcessingPipelineExt for GamepadVirtualDPad { - #[inline] - fn reset_processing_pipeline(mut self) -> Self { - self.processors.clear(); - self - } - - #[inline] - fn replace_processing_pipeline( - mut self, - processor: impl IntoIterator, - ) -> Self { - self.processors = processor.into_iter().collect(); - self - } - - #[inline] - fn with_processor(mut self, processor: impl Into) -> Self { - self.processors.push(processor.into()); - self - } -} - #[cfg(test)] mod tests { use super::*; @@ -1085,17 +738,7 @@ mod tests { let right = GamepadButtonType::DPadRight; assert_eq!(left.kind(), InputControlKind::Button); - let x_axis = GamepadVirtualAxis::DPAD_X; - assert_eq!(x_axis.kind(), InputControlKind::Axis); - - let y_axis = GamepadVirtualAxis::DPAD_Y; - assert_eq!(y_axis.kind(), InputControlKind::Axis); - - let dpad = GamepadVirtualDPad::DPAD; - assert_eq!(dpad.kind(), InputControlKind::DualAxis); - // No inputs - let zeros = Vec2::new(0.0, 0.0); let mut app = test_app(); app.update(); let inputs = app.world().resource::(); @@ -1106,12 +749,8 @@ mod tests { assert!(!left.pressed(inputs, gamepad)); assert!(!down.pressed(inputs, gamepad)); assert!(!right.pressed(inputs, gamepad)); - assert_eq!(x_axis.value(inputs, gamepad), 0.0); - assert_eq!(y_axis.value(inputs, gamepad), 0.0); - assert_eq!(dpad.axis_pair(inputs, gamepad), zeros); // Press DPadLeft - let data = Vec2::new(1.0, 0.0); let mut app = test_app(); GamepadButtonType::DPadLeft.press(app.world_mut()); app.update(); @@ -1121,38 +760,5 @@ mod tests { assert!(left.pressed(inputs, gamepad)); assert!(!down.pressed(inputs, gamepad)); assert!(!right.pressed(inputs, gamepad)); - assert_eq!(x_axis.value(inputs, gamepad), 1.0); - assert_eq!(y_axis.value(inputs, gamepad), 0.0); - assert_eq!(dpad.axis_pair(inputs, gamepad), data); - - // Set the X-axis to 0.6 - let data = Vec2::new(0.6, 0.0); - let mut app = test_app(); - GamepadVirtualAxis::DPAD_X.set_value(app.world_mut(), data.x); - app.update(); - let inputs = app.world().resource::(); - - assert!(!up.pressed(inputs, gamepad)); - assert!(left.pressed(inputs, gamepad)); - assert!(!down.pressed(inputs, gamepad)); - assert!(!right.pressed(inputs, gamepad)); - assert_eq!(x_axis.value(inputs, gamepad), 0.6); - assert_eq!(y_axis.value(inputs, gamepad), 0.0); - assert_eq!(dpad.axis_pair(inputs, gamepad), data); - - // Set the axes to (0.6, 0.4) - let data = Vec2::new(0.6, 0.4); - let mut app = test_app(); - GamepadVirtualDPad::DPAD.set_axis_pair(app.world_mut(), data); - app.update(); - let inputs = app.world().resource::(); - - assert!(!up.pressed(inputs, gamepad)); - assert!(left.pressed(inputs, gamepad)); - assert!(!down.pressed(inputs, gamepad)); - assert!(!right.pressed(inputs, gamepad)); - assert_eq!(x_axis.value(inputs, gamepad), data.x); - assert_eq!(y_axis.value(inputs, gamepad), data.y); - assert_eq!(dpad.axis_pair(inputs, gamepad), data); } } diff --git a/src/user_input/keyboard.rs b/src/user_input/keyboard.rs index 74fef219..122b60d0 100644 --- a/src/user_input/keyboard.rs +++ b/src/user_input/keyboard.rs @@ -2,21 +2,17 @@ use bevy::input::keyboard::{Key, KeyboardInput, NativeKey}; use bevy::input::{ButtonInput, ButtonState}; -use bevy::prelude::{Entity, Events, Gamepad, KeyCode, Reflect, Res, ResMut, Vec2, Vec3, World}; +use bevy::prelude::{Entity, Events, Gamepad, KeyCode, Reflect, Res, ResMut, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; use crate as leafwing_input_manager; use crate::clashing_inputs::BasicInputs; -use crate::input_processing::{ - AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt, - WithDualAxisProcessingPipelineExt, -}; -use crate::user_input::{ButtonlikeChord, TripleAxislike, UserInput}; +use crate::user_input::{ButtonlikeChord, UserInput}; use crate::InputControlKind; use super::updating::{CentralInputStore, UpdatableInput}; -use super::{Axislike, Buttonlike, DualAxislike}; +use super::Buttonlike; // Built-in support for Bevy's KeyCode impl UserInput for KeyCode { @@ -198,492 +194,6 @@ impl Buttonlike for ModifierKey { } } -/// A virtual single-axis control constructed from two [`KeyCode`]s. -/// One key represents the negative direction (left for the X-axis, down for the Y-axis), -/// while the other represents the positive direction (right for the X-axis, up for the Y-axis). -/// -/// # Value Processing -/// -/// You can customize how the values are processed using a pipeline of processors. -/// See [`WithAxisProcessingPipelineExt`] for details. -/// -/// The raw value is determined based on the state of the associated buttons: -/// - `-1.0` if only the negative button is currently pressed. -/// - `1.0` if only the positive button is currently pressed. -/// - `0.0` if neither button is pressed, or both are pressed simultaneously. -/// -/// ```rust -/// use bevy::prelude::*; -/// use bevy::input::InputPlugin; -/// use leafwing_input_manager::prelude::*; -/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; -/// -/// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); -/// -/// // Define a virtual Y-axis using arrow "up" and "down" keys -/// let axis = KeyboardVirtualAxis::VERTICAL_ARROW_KEYS; -/// -/// // Pressing either key activates the input -/// KeyCode::ArrowUp.press(app.world_mut()); -/// app.update(); -/// assert_eq!(app.read_axis_value(axis), 1.0); -/// -/// // You can configure a processing pipeline (e.g., doubling the value) -/// let doubled = KeyboardVirtualAxis::VERTICAL_ARROW_KEYS.sensitivity(2.0); -/// assert_eq!(app.read_axis_value(doubled), 2.0); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] -#[must_use] -pub struct KeyboardVirtualAxis { - /// The key that represents the negative direction. - pub(crate) negative: KeyCode, - - /// The key that represents the positive direction. - pub(crate) positive: KeyCode, - - /// A processing pipeline that handles input values. - pub(crate) processors: Vec, -} - -impl KeyboardVirtualAxis { - /// Creates a new [`KeyboardVirtualAxis`] with two given [`KeyCode`]s. - /// No processing is applied to raw data from the gamepad. - #[inline] - pub fn new(negative: KeyCode, positive: KeyCode) -> Self { - Self { - negative, - positive, - processors: Vec::new(), - } - } - - /// The [`KeyboardVirtualAxis`] using the vertical arrow key mappings. - /// - /// - [`KeyCode::ArrowDown`] for negative direction. - /// - [`KeyCode::ArrowUp`] for positive direction. - pub const VERTICAL_ARROW_KEYS: Self = Self { - negative: KeyCode::ArrowDown, - positive: KeyCode::ArrowUp, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualAxis`] using the horizontal arrow key mappings. - /// - /// - [`KeyCode::ArrowLeft`] for negative direction. - /// - [`KeyCode::ArrowRight`] for positive direction. - pub const HORIZONTAL_ARROW_KEYS: Self = Self { - negative: KeyCode::ArrowLeft, - positive: KeyCode::ArrowRight, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualAxis`] using the common W/S key mappings. - /// - /// - [`KeyCode::KeyS`] for negative direction. - /// - [`KeyCode::KeyW`] for positive direction. - pub const WS: Self = Self { - negative: KeyCode::KeyS, - positive: KeyCode::KeyW, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualAxis`] using the common A/D key mappings. - /// - /// - [`KeyCode::KeyA`] for negative direction. - /// - [`KeyCode::KeyD`] for positive direction. - pub const AD: Self = Self { - negative: KeyCode::KeyA, - positive: KeyCode::KeyD, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualAxis`] using the vertical numpad key mappings. - /// - /// - [`KeyCode::Numpad2`] for negative direction. - /// - [`KeyCode::Numpad8`] for positive direction. - pub const VERTICAL_NUMPAD: Self = Self { - negative: KeyCode::Numpad2, - positive: KeyCode::Numpad8, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualAxis`] using the horizontal numpad key mappings. - /// - /// - [`KeyCode::Numpad4`] for negative direction. - /// - [`KeyCode::Numpad6`] for positive direction. - pub const HORIZONTAL_NUMPAD: Self = Self { - negative: KeyCode::Numpad4, - positive: KeyCode::Numpad6, - processors: Vec::new(), - }; -} - -impl UserInput for KeyboardVirtualAxis { - /// [`KeyboardVirtualAxis`] acts as a virtual axis input. - #[inline] - fn kind(&self) -> InputControlKind { - InputControlKind::Axis - } - - /// [`KeyboardVirtualAxis`] represents a compositions of two [`KeyCode`]s. - #[inline] - fn decompose(&self) -> BasicInputs { - BasicInputs::Composite(vec![Box::new(self.negative), Box::new(self.negative)]) - } -} - -#[serde_typetag] -impl Axislike for KeyboardVirtualAxis { - /// Retrieves the current value of this axis after processing by the associated processors. - #[must_use] - #[inline] - fn value(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> f32 { - let negative = f32::from(input_store.pressed(&self.negative)); - let positive = f32::from(input_store.pressed(&self.positive)); - let value = positive - negative; - self.processors - .iter() - .fold(value, |value, processor| processor.process(value)) - } - - /// Sends a [`KeyboardInput`] event. - /// - /// If the value is negative, the negative button is pressed. - /// If the value is positive, the positive button is pressed. - /// If the value is zero, neither button is pressed. - fn set_value(&self, world: &mut World, value: f32) { - if value < 0.0 { - self.negative.press(world); - } else if value > 0.0 { - self.positive.press(world); - } - } -} - -impl WithAxisProcessingPipelineExt for KeyboardVirtualAxis { - #[inline] - fn reset_processing_pipeline(mut self) -> Self { - self.processors.clear(); - self - } - - #[inline] - fn replace_processing_pipeline( - mut self, - processors: impl IntoIterator, - ) -> Self { - self.processors = processors.into_iter().collect(); - self - } - - #[inline] - fn with_processor(mut self, processor: impl Into) -> Self { - self.processors.push(processor.into()); - self - } -} - -/// A virtual dual-axis control constructed from four [`KeyCode`]s. -/// Each key represents a specific direction (up, down, left, right), -/// functioning similarly to a directional pad (D-pad) on both X and Y axes, -/// and offering intermediate diagonals by means of two-key combinations. -/// -/// # Value Processing -/// -/// You can customize how the values are processed using a pipeline of processors. -/// See [`WithDualAxisProcessingPipelineExt`] for details. -/// -/// The raw axis values are determined based on the state of the associated buttons: -/// - `-1.0` if only the negative button is currently pressed (Down/Left). -/// - `1.0` if only the positive button is currently pressed (Up/Right). -/// - `0.0` if neither button is pressed, or both are pressed simultaneously. -/// -/// ```rust -/// use bevy::prelude::*; -/// use bevy::input::InputPlugin; -/// use leafwing_input_manager::prelude::*; -/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; -/// -/// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); -/// -/// // Define a virtual D-pad using the arrow keys -/// let input = KeyboardVirtualDPad::ARROW_KEYS; -/// -/// // Pressing an arrow key activates the corresponding axis -/// KeyCode::ArrowUp.press(app.world_mut()); -/// app.update(); -/// assert_eq!(app.read_dual_axis_values(input), Vec2::new(0.0, 1.0)); -/// -/// // You can configure a processing pipeline (e.g., doubling the Y value) -/// let doubled = KeyboardVirtualDPad::ARROW_KEYS.sensitivity_y(2.0); -/// assert_eq!(app.read_dual_axis_values(doubled), Vec2::new(0.0, 2.0)); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] -#[must_use] -pub struct KeyboardVirtualDPad { - /// The key for the upward direction. - pub(crate) up: KeyCode, - - /// The key for the downward direction. - pub(crate) down: KeyCode, - - /// The key for the leftward direction. - pub(crate) left: KeyCode, - - /// The key for the rightward direction. - pub(crate) right: KeyCode, - - /// A processing pipeline that handles input values. - pub(crate) processors: Vec, -} - -impl KeyboardVirtualDPad { - /// Creates a new [`KeyboardVirtualDPad`] with four given [`KeyCode`]s. - /// No processing is applied to raw data from the keyboard. - #[inline] - pub fn new(up: KeyCode, down: KeyCode, left: KeyCode, right: KeyCode) -> Self { - Self { - up, - down, - left, - right, - processors: Vec::new(), - } - } - - /// The [`KeyboardVirtualDPad`] using the common arrow key mappings. - /// - /// - [`KeyCode::ArrowUp`] for upward direction. - /// - [`KeyCode::ArrowDown`] for downward direction. - /// - [`KeyCode::ArrowLeft`] for leftward direction. - /// - [`KeyCode::ArrowRight`] for rightward direction. - pub const ARROW_KEYS: Self = Self { - up: KeyCode::ArrowUp, - down: KeyCode::ArrowDown, - left: KeyCode::ArrowLeft, - right: KeyCode::ArrowRight, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualDPad`] using the common WASD key mappings. - /// - /// - [`KeyCode::KeyW`] for upward direction. - /// - [`KeyCode::KeyS`] for downward direction. - /// - [`KeyCode::KeyA`] for leftward direction. - /// - [`KeyCode::KeyD`] for rightward direction. - pub const WASD: Self = Self { - up: KeyCode::KeyW, - down: KeyCode::KeyS, - left: KeyCode::KeyA, - right: KeyCode::KeyD, - processors: Vec::new(), - }; - - /// The [`KeyboardVirtualDPad`] using the common numpad key mappings. - /// - /// - [`KeyCode::Numpad8`] for upward direction. - /// - [`KeyCode::Numpad2`] for downward direction. - /// - [`KeyCode::Numpad4`] for leftward direction. - /// - [`KeyCode::Numpad6`] for rightward direction. - pub const NUMPAD: Self = Self { - up: KeyCode::Numpad8, - down: KeyCode::Numpad2, - left: KeyCode::Numpad4, - right: KeyCode::Numpad6, - processors: Vec::new(), - }; -} - -impl UserInput for KeyboardVirtualDPad { - /// [`KeyboardVirtualDPad`] acts as a virtual dual-axis input. - #[inline] - fn kind(&self) -> InputControlKind { - InputControlKind::DualAxis - } - - /// [`KeyboardVirtualDPad`] represents a compositions of four [`KeyCode`]s. - #[inline] - fn decompose(&self) -> BasicInputs { - BasicInputs::Composite(vec![ - Box::new(self.up), - Box::new(self.down), - Box::new(self.left), - Box::new(self.right), - ]) - } -} - -#[serde_typetag] -impl DualAxislike for KeyboardVirtualDPad { - /// Retrieves the current X and Y values of this D-pad after processing by the associated processors. - #[must_use] - #[inline] - fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> Vec2 { - let up = f32::from(input_store.pressed(&self.up)); - let down = f32::from(input_store.pressed(&self.down)); - let left = f32::from(input_store.pressed(&self.left)); - let right = f32::from(input_store.pressed(&self.right)); - let value = Vec2::new(right - left, up - down); - self.processors - .iter() - .fold(value, |value, processor| processor.process(value)) - } - - /// Presses the corresponding buttons based on the quadrant of the given value. - fn set_axis_pair(&self, world: &mut World, value: Vec2) { - if value.x < 0.0 { - self.left.press(world); - } else if value.x > 0.0 { - self.right.press(world); - } - - if value.y < 0.0 { - self.down.press(world); - } else if value.y > 0.0 { - self.up.press(world); - } - } -} - -impl WithDualAxisProcessingPipelineExt for KeyboardVirtualDPad { - #[inline] - fn reset_processing_pipeline(mut self) -> Self { - self.processors.clear(); - self - } - - #[inline] - fn replace_processing_pipeline( - mut self, - processors: impl IntoIterator, - ) -> Self { - self.processors = processors.into_iter().collect(); - self - } - - #[inline] - fn with_processor(mut self, processor: impl Into) -> Self { - self.processors.push(processor.into()); - self - } -} - -/// A virtual triple-axis control constructed from six [`KeyCode`]s. -/// Each key represents a specific direction (up, down, left, right, forward, backward), -/// functioning similarly to a three-dimensional directional pad (D-pad) on all X, Y, and Z axes, -/// and offering intermediate diagonals by means of two/three-key combinations. -/// -/// The raw axis values are determined based on the state of the associated buttons: -/// - `-1.0` if only the negative button is currently pressed (Down/Left/Forward). -/// - `1.0` if only the positive button is currently pressed (Up/Right/Backward). -/// - `0.0` if neither button is pressed, or both are pressed simultaneously. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] -#[must_use] -pub struct KeyboardVirtualDPad3D { - /// The key for the upward direction. - pub(crate) up: KeyCode, - - /// The key for the downward direction. - pub(crate) down: KeyCode, - - /// The key for the leftward direction. - pub(crate) left: KeyCode, - - /// The key for the rightward direction. - pub(crate) right: KeyCode, - - /// The key for the forward direction. - pub(crate) forward: KeyCode, - - /// The key for the backward direction. - pub(crate) backward: KeyCode, -} - -impl KeyboardVirtualDPad3D { - /// Creates a new [`KeyboardVirtualDPad3D`] with six given [`KeyCode`]s. - /// No processing is applied to raw data from the keyboard. - #[inline] - pub fn new( - up: KeyCode, - down: KeyCode, - left: KeyCode, - right: KeyCode, - forward: KeyCode, - backward: KeyCode, - ) -> Self { - Self { - up, - down, - left, - right, - forward, - backward, - } - } -} - -impl UserInput for KeyboardVirtualDPad3D { - /// [`KeyboardVirtualDPad3D`] acts as a virtual triple-axis input. - #[inline] - fn kind(&self) -> InputControlKind { - InputControlKind::TripleAxis - } - - /// [`KeyboardVirtualDPad3D`] represents a compositions of six [`KeyCode`]s. - #[inline] - fn decompose(&self) -> BasicInputs { - BasicInputs::Composite(vec![ - Box::new(self.up), - Box::new(self.down), - Box::new(self.left), - Box::new(self.right), - Box::new(self.forward), - Box::new(self.backward), - ]) - } -} - -#[serde_typetag] -impl TripleAxislike for KeyboardVirtualDPad3D { - /// Retrieves the current X, Y, and Z values of this D-pad. - #[must_use] - #[inline] - fn axis_triple(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> Vec3 { - let up = f32::from(input_store.pressed(&self.up)); - let down = f32::from(input_store.pressed(&self.down)); - let left = f32::from(input_store.pressed(&self.left)); - let right = f32::from(input_store.pressed(&self.right)); - let forward = f32::from(input_store.pressed(&self.left)); - let back = f32::from(input_store.pressed(&self.right)); - Vec3::new(right - left, up - down, back - forward) - } - - /// Presses the corresponding buttons based on the octant of the given value. - fn set_axis_triple(&self, world: &mut World, value: Vec3) { - if value.x < 0.0 { - self.left.press(world); - } else if value.x > 0.0 { - self.right.press(world); - } - - if value.y < 0.0 { - self.down.press(world); - } else if value.y > 0.0 { - self.up.press(world); - } - - if value.z < 0.0 { - self.forward.press(world); - } else if value.z > 0.0 { - self.backward.press(world); - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -709,14 +219,7 @@ mod tests { let alt = ModifierKey::Alt; assert_eq!(alt.kind(), InputControlKind::Button); - let arrow_y = KeyboardVirtualAxis::VERTICAL_ARROW_KEYS; - assert_eq!(arrow_y.kind(), InputControlKind::Axis); - - let arrows = KeyboardVirtualDPad::ARROW_KEYS; - assert_eq!(arrows.kind(), InputControlKind::DualAxis); - // No inputs - let zeros = Vec2::new(0.0, 0.0); let mut app = test_app(); app.update(); let inputs = app.world().resource::(); @@ -726,11 +229,8 @@ mod tests { assert!(!up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), 0.0); - assert_eq!(arrows.axis_pair(inputs, gamepad), zeros); // Press arrow up - let data = Vec2::new(0.0, 1.0); let mut app = test_app(); KeyCode::ArrowUp.press(app.world_mut()); app.update(); @@ -739,11 +239,8 @@ mod tests { assert!(up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), data.y); - assert_eq!(arrows.axis_pair(inputs, gamepad), data); // Press arrow down - let data = Vec2::new(0.0, -1.0); let mut app = test_app(); KeyCode::ArrowDown.press(app.world_mut()); app.update(); @@ -752,11 +249,8 @@ mod tests { assert!(!up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), data.y); - assert_eq!(arrows.axis_pair(inputs, gamepad), data); // Press arrow left - let data = Vec2::new(-1.0, 0.0); let mut app = test_app(); KeyCode::ArrowLeft.press(app.world_mut()); app.update(); @@ -765,8 +259,6 @@ mod tests { assert!(!up.pressed(inputs, gamepad)); assert!(left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), 0.0); - assert_eq!(arrows.axis_pair(inputs, gamepad), data); // Press arrow down and arrow up let mut app = test_app(); @@ -778,11 +270,8 @@ mod tests { assert!(up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), 0.0); - assert_eq!(arrows.axis_pair(inputs, gamepad), zeros); // Press arrow left and arrow up - let data = Vec2::new(-1.0, 1.0); let mut app = test_app(); KeyCode::ArrowLeft.press(app.world_mut()); KeyCode::ArrowUp.press(app.world_mut()); @@ -792,8 +281,6 @@ mod tests { assert!(up.pressed(inputs, gamepad)); assert!(left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), data.y); - assert_eq!(arrows.axis_pair(inputs, gamepad), data); // Press left Alt let mut app = test_app(); @@ -804,8 +291,6 @@ mod tests { assert!(!up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), 0.0); - assert_eq!(arrows.axis_pair(inputs, gamepad), zeros); // Press right Alt let mut app = test_app(); @@ -816,7 +301,5 @@ mod tests { assert!(!up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(alt.pressed(inputs, gamepad)); - assert_eq!(arrow_y.value(inputs, gamepad), 0.0); - assert_eq!(arrows.axis_pair(inputs, gamepad), zeros); } } diff --git a/src/user_input/mod.rs b/src/user_input/mod.rs index 7f45e49c..736d4dc2 100644 --- a/src/user_input/mod.rs +++ b/src/user_input/mod.rs @@ -52,23 +52,27 @@ //! - Track mouse motion with [`MouseMove`], [`MouseMoveAxis`], and [`MouseMoveDirection`]. //! - Capture mouse wheel events with [`MouseScroll`], [`MouseScrollAxis`], and [`MouseScrollDirection`]. //! -//! ### Complex Composition +//! ### Virtual Axial Controls //! -//! - Combine multiple inputs into a virtual button using [`ButtonlikeChord`]. -//! - Only active if all its inner inputs are active simultaneously. -//! - Combine values from all inner single-axis inputs if available. -//! - Retrieve values from the first encountered dual-axis input within the chord. +//! - [`VirtualAxis`]: Create a virtual axis control from two buttons. //! -//! - Create a virtual axis control: -//! - [`GamepadVirtualAxis`] from two [`GamepadButtonType`]s. -//! - [`KeyboardVirtualAxis`] from two [`KeyCode`]s. +//! - [`VirtualDPad`]: Create a virtual dual-axis control from four buttons. //! -//! - Create a virtual directional pad (D-pad) for dual-axis control: -//! - [`GamepadVirtualDPad`] from four [`GamepadButtonType`]s. -//! - [`KeyboardVirtualDPad`] from four [`KeyCode`]s. +//! - [`VirtualDPad3D`]: Create a virtual triple-axis control from six buttons. //! -//! - Create a virtual directional pad (D-pad) for triple-axis control: -//! - [`KeyboardVirtualDPad3D`] from six [`KeyCode`]s. +//! ### Chords +//! +//! - [`ButtonlikeChord`]: A combined input that groups multiple [`Buttonlike`]s together, +//! allowing you to define complex input combinations like hotkeys, shortcuts, and macros. +//! +//! - [`AxislikeChord`]: A combined input that groups a [`Buttonlike`] and an [`Axislike`] together, +//! allowing you to only read the dual axis data when the button is pressed. +//! +//! - [`DualAxislikeChord`]: A combined input that groups a [`Buttonlike`] and a [`DualAxislike`] together, +//! allowing you to only read the dual axis data when the button is pressed. +//! +//! - [`TripleAxislikeChord`]: A combined input that groups a [`Buttonlike`] and a [`TripleAxislike`] together, +//! allowing you to only read the dual axis data when the button is pressed. //! //! [`GamepadAxisType`]: bevy::prelude::GamepadAxisType //! [`GamepadButtonType`]: bevy::prelude::GamepadButtonType @@ -97,6 +101,7 @@ pub use self::keyboard::*; #[cfg(feature = "mouse")] pub use self::mouse::*; pub use self::trait_serde::RegisterUserInput; +pub use self::virtual_axial::*; pub mod chord; #[cfg(feature = "gamepad")] @@ -109,6 +114,7 @@ pub mod testing_utils; mod trait_reflection; mod trait_serde; pub mod updating; +pub mod virtual_axial; /// A trait for defining the behavior expected from different user input sources. pub trait UserInput: Send + Sync + Debug { diff --git a/src/user_input/trait_serde.rs b/src/user_input/trait_serde.rs index d125ad4d..93bd001b 100644 --- a/src/user_input/trait_serde.rs +++ b/src/user_input/trait_serde.rs @@ -320,13 +320,13 @@ mod tests { #[cfg(feature = "keyboard")] #[test] fn test_triple_axis_serde() { - use crate::prelude::{KeyboardVirtualDPad3D, TripleAxislike}; + use crate::prelude::{TripleAxislike, VirtualDPad3D}; use bevy::prelude::KeyCode; use serde_test::{assert_tokens, Token}; register_input_deserializers(); - let boxed_input: Box = Box::new(KeyboardVirtualDPad3D::new( + let boxed_input: Box = Box::new(VirtualDPad3D::new( KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, @@ -338,41 +338,59 @@ mod tests { &boxed_input, &[ Token::Map { len: Some(1) }, - Token::BorrowedStr("KeyboardVirtualDPad3D"), + Token::BorrowedStr("VirtualDPad3D"), Token::Struct { - name: "KeyboardVirtualDPad3D", + name: "VirtualDPad3D", len: 6, }, Token::Str("up"), + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), Token::UnitVariant { name: "KeyCode", variant: "KeyW", }, + Token::MapEnd, Token::Str("down"), + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), Token::UnitVariant { name: "KeyCode", variant: "KeyS", }, + Token::MapEnd, Token::Str("left"), + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), Token::UnitVariant { name: "KeyCode", variant: "KeyA", }, + Token::MapEnd, Token::Str("right"), + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), Token::UnitVariant { name: "KeyCode", variant: "KeyD", }, + Token::MapEnd, Token::Str("forward"), + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), Token::UnitVariant { name: "KeyCode", variant: "KeyF", }, + Token::MapEnd, Token::Str("backward"), + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), Token::UnitVariant { name: "KeyCode", variant: "KeyB", }, + Token::MapEnd, Token::StructEnd, Token::MapEnd, ], diff --git a/src/user_input/virtual_axial.rs b/src/user_input/virtual_axial.rs new file mode 100644 index 00000000..336f3d15 --- /dev/null +++ b/src/user_input/virtual_axial.rs @@ -0,0 +1,679 @@ +//! This module contains [`VirtualAxis`], [`VirtualDPad`], and [`VirtualDPad3D`]. + +use crate as leafwing_input_manager; +use crate::clashing_inputs::BasicInputs; +use crate::input_processing::{ + AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt, + WithDualAxisProcessingPipelineExt, +}; +use crate::prelude::updating::CentralInputStore; +use crate::prelude::{Axislike, DualAxislike, TripleAxislike, UserInput}; +use crate::user_input::Buttonlike; +use crate::InputControlKind; +use bevy::math::{Vec2, Vec3}; +#[cfg(feature = "gamepad")] +use bevy::prelude::GamepadButtonType; +#[cfg(feature = "keyboard")] +use bevy::prelude::KeyCode; +use bevy::prelude::{Gamepad, Reflect, World}; +use leafwing_input_manager_macros::serde_typetag; +use serde::{Deserialize, Serialize}; + +/// A virtual single-axis control constructed from two [`Buttonlike`]s. +/// One button represents the negative direction (left for the X-axis, down for the Y-axis), +/// while the other represents the positive direction (right for the X-axis, up for the Y-axis). +/// +/// # Value Processing +/// +/// You can customize how the values are processed using a pipeline of processors. +/// See [`WithAxisProcessingPipelineExt`] for details. +/// +/// The raw value is determined based on the state of the associated buttons: +/// - `-1.0` if only the negative button is currently pressed. +/// - `1.0` if only the positive button is currently pressed. +/// - `0.0` if neither button is pressed, or both are pressed simultaneously. +/// +/// ```rust +/// use bevy::prelude::*; +/// use bevy::input::InputPlugin; +/// use leafwing_input_manager::prelude::*; +/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; +/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// +/// let mut app = App::new(); +/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// +/// // Define a virtual Y-axis using arrow "up" and "down" keys +/// let axis = VirtualAxis::vertical_arrow_keys(); +/// +/// // Pressing either key activates the input +/// KeyCode::ArrowUp.press(app.world_mut()); +/// app.update(); +/// assert_eq!(app.read_axis_value(axis), 1.0); +/// +/// // You can configure a processing pipeline (e.g., doubling the value) +/// let doubled = VirtualAxis::vertical_arrow_keys().sensitivity(2.0); +/// assert_eq!(app.read_axis_value(doubled), 2.0); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +#[must_use] +pub struct VirtualAxis { + /// The button that represents the negative direction. + pub(crate) negative: Box, + + /// The button that represents the positive direction. + pub(crate) positive: Box, + + /// A processing pipeline that handles input values. + pub(crate) processors: Vec, +} + +impl VirtualAxis { + /// Creates a new [`VirtualAxis`] with two given [`Buttonlike`]s. + /// No processing is applied to raw data. + #[inline] + pub fn new(negative: impl Buttonlike, positive: impl Buttonlike) -> Self { + Self { + negative: Box::new(negative), + positive: Box::new(positive), + processors: Vec::new(), + } + } + + /// The [`VirtualAxis`] using the vertical arrow key mappings. + /// + /// - [`KeyCode::ArrowDown`] for negative direction. + /// - [`KeyCode::ArrowUp`] for positive direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn vertical_arrow_keys() -> Self { + Self::new(KeyCode::ArrowDown, KeyCode::ArrowUp) + } + + /// The [`VirtualAxis`] using the horizontal arrow key mappings. + /// + /// - [`KeyCode::ArrowLeft`] for negative direction. + /// - [`KeyCode::ArrowRight`] for positive direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn horizontal_arrow_keys() -> Self { + Self::new(KeyCode::ArrowLeft, KeyCode::ArrowRight) + } + + /// The [`VirtualAxis`] using the common W/S key mappings. + /// + /// - [`KeyCode::KeyS`] for negative direction. + /// - [`KeyCode::KeyW`] for positive direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn ws() -> Self { + Self::new(KeyCode::KeyS, KeyCode::KeyW) + } + + /// The [`VirtualAxis`] using the common A/D key mappings. + /// + /// - [`KeyCode::KeyA`] for negative direction. + /// - [`KeyCode::KeyD`] for positive direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn ad() -> Self { + Self::new(KeyCode::KeyA, KeyCode::KeyD) + } + + /// The [`VirtualAxis`] using the vertical numpad key mappings. + /// + /// - [`KeyCode::Numpad2`] for negative direction. + /// - [`KeyCode::Numpad8`] for positive direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn vertical_numpad() -> Self { + Self::new(KeyCode::Numpad2, KeyCode::Numpad8) + } + + /// The [`VirtualAxis`] using the horizontal numpad key mappings. + /// + /// - [`KeyCode::Numpad4`] for negative direction. + /// - [`KeyCode::Numpad6`] for positive direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn horizontal_numpad() -> Self { + Self::new(KeyCode::Numpad4, KeyCode::Numpad6) + } + + /// The [`VirtualAxis`] using the horizontal D-Pad button mappings. + /// No processing is applied to raw data from the gamepad. + /// + /// - [`GamepadButtonType::DPadLeft`] for negative direction. + /// - [`GamepadButtonType::DPadRight`] for positive direction. + #[cfg(feature = "gamepad")] + #[inline] + pub fn dpad_x() -> Self { + Self::new(GamepadButtonType::DPadLeft, GamepadButtonType::DPadRight) + } + + /// The [`VirtualAxis`] using the vertical D-Pad button mappings. + /// No processing is applied to raw data from the gamepad. + /// + /// - [`GamepadButtonType::DPadDown`] for negative direction. + /// - [`GamepadButtonType::DPadUp`] for positive direction. + #[cfg(feature = "gamepad")] + #[inline] + pub fn dpad_y() -> Self { + Self::new(GamepadButtonType::DPadDown, GamepadButtonType::DPadUp) + } + + /// The [`VirtualAxis`] using the horizontal action pad button mappings. + /// No processing is applied to raw data from the gamepad. + /// + /// - [`GamepadButtonType::West`] for negative direction. + /// - [`GamepadButtonType::East`] for positive direction. + #[cfg(feature = "gamepad")] + #[inline] + pub fn action_pad_x() -> Self { + Self::new(GamepadButtonType::West, GamepadButtonType::East) + } + + /// The [`VirtualAxis`] using the vertical action pad button mappings. + /// No processing is applied to raw data from the gamepad. + /// + /// - [`GamepadButtonType::South`] for negative direction. + /// - [`GamepadButtonType::North`] for positive direction. + #[cfg(feature = "gamepad")] + #[inline] + pub fn action_pad_y() -> Self { + Self::new(GamepadButtonType::South, GamepadButtonType::North) + } +} + +impl UserInput for VirtualAxis { + /// [`VirtualAxis`] acts as a virtual axis input. + #[inline] + fn kind(&self) -> InputControlKind { + InputControlKind::Axis + } + + /// [`VirtualAxis`] represents a compositions of two buttons. + #[inline] + fn decompose(&self) -> BasicInputs { + BasicInputs::Composite(vec![self.negative.clone(), self.positive.clone()]) + } +} + +#[serde_typetag] +impl Axislike for VirtualAxis { + /// Retrieves the current value of this axis after processing by the associated processors. + #[must_use] + #[inline] + fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { + let negative = f32::from(self.negative.pressed(input_store, gamepad)); + let positive = f32::from(self.positive.pressed(input_store, gamepad)); + let value = positive - negative; + self.processors + .iter() + .fold(value, |value, processor| processor.process(value)) + } + + /// Presses the corresponding button based on the given value. + /// + /// If the value is negative, the negative button is pressed. + /// If the value is positive, the positive button is pressed. + /// If the value is zero, neither button is pressed. + fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { + if value < 0.0 { + self.negative.press_as_gamepad(world, gamepad); + } else if value > 0.0 { + self.positive.press_as_gamepad(world, gamepad); + } + } +} + +impl WithAxisProcessingPipelineExt for VirtualAxis { + #[inline] + fn reset_processing_pipeline(mut self) -> Self { + self.processors.clear(); + self + } + + #[inline] + fn replace_processing_pipeline( + mut self, + processors: impl IntoIterator, + ) -> Self { + self.processors = processors.into_iter().collect(); + self + } + + #[inline] + fn with_processor(mut self, processor: impl Into) -> Self { + self.processors.push(processor.into()); + self + } +} + +/// A virtual dual-axis control constructed from four [`GamepadButtonType`]s. +/// Each button represents a specific direction (up, down, left, right), +/// functioning similarly to a directional pad (D-pad) on both X and Y axes, +/// and offering intermediate diagonals by means of two-button combinations. +/// +/// By default, it reads from **any connected gamepad**. +/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones. +/// +/// # Value Processing +/// +/// You can customize how the values are processed using a pipeline of processors. +/// See [`WithDualAxisProcessingPipelineExt`] for details. +/// +/// The raw axis values are determined based on the state of the associated buttons: +/// - `-1.0` if only the negative button is currently pressed (Down/Left). +/// - `1.0` if only the positive button is currently pressed (Up/Right). +/// - `0.0` if neither button is pressed, or both are pressed simultaneously. +/// +/// ```rust +/// use bevy::prelude::*; +/// use bevy::input::InputPlugin; +/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; +/// use leafwing_input_manager::prelude::*; +/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// +/// let mut app = App::new(); +/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// +/// // Define a virtual D-pad using the WASD keys +/// let input = VirtualDPad::wasd(); +/// +/// // Pressing the W key activates the corresponding axis +/// KeyCode::KeyW.press(app.world_mut()); +/// app.update(); +/// assert_eq!(app.read_dual_axis_values(input), Vec2::new(0.0, 1.0)); +/// +/// // You can configure a processing pipeline (e.g., doubling the Y value) +/// let doubled = VirtualDPad::wasd().sensitivity_y(2.0); +/// assert_eq!(app.read_dual_axis_values(doubled), Vec2::new(0.0, 2.0)); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +#[must_use] +pub struct VirtualDPad { + /// The button for the upward direction. + pub(crate) up: Box, + + /// The button for the downward direction. + pub(crate) down: Box, + + /// The button for the leftward direction. + pub(crate) left: Box, + + /// The button for the rightward direction. + pub(crate) right: Box, + + /// A processing pipeline that handles input values. + pub(crate) processors: Vec, +} + +impl VirtualDPad { + /// Creates a new [`VirtualDPad`] with four given [`Buttonlike`]s. + /// Each button represents a specific direction (up, down, left, right). + #[inline] + pub fn new( + up: impl Buttonlike, + down: impl Buttonlike, + left: impl Buttonlike, + right: impl Buttonlike, + ) -> Self { + Self { + up: Box::new(up), + down: Box::new(down), + left: Box::new(left), + right: Box::new(right), + processors: Vec::new(), + } + } + + /// The [`VirtualDPad`] using the common arrow key mappings. + /// + /// - [`KeyCode::ArrowUp`] for upward direction. + /// - [`KeyCode::ArrowDown`] for downward direction. + /// - [`KeyCode::ArrowLeft`] for leftward direction. + /// - [`KeyCode::ArrowRight`] for rightward direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn arrow_keys() -> Self { + Self::new( + KeyCode::ArrowUp, + KeyCode::ArrowDown, + KeyCode::ArrowLeft, + KeyCode::ArrowRight, + ) + } + + /// The [`VirtualDPad`] using the common WASD key mappings. + /// + /// - [`KeyCode::KeyW`] for upward direction. + /// - [`KeyCode::KeyS`] for downward direction. + /// - [`KeyCode::KeyA`] for leftward direction. + /// - [`KeyCode::KeyD`] for rightward direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn wasd() -> Self { + Self::new(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD) + } + + /// The [`VirtualDPad`] using the common numpad key mappings. + /// + /// - [`KeyCode::Numpad8`] for upward direction. + /// - [`KeyCode::Numpad2`] for downward direction. + /// - [`KeyCode::Numpad4`] for leftward direction. + /// - [`KeyCode::Numpad6`] for rightward direction. + #[cfg(feature = "keyboard")] + #[inline] + pub fn numpad() -> Self { + Self::new( + KeyCode::Numpad8, + KeyCode::Numpad2, + KeyCode::Numpad4, + KeyCode::Numpad6, + ) + } + + /// Creates a new [`VirtualDPad`] using the common D-Pad button mappings. + /// + /// - [`GamepadButtonType::DPadUp`] for upward direction. + /// - [`GamepadButtonType::DPadDown`] for downward direction. + /// - [`GamepadButtonType::DPadLeft`] for leftward direction. + /// - [`GamepadButtonType::DPadRight`] for rightward direction. + #[cfg(feature = "gamepad")] + #[inline] + pub fn dpad() -> Self { + Self::new( + GamepadButtonType::DPadUp, + GamepadButtonType::DPadDown, + GamepadButtonType::DPadLeft, + GamepadButtonType::DPadRight, + ) + } + + /// Creates a new [`VirtualDPad`] using the common action pad button mappings. + /// + /// - [`GamepadButtonType::North`] for upward direction. + /// - [`GamepadButtonType::South`] for downward direction. + /// - [`GamepadButtonType::West`] for leftward direction. + /// - [`GamepadButtonType::East`] for rightward direction. + #[cfg(feature = "gamepad")] + #[inline] + pub fn action_pad() -> Self { + Self::new( + GamepadButtonType::North, + GamepadButtonType::South, + GamepadButtonType::West, + GamepadButtonType::East, + ) + } +} + +impl UserInput for VirtualDPad { + /// [`VirtualDPad`] acts as a dual-axis input. + #[inline] + fn kind(&self) -> InputControlKind { + InputControlKind::DualAxis + } + + /// Returns the four [`GamepadButtonType`]s used by this D-pad. + #[inline] + fn decompose(&self) -> BasicInputs { + BasicInputs::Composite(vec![ + self.up.clone(), + self.down.clone(), + self.left.clone(), + self.right.clone(), + ]) + } +} + +#[serde_typetag] +impl DualAxislike for VirtualDPad { + /// Retrieves the current X and Y values of this D-pad after processing by the associated processors. + #[must_use] + #[inline] + fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 { + let up = f32::from(self.up.pressed(input_store, gamepad)); + let down = f32::from(self.down.pressed(input_store, gamepad)); + let left = f32::from(self.left.pressed(input_store, gamepad)); + let right = f32::from(self.right.pressed(input_store, gamepad)); + let value = Vec2::new(right - left, up - down); + self.processors + .iter() + .fold(value, |value, processor| processor.process(value)) + } + + /// Presses the corresponding buttons based on the quadrant of the given value. + fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option) { + if value.x < 0.0 { + self.left.press_as_gamepad(world, gamepad); + } else if value.x > 0.0 { + self.right.press_as_gamepad(world, gamepad); + } + + if value.y < 0.0 { + self.down.press_as_gamepad(world, gamepad); + } else if value.y > 0.0 { + self.up.press_as_gamepad(world, gamepad); + } + } +} + +impl WithDualAxisProcessingPipelineExt for VirtualDPad { + #[inline] + fn reset_processing_pipeline(mut self) -> Self { + self.processors.clear(); + self + } + + #[inline] + fn replace_processing_pipeline( + mut self, + processor: impl IntoIterator, + ) -> Self { + self.processors = processor.into_iter().collect(); + self + } + + #[inline] + fn with_processor(mut self, processor: impl Into) -> Self { + self.processors.push(processor.into()); + self + } +} + +/// A virtual triple-axis control constructed from six [`Buttonlike`]s. +/// Each button represents a specific direction (up, down, left, right, forward, backward), +/// functioning similarly to a three-dimensional directional pad (D-pad) on all X, Y, and Z axes, +/// and offering intermediate diagonals by means of two/three-key combinations. +/// +/// The raw axis values are determined based on the state of the associated buttons: +/// - `-1.0` if only the negative button is currently pressed (Down/Left/Forward). +/// - `1.0` if only the positive button is currently pressed (Up/Right/Backward). +/// - `0.0` if neither button is pressed, or both are pressed simultaneously. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +#[must_use] +pub struct VirtualDPad3D { + /// The button for the upward direction. + pub(crate) up: Box, + + /// The button for the downward direction. + pub(crate) down: Box, + + /// The button for the leftward direction. + pub(crate) left: Box, + + /// The button for the rightward direction. + pub(crate) right: Box, + + /// The button for the forward direction. + pub(crate) forward: Box, + + /// The button for the backward direction. + pub(crate) backward: Box, +} + +impl VirtualDPad3D { + /// Creates a new [`VirtualDPad3D`] with six given [`Buttonlike`]s. + /// Each button represents a specific direction (up, down, left, right, forward, backward). + #[inline] + pub fn new( + up: impl Buttonlike, + down: impl Buttonlike, + left: impl Buttonlike, + right: impl Buttonlike, + forward: impl Buttonlike, + backward: impl Buttonlike, + ) -> Self { + Self { + up: Box::new(up), + down: Box::new(down), + left: Box::new(left), + right: Box::new(right), + forward: Box::new(forward), + backward: Box::new(backward), + } + } +} + +impl UserInput for VirtualDPad3D { + /// [`VirtualDPad3D`] acts as a virtual triple-axis input. + #[inline] + fn kind(&self) -> InputControlKind { + InputControlKind::TripleAxis + } + + /// [`VirtualDPad3D`] represents a compositions of six [`Buttonlike`]s. + #[inline] + fn decompose(&self) -> BasicInputs { + BasicInputs::Composite(vec![ + self.up.clone(), + self.down.clone(), + self.left.clone(), + self.right.clone(), + self.forward.clone(), + self.backward.clone(), + ]) + } +} + +#[serde_typetag] +impl TripleAxislike for VirtualDPad3D { + /// Retrieves the current X, Y, and Z values of this D-pad. + #[must_use] + #[inline] + fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec3 { + let up = f32::from(self.up.pressed(input_store, gamepad)); + let down = f32::from(self.down.pressed(input_store, gamepad)); + let left = f32::from(self.left.pressed(input_store, gamepad)); + let right = f32::from(self.right.pressed(input_store, gamepad)); + let forward = f32::from(self.forward.pressed(input_store, gamepad)); + let backward = f32::from(self.backward.pressed(input_store, gamepad)); + Vec3::new(right - left, up - down, backward - forward) + } + + /// Presses the corresponding buttons based on the octant of the given value. + fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, gamepad: Option) { + if value.x < 0.0 { + self.left.press_as_gamepad(world, gamepad); + } else if value.x > 0.0 { + self.right.press_as_gamepad(world, gamepad); + } + + if value.y < 0.0 { + self.down.press_as_gamepad(world, gamepad); + } else if value.y > 0.0 { + self.up.press_as_gamepad(world, gamepad); + } + + if value.z < 0.0 { + self.forward.press_as_gamepad(world, gamepad); + } else if value.z > 0.0 { + self.backward.press_as_gamepad(world, gamepad); + } + } +} + +#[cfg(feature = "keyboard")] +#[cfg(test)] +mod tests { + use bevy::input::InputPlugin; + use bevy::prelude::*; + + use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::prelude::updating::CentralInputStore; + use crate::prelude::*; + + fn test_app() -> App { + let mut app = App::new(); + app.add_plugins(InputPlugin) + .add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); + app + } + + #[test] + fn test_virtual() { + let x = VirtualAxis::horizontal_arrow_keys(); + let xy = VirtualDPad::arrow_keys(); + let xyz = VirtualDPad3D::new( + KeyCode::ArrowUp, + KeyCode::ArrowDown, + KeyCode::ArrowLeft, + KeyCode::ArrowRight, + KeyCode::KeyF, + KeyCode::KeyB, + ); + + // No inputs + let mut app = test_app(); + app.update(); + let inputs = app.world().resource::(); + + let gamepad = Gamepad::new(0); + + assert_eq!(x.value(inputs, gamepad), 0.0); + assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::ZERO); + assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::ZERO); + + // Press arrow left + let mut app = test_app(); + KeyCode::ArrowLeft.press(app.world_mut()); + app.update(); + let inputs = app.world().resource::(); + + assert_eq!(x.value(inputs, gamepad), -1.0); + assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(-1.0, 0.0)); + assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(-1.0, 0.0, 0.0)); + + // Press arrow up + let mut app = test_app(); + KeyCode::ArrowUp.press(app.world_mut()); + app.update(); + let inputs = app.world().resource::(); + + assert_eq!(x.value(inputs, gamepad), 0.0); + assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 1.0)); + assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 1.0, 0.0)); + + // Press arrow right + let mut app = test_app(); + KeyCode::ArrowRight.press(app.world_mut()); + app.update(); + let inputs = app.world().resource::(); + + assert_eq!(x.value(inputs, gamepad), 1.0); + assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(1.0, 0.0)); + assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(1.0, 0.0, 0.0)); + + // Press key B + let mut app = test_app(); + KeyCode::KeyB.press(app.world_mut()); + app.update(); + let inputs = app.world().resource::(); + + assert_eq!(x.value(inputs, gamepad), 0.0); + assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0)); + assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 0.0, 1.0)); + } +} diff --git a/tests/gamepad_axis.rs b/tests/gamepad_axis.rs index 4d5de06b..9ab828f7 100644 --- a/tests/gamepad_axis.rs +++ b/tests/gamepad_axis.rs @@ -296,7 +296,7 @@ fn test_zero_circle_deadzone() { fn gamepad_virtual_dpad() { let mut app = test_app(); app.insert_resource( - InputMap::default().with_dual_axis(AxislikeTestAction::XY, GamepadVirtualDPad::DPAD), + InputMap::default().with_dual_axis(AxislikeTestAction::XY, VirtualDPad::dpad()), ); GamepadButtonType::DPadLeft.press(app.world_mut()); From 4f0a2df451603d1007c964f47ca5cd3e8b61be17 Mon Sep 17 00:00:00 2001 From: Shute052 Date: Wed, 18 Sep 2024 20:17:20 +0800 Subject: [PATCH 2/3] minor --- src/user_input/trait_serde.rs | 4 ++-- src/user_input/virtual_axial.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/user_input/trait_serde.rs b/src/user_input/trait_serde.rs index 93bd001b..0fccd995 100644 --- a/src/user_input/trait_serde.rs +++ b/src/user_input/trait_serde.rs @@ -34,12 +34,12 @@ pub trait RegisterUserInput { /// Registers the specified [`Buttonlike`]. fn register_buttonlike_input<'de, T>(&mut self) -> &mut Self where - T: RegisterTypeTag<'de, dyn Buttonlike> + GetTypeRegistration + TypePath; + T: RegisterTypeTag<'de, dyn Buttonlike> + GetTypeRegistration; /// Registers the specified [`Axislike`]. fn register_axislike_input<'de, T>(&mut self) -> &mut Self where - T: RegisterTypeTag<'de, dyn Axislike> + GetTypeRegistration + TypePath; + T: RegisterTypeTag<'de, dyn Axislike> + GetTypeRegistration; /// Registers the specified [`DualAxislike`]. fn register_dual_axislike_input<'de, T>(&mut self) -> &mut Self diff --git a/src/user_input/virtual_axial.rs b/src/user_input/virtual_axial.rs index 336f3d15..330da4ec 100644 --- a/src/user_input/virtual_axial.rs +++ b/src/user_input/virtual_axial.rs @@ -250,7 +250,7 @@ impl WithAxisProcessingPipelineExt for VirtualAxis { } } -/// A virtual dual-axis control constructed from four [`GamepadButtonType`]s. +/// A virtual dual-axis control constructed from four [`Buttonlike`]s. /// Each button represents a specific direction (up, down, left, right), /// functioning similarly to a directional pad (D-pad) on both X and Y axes, /// and offering intermediate diagonals by means of two-button combinations. From c4cb5fa3c70028e75af58fbbc79df377bdb308c7 Mon Sep 17 00:00:00 2001 From: Shute052 Date: Wed, 18 Sep 2024 20:59:56 +0800 Subject: [PATCH 3/3] fix CI --- src/user_input/trait_serde.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/user_input/trait_serde.rs b/src/user_input/trait_serde.rs index 0fccd995..07c3e3f8 100644 --- a/src/user_input/trait_serde.rs +++ b/src/user_input/trait_serde.rs @@ -3,7 +3,6 @@ use std::sync::RwLock; use bevy::app::App; -use bevy::prelude::TypePath; use bevy::reflect::GetTypeRegistration; use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer, Serialize, Serializer};