Merge pull request #3382 from dotCooCoo/fix/altgr-typing-runs-no-command

Type: a character typed with AltGr while editing text runs no command
This commit is contained in:
BFlat
2026-10-21 23:50:18 +07:00
committed by GitHub
2 changed files with 105 additions and 0 deletions
+104
View File
@@ -150,6 +150,25 @@ fn unclip_chords(i: &mut egui::InputState) {
}
}
/// Drop each key press made with Ctrl and Alt held that is followed by the text it typed. Firefox
/// on Windows reports AltGr as Ctrl and Alt held, so AltGr+L on a Polish layout arrives as a
/// Ctrl+Alt+L press and then the text "ł", which the Type tool types. Without the press no command
/// chord runs for it: neither Cmd+Alt+L nor Cmd+L, which egui matches with the extra Alt held.
fn drop_typing_presses(i: &mut egui::InputState) {
let next = i.events.iter().skip(1).map(Some).chain([None]);
let typed: Vec<bool> = i
.events
.iter()
.zip(next)
.map(|(e, next)| {
matches!(e, egui::Event::Key { pressed: true, modifiers, .. } if modifiers.ctrl && modifiers.alt)
&& matches!(next, Some(egui::Event::Text(t)) if !t.is_empty())
})
.collect();
let mut typed = typed.into_iter();
i.events.retain(|_| !typed.next().unwrap_or(false));
}
/// Keyboard pastes of something other than text. egui swallows a paste chord's key press and
/// sends a Paste event only when the clipboard holds text, so a paste key released without its
/// press (or a Paste event) reported was a paste of a bitmap or a PDF.
@@ -378,6 +397,10 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
if ctx.input(|i| i.key_pressed(Key::Tab)) {
ctx.memory_mut(|m| m.move_focus(egui::FocusDirection::None));
}
// While the Type tool edits text, a character typed with AltGr runs no command chord.
if app.session.tool_wants_text() {
ctx.input_mut(drop_typing_presses);
}
// Type tool editing: text, IME composition and editing keys go to the tool.
if app.session.tool_wants_text() {
type_text(app, ctx);
@@ -775,6 +798,87 @@ mod tests {
assert!(!is_shortcut_chord(M::CTRL | M::ALT), "AltGr types @ on a German keyboard");
}
/// A press of `key` with Ctrl and Alt held (as Firefox on Windows reports AltGr), followed by
/// the text it typed when `typed` is given.
fn ctrl_alt_press(key: Key, typed: Option<&str>) -> Vec<egui::Event> {
let m = Modifiers::CTRL | Modifiers::ALT | Modifiers::COMMAND;
let mut v =
vec![egui::Event::ModifiersChanged(m), egui::Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers: m }];
v.extend(typed.map(|t| egui::Event::Text(t.into())));
v
}
/// The text of the type object `id`, and the document's layer count.
fn text_and_layers(app: &mut VectorcraftApp, id: u64) -> (String, usize) {
let r = app.session.execute("text.getRange", &json!({"id": id})).unwrap();
let text = r["runs"].as_array().unwrap().iter().filter_map(|run| run["text"].as_str()).collect();
(text, app.session.active().unwrap().doc.layers.len())
}
/// While the Type tool edits text, a character typed with AltGr (Ctrl+Alt in Firefox on
/// Windows) is typed and runs no command: not New Layer Options… (Cmd+Alt+L), Reset Tracking
/// (Cmd+Alt+Q), Export for Screens (Cmd+Alt+E), Select All on Active Artboard (Cmd+Alt+A) or
/// Undo (Cmd+Z).
#[test]
fn altgr_characters_typed_in_text_run_no_command() {
let mut wrong = vec![];
for (key, typed) in [(Key::L, "ł"), (Key::Q, "@"), (Key::E, "€"), (Key::A, "ą"), (Key::Z, "ż")] {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
app.session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap();
let id = app.session.execute("text.create", &json!({"x": 20, "y": 50, "text": "Hi"})).unwrap()["id"].as_u64().unwrap();
app.session.execute("text.setRangeStyle", &json!({"id": id, "tracking": 200, "kerning": -40})).unwrap();
app.select_tool("type");
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
app.session.set_tool_option("select", &json!({"start": 2, "end": 2}));
assert!(app.session.tool_wants_text());
let layers = text_and_layers(&mut app, id).1;
let selected = |app: &VectorcraftApp| app.session.active().unwrap().selection.objects.len();
let before = selected(&app);
frame(&mut app, ctrl_alt_press(key, Some(typed)));
let r = app.session.execute("text.getRange", &json!({"id": id})).unwrap();
let spaced =
r["runs"].as_array().unwrap().iter().all(|run| run["style"]["tracking"] == json!(200.0) && run["style"]["kerning"] == json!(-40.0));
let got = (text_and_layers(&mut app, id), spaced, selected(&app) == before, app.ui.dialog.is_none(), app.session.tool_wants_text());
if got != ((format!("Hi{typed}"), layers), true, true, true, true) {
wrong.push(format!("{key:?}: (text, layers), tracking and kerning kept, selection kept, no dialog, still editing = {got:?}"));
}
}
assert!(wrong.is_empty(), "{wrong:#?}");
}
/// Ctrl and Ctrl+Alt chords that type no text still run their commands while the Type tool
/// edits, and with the Selection tool a Ctrl+Alt press runs its chord as before. Ctrl+L is New
/// Layer, and Ctrl+Alt+L is New Layer Options…, which opens Layer Options for a new layer.
#[test]
fn ctrl_alt_chords_without_typed_text_still_run() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
let id = app.session.execute("text.create", &json!({"x": 20, "y": 50, "text": "Hi"})).unwrap()["id"].as_u64().unwrap();
app.select_tool("type");
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
assert!(app.session.tool_wants_text());
let layers = text_and_layers(&mut app, id).1;
let new_layer_options =
|app: &VectorcraftApp| app.ui.dialog.as_ref().is_some_and(|d| d.kind == "layerOptions" && d.fields.get("mode") == Some(&json!("new")));
let m = Modifiers::COMMAND | Modifiers::CTRL;
let ctrl_l = egui::Event::Key { key: Key::L, physical_key: Some(Key::L), pressed: true, repeat: false, modifiers: m };
frame(&mut app, vec![egui::Event::ModifiersChanged(m), ctrl_l]);
assert_eq!(text_and_layers(&mut app, id), ("Hi".into(), layers + 1), "Ctrl+L while editing: New Layer");
assert!(app.ui.dialog.is_none(), "Ctrl+L opens no dialog");
app.select_tool("type");
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
assert!(app.session.tool_wants_text());
frame(&mut app, ctrl_alt_press(Key::L, None));
assert!(new_layer_options(&app), "Ctrl+Alt+L with no text typed while editing: New Layer Options…");
assert_eq!(text_and_layers(&mut app, id), ("Hi".into(), layers + 1), "no layer is added until OK");
crate::dialogs::cancel(&mut app);
app.select_tool("selection");
frame(&mut app, ctrl_alt_press(Key::L, Some("ł")));
assert!(new_layer_options(&app), "Selection tool: Ctrl+Alt+L runs New Layer Options… as before");
assert_eq!(text_and_layers(&mut app, id), ("Hi".into(), layers + 1), "no layer is added until OK");
}
/// #2094: Cmd+Shift+O is Type › Create Outlines (not File › Open's Cmd+O, not the Artboard
/// tool's Shift+O), with the Selection tool or while the Type tool edits the text.
#[test]
+1
View File
@@ -518,6 +518,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Shift+Tab (`window.panels`) shows and hides the dock and the floating panels and leaves the toolbar and the Control bar as they are; hiding the panels with Tab or Shift+Tab also closes a panel popped out of the icon column.
- The second Tab (`window.dock`) shows again only the bars the first Tab hid, after a restart too: a Control bar that was off stays off, a toolbar hidden with Window › Tools stays hidden, and panels shown with Shift+Tab in between stay shown.
- Cmd/Ctrl+A follows the keyboard: in a panel, dialog or search field it selects the field's text, while the Type tool edits it selects all of that text (also `select.all` over MCP), and only otherwise every object. On macOS the menu bar's Select All, Cut, Copy and Paste (keys or clicks) act on a focused field too, and elsewhere the in-window menus' do: the field that had the keyboard when the menu opened gets them, with its selection, and the keyboard back (#516). Not yet: Select All across threaded frames (one frame's text is selected).
- While the Type tool edits text, a character typed with AltGr is typed and runs no command. Firefox on Windows reports AltGr as Ctrl and Alt held, so in the web build AltGr+L (ł on a Polish layout) does not also open Layer Options for a new layer, and AltGr+Q (@ on a German layout) does not also run Reset Tracking and Kerning. A Ctrl+Alt chord that types no character runs its command.
- With the Artboard tool, Cmd/Ctrl+A after a click on a row of the Artboards panel selects every artboard, as the macOS menu bar's Select All does then; the active artboard stays active, and the art's selection does not change. After a press outside the panel's list, Cmd/Ctrl+A selects every object again. It used to select every object after a click in the panel too. Public reference: [Select artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/select-artboards.html).
- Cmd/Ctrl+/ (`appearance.addFill`) and Cmd/Ctrl+Alt+/ (`appearance.addStroke`) add a new fill and a new stroke to the selected objects, as the Appearance panel's Add New Fill and Add New Stroke buttons do, on the keys the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists for "Add new fill" and "Add new stroke". Keyboard Shortcuts lists both under Window › Appearance.
- Cmd/Ctrl+Alt+Shift+. and , (`type.size.increaseByFive`, `type.size.decreaseByFive`) step the font size of the selected type, or of the text the Type tool has selected, by five times the Size/Leading increment (Preferences › Type); Cmd/Ctrl+Shift+. and , still step it by one increment.