diff --git a/crates/bevy_input/src/common_conditions.rs b/crates/bevy_input/src/common_conditions.rs index 06043a0ae9f17..d5c46bece70db 100644 --- a/crates/bevy_input/src/common_conditions.rs +++ b/crates/bevy_input/src/common_conditions.rs @@ -1,5 +1,6 @@ use crate::Input; use bevy_ecs::system::Res; +use bevy_reflect::FromReflect; use std::hash::Hash; /// Stateful run condition that can be toggled via a input press using [`Input::just_pressed`]. @@ -49,7 +50,7 @@ use std::hash::Hash; /// ``` pub fn input_toggle_active(default: bool, input: T) -> impl FnMut(Res>) -> bool + Clone where - T: Copy + Eq + Hash + Send + Sync + 'static, + T: FromReflect + Copy + Eq + Hash + Send + Sync + 'static, { let mut active = default; move |inputs: Res>| { @@ -61,7 +62,7 @@ where /// Run condition that is active if [`Input::pressed`] is true for the given input. pub fn input_pressed(input: T) -> impl FnMut(Res>) -> bool + Clone where - T: Copy + Eq + Hash + Send + Sync + 'static, + T: FromReflect + Copy + Eq + Hash + Send + Sync + 'static, { move |inputs: Res>| inputs.pressed(input) } @@ -82,15 +83,15 @@ where /// ``` pub fn input_just_pressed(input: T) -> impl FnMut(Res>) -> bool + Clone where - T: Copy + Eq + Hash + Send + Sync + 'static, + T: FromReflect + Clone + Eq + Hash + Send + Sync + 'static, { - move |inputs: Res>| inputs.just_pressed(input) + move |inputs: Res>| inputs.just_pressed(input.clone()) } /// Run condition that is active if [`Input::just_released`] is true for the given input. pub fn input_just_released(input: T) -> impl FnMut(Res>) -> bool + Clone where - T: Copy + Eq + Hash + Send + Sync + 'static, + T: FromReflect + Copy + Eq + Hash + Send + Sync + 'static, { move |inputs: Res>| inputs.just_released(input) } diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index b1ecd8c3ba79c..e731131e92bfb 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -1,8 +1,9 @@ //! The generic input type. use bevy_ecs::system::Resource; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_utils::HashSet; + +use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect}; +use bevy_utils::{HashMap, HashSet}; use std::hash::Hash; // unused import, but needed for intra doc link to work @@ -45,31 +46,49 @@ use bevy_ecs::schedule::State; ///[`DetectChangesMut::bypass_change_detection`]: bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection #[derive(Debug, Clone, Resource, Reflect)] #[reflect(Default)] -pub struct Input { +pub struct Input { /// A collection of every button that is currently being pressed. pressed: HashSet, /// A collection of every button that has just been pressed. just_pressed: HashSet, /// A collection of every button that has just been released. just_released: HashSet, + /// To map logical keys with their respecting physical keys for reliability. + dynamic_map_value: HashMap, } -impl Default for Input { +impl Default for Input { fn default() -> Self { Self { pressed: Default::default(), just_pressed: Default::default(), just_released: Default::default(), + dynamic_map_value: HashMap::default(), } } } impl Input where - T: Clone + Eq + Hash + Send + Sync + 'static, + T: FromReflect + Clone + Eq + Hash + Send + Sync + 'static, { + /// When user adds modifiers to a keypress, the underlying Logical key might change.
+ /// Use this to store a reference to the original `LogicalKey`. + pub fn add_dynamic_mapping, S: Into>(&mut self, user_visible: V, stored: S) { + self.dynamic_map_value + .insert(user_visible.into(), stored.into()); + } + + /// To call when a dynamic key mapping is not longer needed. + pub fn release_dynamic_mapping>(&mut self, key: I) { + let to_remove = key.into(); + self.dynamic_map_value + .retain(|k, v| k != &to_remove && v != &to_remove); + } + /// Registers a press for the given `input`. - pub fn press(&mut self, input: T) { + pub fn press>(&mut self, input: I) { + let input = input.into(); // Returns `true` if the `input` wasn't pressed. if self.pressed.insert(input.clone()) { self.just_pressed.insert(input); @@ -77,20 +96,24 @@ where } /// Returns `true` if the `input` has been pressed. - pub fn pressed(&self, input: T) -> bool { + pub fn pressed>(&self, input: I) -> bool { + let input = input.into(); + let input = self.dynamic_map_value.get(&input).unwrap_or(&input).clone(); self.pressed.contains(&input) } /// Returns `true` if any item in `inputs` has been pressed. - pub fn any_pressed(&self, inputs: impl IntoIterator) -> bool { + pub fn any_pressed>(&self, inputs: impl IntoIterator) -> bool { inputs.into_iter().any(|it| self.pressed(it)) } /// Registers a release for the given `input`. - pub fn release(&mut self, input: T) { + pub fn release>(&mut self, input: I) { + let input = input.into(); + let input = self.dynamic_map_value.get(&input).unwrap_or(&input).clone(); // Returns `true` if the `input` was pressed. if self.pressed.remove(&input) { - self.just_released.insert(input); + self.just_released.insert(input.clone()); } } @@ -101,44 +124,53 @@ where } /// Returns `true` if the `input` has just been pressed. - pub fn just_pressed(&self, input: T) -> bool { + pub fn just_pressed>(&self, input: I) -> bool { + let input = input.into(); + let input = self.dynamic_map_value.get(&input).unwrap_or(&input).clone(); self.just_pressed.contains(&input) } /// Returns `true` if any item in `inputs` has just been pressed. - pub fn any_just_pressed(&self, inputs: impl IntoIterator) -> bool { + pub fn any_just_pressed>(&self, inputs: impl IntoIterator) -> bool { inputs.into_iter().any(|it| self.just_pressed(it)) } /// Clears the `just_pressed` state of the `input` and returns `true` if the `input` has just been pressed. /// /// Future calls to [`Input::just_pressed`] for the given input will return false until a new press event occurs. - pub fn clear_just_pressed(&mut self, input: T) -> bool { - self.just_pressed.remove(&input) + pub fn clear_just_pressed>(&mut self, input: I) -> bool { + self.just_pressed.remove(&input.into()) } /// Returns `true` if the `input` has just been released. - pub fn just_released(&self, input: T) -> bool { + pub fn just_released>(&self, input: I) -> bool { + let input = input.into(); + let input = self.dynamic_map_value.get(&input).unwrap_or(&input).clone(); self.just_released.contains(&input) } /// Returns `true` if any item in `inputs` has just been released. - pub fn any_just_released(&self, inputs: impl IntoIterator) -> bool { + pub fn any_just_released>(&self, inputs: impl IntoIterator) -> bool { inputs.into_iter().any(|it| self.just_released(it)) } /// Clears the `just_released` state of the `input` and returns `true` if the `input` has just been released. /// /// Future calls to [`Input::just_released`] for the given input will return false until a new release event occurs. - pub fn clear_just_released(&mut self, input: T) -> bool { + pub fn clear_just_released>(&mut self, input: I) -> bool { + let input = input.into(); + let input = self.dynamic_map_value.get(&input).unwrap_or(&input).clone(); + self.release_dynamic_mapping(input.clone()); self.just_released.remove(&input) } /// Clears the `pressed`, `just_pressed` and `just_released` data of the `input`. - pub fn reset(&mut self, input: T) { + pub fn reset>(&mut self, input: I) { + let input = input.into(); self.pressed.remove(&input); self.just_pressed.remove(&input); self.just_released.remove(&input); + self.dynamic_map_value.clear(); } /// Clears the `pressed`, `just_pressed`, and `just_released` data for every input. @@ -148,6 +180,7 @@ where self.pressed.clear(); self.just_pressed.clear(); self.just_released.clear(); + self.dynamic_map_value.clear(); } /// Clears the `just pressed` and `just released` data for every input. @@ -155,7 +188,10 @@ where /// See also [`Input::reset_all`] for a full reset. pub fn clear(&mut self) { self.just_pressed.clear(); - self.just_released.clear(); + let inputs_to_release = self.just_released.drain().collect::>(); + for r in inputs_to_release { + self.release_dynamic_mapping(r); + } } /// An iterator visiting every pressed input in arbitrary order. @@ -176,12 +212,13 @@ where #[cfg(test)] mod test { - use bevy_reflect::TypePath; + use bevy_reflect::Reflect; use crate::Input; /// Used for testing the functionality of [`Input`]. - #[derive(TypePath, Copy, Clone, Eq, PartialEq, Hash)] + #[derive(Copy, Clone, Eq, PartialEq, Hash, Reflect)] + #[reflect_value] enum DummyInput { Input1, Input2, @@ -189,7 +226,7 @@ mod test { #[test] fn test_press() { - let mut input = Input::default(); + let mut input = Input::::default(); assert!(!input.pressed.contains(&DummyInput::Input1)); assert!(!input.just_pressed.contains(&DummyInput::Input1)); input.press(DummyInput::Input1); @@ -199,7 +236,7 @@ mod test { #[test] fn test_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); assert!(!input.pressed(DummyInput::Input1)); input.press(DummyInput::Input1); assert!(input.pressed(DummyInput::Input1)); @@ -207,7 +244,7 @@ mod test { #[test] fn test_any_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); assert!(!input.any_pressed([DummyInput::Input1])); assert!(!input.any_pressed([DummyInput::Input2])); assert!(!input.any_pressed([DummyInput::Input1, DummyInput::Input2])); @@ -219,7 +256,7 @@ mod test { #[test] fn test_release() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); assert!(input.pressed.contains(&DummyInput::Input1)); assert!(!input.just_released.contains(&DummyInput::Input1)); @@ -230,7 +267,7 @@ mod test { #[test] fn test_release_all() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.press(DummyInput::Input2); input.release_all(); @@ -241,7 +278,7 @@ mod test { #[test] fn test_just_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); assert!(!input.just_pressed(DummyInput::Input1)); input.press(DummyInput::Input1); assert!(input.just_pressed(DummyInput::Input1)); @@ -249,7 +286,7 @@ mod test { #[test] fn test_any_just_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); assert!(!input.any_just_pressed([DummyInput::Input1])); assert!(!input.any_just_pressed([DummyInput::Input2])); assert!(!input.any_just_pressed([DummyInput::Input1, DummyInput::Input2])); @@ -261,7 +298,7 @@ mod test { #[test] fn test_clear_just_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); assert!(input.just_pressed(DummyInput::Input1)); input.clear_just_pressed(DummyInput::Input1); @@ -270,7 +307,7 @@ mod test { #[test] fn test_just_released() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); assert!(!input.just_released(DummyInput::Input1)); input.release(DummyInput::Input1); @@ -279,7 +316,7 @@ mod test { #[test] fn test_any_just_released() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); assert!(!input.any_just_released([DummyInput::Input1])); assert!(!input.any_just_released([DummyInput::Input2])); @@ -292,7 +329,7 @@ mod test { #[test] fn test_clear_just_released() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.release(DummyInput::Input1); assert!(input.just_released(DummyInput::Input1)); @@ -302,7 +339,7 @@ mod test { #[test] fn test_reset() { - let mut input = Input::default(); + let mut input = Input::::default(); // Pressed input.press(DummyInput::Input1); @@ -328,7 +365,7 @@ mod test { #[test] fn test_reset_all() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.press(DummyInput::Input2); @@ -344,7 +381,7 @@ mod test { #[test] fn test_clear() { - let mut input = Input::default(); + let mut input = Input::::default(); // Pressed input.press(DummyInput::Input1); @@ -370,7 +407,7 @@ mod test { #[test] fn test_get_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.press(DummyInput::Input2); let pressed = input.get_pressed(); @@ -382,7 +419,7 @@ mod test { #[test] fn test_get_just_pressed() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.press(DummyInput::Input2); let just_pressed = input.get_just_pressed(); @@ -394,7 +431,7 @@ mod test { #[test] fn test_get_just_released() { - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.press(DummyInput::Input2); input.release(DummyInput::Input1); @@ -408,7 +445,7 @@ mod test { #[test] fn test_general_input_handling() { - let mut input = Input::default(); + let mut input = Input::::default(); // Test pressing input.press(DummyInput::Input1); @@ -453,7 +490,7 @@ mod test { assert!(!input.just_released(DummyInput::Input2)); // Set up an `Input` to test resetting - let mut input = Input::default(); + let mut input = Input::::default(); input.press(DummyInput::Input1); input.release(DummyInput::Input2); diff --git a/crates/bevy_input/src/keyboard.rs b/crates/bevy_input/src/keyboard.rs index 73330617e0965..24e893cfd4057 100644 --- a/crates/bevy_input/src/keyboard.rs +++ b/crates/bevy_input/src/keyboard.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize}; /// ## Usage /// /// The event is consumed inside of the [`keyboard_input_system`] -/// to update the [`Input`](crate::Input) resource. +/// to update the [`Input`] resource. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] #[reflect(Debug, PartialEq)] #[cfg_attr( @@ -43,19 +43,61 @@ pub struct KeyboardInput { pub window: Entity, } -/// Updates the [`Input`] resource with the latest [`KeyboardInput`] events. +/// The stored logical key code for `Res>` +/// +/// We need to store the corresponding `KeyCode` because when modifiers are applied, +/// the released logical key might be different from the pressed. +/// For example, the sequence of physical keys: +/// - Press shift +/// - Press 'a' +/// - Release shift +/// - Release 'a', +/// +/// Leads to the following underlying logical events: +/// - Press shift +/// - Press logic character key 'A' +/// - Release shift +/// - Release logic character key 'a' +/// Note the different capitalization. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Reflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub enum KeyLogic { + /// Represents the logical key, typically what's visible on the keyboard. + Logic(Key), + /// Represents the ScanCode + Physical(KeyCode), +} + +impl From for KeyLogic +where + I: Into, +{ + fn from(value: I) -> Self { + KeyLogic::Logic(value.into()) + } +} + +impl From for KeyLogic { + fn from(value: KeyCode) -> Self { + KeyLogic::Physical(value) + } +} + +/// Updates the [`Input`] resource with the latest [`KeyboardInput`] events. /// /// ## Differences /// -/// The main difference between the [`KeyboardInput`] event and the [`Input`] or [`Input`] resources is that +/// The main difference between the [`KeyboardInput`] event and the [`Input`] resources is that /// the latter have convenient functions such as [`Input::pressed`], [`Input::just_pressed`] and [`Input::just_released`]. pub fn keyboard_input_system( - mut logical_key_input: ResMut>, - mut key_input: ResMut>, + mut key_input: ResMut>, mut keyboard_input_events: EventReader, ) { // Avoid clearing if it's not empty to ensure change detection is not triggered. - logical_key_input.bypass_change_detection().clear(); key_input.bypass_change_detection().clear(); for event in keyboard_input_events.read() { let KeyboardInput { @@ -65,13 +107,12 @@ pub fn keyboard_input_system( .. } = event; match state { - ButtonState::Pressed => key_input.press(*key_code), + ButtonState::Pressed => { + key_input.add_dynamic_mapping(logical_key.clone(), *key_code); + key_input.press(*key_code); + } ButtonState::Released => key_input.release(*key_code), } - match state { - ButtonState::Pressed => logical_key_input.press(logical_key.clone()), - ButtonState::Released => logical_key_input.release(logical_key.clone()), - } } } @@ -109,7 +150,7 @@ pub enum NativeKeyCode { /// /// ## Usage /// -/// It is used as the generic `T` value of an [`Input`] to create a `Res>`. +/// It is used as the generic `T` value of an [`Input`] to create a `Res>`. /// The resource values are mapped to the current layout of the keyboard and correlate to a [`NativeKeyCode`]. /// /// ## Updating @@ -626,7 +667,9 @@ pub enum NativeKey { /// /// ## Usage /// -/// It is used as the generic `T` value of an [`Input`] to create a `Res>`. +/// It is used as the generic `T` value of an [`Input`] to create a `Res>`, +/// storing a `Key` and its corresponding `KeyCode`. +/// /// The resource values are mapped to the current layout of the keyboard and correlate to a [`KeyCode`]. /// /// ## Updating @@ -1385,3 +1428,9 @@ pub enum Key { /// General-purpose function key. F35, } + +impl From<&str> for Key { + fn from(value: &str) -> Self { + Key::Character(value.into()) + } +} diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 1539e02c36aa4..181f5b0214809 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -27,7 +27,7 @@ pub mod prelude { gamepad::{ Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads, }, - keyboard::{Key, KeyCode}, + keyboard::{Key, KeyCode, KeyLogic}, mouse::MouseButton, touch::{TouchInput, Touches}, Axis, Input, @@ -36,8 +36,9 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::prelude::*; + use bevy_reflect::Reflect; -use keyboard::{keyboard_input_system, Key, KeyCode, KeyboardInput, NativeKey}; +use keyboard::{keyboard_input_system, Key, KeyCode, KeyLogic, KeyboardInput, NativeKey}; use mouse::{ mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel, @@ -69,8 +70,7 @@ impl Plugin for InputPlugin { app // keyboard .add_event::() - .init_resource::>() - .init_resource::>() + .init_resource::>() .add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem)) // mouse .add_event::() @@ -116,6 +116,7 @@ impl Plugin for InputPlugin { // Register keyboard types app.register_type::() + .register_type::() .register_type::() .register_type::() .register_type::(); diff --git a/crates/bevy_window/src/system.rs b/crates/bevy_window/src/system.rs index b7dec35a102eb..9d4351643e0d9 100644 --- a/crates/bevy_window/src/system.rs +++ b/crates/bevy_window/src/system.rs @@ -2,7 +2,10 @@ use crate::{PrimaryWindow, Window, WindowCloseRequested}; use bevy_app::AppExit; use bevy_ecs::prelude::*; -use bevy_input::{keyboard::KeyCode, Input}; +use bevy_input::{ + keyboard::{KeyCode, KeyLogic}, + Input, +}; /// Exit the application when there are no open windows. /// @@ -52,7 +55,7 @@ pub fn close_when_requested(mut commands: Commands, mut closed: EventReader, - input: Res>, + input: Res>, ) { for (window, focus) in focused_windows.iter() { if !focus.focused { diff --git a/examples/2d/2d_gizmos.rs b/examples/2d/2d_gizmos.rs index a70cd250a2976..624b130232291 100644 --- a/examples/2d/2d_gizmos.rs +++ b/examples/2d/2d_gizmos.rs @@ -55,7 +55,7 @@ fn system(mut gizmos: Gizmos, time: Res