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,
|
pub logical_key: Key,
|
||||||
/// The press state of the key.
|
/// The press state of the key.
|
||||||
pub state: ButtonState,
|
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
|
/// 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
|
/// as though it were being pressed and released repeatedly. This field is [`true`] if this
|
||||||
/// event is the result of one of those repeats.
|
/// 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
|
/// 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
|
/// user’s current locale setting, and any system-level keyboard mapping overrides that are in
|
||||||
/// effect.
|
/// effect.
|
||||||
|
///
|
||||||
|
/// Note that behavior may vary across platforms and keyboard layouts.
|
||||||
|
/// See the `text` field of [`KeyboardInput`] for more information.
|
||||||
Character(SmolStr),
|
Character(SmolStr),
|
||||||
|
|
||||||
/// This variant is used when the key cannot be translated to any other variant.
|
/// This variant is used when the key cannot be translated to any other variant.
|
||||||
|
@ -322,6 +322,7 @@ mod tests {
|
|||||||
key_code: KeyCode::KeyA,
|
key_code: KeyCode::KeyA,
|
||||||
logical_key: Key::Character(SmolStr::new_static("A")),
|
logical_key: Key::Character(SmolStr::new_static("A")),
|
||||||
state: ButtonState::Pressed,
|
state: ButtonState::Pressed,
|
||||||
|
text: Some(SmolStr::new_static("A")),
|
||||||
repeat: false,
|
repeat: false,
|
||||||
window: Entity::PLACEHOLDER,
|
window: Entity::PLACEHOLDER,
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,7 @@ pub fn convert_keyboard_input(
|
|||||||
state: convert_element_state(keyboard_input.state),
|
state: convert_element_state(keyboard_input.state),
|
||||||
key_code: convert_physical_key_code(keyboard_input.physical_key),
|
key_code: convert_physical_key_code(keyboard_input.physical_key),
|
||||||
logical_key: convert_logical_key(&keyboard_input.logical_key),
|
logical_key: convert_logical_key(&keyboard_input.logical_key),
|
||||||
|
text: keyboard_input.text.clone(),
|
||||||
repeat: keyboard_input.repeat,
|
repeat: keyboard_input.repeat,
|
||||||
window,
|
window,
|
||||||
}
|
}
|
||||||
|
@ -144,8 +144,8 @@ fn listen_keyboard_input_events(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &event.logical_key {
|
match (&event.logical_key, &event.text) {
|
||||||
Key::Enter => {
|
(Key::Enter, _) => {
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -159,16 +159,27 @@ fn listen_keyboard_input_events(
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Key::Space => {
|
(Key::Backspace, _) => {
|
||||||
text.push(' ');
|
|
||||||
}
|
|
||||||
Key::Backspace => {
|
|
||||||
text.pop();
|
text.pop();
|
||||||
}
|
}
|
||||||
Key::Character(character) => {
|
(_, Some(inserted_text)) => {
|
||||||
text.push_str(character);
|
// 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,
|
_ => 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