From ec646fd59c5a7e56c4500e111978529384f5525a Mon Sep 17 00:00:00 2001 From: Ilia Malanin Date: Thu, 26 Feb 2026 22:34:28 +0100 Subject: [PATCH 1/2] feat(keybind): add layout-aware matching for non-Latin keyboard layouts --- src/widget/menu/key_bind.rs | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/widget/menu/key_bind.rs b/src/widget/menu/key_bind.rs index 8b4ed227..150b3576 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -1,3 +1,4 @@ +use iced_core::keyboard::key::{Code, Physical}; use iced_core::keyboard::{Key, Modifiers}; use std::fmt; @@ -37,6 +38,7 @@ impl KeyBind { /// # Returns /// /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. + #[deprecated(note = "Use `matches_layout_aware` instead for correct non-Latin keyboard layout support")] pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool { let key_eq = match (key, &self.key) { // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way @@ -49,6 +51,94 @@ impl KeyBind { && modifiers.alt() == self.modifiers.contains(&Modifier::Alt) && modifiers.shift() == self.modifiers.contains(&Modifier::Shift) } + + /// Checks if the given key and modifiers match the `KeyBind`, with a + /// fallback to the physical key position for non-Latin keyboard layouts. + /// + /// This is the recommended replacement for [`Self::matches`], which does not + /// handle non-Latin layouts correctly. + /// + /// # Arguments + /// + /// * `modifiers` - A `Modifiers` instance representing the current active modifiers. + /// * `key` - A reference to the `Key` that is being checked. + /// * `physical_key` - An optional reference to the physical key position, + /// used as a fallback when the logical `key` does not match (e.g. on + /// Cyrillic or other non-Latin layouts). Can be `None` for keys where + /// the physical position is not relevant (e.g. `Key::Named`). + /// + /// # Returns + /// + /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. + #[allow(deprecated)] + pub fn matches_layout_aware( + &self, + modifiers: Modifiers, + key: &Key, + physical_key: Option<&Physical>, + ) -> bool { + self.matches(modifiers, key) + || physical_key + .and_then(physical_key_to_latin) + .map(|latin| self.matches(modifiers, &latin)) + .unwrap_or(false) + } +} + +/// Converts a physical key code to the corresponding US-layout Latin `Key`. +/// +/// This mapping is intentionally limited to keys that may produce different +/// characters on non-Latin keyboard layouts (letters and punctuation). Keys +/// like digits are not included because they remain the same across layouts. +/// +/// Only used as a fallback when the primary key comparison in +/// [`KeyBind::matches`] does not match. +fn physical_key_to_latin(physical_key: &Physical) -> Option { + let code = match physical_key { + Physical::Code(code) => code, + Physical::Unidentified(_) => return None, + }; + let ch = match code { + Code::KeyA => "a", + Code::KeyB => "b", + Code::KeyC => "c", + Code::KeyD => "d", + Code::KeyE => "e", + Code::KeyF => "f", + Code::KeyG => "g", + Code::KeyH => "h", + Code::KeyI => "i", + Code::KeyJ => "j", + Code::KeyK => "k", + Code::KeyL => "l", + Code::KeyM => "m", + Code::KeyN => "n", + Code::KeyO => "o", + Code::KeyP => "p", + Code::KeyQ => "q", + Code::KeyR => "r", + Code::KeyS => "s", + Code::KeyT => "t", + Code::KeyU => "u", + Code::KeyV => "v", + Code::KeyW => "w", + Code::KeyX => "x", + Code::KeyY => "y", + Code::KeyZ => "z", + Code::Minus => "-", + Code::Equal => "=", + Code::BracketLeft => "[", + Code::BracketRight => "]", + Code::Backslash => "\\", + Code::Semicolon => ";", + Code::Quote => "'", + Code::Backquote => "`", + Code::Comma => ",", + Code::Period => ".", + Code::Slash => "/", + _ => return None, + }; + Some(Key::Character(ch.into())) } impl fmt::Display for KeyBind { From ba2156d8fbee3fbba29341560621fefd2b540d1b Mon Sep 17 00:00:00 2001 From: Ilia Malanin Date: Fri, 13 Mar 2026 22:36:23 +0100 Subject: [PATCH 2/2] refactor(keybind): add physical_key fallback directly to matches --- src/widget/menu/key_bind.rs | 60 +++++++++++++------------------------ 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/widget/menu/key_bind.rs b/src/widget/menu/key_bind.rs index 150b3576..d6e40e07 100644 --- a/src/widget/menu/key_bind.rs +++ b/src/widget/menu/key_bind.rs @@ -28,35 +28,9 @@ pub struct KeyBind { } impl KeyBind { - /// Checks if the given key and modifiers match the `KeyBind`. - /// - /// # Arguments - /// - /// * `modifiers` - A `Modifiers` instance representing the current active modifiers. - /// * `key` - A reference to the `Key` that is being checked. - /// - /// # Returns - /// - /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. - #[deprecated(note = "Use `matches_layout_aware` instead for correct non-Latin keyboard layout support")] - pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool { - let key_eq = match (key, &self.key) { - // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way - (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), - (a, b) => a.eq(b), - }; - key_eq - && modifiers.logo() == self.modifiers.contains(&Modifier::Super) - && modifiers.control() == self.modifiers.contains(&Modifier::Ctrl) - && modifiers.alt() == self.modifiers.contains(&Modifier::Alt) - && modifiers.shift() == self.modifiers.contains(&Modifier::Shift) - } - - /// Checks if the given key and modifiers match the `KeyBind`, with a - /// fallback to the physical key position for non-Latin keyboard layouts. - /// - /// This is the recommended replacement for [`Self::matches`], which does not - /// handle non-Latin layouts correctly. + /// Checks if the given key and modifiers match the `KeyBind`, with an + /// optional fallback to the physical key position for non-Latin keyboard + /// layouts. /// /// # Arguments /// @@ -70,18 +44,24 @@ impl KeyBind { /// # Returns /// /// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise. - #[allow(deprecated)] - pub fn matches_layout_aware( - &self, - modifiers: Modifiers, - key: &Key, - physical_key: Option<&Physical>, - ) -> bool { - self.matches(modifiers, key) + pub fn matches(&self, modifiers: Modifiers, key: &Key, physical_key: Option<&Physical>) -> bool { + let key_eq = self.key_eq(key) || physical_key .and_then(physical_key_to_latin) - .map(|latin| self.matches(modifiers, &latin)) - .unwrap_or(false) + .is_some_and(|latin| self.key_eq(&latin)); + key_eq + && modifiers.logo() == self.modifiers.contains(&Modifier::Super) + && modifiers.control() == self.modifiers.contains(&Modifier::Ctrl) + && modifiers.alt() == self.modifiers.contains(&Modifier::Alt) + && modifiers.shift() == self.modifiers.contains(&Modifier::Shift) + } + + fn key_eq(&self, key: &Key) -> bool { + match (key, &self.key) { + // CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way + (Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b), + (a, b) => a.eq(b), + } } } @@ -152,4 +132,4 @@ impl fmt::Display for KeyBind { other => write!(f, "{:?}", other), } } -} +} \ No newline at end of file