keymap: Shift presses egui reports as their position run nothing on moved layouts

Dvorak's ⇧, types <, which egui has no key for, so egui reports the
press as W and Ctrl+Shift+, ran Ctrl+Shift+W. Once typing shows the
layout's letters are not on QWERTY positions, such a press resolves to
nothing unless the position is known to type its own letter.
This commit is contained in:
aiongg committed 2026-10-21 05:47:51 +00:00
1 parent 86248331bb
commit d0acf36588
3 files changed
+62 -3

No files matched your search

+4 -1
View File
@@ -93,7 +93,10 @@ macOS it folds into `cmd` (`Chord::effective`).
3. Punctuation: the character typed, without Shift when the layout needs Shift for it: German
`/` is ⇧7, so ⇧⌘7 runs ⌘/.
4. Whether a layout is Latin is decided for the layout, not per key.
5. Labels show letters and digits by name, and a punctuation key as the character the layout
5. With Shift held on a Latin layout whose letters are known to be moved (Dvorak), a press that
egui reports as the letter at its own position, because the character typed has no key
(Dvorak's ⇧, types `<`), runs nothing: Ctrl+Shift+, never runs Ctrl+Shift+W.
6. Labels show letters and digits by name, and a punctuation key as the character the layout
types at its position when that character has no key of its own (`Ctrl+Ü` for Ctrl+[ on
German).
+30 -2
View File
@@ -16,7 +16,12 @@
//! each other so zoom keys work with and without Shift.
//! 4. **Whether a layout is Latin** is decided for the whole layout ([`Layout::is_latin`]), never
//! per key: Dvorak's `'` key on the Q position stays `'`.
//! 5. **Labels** show letters and digits by name, and a punctuation key as the character the
//! 5. **No position fallback for unnamed characters on moved layouts:** egui reports a character
//! that has no key (Dvorak's ⇧, types `<`) as the key's position, W. With Shift held on a
//! Latin layout whose letters are known to sit elsewhere than on QWERTY, such a press at a
//! letter position resolves to nothing unless that position is known to type its own letter,
//! so Ctrl+Shift+, never runs Ctrl+Shift+W.
//! 6. **Labels** show letters and digits by name, and a punctuation key as the character the
//! layout types at its position when that character has no key of its own: Ctrl+[ is `Ctrl+Ü`
//! on German and `Ctrl+Х` on Russian.
//!
@@ -92,6 +97,8 @@ pub struct Layout {
seen_latin: Option<bool>,
/// The character typed without modifiers at each punctuation position.
chars: BTreeMap<Key, char>,
/// The character, lower case, typed without Cmd, Ctrl or Alt at each letter position.
letters: BTreeMap<Key, char>,
}
/// US base keys and the key egui reports for them with Shift held.
@@ -175,6 +182,9 @@ impl Layout {
if c.is_alphabetic() {
self.seen_latin = Some(is_latin_letter(c));
}
if !c.is_control() {
self.letters.insert(physical, c.to_lowercase().next().unwrap_or(c));
}
} else if !mods.shift && !c.is_control() {
self.set_char(physical, c);
}
@@ -192,11 +202,29 @@ impl Layout {
{
return Some(Chord::new(held, position));
}
if held.shift
&& let Some(position) = input.physical.filter(|k| k.is_letter())
&& input.typed.is_none_or(|typed| typed == position)
&& !self.types_own_letter(position)
{
return None;
}
let key = input.typed.or(input.physical)?;
let drop_shift = held.shift && key.is_symbol() && input.needs_shift(key);
Some(Chord::new(if drop_shift { without_shift(held) } else { held }, key))
}
/// Rule 5: whether a press reported as the letter at its own position can be that letter.
/// Yes when the position is known to type it, or nothing is known to sit elsewhere than on
/// QWERTY; otherwise the press likely typed a character egui has no key for.
fn types_own_letter(&self, position: Key) -> bool {
let own = |key: Key, c: char| Key::from_char(c) == Some(key);
match self.letters.get(&position) {
Some(&c) => own(position, c),
None => self.letters.iter().all(|(&key, &c)| own(key, c)),
}
}
/// The chords a press can run, most specific first: [`resolve`](Self::resolve)'s chord, the
/// same press written as Shift with a US base key (`Shift+;` for `:`), `=` for `+` and `+`
/// for Shift+`=`, and on the digit row the position's digit.
@@ -235,7 +263,7 @@ impl Layout {
self.candidates(input).iter().any(|c| c.effective(platform) == want)
}
/// Rule 5: the layout's character for the punctuation key `key`, when pressing that position
/// Rule 6: the layout's character for the punctuation key `key`, when pressing that position
/// means `key` and the layout types `key`'s own character nowhere else that is known.
pub(crate) fn key_label(&self, key: Key) -> Option<String> {
let typed = self.char_at(key)?;
+28
View File
@@ -136,6 +136,34 @@ fn typing_decides_whether_the_layout_is_latin() {
assert!(reported.is_latin());
}
#[test]
fn dvorak_shift_punctuation_never_runs_its_position() {
// Dvorak's , key sits on W and types < with Shift, which egui has no key for, so egui
// reports the press as W.
let ctrl_shift_lt = at("Cmd+Shift+W", Key::W);
let mut dvorak = Layout::new();
assert_eq!(dvorak.resolve(&ctrl_shift_lt), Some(chord("Cmd+Shift+W")), "nothing known yet");
dvorak.observe(Key::S, Mods::NONE, "o");
assert_eq!(dvorak.resolve(&ctrl_shift_lt), None, "letters are known to be moved");
assert!(!dvorak.matches(&chord("Ctrl+Shift+W"), &ctrl_shift_lt, Platform::Windows));
dvorak.observe(Key::W, Mods::NONE, ",");
assert_eq!(dvorak.resolve(&ctrl_shift_lt), None);
// Without Shift, and for letters typed where they sit (Dvorak's A) or elsewhere, presses
// resolve as before.
assert_eq!(dvorak.resolve(&at("Cmd+W", Key::W)), Some(chord("Cmd+W")));
dvorak.observe(Key::A, Mods::NONE, "a");
assert_eq!(dvorak.resolve(&at("Cmd+Shift+A", Key::A)), Some(chord("Cmd+Shift+A")));
assert_eq!(dvorak.resolve(&at("Cmd+Shift+O", Key::S)), Some(chord("Cmd+Shift+O")));
// QWERTY: a position typing its own letter keeps the position fallback.
let mut qwerty = Layout::new();
qwerty.observe(Key::S, Mods::NONE, "s");
qwerty.observe(Key::W, Mods::SHIFT, "W");
assert_eq!(qwerty.resolve(&ctrl_shift_lt), Some(chord("Cmd+Shift+W")));
assert_eq!(qwerty.resolve(&at("Cmd+Shift+E", Key::E)), Some(chord("Cmd+Shift+E")));
}
#[test]
fn labels_show_the_layouts_own_punctuation() {
let mut german = Layout::new().with_char(Key::OpenBracket, 'ü').with_char(Key::Slash, '-').with_char(Key::CloseBracket, '+');