Skip to content

Commit

Permalink
dynamic key mapping to rely on physical key rather than logical key
Browse files Browse the repository at this point in the history
  • Loading branch information
Vrixyz committed Jun 16, 2023
1 parent 4676e2c commit ea9339c
Show file tree
Hide file tree
Showing 40 changed files with 156 additions and 68 deletions.
42 changes: 35 additions & 7 deletions crates/bevy_input/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bevy_ecs::system::Resource;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_utils::HashSet;
use bevy_utils::{HashMap, HashSet};
use std::hash::Hash;

// unused import, but needed for intra doc link to work
Expand Down Expand Up @@ -50,6 +50,8 @@ pub struct Input<T: Clone + Eq + Hash + Send + Sync + 'static> {
just_pressed: HashSet<T>,
/// A collection of every button that has just been released.
just_released: HashSet<T>,
/// To map logical keys with their respecting physical keys for reliability.
dynamic_map_value: HashMap<T, T>,
}

impl<T: Clone + Eq + Hash + Send + Sync + 'static> Default for Input<T> {
Expand All @@ -58,6 +60,7 @@ impl<T: Clone + Eq + Hash + Send + Sync + 'static> Default for Input<T> {
pressed: Default::default(),
just_pressed: Default::default(),
just_released: Default::default(),
dynamic_map_value: HashMap::default(),
}
}
}
Expand All @@ -66,6 +69,16 @@ impl<T> Input<T>
where
T: Clone + Eq + Hash + Send + Sync + 'static,
{
pub fn add_dynamic_mapping<I: Into<T>>(&mut self, user_visible: I, stored: I) {
self.dynamic_map_value
.insert(user_visible.into(), stored.into());
}
pub fn release_dynamic_mapping<I: Into<T>>(&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<I: Into<T>>(&mut self, input: I) {
let input = input.into();
Expand All @@ -77,7 +90,9 @@ where

/// Returns `true` if the `input` has been pressed.
pub fn pressed<I: Into<T>>(&self, input: I) -> bool {
self.pressed.contains(&input.into())
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.
Expand All @@ -88,9 +103,10 @@ where
/// Registers a release for the given `input`.
pub fn release<I: Into<T>>(&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());
}
}

Expand All @@ -102,7 +118,9 @@ where

/// Returns `true` if the `input` has just been pressed.
pub fn just_pressed<I: Into<T>>(&self, input: I) -> bool {
self.just_pressed.contains(&input.into())
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.
Expand All @@ -119,7 +137,9 @@ where

/// Returns `true` if the `input` has just been released.
pub fn just_released<I: Into<T>>(&self, input: I) -> bool {
self.just_released.contains(&input.into())
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.
Expand All @@ -131,7 +151,10 @@ where
///
/// Future calls to [`Input::just_released`] for the given input will return false until a new release event occurs.
pub fn clear_just_released<I: Into<T>>(&mut self, input: I) -> bool {
self.just_released.remove(&input.into())
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`.
Expand All @@ -140,6 +163,7 @@ where
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.
Expand All @@ -149,14 +173,18 @@ 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.
///
/// 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::<Vec<_>>();
for r in inputs_to_release {
self.release_dynamic_mapping(r);
}
}

/// An iterator visiting every pressed input in arbitrary order.
Expand Down
50 changes: 46 additions & 4 deletions crates/bevy_input/src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,50 @@ pub struct KeyboardInput {
pub state: ButtonState,
}

/// The stored logical key code for `Res<Input<KeyLogic>>`
///
/// 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, FromReflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum KeyLogic {
Logic(Key),
Physical(KeyCode),
}

impl<I> From<I> for KeyLogic
where
I: Into<Key>,
{
fn from(value: I) -> Self {
KeyLogic::Logic(value.into())
}
}

/// Updates the [`Input<KeyCode>`] resource with the latest [`KeyboardInput`] events.
///
/// ## Differences
///
/// The main difference between the [`KeyboardInput`] event and the [`Input<KeyCode>`] or [`Input<ScanCode>`] 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<Input<Key>>,
mut logical_key_input: ResMut<Input<KeyLogic>>,
mut key_input: ResMut<Input<KeyCode>>,
mut keyboard_input_events: EventReader<KeyboardInput>,
) {
Expand All @@ -63,9 +99,13 @@ pub fn keyboard_input_system(
ButtonState::Pressed => key_input.press(*key_code),
ButtonState::Released => key_input.release(*key_code),
}
let logic = KeyLogic::Logic(logical_key.clone());
match state {
ButtonState::Pressed => logical_key_input.press(logical_key.clone()),
ButtonState::Released => logical_key_input.release(logical_key.clone()),
ButtonState::Pressed => {
logical_key_input.add_dynamic_mapping(logic.clone(), KeyLogic::Physical(*key_code));
logical_key_input.press(KeyLogic::Physical(*key_code));
}
ButtonState::Released => logical_key_input.release(KeyLogic::Physical(*key_code)),
}
}
}
Expand Down Expand Up @@ -599,7 +639,9 @@ pub enum NativeKey {
///
/// ## Usage
///
/// It is used as the generic `T` value of an [`Input`](crate::Input) to create a `Res<Input<Key>>`.
/// It is used as the generic `T` value of an [`Input`](crate::Input) to create a `Res<Input<KeyLogic>>`,
/// storing a `Key` and its corresponding `KeyCode`.
///
/// The resource values are mapped to the current layout of the keyboard and correlate to a [`KeyCode`](KeyCode).
///
/// ## Updating
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_input/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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,
Expand All @@ -29,7 +29,7 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::{FromReflect, 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,
Expand Down Expand Up @@ -61,7 +61,7 @@ impl Plugin for InputPlugin {
// keyboard
.add_event::<KeyboardInput>()
.init_resource::<Input<KeyCode>>()
.init_resource::<Input<Key>>()
.init_resource::<Input<KeyLogic>>()
.add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem))
// mouse
.add_event::<MouseButtonInput>()
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,8 @@ pub fn winit_runner(mut app: App) {
}
WindowEvent::KeyboardInput { ref event, .. } => {
let keyboard_event = converters::convert_keyboard_input(event);
if let bevy_input::keyboard::Key::Character(c) = &keyboard_event.logical_key
if let bevy_input::keyboard::Key::Character(c) =
converters::convert_logical_key_code(&event.logical_key)
{
if let Some(first_char) = c.chars().next() {
input_events.character_input.send(ReceivedCharacter {
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/bloom_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ fn update_bloom_settings(
mut camera: Query<(Entity, Option<&mut BloomSettings>), With<Camera>>,
mut text: Query<&mut Text>,
mut commands: Commands,
key: Res<Input<Key>>,
key: Res<Input<KeyLogic>>,
time: Res<Time>,
) {
let bloom_settings = camera.single_mut();
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {

/// Demonstrates applying rotation and movement based on keyboard input.
fn player_movement_system(
keyboard_input: Res<Input<Key>>,
keyboard_input: Res<Input<KeyLogic>>,
mut query: Query<(&Player, &mut Transform)>,
) {
let (ship, mut transform) = query.single_mut();
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/3d_gizmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
}

fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<Key>>, time: Res<Time>) {
fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyLogic>>, time: Res<Time>) {
if keyboard.just_pressed("d") {
config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
}
Expand Down
4 changes: 2 additions & 2 deletions examples/3d/anti_aliasing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn main() {
}

fn modify_aa(
keys: Res<Input<Key>>,
keys: Res<Input<KeyLogic>>,
mut camera: Query<
(
Entity,
Expand Down Expand Up @@ -114,7 +114,7 @@ fn modify_aa(
}

fn modify_sharpening(
keys: Res<Input<Key>>,
keys: Res<Input<KeyLogic>>,
mut query: Query<&mut ContrastAdaptiveSharpeningSettings>,
) {
for mut cas in &mut query {
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/atmospheric_fog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ fn setup_instructions(mut commands: Commands) {
}),));
}

fn toggle_system(key: Res<Input<Key>>, mut fog: Query<&mut FogSettings>) {
fn toggle_system(key: Res<Input<KeyLogic>>, mut fog: Query<&mut FogSettings>) {
let mut fog_settings = fog.single_mut();

if key.just_pressed(Key::Space) {
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/blend_modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ fn example_control_system(
labelled: Query<&GlobalTransform>,
mut state: Local<ExampleState>,
time: Res<Time>,
input: Res<Input<Key>>,
input: Res<Input<KeyLogic>>,
) {
if input.pressed(Key::ArrowUp) {
state.alpha = (state.alpha + time.delta_seconds()).min(1.0);
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/bloom_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn update_bloom_settings(
mut camera: Query<(Entity, Option<&mut BloomSettings>), With<Camera>>,
mut text: Query<&mut Text>,
mut commands: Commands,
key: Res<Input<Key>>,
key: Res<Input<KeyLogic>>,
time: Res<Time>,
) {
let bloom_settings = camera.single_mut();
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/fog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn update_system(
mut camera: Query<(&mut FogSettings, &mut Transform)>,
mut text: Query<&mut Text>,
time: Res<Time>,
key: Res<Input<Key>>,
key: Res<Input<KeyLogic>>,
key_code: Res<Input<KeyCode>>,
) {
let now = time.elapsed_seconds();
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/lighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ fn animate_light_direction(
}

fn movement(
input: Res<Input<Key>>,
input: Res<Input<KeyLogic>>,
time: Res<Time>,
mut query: Query<&mut Transform, With<Movable>>,
) {
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/shadow_biases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ fn setup(
}

fn toggle_light(
input: Res<Input<Key>>,
input: Res<Input<KeyLogic>>,
mut point_lights: Query<&mut PointLight>,
mut directional_lights: Query<&mut DirectionalLight>,
mut example_text: Query<&mut Text>,
Expand Down
4 changes: 2 additions & 2 deletions examples/3d/shadow_caster_receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn setup(
}

fn toggle_light(
input: Res<Input<Key>>,
input: Res<Input<KeyLogic>>,
mut point_lights: Query<&mut PointLight>,
mut directional_lights: Query<&mut DirectionalLight>,
) {
Expand All @@ -152,7 +152,7 @@ fn toggle_light(

fn toggle_shadows(
mut commands: Commands,
input: Res<Input<Key>>,
input: Res<Input<KeyLogic>>,
mut queries: ParamSet<(
Query<Entity, (With<Handle<Mesh>>, With<NotShadowCaster>)>,
Query<Entity, (With<Handle<Mesh>>, With<NotShadowReceiver>)>,
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/skybox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ pub fn camera_controller(
mut mouse_events: EventReader<MouseMotion>,
mouse_button_input: Res<Input<MouseButton>>,
key_input: Res<Input<KeyCode>>,
key_input_logic: Res<Input<Key>>,
key_input_logic: Res<Input<KeyLogic>>,
mut move_toggled: Local<bool>,
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
) {
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/spotlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ fn light_sway(time: Res<Time>, mut query: Query<(&mut Transform, &mut SpotLight)
}

fn movement(
input: Res<Input<Key>>,
input: Res<Input<KeyLogic>>,
time: Res<Time>,
mut query: Query<&mut Transform, With<Movable>>,
) {
Expand Down
Loading

0 comments on commit ea9339c

Please sign in to comment.