1
0
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:
Sylvain
2026-06-10 09:53:48 +02:00
committed by GitHub
parent 858c8fd99f
commit 0b920aae42
2 changed files with 82 additions and 1 deletions

View File

@@ -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;
}

View File

@@ -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"
);