diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 0818a587b..76ea6b000 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -483,7 +483,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ( "ui.symbolOptions", "Symbol Options…", - "", + "F8", "{name?} open Symbol Options (dialog `symbolOptions`, fields `name`, `exportType`: movieClip|graphic, `registration` 0..8). Without `name` and with a selection: New Symbol (default name the first free \"New Symbol\"/\"New Symbol N\", Export Type Movie Clip, Registration centre); OK runs symbol.new {name, exportType, registration}. With `edit: true` or `name`: edit that symbol; OK runs symbol.options {name, newName?, exportType, registration}", ), ( diff --git a/crates/ui-egui/src/panels/symbols.rs b/crates/ui-egui/src/panels/symbols.rs index 9bda11cf1..016a5e327 100644 --- a/crates/ui-egui/src/panels/symbols.rs +++ b/crates/ui-egui/src/panels/symbols.rs @@ -307,6 +307,97 @@ mod tests { assert_eq!(app.ui.dialog.as_ref().unwrap().f64("sliceLeft", 0.0), 20.0); } + /// F8 is Symbol Options… (`ui.symbolOptions`) by default, on every platform. Shift+F8 and Cmd+F8 + /// are still the Transform and Info panels' keys, and Cmd+Shift+F8 is still Edit Pattern's. No + /// other command, tool or panel has F8 by default. + #[test] + fn f8_is_symbol_options() { + use crate::shortcut_editor::{PANEL_SHORTCUTS, command_for_key, default_command_shortcut}; + assert_eq!(default_command_shortcut("ui.symbolOptions"), Some("F8")); + assert_eq!(command_for_key("F8"), Some("ui.symbolOptions")); + assert_eq!(command_for_key("Cmd+Shift+F8"), Some("object.pattern.edit")); + let panel = |key: &str| PANEL_SHORTCUTS.iter().find(|(_, sc)| *sc == key).map(|(p, _)| *p); + assert_eq!((panel("Shift+F8"), panel("Cmd+F8"), panel("F8")), (Some("transform"), Some("info"), None)); + let chord = crate::shortcuts::parse("F8").expect("parses"); + assert_eq!((chord.modifiers, chord.logical_key), (egui::Modifiers::NONE, egui::Key::F8)); + assert_eq!(crate::menus::pretty_shortcut("F8"), "F8"); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + let listed = app.run("shortcuts.list", json!({"query": "ui.symbolOptions"})).unwrap(); + let rows: Vec<_> = listed.as_array().expect("rows").iter().map(|r| (r["label"].clone(), r["default"].clone())).collect(); + assert_eq!(rows, [(json!("Symbol Options…"), json!("F8"))], "Keyboard Shortcuts lists it"); + assert_eq!(app.run("shortcuts.conflicts", json!({})).unwrap(), json!([]), "no other default has the key"); + } + + /// F8, pressed through the shortcut handler, opens Symbol Options for a new symbol from the + /// selection, as the panel menu's New Symbol… does, and no symbol is made until OK. OK makes the + /// symbol and puts an instance of it in the art's place, and Cancel makes none. With nothing + /// selected, neither F8 nor New Symbol… opens a dialog or makes a symbol, and after F8 the + /// status bar says that nothing is selected. + #[test] + fn f8_opens_symbol_options_for_a_new_symbol_as_new_symbol_does() { + fn press(app: &mut VectorcraftApp) { + let ctx = egui::Context::default(); + let ev = egui::Event::Key { key: egui::Key::F8, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }; + let mut out = ctx.run_ui(egui::RawInput { events: vec![ev], ..Default::default() }, |ui| crate::shortcuts::handle(app, ui.ctx())); + out.textures_delta.clear(); + } + /// A document with one selected rectangle. + fn one_rect() -> VectorcraftApp { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap(); + app + } + /// Click the panel menu's New Symbol… item. + fn click_new_symbol(app: &mut VectorcraftApp) { + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let labels = menu_frame(&ctx, app, vec![]); + let item = labels.iter().find(|(t, _)| t.trim() == "New Symbol…").map(|(_, r)| r.center()).expect("New Symbol… in the menu"); + menu_frame(&ctx, app, vec![egui::Event::PointerMoved(item), button(item, true)]); + menu_frame(&ctx, app, vec![button(item, false)]); + } + let dialog = |app: &VectorcraftApp| app.ui.dialog.as_ref().map(|d| (d.kind.clone(), d.fields.clone())); + let mut keyed = one_rect(); + press(&mut keyed); + let mut clicked = one_rect(); + click_new_symbol(&mut clicked); + let opened = dialog(&keyed).expect("the key opens a dialog"); + assert_eq!( + (opened.0.as_str(), opened.1.get("__edit"), opened.1.get("name")), + ("symbolOptions", Some(&json!(false)), Some(&json!("New Symbol"))) + ); + assert_eq!(dialog(&clicked), Some(opened), "the key opens what New Symbol… opens"); + assert!(symbols(&mut keyed).is_empty(), "no symbol until OK"); + for app in [&mut keyed, &mut clicked] { + crate::dialogs::confirm(app).unwrap(); + } + assert_eq!(symbols(&mut keyed), ["New Symbol"]); + assert_eq!(symbols(&mut clicked), ["New Symbol"]); + let doc = &keyed.session.active().unwrap().doc; + let art = doc.layers[0].children().unwrap(); + assert_eq!(art.len(), 1); + assert!(matches!(&art[0].kind, NodeKind::SymbolInstance { symbol, .. } if symbol == "New Symbol"), "the rectangle became an instance"); + // Cancel makes no symbol. + keyed.run("shape.rectangle", json!({"x": 60, "y": 10, "width": 30, "height": 20})).unwrap(); + press(&mut keyed); + assert!(keyed.ui.dialog.is_some()); + crate::dialogs::cancel(&mut keyed); + assert!(keyed.ui.dialog.is_none()); + assert_eq!(symbols(&mut keyed), ["New Symbol"]); + // With nothing selected, neither the key nor New Symbol… opens a dialog. + for app in [&mut keyed, &mut clicked] { + app.run("select.none", json!({})).unwrap(); + } + press(&mut keyed); + click_new_symbol(&mut clicked); + for app in [&mut keyed, &mut clicked] { + assert!(app.ui.dialog.is_none()); + assert_eq!(symbols(app), ["New Symbol"]); + } + assert_eq!(keyed.ui.status, "nothing selected"); + } + #[test] fn an_imported_symbols_thumbnail_frames_its_whole_art() { // The symbol of the SVG import tests: an orange 20 × 20 square from (0, 0) with a white dot. diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 7c2d0f9a2..3978a27ab 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -155,7 +155,8 @@ dialog open); without `name` it names a new style made from the selection (`grap (`graphicStyle.merge`). Deleting styles from the panel asks first with a `confirm` dialog (`graphicStyle.delete`). Symbols: `ui.symbolOptions` opens the `symbolOptions` dialog (field `name`) that the Symbols panel's New Symbol asks -first; `ui.dialog.confirm` makes the symbol from the selection with that name (`symbol.new`). +first; `ui.dialog.confirm` makes the symbol from the selection with that name (`symbol.new`). F8 runs +`ui.symbolOptions` without params. Gradient panel: double-clicking a stop on the panel's slider opens the same `gradientStop` popover, with a `screen` field (`[x, y]`, screen points) in place of `x`/`y`. The panel's stop eyedropper selects the Eyedropper tool with the diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index c4cb7dc05..56538c2c5 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -525,6 +525,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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. - Cmd/Ctrl+Alt+Q (`type.resetTracking`) sets the tracking of the selected type, or of the text the Type tool has selected, to 0 and its kerning to Auto (0 in the Character panel), in one undo step; at a caret it resets the kerning of the character before it. - Cmd/Ctrl+Alt+L (`ui.newLayer`) opens Layer Options for a new layer, as an Alt-click on the Layers panel's New Layer button does, on the key 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 a layer while opening the New Layer dialog box". OK adds the layer above the current layer, and Cancel adds none. Cmd/Ctrl+L still adds a layer without the dialog. + - F8 (`ui.symbolOptions`) opens Symbol Options for a new symbol made from the selection, as the Symbols panel's New Symbol button does, on the key 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 "Create new symbol". OK makes the symbol and puts an instance of it in place of the art, and Cancel makes none. With nothing selected, F8 opens no dialog, and the status bar says that nothing is selected. Shift+F8 and Cmd/Ctrl+F8 still show the Transform and Info panels. - Cmd/Ctrl+Shift+X (`type.resetHorizontalScale`) sets the horizontal scale of the selected type, or of the text the Type tool has selected, to 100% as the Character panel shows it (the object's own stretch included), in one undo step; at a caret it leaves the text as it is. - Cmd/Ctrl+Alt+N (`file.newWithoutDialog`) makes a document without opening the New Document dialog, on the key 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 "Create a document without opening the New Document dialog box". The page does not say which settings the document gets. VectorCraft gives it the settings the dialog opens on (the first Recent entry, or the Print category's Letter while Recent is empty) and the next untitled name. - Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`). diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b0543a46c..88b931236 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -197,7 +197,7 @@ Cmd/Ctrl+/ (`appearance.addFill`) and Cmd/Ctrl+Alt+/ (`appearance.addStroke`) ad selected objects, as the Appearance panel's Add New Fill and Add New Stroke buttons do. ~110 distinct modified shortcuts on menu commands plus single-key tool shortcuts for the default toolbar -(`crates/tools/src/catalog.rs`), a shortcut editor with chords, and saved sets. Cmd/Ctrl+Alt+L (`ui.newLayer`) opens Layer Options for a new layer, as an Alt-click on the Layers panel's New Layer button does, and Cmd/Ctrl+L still adds a layer without the dialog. Not yet diffed item by item +(`crates/tools/src/catalog.rs`), a shortcut editor with chords, and saved sets. Cmd/Ctrl+Alt+L (`ui.newLayer`) opens Layer Options for a new layer, as an Alt-click on the Layers panel's New Layer button does, and Cmd/Ctrl+L still adds a layer without the dialog. F8 (`ui.symbolOptions`) opens Symbol Options for a new symbol made from the selection, as the Symbols panel's New Symbol button does. Not yet diffed item by item against Illustrator's published default shortcut list; estimated ~70% of it. Remaining: the panel shortcuts, the type shortcuts (tracking/kerning/leading/baseline steps with Alt/Shift), and selection shortcuts in the Layers panel. The shortcut editor also lists the commands that are in no menu but run without parameters (Edit › Duplicate,