mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 14:49:06 -04:00
Add modifier keys to egui::Key (#8127)
## Problem winit has always delivered distinct physical variants for every keyboard key — \`KeyCode::ShiftLeft\` vs \`KeyCode::ShiftRight\`, \`KeyCode::ControlLeft\`/\`ControlRight\`, \`AltLeft\`/\`AltRight\`, \`SuperLeft\`/\`SuperRight\`, plus the ISO 102nd key \`KeyCode::IntlBackslash\` (the one between LShift and Z, labelled \`<>|\` on French AZERTY and \`\\|\` on UK QWERTY). Today none of these reach egui: - Pressing Shift / Ctrl / Alt alone produces *no* \`Event::Key\` at all. \`key_from_key_code\` and \`key_from_named_key\` both return \`None\` for modifiers, so the \`if let Some(active_key)\` branch in \`on_keyboard_input\` is skipped. The collapsed \`Modifiers\` bools are the only trace of the press, and they don't distinguish left vs right. - \`KeyCode::IntlBackslash\` has no arm in \`key_from_key_code\`, so on French / UK ISO keyboards the \`<>|\` key is completely invisible to egui apps — neither \`key\` nor \`physical_key\` is ever set. ## Who hits this - Games / kiosk frontends / pincab UIs that bind \`LeftFlipper = LShift\` vs \`RightFlipper = RShift\` (or \`LeftMagna = LCtrl\` vs \`RightMagna = RCtrl\`) — currently impossible inside egui without shelling out to platform APIs (\`device_query\`, raw X11, etc.). - Anyone on an ISO keyboard who wants to capture the 102nd key in an input-binding UI. Previously discussed: context in #2977 (closed by #3649 which added \`physical_key\`, but only for keys already in \`egui::Key\`). ## Change Two small additions, no behaviour change for existing code: **\`crates/egui/src/data/key.rs\`** — new variants at the end of \`Key\`: - \`ShiftLeft\`, \`ShiftRight\`, \`ControlLeft\`, \`ControlRight\`, \`AltLeft\`, \`AltRight\`, \`SuperLeft\`, \`SuperRight\` - \`IntlBackslash\` plus their entries in \`Key::ALL\`, \`Key::from_name\`, and \`Key::name\` (the \`key_from_name\` roundtrip test at the bottom of the file still passes). **\`crates/egui-winit/src/lib.rs\`** — new arms in \`key_from_key_code\`: \`\`\`rust KeyCode::ShiftLeft => Key::ShiftLeft, KeyCode::ShiftRight => Key::ShiftRight, // ...ControlLeft/Right, AltLeft/Right, SuperLeft/Right... KeyCode::IntlBackslash => Key::IntlBackslash, \`\`\` The existing \`Modifiers\` struct is untouched — shortcut matching (\"Ctrl+C\"), \`consume_shortcut\`, etc. still see the collapsed state. The new variants are purely additive and only surface as physical \`Event::Key\` presses when someone is specifically looking for them. ## Test - Existing \`test_key_from_name\` test still passes (updated the sentinel to \`Key::IntlBackslash as usize + 1\`). - Manual smoke test: pressing left vs right Shift, Ctrl, Alt each produces an \`Event::Key { key: Key::ShiftLeft/Right/..., physical_key: Some(Key::ShiftLeft/Right/...), ... }\`; pressing the AZERTY 102nd key yields \`Key::IntlBackslash\`. Character-key behaviour and \`Modifiers\` bools are unchanged. ## Not included - **Web backend** (\`eframe_web\`): \`PhysicalKey\` isn't fully exposed there yet per the existing \`physical_key\` docs, so these new variants are only emitted on native. Happy to extend to web in a follow-up if wanted. - \`ModifiersSymmetric\` / per-side sticky state: would be a bigger API change in \`Modifiers\`. This PR stays at the minimum: forward what winit already gives us for the event path. Closes no issue directly but addresses the underlying gap noted in the thread of #2977 (scancode forwarding) for the modifier / Intl-key subset.
This commit is contained in:
@@ -1582,6 +1582,22 @@ fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
|
||||
KeyCode::F34 => Key::F34,
|
||||
KeyCode::F35 => Key::F35,
|
||||
|
||||
// Modifier keys — egui now surfaces them as distinct physical
|
||||
// variants so games / capture UIs can bind them independently.
|
||||
// The collapsed `Modifiers.shift/ctrl/alt/command` booleans still
|
||||
// track just the "any side is pressed" state for shortcut matching.
|
||||
KeyCode::ShiftLeft => Key::ShiftLeft,
|
||||
KeyCode::ShiftRight => Key::ShiftRight,
|
||||
KeyCode::ControlLeft => Key::ControlLeft,
|
||||
KeyCode::ControlRight => Key::ControlRight,
|
||||
KeyCode::AltLeft => Key::AltLeft,
|
||||
KeyCode::AltRight => Key::AltRight,
|
||||
KeyCode::SuperLeft => Key::SuperLeft,
|
||||
KeyCode::SuperRight => Key::SuperRight,
|
||||
|
||||
// ISO 102nd key — `<>|` on French AZERTY, `\|` on UK QWERTY.
|
||||
KeyCode::IntlBackslash => Key::IntlBackslash,
|
||||
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -188,6 +188,38 @@ pub enum Key {
|
||||
/// Android sends this key on Back button press.
|
||||
/// Does not work on Web.
|
||||
BrowserBack,
|
||||
|
||||
// ----------------------------------------------
|
||||
// Modifier keys (exposed as distinct left/right variants so that
|
||||
// games and input-capture UIs can bind them independently). egui's
|
||||
// `Modifiers` struct still collapses both sides for the common case
|
||||
// (e.g. "Ctrl+C"); these variants are emitted only as physical
|
||||
// `Event::Key` presses.
|
||||
/// Left Shift key.
|
||||
ShiftLeft,
|
||||
/// Right Shift key.
|
||||
ShiftRight,
|
||||
/// Left Control key.
|
||||
ControlLeft,
|
||||
/// Right Control key.
|
||||
ControlRight,
|
||||
/// Left Alt / Option key.
|
||||
AltLeft,
|
||||
/// Right Alt / AltGr / Option key.
|
||||
AltRight,
|
||||
/// Left Super / Meta / Command / Windows key.
|
||||
SuperLeft,
|
||||
/// Right Super / Meta / Command / Windows key.
|
||||
SuperRight,
|
||||
|
||||
// ----------------------------------------------
|
||||
// International keys — physical positions that only exist on
|
||||
// non-US keyboards.
|
||||
/// ISO 102nd key: physically located between the left Shift and Z
|
||||
/// on ISO layouts. On French AZERTY it produces `<>|`; on UK
|
||||
/// QWERTY a secondary `\` / `|`. Missing from US ANSI keyboards.
|
||||
IntlBackslash,
|
||||
|
||||
// When adding keys, remember to also update:
|
||||
// * crates/egui-winit/src/lib.rs
|
||||
// * Key::ALL
|
||||
@@ -314,6 +346,17 @@ impl Key {
|
||||
Self::F35,
|
||||
// Navigation keys:
|
||||
Self::BrowserBack,
|
||||
// Modifier keys (physical L/R):
|
||||
Self::ShiftLeft,
|
||||
Self::ShiftRight,
|
||||
Self::ControlLeft,
|
||||
Self::ControlRight,
|
||||
Self::AltLeft,
|
||||
Self::AltRight,
|
||||
Self::SuperLeft,
|
||||
Self::SuperRight,
|
||||
// International keys:
|
||||
Self::IntlBackslash,
|
||||
];
|
||||
|
||||
/// Converts `"A"` to `Key::A`, `Space` to `Key::Space`, etc.
|
||||
@@ -444,6 +487,17 @@ impl Key {
|
||||
|
||||
"BrowserBack" => Self::BrowserBack,
|
||||
|
||||
"ShiftLeft" => Self::ShiftLeft,
|
||||
"ShiftRight" => Self::ShiftRight,
|
||||
"ControlLeft" => Self::ControlLeft,
|
||||
"ControlRight" => Self::ControlRight,
|
||||
"AltLeft" => Self::AltLeft,
|
||||
"AltRight" => Self::AltRight,
|
||||
"SuperLeft" => Self::SuperLeft,
|
||||
"SuperRight" => Self::SuperRight,
|
||||
|
||||
"IntlBackslash" => Self::IntlBackslash,
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -599,6 +653,17 @@ impl Key {
|
||||
Self::F35 => "F35",
|
||||
|
||||
Self::BrowserBack => "BrowserBack",
|
||||
|
||||
Self::ShiftLeft => "ShiftLeft",
|
||||
Self::ShiftRight => "ShiftRight",
|
||||
Self::ControlLeft => "ControlLeft",
|
||||
Self::ControlRight => "ControlRight",
|
||||
Self::AltLeft => "AltLeft",
|
||||
Self::AltRight => "AltRight",
|
||||
Self::SuperLeft => "SuperLeft",
|
||||
Self::SuperRight => "SuperRight",
|
||||
|
||||
Self::IntlBackslash => "IntlBackslash",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -607,7 +672,7 @@ impl Key {
|
||||
fn test_key_from_name() {
|
||||
assert_eq!(
|
||||
Key::ALL.len(),
|
||||
Key::BrowserBack as usize + 1,
|
||||
Key::IntlBackslash as usize + 1,
|
||||
"Some keys are missing in Key::ALL"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user