Merge pull request #5587 from dotCooCoo/fix/backtick-text-pairing

Keyboard Shortcuts: the backtick key's label ignores the next key's text on the web
This commit is contained in:
BFlat
2026-11-04 19:18:01 +07:00
committed by GitHub
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