Keyboard Shortcuts: the backtick key's label ignores the next key's text on the web

eframe's web build sends each key's text before its press. When several
keys arrive in one frame, the editor paired the backtick press with the
text typed by the key after it, so a shortcut on that key could show the
next key's character. The editor now pairs each text with its press
through `shortcuts::typing_presses`, which shortcut dispatch already
uses, for both event orders.
This commit is contained in:
dotCooCoo
2026-11-04 18:56:43 +07:00
committed by bflatastic
parent af14fe3d59
commit 7a3c09c05d
2 changed files with 19 additions and 13 deletions
+18 -12
View File
@@ -274,19 +274,18 @@ pub fn chord_from_event(key: Key, m: Modifiers) -> Option<String> {
ui_mirror!(fn backtick_store() -> Option<String>);
/// Notes what the backtick key (left of 1) typed in `events`: the one-character text whose key, a
/// press of it without modifiers, comes right before it (egui-winit) or, when no press comes
/// before, right after it (eframe's web build with no text field focused). egui reports that key
/// as [`Key::Backtick`] on layouts where it types a character egui has no key for, such as `"` on
/// Turkish Q.
/// Notes what the backtick key (left of 1) typed in `events`: the last one-character text that
/// [`crate::shortcuts::typing_presses`] pairs with a press of that key without modifiers, whether
/// the press comes before its text (egui-winit) or after it (eframe's web build with no text field
/// focused). egui reports that key as [`Key::Backtick`] on layouts where it types a character egui
/// has no key for, such as `"` on Turkish Q.
pub(crate) fn note_backtick_text(events: &[egui::Event]) {
let is_press = |e: Option<&egui::Event>| matches!(e, Some(egui::Event::Key { pressed: true, .. }));
let is_backtick =
|e: Option<&egui::Event>| matches!(e, Some(egui::Event::Key { key: Key::Backtick, pressed: true, modifiers, .. }) if modifiers.is_none());
let typed = events.iter().enumerate().rev().find_map(|(j, e)| match e {
egui::Event::Text(t) if t.chars().count() == 1 && !t.chars().any(char::is_whitespace) => {
let before = j.checked_sub(1).and_then(|j| events.get(j));
(is_backtick(before) || (!is_press(before) && is_backtick(events.get(j + 1)))).then(|| t.clone())
let typed_by = crate::shortcuts::typing_presses(events);
let typed = events.iter().zip(typed_by).rev().find_map(|(e, by)| match (e, by.and_then(|k| events.get(k))) {
(egui::Event::Text(t), Some(egui::Event::Key { key: Key::Backtick, modifiers, .. }))
if modifiers.is_none() && t.chars().count() == 1 && !t.chars().any(char::is_whitespace) =>
{
Some(t.clone())
}
_ => None,
});
@@ -1322,6 +1321,13 @@ mod tests {
assert_eq!(backtick_text().as_deref(), Some("\""));
note_backtick_text(&[text("`"), press(Key::Backtick)]);
assert_eq!(backtick_text(), None);
// Several keys typed in one frame on the web: each text goes with the press after it.
let web = [text("a"), press(Key::A), text("\""), press(Key::Backtick), text("b"), press(Key::B)];
note_backtick_text(&web);
assert_eq!(backtick_text().as_deref(), Some("\""));
note_backtick_text(&[text("`"), press(Key::Backtick)]);
note_backtick_text(&[press(Key::A), text("a"), press(Key::Backtick), text("\""), press(Key::B), text("b")]);
assert_eq!(backtick_text().as_deref(), Some("\""));
// Only the unshifted key types that character.
assert_eq!(on_layout("Shift+~".into(), Some("\"")), "Shift+~");
assert_eq!(on_layout("Cmd+~".into(), Some("\"")), "Cmd+\"");
+1 -1
View File
@@ -929,7 +929,7 @@ fn latin(text: &str, physical: Option<Key>) -> String {
/// types at most one text. A text takes the press after it only when every later text in the same
/// run of alternating texts and presses has a press after it too; otherwise the text after that
/// press takes it as the press before it, and this text has none.
fn typing_presses(events: &[egui::Event]) -> Vec<Option<usize>> {
pub(crate) fn typing_presses(events: &[egui::Event]) -> Vec<Option<usize>> {
let is_press = |k: usize| matches!(events.get(k), Some(egui::Event::Key { key, pressed: true, .. }) if !is_modifier(*key));
let is_text = |k: usize| matches!(events.get(k), Some(egui::Event::Text(_)));
// Whether the run of alternating texts and presses that starts with the text at `j` ends