diff --git a/crates/keymap/src/egui.rs b/crates/keymap/src/egui.rs index b85b60e0..95a02114 100644 --- a/crates/keymap/src/egui.rs +++ b/crates/keymap/src/egui.rs @@ -199,6 +199,9 @@ pub struct Dispatcher { /// web build sends them. text_first: bool, layout: Layout, + /// Whether right Alt is held. egui-winit reports AltGr with neither Ctrl nor Alt, so its + /// presses would otherwise teach the layout as plain ones. + right_alt: bool, } /// eframe's web build sends a press's Text event first when no text field has focus. @@ -206,7 +209,7 @@ const TEXT_FIRST: bool = cfg!(target_arch = "wasm32"); impl Default for Dispatcher { fn default() -> Dispatcher { - Dispatcher { last: None, repeat: false, clipboard: false, text_first: TEXT_FIRST, layout: Layout::new() } + Dispatcher { last: None, repeat: false, clipboard: false, text_first: TEXT_FIRST, layout: Layout::new(), right_alt: false } } } @@ -350,6 +353,14 @@ impl Dispatcher { /// Teaches the layout what this frame's presses typed. fn learn(&mut self, events: &[Event], text_of: &[Option], platform: Platform) { for (event, text) in events.iter().zip(text_of) { + match event { + Event::Key { physical_key: Some(EguiKey::AltRight), pressed, .. } => self.right_alt = *pressed, + Event::WindowFocused(false) => self.right_alt = false, + _ => {} + } + if self.right_alt { + continue; + } let Some(Event::Text(text)) = text.and_then(|j| events.get(j)) else { continue }; if let Some(KeyInput { mods, physical: Some(physical), .. }) = KeyInput::from_egui(event, platform) { self.layout.observe(physical, mods, text); diff --git a/crates/keymap/src/layout.rs b/crates/keymap/src/layout.rs index ce486a5d..74f2c2f5 100644 --- a/crates/keymap/src/layout.rs +++ b/crates/keymap/src/layout.rs @@ -179,7 +179,8 @@ impl Layout { return; } if physical.is_letter() { - if c.is_alphabetic() { + // ª, µ and º are Latin-1 symbols on Latin layouts, no sign of another script. + if c.is_alphabetic() && !matches!(c, 'ª' | 'µ' | 'º') { self.seen_latin = Some(is_latin_letter(c)); } if !c.is_control() { diff --git a/crates/keymap/tests/egui.rs b/crates/keymap/tests/egui.rs index c7332b91..85f4ea48 100644 --- a/crates/keymap/tests/egui.rs +++ b/crates/keymap/tests/egui.rs @@ -1,7 +1,7 @@ #![cfg(feature = "egui")] use craft_keymap::egui::{Dispatcher, Hit, matches, platform}; -use craft_keymap::{Active, Bindable, Binding, Chord, CommandRef, Key, Keymap, Kind, Layout, Mods, Platform, Scope, Scopes, Trigger}; +use craft_keymap::{Active, Bindable, Binding, Chord, CommandRef, Key, KeyInput, Keymap, Kind, Layout, Mods, Platform, Scope, Scopes, Trigger}; use egui::{Event, InputState, Key as EguiKey, Modifiers}; fn chord(text: &str) -> Chord { @@ -482,6 +482,26 @@ fn the_dispatcher_reads_presses_by_the_layout() { let (ran, _) = run(&mut d, &km, &Active::NONE, frame(2.0, ctrl_shift, vec![at(EguiKey::Slash, EguiKey::Num7, ctrl_shift)])); assert_eq!(ran, ["edit.comment"]); + // German on egui-winit: AltGr+M types µ and AltGr+] types ~ with no modifiers reported, so + // presses while right Alt is held teach nothing and Ctrl+Z stays Ctrl+Z on the Y position. + let mut d = Dispatcher::new(); + let typed = vec![at(EguiKey::Plus, EguiKey::CloseBracket, Modifiers::NONE), Event::Text("+".into())]; + run(&mut d, &km, &Active::NONE, frame(1.0, Modifiers::NONE, typed)); + let right_alt = + |pressed| Event::Key { key: EguiKey::AltRight, physical_key: Some(EguiKey::AltRight), pressed, repeat: false, modifiers: Modifiers::NONE }; + run(&mut d, &km, &Active::NONE, frame(2.0, Modifiers::NONE, vec![right_alt(true)])); + let alt_graph = vec![ + at(EguiKey::M, EguiKey::M, Modifiers::NONE), + Event::Text("µ".into()), + at(EguiKey::CloseBracket, EguiKey::CloseBracket, Modifiers::NONE), + Event::Text("~".into()), + right_alt(false), + ]; + run(&mut d, &km, &Active::NONE, frame(3.0, Modifiers::NONE, alt_graph)); + assert!(d.layout().is_latin()); + assert_eq!(d.layout().char_at(Key::CloseBracket), Some('+')); + assert_eq!(d.layout().resolve(&KeyInput::new(Mods::CMD).with_typed(Key::Z).with_physical(Key::Y)), Some(chord("Cmd+Z"))); + // A layout the platform reports applies from the first press. let mut d = Dispatcher::new().with_layout(Layout::new().with_latin(false)); let (ran, _) = run(&mut d, &km, &Active::NONE, frame(1.0, ctrl_shift, vec![at(EguiKey::CloseBracket, EguiKey::D, ctrl_shift)])); diff --git a/crates/keymap/tests/layout.rs b/crates/keymap/tests/layout.rs index 1786f89a..9d4f440c 100644 --- a/crates/keymap/tests/layout.rs +++ b/crates/keymap/tests/layout.rs @@ -127,6 +127,8 @@ fn typing_decides_whether_the_layout_is_latin() { assert!(!layout.is_latin(), "a command chord teaches nothing"); layout.observe(Key::S, Mods::NONE, "š"); assert!(layout.is_latin(), "Latin letters beyond ASCII count as Latin"); + layout.observe(Key::M, Mods::NONE, "µ"); + assert!(layout.is_latin(), "Latin-1 symbols that count as letters say nothing"); // What the platform reports wins. let mut reported = Layout::new().with_latin(false);