Skip to content

Commit 2119838

Browse files
authored
Add support for ButtonInput<Key> (#19684)
# Objective While `KeyCode` is very often the correct way to interact with keyboard input there are a bunch of cases where it isn't, notably most of the symbols (e.g. plus, minus, different parentheses). Currently the only way to get these is to read from `EventReader<KeyboardInput>`, but then you'd have to redo the `ButtonInput` logic for pressed/released to e.g. make zoom functionality that depends on plus/minus keys. This has led to confusion previously, like #3278 ## Solution Add a `ButtonInput<Key>` resource. ## Testing Modified the `keyboard_input` example to test it. ## Open questions I'm not 100% sure this is the right way forward, since it duplicates the key processing logic and might make people use the shorter `ButtonInput<Key>` even when it's not appropriate. Another option is to add a new struct with both `Key` and `KeyCode`, and use `ButtonInput` with that instead. That would make it more explanatory, but that is a lot of churn. The third alternative is to not do this because it's too niche. I'll add more documentation and take it out of draft if we want to move forward with it.
1 parent a1d3c61 commit 2119838

File tree

5 files changed

+86
-21
lines changed

5 files changed

+86
-21
lines changed

crates/bevy_input/src/button_input.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ use {
122122
/// [`DetectChangesMut::bypass_change_detection`]: bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection
123123
#[derive(Debug, Clone, Resource)]
124124
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default, Resource))]
125-
pub struct ButtonInput<T: Copy + Eq + Hash + Send + Sync + 'static> {
125+
pub struct ButtonInput<T: Clone + Eq + Hash + Send + Sync + 'static> {
126126
/// A collection of every button that is currently being pressed.
127127
pressed: HashSet<T>,
128128
/// A collection of every button that has just been pressed.
@@ -131,7 +131,7 @@ pub struct ButtonInput<T: Copy + Eq + Hash + Send + Sync + 'static> {
131131
just_released: HashSet<T>,
132132
}
133133

134-
impl<T: Copy + Eq + Hash + Send + Sync + 'static> Default for ButtonInput<T> {
134+
impl<T: Clone + Eq + Hash + Send + Sync + 'static> Default for ButtonInput<T> {
135135
fn default() -> Self {
136136
Self {
137137
pressed: Default::default(),
@@ -143,12 +143,12 @@ impl<T: Copy + Eq + Hash + Send + Sync + 'static> Default for ButtonInput<T> {
143143

144144
impl<T> ButtonInput<T>
145145
where
146-
T: Copy + Eq + Hash + Send + Sync + 'static,
146+
T: Clone + Eq + Hash + Send + Sync + 'static,
147147
{
148148
/// Registers a press for the given `input`.
149149
pub fn press(&mut self, input: T) {
150150
// Returns `true` if the `input` wasn't pressed.
151-
if self.pressed.insert(input) {
151+
if self.pressed.insert(input.clone()) {
152152
self.just_pressed.insert(input);
153153
}
154154
}

crates/bevy_input/src/keyboard.rs

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
9292
///
9393
/// ## Usage
9494
///
95-
/// The event is consumed inside of the [`keyboard_input_system`]
96-
/// to update the [`ButtonInput<KeyCode>`](ButtonInput<KeyCode>) resource.
95+
/// The event is consumed inside of the [`keyboard_input_system`] to update the
96+
/// [`ButtonInput<KeyCode>`](ButtonInput<KeyCode>) and
97+
/// [`ButtonInput<Key>`](ButtonInput<Key>) resources.
9798
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq, Hash)]
9899
#[cfg_attr(
99100
feature = "bevy_reflect",
@@ -107,8 +108,12 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
107108
)]
108109
pub struct KeyboardInput {
109110
/// The physical key code of the key.
111+
///
112+
/// This corresponds to the location of the key independent of the keyboard layout.
110113
pub key_code: KeyCode,
111-
/// The logical key of the input
114+
/// The logical key of the input.
115+
///
116+
/// This corresponds to the actual key taking keyboard layout into account.
112117
pub logical_key: Key,
113118
/// The press state of the key.
114119
pub state: ButtonState,
@@ -148,32 +153,46 @@ pub struct KeyboardInput {
148153
)]
149154
pub struct KeyboardFocusLost;
150155

151-
/// Updates the [`ButtonInput<KeyCode>`] resource with the latest [`KeyboardInput`] events.
156+
/// Updates the [`ButtonInput<KeyCode>`] and [`ButtonInput<Key>`] resources with the latest [`KeyboardInput`] events.
152157
///
153158
/// ## Differences
154159
///
155-
/// The main difference between the [`KeyboardInput`] event and the [`ButtonInput<KeyCode>`] resources is that
160+
/// The main difference between the [`KeyboardInput`] event and the [`ButtonInput`] resources are that
156161
/// the latter has convenient functions such as [`ButtonInput::pressed`], [`ButtonInput::just_pressed`] and [`ButtonInput::just_released`] and is window id agnostic.
162+
///
163+
/// There is a [`ButtonInput`] for both [`KeyCode`] and [`Key`] as they are both useful in different situations, see their documentation for the details.
157164
pub fn keyboard_input_system(
158-
mut key_input: ResMut<ButtonInput<KeyCode>>,
165+
mut keycode_input: ResMut<ButtonInput<KeyCode>>,
166+
mut key_input: ResMut<ButtonInput<Key>>,
159167
mut keyboard_input_events: EventReader<KeyboardInput>,
160168
mut focus_events: EventReader<KeyboardFocusLost>,
161169
) {
162-
// Avoid clearing if it's not empty to ensure change detection is not triggered.
170+
// Avoid clearing if not empty to ensure change detection is not triggered.
171+
keycode_input.bypass_change_detection().clear();
163172
key_input.bypass_change_detection().clear();
173+
164174
for event in keyboard_input_events.read() {
165175
let KeyboardInput {
166-
key_code, state, ..
176+
key_code,
177+
logical_key,
178+
state,
179+
..
167180
} = event;
168181
match state {
169-
ButtonState::Pressed => key_input.press(*key_code),
170-
ButtonState::Released => key_input.release(*key_code),
182+
ButtonState::Pressed => {
183+
keycode_input.press(*key_code);
184+
key_input.press(logical_key.clone());
185+
}
186+
ButtonState::Released => {
187+
keycode_input.release(*key_code);
188+
key_input.release(logical_key.clone());
189+
}
171190
}
172191
}
173192

174193
// Release all cached input to avoid having stuck input when switching between windows in os
175194
if !focus_events.is_empty() {
176-
key_input.release_all();
195+
keycode_input.release_all();
177196
focus_events.clear();
178197
}
179198
}
@@ -220,13 +239,13 @@ pub enum NativeKeyCode {
220239
/// It is used as the generic `T` value of an [`ButtonInput`] to create a `Res<ButtonInput<KeyCode>>`.
221240
///
222241
/// Code representing the location of a physical key
223-
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
242+
/// This mostly conforms to the [`UI Events Specification's KeyboardEvent.code`] with a few
224243
/// exceptions:
225244
/// - The keys that the specification calls `MetaLeft` and `MetaRight` are named `SuperLeft` and
226245
/// `SuperRight` here.
227246
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
228247
///
229-
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
248+
/// [`UI Events Specification's KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
230249
///
231250
/// ## Updating
232251
///
@@ -756,6 +775,19 @@ pub enum NativeKey {
756775

757776
/// The logical key code of a [`KeyboardInput`].
758777
///
778+
/// This contains the actual value that is produced by pressing the key. This is
779+
/// useful when you need the actual letters, and for symbols like `+` and `-`
780+
/// when implementing zoom, as they can be in different locations depending on
781+
/// the keyboard layout.
782+
///
783+
/// In many cases you want the key location instead, for example when
784+
/// implementing WASD controls so the keys are located the same place on QWERTY
785+
/// and other layouts. In that case use [`KeyCode`] instead.
786+
///
787+
/// ## Usage
788+
///
789+
/// It is used as the generic `T` value of an [`ButtonInput`] to create a `Res<ButtonInput<Key>>`.
790+
///
759791
/// ## Technical
760792
///
761793
/// Its values map 1 to 1 to winit's Key.

crates/bevy_input/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use bevy_ecs::prelude::*;
4949
#[cfg(feature = "bevy_reflect")]
5050
use bevy_reflect::Reflect;
5151
use gestures::*;
52-
use keyboard::{keyboard_input_system, KeyCode, KeyboardFocusLost, KeyboardInput};
52+
use keyboard::{keyboard_input_system, Key, KeyCode, KeyboardFocusLost, KeyboardInput};
5353
use mouse::{
5454
accumulate_mouse_motion_system, accumulate_mouse_scroll_system, mouse_button_input_system,
5555
AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseButtonInput, MouseMotion,
@@ -89,6 +89,7 @@ impl Plugin for InputPlugin {
8989
.add_event::<KeyboardInput>()
9090
.add_event::<KeyboardFocusLost>()
9191
.init_resource::<ButtonInput<KeyCode>>()
92+
.init_resource::<ButtonInput<Key>>()
9293
.add_systems(PreUpdate, keyboard_input_system.in_set(InputSystems))
9394
// mouse
9495
.add_event::<MouseButtonInput>()

examples/input/keyboard_input.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Demonstrates handling a key press/release.
22
3-
use bevy::prelude::*;
3+
use bevy::{input::keyboard::Key, prelude::*};
44

55
fn main() {
66
App::new()
@@ -9,8 +9,13 @@ fn main() {
99
.run();
1010
}
1111

12-
/// This system prints 'A' key state
13-
fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>) {
12+
/// This system responds to certain key presses
13+
fn keyboard_input_system(
14+
keyboard_input: Res<ButtonInput<KeyCode>>,
15+
key_input: Res<ButtonInput<Key>>,
16+
) {
17+
// KeyCode is used when you want the key location across different keyboard layouts
18+
// See https://w3c.github.io/uievents-code/#code-value-tables for the locations
1419
if keyboard_input.pressed(KeyCode::KeyA) {
1520
info!("'A' currently pressed");
1621
}
@@ -21,4 +26,18 @@ fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>) {
2126
if keyboard_input.just_released(KeyCode::KeyA) {
2227
info!("'A' just released");
2328
}
29+
30+
// Key is used when you want a specific key, no matter where it is located.
31+
// This is useful for symbols that have a specific connotation, e.g. '?' for
32+
// a help menu or '+'/'-' for zoom
33+
let key = Key::Character("?".into());
34+
if key_input.pressed(key.clone()) {
35+
info!("'?' currently pressed");
36+
}
37+
if key_input.just_pressed(key.clone()) {
38+
info!("'?' just pressed");
39+
}
40+
if key_input.just_released(key) {
41+
info!("'?' just released");
42+
}
2443
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: ButtonInput for Key
3+
authors: ["@kristoff3r"]
4+
pull_requests: [19684]
5+
---
6+
7+
Bevy now has a `ButtonInput<Key>` resource, similarly to the existing `ButtonInput<KeyCode>` resource.
8+
9+
The difference between `KeyCode` and `Key` is that the former refers to the
10+
button location on a US keyboard independent of the actual layout in use, while
11+
`Key` gives you the actual letter or symbol that was entered. In most cases you
12+
still want to use `KeyCode`, but in some cases it makes more sense to use `Key`,
13+
for example when using '+'/'-' to zoom.

0 commit comments

Comments
 (0)