Merge pull request #3134 from sopvop/fix_text_input_cua

Fix TextInput copy/paste shortcuts for non latin keyboard layouts
This commit is contained in:
Héctor 2025-12-02 04:13:09 +01:00 committed by GitHub
commit e95f5248cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 103 additions and 20 deletions

View file

@ -30,6 +30,91 @@ impl Key {
Self::Unidentified => Key::Unidentified, Self::Unidentified => Key::Unidentified,
} }
} }
/// Tries to convert this logical [`Key`] into its latin character, using the
/// [`Physical`] key provided for translation if it isn't already in latin.
///
/// Returns `None` if no latin variant could be found.
///
/// ```
/// use iced_core::keyboard::key::{Key, Named, Physical, Code};
///
/// // Latin c
/// assert_eq!(
/// Key::Character("c".into()).to_latin(Physical::Code(Code::KeyC)),
/// Some('c'),
/// );
///
/// // Cyrillic с
/// assert_eq!(
/// Key::Character("с".into()).to_latin(Physical::Code(Code::KeyC)),
/// Some('c'),
/// );
///
/// // Arrow Left
/// assert_eq!(
/// Key::Named(Named::ArrowLeft).to_latin(Physical::Code(Code::ArrowLeft)),
/// None,
/// );
/// ```
pub fn to_latin(&self, physical_key: Physical) -> Option<char> {
let Self::Character(s) = self else {
return None;
};
let mut chars = s.chars();
let c = chars.next()?;
if chars.next().is_none() && c <= '\u{ff}' {
return Some(c);
}
let Physical::Code(code) = physical_key else {
return None;
};
let latin = 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::Digit0 => '0',
Code::Digit1 => '1',
Code::Digit2 => '2',
Code::Digit3 => '3',
Code::Digit4 => '4',
Code::Digit5 => '5',
Code::Digit6 => '6',
Code::Digit7 => '7',
Code::Digit8 => '8',
Code::Digit9 => '9',
_ => return None,
};
Some(latin)
}
} }
impl From<Named> for Key { impl From<Named> for Key {

View file

@ -1160,6 +1160,10 @@ pub struct KeyPress {
/// ///
/// You should use this key for any single key bindings (e.g. motions). /// You should use this key for any single key bindings (e.g. motions).
pub modified_key: keyboard::Key, pub modified_key: keyboard::Key,
/// The physical key pressed.
///
/// You should use this key for layout-independent bindings.
pub physical_key: keyboard::key::Physical,
/// The state of the keyboard modifiers. /// The state of the keyboard modifiers.
pub modifiers: keyboard::Modifiers, pub modifiers: keyboard::Modifiers,
/// The text produced by the key press. /// The text produced by the key press.
@ -1174,6 +1178,7 @@ impl<Message> Binding<Message> {
let KeyPress { let KeyPress {
key, key,
modified_key, modified_key,
physical_key,
modifiers, modifiers,
text, text,
status, status,
@ -1183,21 +1188,13 @@ impl<Message> Binding<Message> {
return None; return None;
} }
let combination = match key.as_ref() { let combination = match key.to_latin(physical_key) {
keyboard::Key::Character("c") if modifiers.command() => { Some('c') if modifiers.command() => Some(Self::Copy),
Some(Self::Copy) Some('x') if modifiers.command() => Some(Self::Cut),
} Some('v') if modifiers.command() && !modifiers.alt() => {
keyboard::Key::Character("x") if modifiers.command() => {
Some(Self::Cut)
}
keyboard::Key::Character("v")
if modifiers.command() && !modifiers.alt() =>
{
Some(Self::Paste) Some(Self::Paste)
} }
keyboard::Key::Character("a") if modifiers.command() => { Some('a') if modifiers.command() => Some(Self::SelectAll),
Some(Self::SelectAll)
}
_ => None, _ => None,
}; };
@ -1359,6 +1356,7 @@ impl<Message> Update<Message> {
Event::Keyboard(keyboard::Event::KeyPressed { Event::Keyboard(keyboard::Event::KeyPressed {
key, key,
modified_key, modified_key,
physical_key,
modifiers, modifiers,
text, text,
.. ..
@ -1374,6 +1372,7 @@ impl<Message> Update<Message> {
let key_press = KeyPress { let key_press = KeyPress {
key: key.clone(), key: key.clone(),
modified_key: modified_key.clone(), modified_key: modified_key.clone(),
physical_key: *physical_key,
modifiers: *modifiers, modifiers: *modifiers,
text: text.clone(), text: text.clone(),
status, status,

View file

@ -902,6 +902,7 @@ where
key, key,
text, text,
modified_key, modified_key,
physical_key,
.. ..
}) => { }) => {
let state = state::<Renderer>(tree); let state = state::<Renderer>(tree);
@ -909,8 +910,8 @@ where
if let Some(focus) = &mut state.is_focused { if let Some(focus) = &mut state.is_focused {
let modifiers = state.keyboard_modifiers; let modifiers = state.keyboard_modifiers;
match key.as_ref() { match key.to_latin(*physical_key) {
keyboard::Key::Character("c") Some('c')
if state.keyboard_modifiers.command() if state.keyboard_modifiers.command()
&& !self.is_secure => && !self.is_secure =>
{ {
@ -926,7 +927,7 @@ where
shell.capture_event(); shell.capture_event();
return; return;
} }
keyboard::Key::Character("x") Some('x')
if state.keyboard_modifiers.command() if state.keyboard_modifiers.command()
&& !self.is_secure => && !self.is_secure =>
{ {
@ -955,7 +956,7 @@ where
update_cache(state, &self.value); update_cache(state, &self.value);
return; return;
} }
keyboard::Key::Character("v") Some('v')
if state.keyboard_modifiers.command() if state.keyboard_modifiers.command()
&& !state.keyboard_modifiers.alt() => && !state.keyboard_modifiers.alt() =>
{ {
@ -994,9 +995,7 @@ where
update_cache(state, &self.value); update_cache(state, &self.value);
return; return;
} }
keyboard::Key::Character("a") Some('a') if state.keyboard_modifiers.command() => {
if state.keyboard_modifiers.command() =>
{
let cursor_before = state.cursor; let cursor_before = state.cursor;
state.cursor.select_all(&self.value); state.cursor.select_all(&self.value);