mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 02:07:09 -06:00
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:
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user