Expose text
field from winit in KeyboardInput
(#16864)
# Objective
Allow handling of dead keys on some keyboard layouts.
In some cases, dead keys were impossible to get using the
`KeyboardInput` event. This information is already present in the
underlying winit `KeyEvent`, but it wasn't exposed.
## Solution
Expose the `text` field from winit's `KeyEvent` in `KeyboardInput`.
This logic is inspired egui's implementation here:
adfc0bebfc/crates/egui-winit/src/lib.rs (L790-L807)
## Testing
This is a new field, so it shouldn't break any existing functionality. I
tested that this change works by running the modified `text_input`
example on different keyboard layouts.
## Example
Using a Portuguese/ABNT2 keyboard layout on windows and pressing
<kbd>\~</kbd> followed by
<kbd>a</kbd>/<kbd>Space</kbd>/<kbd>d</kbd>/<kbd>\~</kbd> now generates
the following events:
```
KeyboardInput { key_code: Quote, logical_key: Dead(Some('~')), state: Pressed, text: None, repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: KeyA, logical_key: Character("ã"), state: Pressed, text: Some("ã"), repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: Quote, logical_key: Dead(Some('~')), state: Pressed, text: None, repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: Space, logical_key: Space, state: Pressed, text: Some("~"), repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: Quote, logical_key: Dead(Some('~')), state: Pressed, text: None, repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: KeyD, logical_key: Character("d"), state: Pressed, text: Some("~d"), repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: Quote, logical_key: Dead(Some('~')), state: Pressed, text: None, repeat: false, window: 0v1#4294967296 }
KeyboardInput { key_code: Quote, logical_key: Dead(Some('~')), state: Pressed, text: Some("~~"), repeat: false, window: 0v1#4294967296 }
```
The logic for getting an input is pretty simple: check if `text` is
`Some`. If it is, this is actual input text, otherwise it isn't.
There's a small caveat: certain keys generate control characters in the
input text, which needs to be filtered out:
```
KeyboardInput { key_code: Escape, logical_key: Escape, state: Pressed, text: Some("\u{1b}"), repeat: false, window: 0v1#4294967296 }
```
I've updated the text_input example to include egui's solution to this,
which works well.
## Migration Guide
The `KeyboardInput` event now has a new `text` field.
This commit is contained in:
parent
1371619d84
commit
6ca1e756dc
@ -106,6 +106,19 @@ pub struct KeyboardInput {
|
||||
pub logical_key: Key,
|
||||
/// The press state of the key.
|
||||
pub state: ButtonState,
|
||||
/// Contains the text produced by this keypress.
|
||||
///
|
||||
/// In most cases this is identical to the content
|
||||
/// of the `Character` variant of `logical_key`.
|
||||
/// However, on Windows when a dead key was pressed earlier
|
||||
/// but cannot be combined with the character from this
|
||||
/// keypress, the produced text will consist of two characters:
|
||||
/// the dead-key-character followed by the character resulting
|
||||
/// from this keypress.
|
||||
///
|
||||
/// This is `None` if the current keypress cannot
|
||||
/// be interpreted as text.
|
||||
pub text: Option<SmolStr>,
|
||||
/// On some systems, holding down a key for some period of time causes that key to be repeated
|
||||
/// as though it were being pressed and released repeatedly. This field is [`true`] if this
|
||||
/// event is the result of one of those repeats.
|
||||
@ -750,6 +763,9 @@ pub enum Key {
|
||||
/// A key string that corresponds to the character typed by the user, taking into account the
|
||||
/// user’s current locale setting, and any system-level keyboard mapping overrides that are in
|
||||
/// effect.
|
||||
///
|
||||
/// Note that behavior may vary across platforms and keyboard layouts.
|
||||
/// See the `text` field of [`KeyboardInput`] for more information.
|
||||
Character(SmolStr),
|
||||
|
||||
/// This variant is used when the key cannot be translated to any other variant.
|
||||
|
@ -322,6 +322,7 @@ mod tests {
|
||||
key_code: KeyCode::KeyA,
|
||||
logical_key: Key::Character(SmolStr::new_static("A")),
|
||||
state: ButtonState::Pressed,
|
||||
text: Some(SmolStr::new_static("A")),
|
||||
repeat: false,
|
||||
window: Entity::PLACEHOLDER,
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ pub fn convert_keyboard_input(
|
||||
state: convert_element_state(keyboard_input.state),
|
||||
key_code: convert_physical_key_code(keyboard_input.physical_key),
|
||||
logical_key: convert_logical_key(&keyboard_input.logical_key),
|
||||
text: keyboard_input.text.clone(),
|
||||
repeat: keyboard_input.repeat,
|
||||
window,
|
||||
}
|
||||
|
@ -144,8 +144,8 @@ fn listen_keyboard_input_events(
|
||||
continue;
|
||||
}
|
||||
|
||||
match &event.logical_key {
|
||||
Key::Enter => {
|
||||
match (&event.logical_key, &event.text) {
|
||||
(Key::Enter, _) => {
|
||||
if text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@ -159,16 +159,27 @@ fn listen_keyboard_input_events(
|
||||
},
|
||||
));
|
||||
}
|
||||
Key::Space => {
|
||||
text.push(' ');
|
||||
}
|
||||
Key::Backspace => {
|
||||
(Key::Backspace, _) => {
|
||||
text.pop();
|
||||
}
|
||||
Key::Character(character) => {
|
||||
text.push_str(character);
|
||||
(_, Some(inserted_text)) => {
|
||||
// Make sure the text doesn't have any control characters,
|
||||
// which can happen when keys like Escape are pressed
|
||||
if inserted_text.chars().all(is_printable_char) {
|
||||
text.push_str(inserted_text);
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this logic is taken from egui-winit:
|
||||
// https://github.com/emilk/egui/blob/adfc0bebfc6be14cee2068dee758412a5e0648dc/crates/egui-winit/src/lib.rs#L1014-L1024
|
||||
fn is_printable_char(chr: char) -> bool {
|
||||
let is_in_private_use_area = ('\u{e000}'..='\u{f8ff}').contains(&chr)
|
||||
|| ('\u{f0000}'..='\u{ffffd}').contains(&chr)
|
||||
|| ('\u{100000}'..='\u{10fffd}').contains(&chr);
|
||||
|
||||
!is_in_private_use_area && !chr.is_ascii_control()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user