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 https://github.com/bevyengine/bevy/issues/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.
This commit is contained in:
parent
a1d3c6197f
commit
2119838e27
@ -122,7 +122,7 @@ use {
|
||||
/// [`DetectChangesMut::bypass_change_detection`]: bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection
|
||||
#[derive(Debug, Clone, Resource)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default, Resource))]
|
||||
pub struct ButtonInput<T: Copy + Eq + Hash + Send + Sync + 'static> {
|
||||
pub struct ButtonInput<T: Clone + Eq + Hash + Send + Sync + 'static> {
|
||||
/// A collection of every button that is currently being pressed.
|
||||
pressed: HashSet<T>,
|
||||
/// A collection of every button that has just been pressed.
|
||||
@ -131,7 +131,7 @@ pub struct ButtonInput<T: Copy + Eq + Hash + Send + Sync + 'static> {
|
||||
just_released: HashSet<T>,
|
||||
}
|
||||
|
||||
impl<T: Copy + Eq + Hash + Send + Sync + 'static> Default for ButtonInput<T> {
|
||||
impl<T: Clone + Eq + Hash + Send + Sync + 'static> Default for ButtonInput<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pressed: Default::default(),
|
||||
@ -143,12 +143,12 @@ impl<T: Copy + Eq + Hash + Send + Sync + 'static> Default for ButtonInput<T> {
|
||||
|
||||
impl<T> ButtonInput<T>
|
||||
where
|
||||
T: Copy + Eq + Hash + Send + Sync + 'static,
|
||||
T: Clone + Eq + Hash + Send + Sync + 'static,
|
||||
{
|
||||
/// Registers a press for the given `input`.
|
||||
pub fn press(&mut self, input: T) {
|
||||
// Returns `true` if the `input` wasn't pressed.
|
||||
if self.pressed.insert(input) {
|
||||
if self.pressed.insert(input.clone()) {
|
||||
self.just_pressed.insert(input);
|
||||
}
|
||||
}
|
||||
|
@ -92,8 +92,9 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// The event is consumed inside of the [`keyboard_input_system`]
|
||||
/// to update the [`ButtonInput<KeyCode>`](ButtonInput<KeyCode>) resource.
|
||||
/// The event is consumed inside of the [`keyboard_input_system`] to update the
|
||||
/// [`ButtonInput<KeyCode>`](ButtonInput<KeyCode>) and
|
||||
/// [`ButtonInput<Key>`](ButtonInput<Key>) resources.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
@ -107,8 +108,12 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
)]
|
||||
pub struct KeyboardInput {
|
||||
/// The physical key code of the key.
|
||||
///
|
||||
/// This corresponds to the location of the key independent of the keyboard layout.
|
||||
pub key_code: KeyCode,
|
||||
/// The logical key of the input
|
||||
/// The logical key of the input.
|
||||
///
|
||||
/// This corresponds to the actual key taking keyboard layout into account.
|
||||
pub logical_key: Key,
|
||||
/// The press state of the key.
|
||||
pub state: ButtonState,
|
||||
@ -148,32 +153,46 @@ pub struct KeyboardInput {
|
||||
)]
|
||||
pub struct KeyboardFocusLost;
|
||||
|
||||
/// Updates the [`ButtonInput<KeyCode>`] resource with the latest [`KeyboardInput`] events.
|
||||
/// Updates the [`ButtonInput<KeyCode>`] and [`ButtonInput<Key>`] resources with the latest [`KeyboardInput`] events.
|
||||
///
|
||||
/// ## Differences
|
||||
///
|
||||
/// The main difference between the [`KeyboardInput`] event and the [`ButtonInput<KeyCode>`] resources is that
|
||||
/// The main difference between the [`KeyboardInput`] event and the [`ButtonInput`] resources are that
|
||||
/// the latter has convenient functions such as [`ButtonInput::pressed`], [`ButtonInput::just_pressed`] and [`ButtonInput::just_released`] and is window id agnostic.
|
||||
///
|
||||
/// There is a [`ButtonInput`] for both [`KeyCode`] and [`Key`] as they are both useful in different situations, see their documentation for the details.
|
||||
pub fn keyboard_input_system(
|
||||
mut key_input: ResMut<ButtonInput<KeyCode>>,
|
||||
mut keycode_input: ResMut<ButtonInput<KeyCode>>,
|
||||
mut key_input: ResMut<ButtonInput<Key>>,
|
||||
mut keyboard_input_events: EventReader<KeyboardInput>,
|
||||
mut focus_events: EventReader<KeyboardFocusLost>,
|
||||
) {
|
||||
// Avoid clearing if it's not empty to ensure change detection is not triggered.
|
||||
// Avoid clearing if not empty to ensure change detection is not triggered.
|
||||
keycode_input.bypass_change_detection().clear();
|
||||
key_input.bypass_change_detection().clear();
|
||||
|
||||
for event in keyboard_input_events.read() {
|
||||
let KeyboardInput {
|
||||
key_code, state, ..
|
||||
key_code,
|
||||
logical_key,
|
||||
state,
|
||||
..
|
||||
} = event;
|
||||
match state {
|
||||
ButtonState::Pressed => key_input.press(*key_code),
|
||||
ButtonState::Released => key_input.release(*key_code),
|
||||
ButtonState::Pressed => {
|
||||
keycode_input.press(*key_code);
|
||||
key_input.press(logical_key.clone());
|
||||
}
|
||||
ButtonState::Released => {
|
||||
keycode_input.release(*key_code);
|
||||
key_input.release(logical_key.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Release all cached input to avoid having stuck input when switching between windows in os
|
||||
if !focus_events.is_empty() {
|
||||
key_input.release_all();
|
||||
keycode_input.release_all();
|
||||
focus_events.clear();
|
||||
}
|
||||
}
|
||||
@ -220,13 +239,13 @@ pub enum NativeKeyCode {
|
||||
/// It is used as the generic `T` value of an [`ButtonInput`] to create a `Res<ButtonInput<KeyCode>>`.
|
||||
///
|
||||
/// Code representing the location of a physical key
|
||||
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
|
||||
/// This mostly conforms to the [`UI Events Specification's KeyboardEvent.code`] with a few
|
||||
/// exceptions:
|
||||
/// - The keys that the specification calls `MetaLeft` and `MetaRight` are named `SuperLeft` and
|
||||
/// `SuperRight` here.
|
||||
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
|
||||
///
|
||||
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
|
||||
/// [`UI Events Specification's KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
|
||||
///
|
||||
/// ## Updating
|
||||
///
|
||||
@ -756,6 +775,19 @@ pub enum NativeKey {
|
||||
|
||||
/// The logical key code of a [`KeyboardInput`].
|
||||
///
|
||||
/// This contains the actual value that is produced by pressing the key. This is
|
||||
/// useful when you need the actual letters, and for symbols like `+` and `-`
|
||||
/// when implementing zoom, as they can be in different locations depending on
|
||||
/// the keyboard layout.
|
||||
///
|
||||
/// In many cases you want the key location instead, for example when
|
||||
/// implementing WASD controls so the keys are located the same place on QWERTY
|
||||
/// and other layouts. In that case use [`KeyCode`] instead.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// It is used as the generic `T` value of an [`ButtonInput`] to create a `Res<ButtonInput<Key>>`.
|
||||
///
|
||||
/// ## Technical
|
||||
///
|
||||
/// Its values map 1 to 1 to winit's Key.
|
||||
|
@ -49,7 +49,7 @@ use bevy_ecs::prelude::*;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use gestures::*;
|
||||
use keyboard::{keyboard_input_system, KeyCode, KeyboardFocusLost, KeyboardInput};
|
||||
use keyboard::{keyboard_input_system, Key, KeyCode, KeyboardFocusLost, KeyboardInput};
|
||||
use mouse::{
|
||||
accumulate_mouse_motion_system, accumulate_mouse_scroll_system, mouse_button_input_system,
|
||||
AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseButtonInput, MouseMotion,
|
||||
@ -89,6 +89,7 @@ impl Plugin for InputPlugin {
|
||||
.add_event::<KeyboardInput>()
|
||||
.add_event::<KeyboardFocusLost>()
|
||||
.init_resource::<ButtonInput<KeyCode>>()
|
||||
.init_resource::<ButtonInput<Key>>()
|
||||
.add_systems(PreUpdate, keyboard_input_system.in_set(InputSystems))
|
||||
// mouse
|
||||
.add_event::<MouseButtonInput>()
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Demonstrates handling a key press/release.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{input::keyboard::Key, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -9,8 +9,13 @@ fn main() {
|
||||
.run();
|
||||
}
|
||||
|
||||
/// This system prints 'A' key state
|
||||
fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>) {
|
||||
/// This system responds to certain key presses
|
||||
fn keyboard_input_system(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
key_input: Res<ButtonInput<Key>>,
|
||||
) {
|
||||
// KeyCode is used when you want the key location across different keyboard layouts
|
||||
// See https://w3c.github.io/uievents-code/#code-value-tables for the locations
|
||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||
info!("'A' currently pressed");
|
||||
}
|
||||
@ -21,4 +26,18 @@ fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>) {
|
||||
if keyboard_input.just_released(KeyCode::KeyA) {
|
||||
info!("'A' just released");
|
||||
}
|
||||
|
||||
// Key is used when you want a specific key, no matter where it is located.
|
||||
// This is useful for symbols that have a specific connotation, e.g. '?' for
|
||||
// a help menu or '+'/'-' for zoom
|
||||
let key = Key::Character("?".into());
|
||||
if key_input.pressed(key.clone()) {
|
||||
info!("'?' currently pressed");
|
||||
}
|
||||
if key_input.just_pressed(key.clone()) {
|
||||
info!("'?' just pressed");
|
||||
}
|
||||
if key_input.just_released(key) {
|
||||
info!("'?' just released");
|
||||
}
|
||||
}
|
||||
|
13
release-content/release-notes/key_buttoninput.md
Normal file
13
release-content/release-notes/key_buttoninput.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: ButtonInput for Key
|
||||
authors: ["@kristoff3r"]
|
||||
pull_requests: [19684]
|
||||
---
|
||||
|
||||
Bevy now has a `ButtonInput<Key>` resource, similarly to the existing `ButtonInput<KeyCode>` resource.
|
||||
|
||||
The difference between `KeyCode` and `Key` is that the former refers to the
|
||||
button location on a US keyboard independent of the actual layout in use, while
|
||||
`Key` gives you the actual letter or symbol that was entered. In most cases you
|
||||
still want to use `KeyCode`, but in some cases it makes more sense to use `Key`,
|
||||
for example when using '+'/'-' to zoom.
|
Loading…
Reference in New Issue
Block a user