From 316d28407f622141924606697af2b54fe1cd3646 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 29 Oct 2026 23:29:53 +0700 Subject: [PATCH] Window: Cmd+F6 and Cmd+Shift+F6 go to the next and previous document Squashed from #4377 onto main. --- crates/ui-egui/src/chrome.rs | 2 +- crates/ui-egui/src/chrome/tabs.rs | 50 +++++++--- crates/ui-egui/src/chrome/tests.rs | 121 ++++++++++++++++++++++++ crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fi.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ka.tsv | 2 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/tr.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/menus/run.rs | 2 + crates/ui-egui/src/menus/state.rs | 1 + crates/ui-egui/src/menus/ui_commands.rs | 12 +++ docs/mcp.md | 5 + docs/target-app-parity.md | 1 + 32 files changed, 229 insertions(+), 13 deletions(-) diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 2000aaacb..b453cc685 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -29,7 +29,7 @@ pub use status_bar::status_bar; use status_bar::{StatusReadoutWidths, status_readouts}; #[cfg(test)] use tabs::{CLOSE_BEFORE_TITLE, first_tab_shown, tab_title}; -pub use tabs::{doc_tabs, isolation_bar}; +pub use tabs::{doc_tabs, isolation_bar, step_document}; /// The application bar's height, in points (compact, as in PhotoCraft). pub const APP_BAR_HEIGHT: f32 = 32.0; diff --git a/crates/ui-egui/src/chrome/tabs.rs b/crates/ui-egui/src/chrome/tabs.rs index 69c6a9608..06d2964a7 100644 --- a/crates/ui-egui/src/chrome/tabs.rs +++ b/crates/ui-egui/src/chrome/tabs.rs @@ -114,21 +114,47 @@ pub fn doc_tabs(app: &mut VectorcraftApp, ui: &mut Ui) { app.status(e); } } else if let Some(i) = activate { - app.ui.home = None; - let changed = app.session.active_index() != Some(i); - if app.session.set_active(i) - && changed - && app.session.tool_id() == "artboard" - && let Some(n) = app.session.active().map(|d| d.doc.artboards.len()) - { - // A document switch recreates the tool; restore the displayed board as its target. - // Clicking the current tab keeps its existing multiselection and tool options. - let selected = crate::panels::artboards::selected(app, n); - crate::panels::artboards::select(app, selected); - } + show_document(app, i); } } +/// Makes document `i` the current one, as a click on its tab does: the Home screen closes, and +/// with the Artboard tool the artboard the document shows becomes the tool's target. +fn show_document(app: &mut VectorcraftApp, i: usize) { + app.ui.home = None; + let changed = app.session.active_index() != Some(i); + if app.session.set_active(i) + && changed + && app.session.tool_id() == "artboard" + && let Some(n) = app.session.active().map(|d| d.doc.artboards.len()) + { + // A document switch recreates the tool; restore the displayed board as its target. + // Clicking the current tab keeps its existing multiselection and tool options. + let selected = crate::panels::artboards::selected(app, n); + crate::panels::artboards::select(app, selected); + } +} + +/// `window.nextDocument` (`forward`) and `window.previousDocument`: makes the open document after +/// the current one in tab order (before it) the current one, as a click on its tab does. After the +/// last document comes the first, and before the first document comes the last. On the Home +/// screen, where no tab is the current one, the next document is the first and the previous +/// document is the last. Returns `{index}`. +pub fn step_document(app: &mut VectorcraftApp, forward: bool) -> Result { + let n = app.session.documents().len(); + if n == 0 { + return Err("no document open".into()); + } + let i = match (app.session.active_index().filter(|_| app.ui.home.is_none()), forward) { + (Some(a), true) => (a + 1) % n, + (Some(a), false) => (a + n - 1) % n, + (None, true) => 0, + (None, false) => n - 1, + }; + show_document(app, i); + Ok(json!({ "index": i })) +} + /// The isolation mode breadcrumb bar. It is drawn over the top of `area` (the canvas), after the /// canvas, so entering isolation mode does not change the canvas's size (#3126). pub fn isolation_bar(app: &mut VectorcraftApp, ui: &mut Ui, area: egui::Rect) { diff --git a/crates/ui-egui/src/chrome/tests.rs b/crates/ui-egui/src/chrome/tests.rs index 6d8611190..929034a72 100644 --- a/crates/ui-egui/src/chrome/tests.rs +++ b/crates/ui-egui/src/chrome/tests.rs @@ -1294,6 +1294,127 @@ fn artboard_document_tab_roundtrip_preserves_other_tools() { } } +/// Press F6 with `modifiers` through the shortcut handler. +fn press_f6(app: &mut crate::VectorcraftApp, modifiers: egui::Modifiers) { + let ctx = egui::Context::default(); + let ev = egui::Event::Key { key: egui::Key::F6, physical_key: None, pressed: true, repeat: false, modifiers }; + let mut out = ctx.run_ui(egui::RawInput { events: vec![ev], ..Default::default() }, |ui| crate::shortcuts::handle(app, ui.ctx())); + out.textures_delta.clear(); +} + +/// Cmd+F6 is Next Document and Cmd+Shift+F6 is Previous Document by default: Ctrl+F6 and +/// Ctrl+Shift+F6 on Windows and Linux, Command+F6 and Shift+Command+F6 on macOS. Keyboard +/// Shortcuts lists both under Window. F6 and Shift+F6 are still the Color and Appearance panels' +/// keys, and no other command, tool or panel has either key by default. +#[test] +fn cmd_f6_and_cmd_shift_f6_are_next_and_previous_document() { + use crate::shortcut_editor::{command_for_key, default_command_shortcut, panel_shortcut}; + assert_eq!(default_command_shortcut("window.nextDocument"), Some("Cmd+F6")); + assert_eq!(default_command_shortcut("window.previousDocument"), Some("Cmd+Shift+F6")); + assert_eq!(command_for_key("Cmd+F6"), Some("window.nextDocument")); + assert_eq!(command_for_key("Cmd+Shift+F6"), Some("window.previousDocument")); + assert_eq!((panel_shortcut("color"), panel_shortcut("appearance")), (Some("F6"), Some("Shift+F6"))); + let chord = crate::shortcuts::parse("Cmd+Shift+F6").expect("parses"); + assert_eq!((chord.modifiers, chord.logical_key), (egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, egui::Key::F6)); + let shown = (crate::menus::pretty_shortcut("Cmd+F6"), crate::menus::pretty_shortcut("Cmd+Shift+F6")); + let want = if cfg!(target_os = "macos") { ("⌘F6", "⇧⌘F6") } else { ("Ctrl+F6", "Ctrl+Shift+F6") }; + assert_eq!((shown.0.as_str(), shown.1.as_str()), want); + let mut app = crate::VectorcraftApp::new(Session::new(), Default::default()); + let listed = app.run("shortcuts.list", json!({"query": "Document"})).unwrap(); + let rows: Vec<_> = listed + .as_array() + .expect("rows") + .iter() + .filter(|r| r["id"].as_str().is_some_and(|id| id.ends_with("Document") && id.starts_with("window."))) + .map(|r| (r["label"].clone(), r["group"].clone(), r["default"].clone())) + .collect(); + assert_eq!( + rows, + [(json!("Next Document"), json!("Window"), json!("Cmd+F6")), (json!("Previous Document"), json!("Window"), json!("Cmd+Shift+F6"))], + "Keyboard Shortcuts lists both" + ); + assert_eq!(app.run("shortcuts.conflicts", json!({})).unwrap(), json!([]), "no other default has the keys"); +} + +/// Cmd+F6 makes the next open document in tab order the current one, and Cmd+Shift+F6 makes the +/// previous one the current one. After the last document comes the first, and before the first document comes the +/// last. From the Home screen the keys go to the first and the last document and close the screen. +/// With no document open the commands are disabled and fail with "no document open". +#[test] +fn cmd_f6_goes_to_the_next_document_and_cmd_shift_f6_to_the_previous_one() { + let mut app = crate::VectorcraftApp::new(Session::new(), Default::default()); + assert!(!crate::menus::enabled(&app, "window.nextDocument") && !crate::menus::enabled(&app, "window.previousDocument")); + assert_eq!(app.run("window.nextDocument", json!({})).unwrap_err(), "no document open"); + assert_eq!(app.run("window.previousDocument", json!({})).unwrap_err(), "no document open"); + for name in ["A", "B", "C"] { + app.run("file.new", json!({"width": 100, "height": 100, "name": name})).unwrap(); + } + assert!(crate::menus::enabled(&app, "window.nextDocument") && crate::menus::enabled(&app, "window.previousDocument")); + assert_eq!(app.session.active_index(), Some(2)); + let mut seen = vec![]; + for modifiers in [egui::Modifiers::COMMAND; 4] { + press_f6(&mut app, modifiers); + seen.push(app.session.active_index()); + } + assert_eq!(seen, [Some(0), Some(1), Some(2), Some(0)], "Cmd+F6 goes right and wraps from the last to the first"); + seen.clear(); + for modifiers in [egui::Modifiers::COMMAND | egui::Modifiers::SHIFT; 4] { + press_f6(&mut app, modifiers); + seen.push(app.session.active_index()); + } + assert_eq!(seen, [Some(2), Some(1), Some(0), Some(2)], "Cmd+Shift+F6 goes left and wraps from the first to the last"); + assert_eq!(app.run("window.nextDocument", json!({})).unwrap(), json!({"index": 0})); + assert_eq!(app.run("window.previousDocument", json!({})).unwrap(), json!({"index": 2})); + app.session.set_active(1); + app.run("app.home", json!({})).unwrap(); + press_f6(&mut app, egui::Modifiers::COMMAND); + assert_eq!((app.session.active_index(), app.ui.home), (Some(0), None), "from the Home screen Cmd+F6 shows the first"); + app.session.set_active(1); + app.run("app.home", json!({})).unwrap(); + press_f6(&mut app, egui::Modifiers::COMMAND | egui::Modifiers::SHIFT); + assert_eq!((app.session.active_index(), app.ui.home), (Some(2), None), "and Cmd+Shift+F6 the last"); + // One document: the keys keep it current. + let mut one = crate::VectorcraftApp::new(Session::new(), Default::default()); + one.run("file.new", json!({"width": 100, "height": 100})).unwrap(); + press_f6(&mut one, egui::Modifiers::COMMAND); + press_f6(&mut one, egui::Modifiers::COMMAND | egui::Modifiers::SHIFT); + assert_eq!(one.session.active_index(), Some(0)); +} + +/// With the Artboard tool, Cmd+F6 and Cmd+Shift+F6 switch documents as a click on a tab does: the +/// document switched back to keeps the artboard it shows as the tool's target, so the Properties +/// panel's Delete Artboard then removes that artboard. Switching is not an edit. +#[test] +fn cmd_f6_switches_documents_as_a_tab_click_does_with_the_artboard_tool() { + let (mut app, ctx) = artboard_document_app(); + app.select_tool("artboard"); + artboard_navigation_next(&mut app, &ctx); + assert_eq!(app.session.tool_options()["selected"], json!([1])); + let before = app.session.active().unwrap().doc.clone(); + let undo = app.session.active().unwrap().history.undo.len(); + press_f6(&mut app, egui::Modifiers::COMMAND); + assert_eq!(app.session.active_index(), Some(1)); + // The app draws frames while the other document is the current one. + artboard_navigation_properties_frame(&mut app, &ctx, vec![]); + artboard_document_tabs_frame(&mut app, &ctx, vec![]); + press_f6(&mut app, egui::Modifiers::COMMAND | egui::Modifiers::SHIFT); + assert_eq!(app.session.active_index(), Some(0)); + app.sync_views(); + assert_eq!(app.session.tool_options()["selected"], json!([1]), "the tool targets the displayed artboard"); + assert_eq!(app.view().unwrap().artboard, 1, "the document keeps its displayed artboard"); + assert_eq!(app.session.active().unwrap().doc, before, "switching documents is not a document edit"); + assert_eq!(app.session.active().unwrap().history.undo.len(), undo); + artboard_navigation_properties_frame(&mut app, &ctx, vec![]); + let texts = artboard_navigation_properties_frame(&mut app, &ctx, vec![]); + let at = texts.iter().find(|(label, _)| label == "Delete Artboard").map(|(_, r)| r.center()).expect("Properties Delete button"); + let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + for event in [egui::Event::PointerMoved(at), button(true), button(false)] { + artboard_navigation_properties_frame(&mut app, &ctx, vec![event]); + } + let names: Vec<_> = app.session.active().unwrap().doc.artboards.iter().map(|a| a.name.clone()).collect(); + assert_eq!(names, ["Keep"], "Delete Artboard after the keys removes the artboard the document shows"); +} + /// One history entry: its label, document and selection. type HistoryRow = (String, Arc, Arc); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 21d58ecb1..63be7b2d8 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1991,6 +1991,8 @@ font-technology Variable Променливи New View Нов изглед New View… Нов изглед… New Window Нов прозорец + Next Document Следващ документ + Previous Document Предишен документ New Without Dialog Нов без диалогов прозорец New Workspace Ново работно пространство New Workspace… Ново работно пространство… diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index d20759fbf..8cf9280c4 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -625,6 +625,8 @@ Saved View 25 Uložené zobrazení 25 Window Okno New Window Nové okno + Next Document Další dokument + Previous Document Předchozí dokument Cascade Do kaskády Tile Vedle sebe Float in Window Uvolnit do samostatného okna diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index c5f721256..2358aef32 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -578,6 +578,8 @@ Saved View 25 Gemt visning 25 Window Vindue New Window Nyt vindue + Next Document Næste dokument + Previous Document Forrige dokument Cascade Kaskade Tile Fliselæg Float in Window Flyd i vindue diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index a099f79c5..f16b25dab 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2004,6 +2004,8 @@ file-filter Templates Vorlagen New View Neue Ansicht New View… Neue Ansicht… New Window Neues Fenster + Next Document Nächstes Dokument + Previous Document Vorheriges Dokument New Without Dialog Neu ohne Dialogfeld New Workspace Neuer Arbeitsbereich New Workspace… Neuer Arbeitsbereich… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 8ef3e7456..e0036e678 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1674,6 +1674,8 @@ font-technology Variable Μεταβλητές New Layer… Νέο επίπεδο… New Swatch… Νέο δείγμα χρώματος… New Window Νέο παράθυρο + Next Document Επόμενο έγγραφο + Previous Document Προηγούμενο έγγραφο New Without Dialog Νέο χωρίς παράθυρο διαλόγου New Workspace Νέος χώρος εργασίας New Workspace… Νέος χώρος εργασίας… diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 044164713..c8152a3f3 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2046,6 +2046,8 @@ font-technology Variable Variables New View Nueva vista New View… Nueva vista… New Window Nueva ventana + Next Document Documento siguiente + Previous Document Documento anterior New Without Dialog Nuevo sin cuadro de diálogo New Workspace Nuevo espacio de trabajo New Workspace… Nuevo espacio de trabajo… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index c05ff7b05..c3dffe8ec 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -616,6 +616,8 @@ Saved View 25 Tallennettu näkymä 25 Window Ikkuna New Window Uusi ikkuna + Next Document Seuraava asiakirja + Previous Document Edellinen asiakirja Cascade Porrasta Tile Vierekkäin Float in Window Kelluta ikkunassa diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 4c7a607d0..bbae03ed5 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2043,6 +2043,8 @@ font-technology Variable Variables New View Nouvelle vue New View… Nouvelle vue… New Window Nouvelle fenêtre + Next Document Document suivant + Previous Document Document précédent New Without Dialog Nouveau sans boîte de dialogue New Workspace Nouvel espace de travail New Workspace… Nouvel espace de travail… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index dedf1ed77..e34d5c8d5 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2492,6 +2492,8 @@ file-filter Templates Sablonok New View Új nézet New View… Új nézet… New Window Új ablak + Next Document Következő dokumentum + Previous Document Előző dokumentum New Without Dialog Új párbeszédablak nélkül New Workspace Új munkaterület New Workspace… Új munkaterület… diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index d15951a05..eb2c01389 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -578,6 +578,8 @@ Saved View 25 Tampilan tersimpan 25 Window Jendela New Window Jendela baru + Next Document Dokumen berikutnya + Previous Document Dokumen sebelumnya Cascade Bertingkat Tile Ubin Float in Window Mengambang di jendela diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 9c828d3e2..a3c015e4d 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2073,6 +2073,8 @@ file-filter Templates Modelli New View Nuova vista New View… Nuova vista… New Window Nuova finestra + Next Document Documento successivo + Previous Document Documento precedente New Without Dialog Nuovo senza finestra di dialogo New Workspace Nuova area di lavoro New Workspace… Nuova area di lavoro… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1eccc77d5..15af584c3 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -616,6 +616,8 @@ font-technology Variable バリアブル Saved View 25 保存した表示 25 Window ウィンドウ New Window 新規ウィンドウ + Next Document 次のドキュメント + Previous Document 前のドキュメント Cascade 重ねて表示 Tile 並べて表示 Float in Window ウィンドウを分離 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 1edc8854c..1a2adecdb 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -530,6 +530,8 @@ Saved View 25 შენახული ხედი 25 Window ფანჯარა New Window ახალი ფანჯარა + Next Document შემდეგი დოკუმენტი + Previous Document წინა დოკუმენტი Cascade კასკადად Tile ფილებად Float in Window ცალკე ფანჯარაში გამოტანა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index efaf210a9..5225910eb 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -614,6 +614,8 @@ font-technology Variable 가변 Saved View 25 저장된 보기 25 Window 윈도우 New Window 새 창 + Next Document 다음 문서 + Previous Document 이전 문서 Cascade 계단식 배열 Tile 바둑판식 배열 Float in Window 창에 플로팅 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 255933816..04410cdca 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -592,6 +592,8 @@ Saved View 25 Opgeslagen weergave 25 Window Venster New Window Nieuw venster + Next Document Volgend document + Previous Document Vorig document Cascade Trapsgewijs schikken Tile Naast elkaar schikken Float in Window In venster zweven diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 5bc9267c4..3c459fe4e 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -583,6 +583,8 @@ Saved View 25 Zapisany widok 25 Window Okno New Window Nowe okno + Next Document Następny dokument + Previous Document Poprzedni dokument Cascade Kaskadowo Tile Sąsiadująco Float in Window Pływające w oknie diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3b4029dc3..59343e3a3 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1653,6 +1653,8 @@ New Symbol… Novo Símbolo… New View… Nova Visualização… New Window Nova Janela + Next Document Próximo Documento + Previous Document Documento Anterior New Without Dialog Novo sem Diálogo New Workspace Novo Espaço de Trabalho New Workspace… Novo Espaço de Trabalho… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index fb032f515..b703c6206 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2069,6 +2069,8 @@ file-filter Templates Шаблоны New View Новый вид New View… Новый вид… New Window Новое окно + Next Document Следующий документ + Previous Document Предыдущий документ New Without Dialog Создать без диалогового окна New Workspace Новая рабочая среда New Workspace… Новая рабочая среда… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 20d4c2f6a..50f81c216 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1991,6 +1991,8 @@ font-technology Variable Variabla New View Ny vy New View… Ny vy… New Window Nytt fönster + Next Document Nästa dokument + Previous Document Föregående dokument New Without Dialog Ny utan dialogruta New Workspace Ny arbetslayout New Workspace… Ny arbetslayout… diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 1ef8d2fb4..24f375dd6 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1871,6 +1871,8 @@ font-technology Variable Değişken New View Yeni Görünüm New View… Yeni Görünüm… New Window Yeni Pencere + Next Document Sonraki Belge + Previous Document Önceki Belge New Workspace Yeni Çalışma Alanı New Workspace… Yeni Çalışma Alanı… New {kind} Style… Yeni {kind} Stili… diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 1301dffc0..4a43763d5 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2065,6 +2065,8 @@ file-filter Templates Шаблони New View Новий вигляд New View… Новий вигляд… New Window Нове вікно + Next Document Наступний документ + Previous Document Попередній документ New Without Dialog Створити без діалогового вікна New Workspace Нове робоче середовище New Workspace… Нове робоче середовище… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index e570cefb9..47e432bd5 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2300,6 +2300,8 @@ file-filter Templates Mẫu New View Chế độ xem mới New View… Chế độ xem mới… New Window Cửa sổ mới + Next Document Tài liệu tiếp theo + Previous Document Tài liệu trước New Without Dialog Mới không qua hộp thoại New Workspace Không gian làm việc mới New Workspace… Không gian làm việc mới… diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 89aee91f9..7797a7e52 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1944,6 +1944,8 @@ file-filter Templates 模板 New View 新建视图 New View… 新建视图… New Window 新建窗口 + Next Document 下一个文档 + Previous Document 上一个文档 New Without Dialog 无对话框新建 New Workspace 新建工作区 New Workspace… 新建工作区… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index c5a0737e7..907b56c30 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2869,6 +2869,8 @@ file-filter Templates 範本 New View 新增檢視 New View… 新增檢視… New Window 新增視窗 + Next Document 下一個文件 + Previous Document 上一個文件 New Without Dialog 新增但不開啟對話方塊 New Workspace 新增工作區 New Workspace… 新增工作區… diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 61bb710dd..ab1ab5a54 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -511,6 +511,8 @@ fn run_ui_part6(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option Err("brightness must be dark|mediumDark|mediumLight|light".into()), }, "window.newWindow" => Err("New Window isn't available yet: a document opens in one window".into()), + "window.nextDocument" => crate::chrome::step_document(app, true), + "window.previousDocument" => crate::chrome::step_document(app, false), "tool.select" => match s("tool") { Some(t) if vectorcraft_tools::tool_info(&t).is_some() => { app.select_tool(&t); diff --git a/crates/ui-egui/src/menus/state.rs b/crates/ui-egui/src/menus/state.rs index da91a9d3f..8f2721f44 100644 --- a/crates/ui-egui/src/menus/state.rs +++ b/crates/ui-egui/src/menus/state.rs @@ -263,6 +263,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool { "ui.perspectivePlane" => app.session.active().is_some(), // Only one window per document so far: greyed out, as Window › Arrange's items are (#3724). "window.newWindow" => false, + "window.nextDocument" | "window.previousDocument" => !app.session.documents().is_empty(), _ => true, } } diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 3164f2e7f..2104e5979 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -518,6 +518,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ("window.workspace.rename", "Rename Workspace", "", "{name, to}"), ("window.workspace.list", "List Workspaces", "", "{}"), ("window.newWindow", "New Window", "", "{}"), + ( + "window.nextDocument", + "Next Document", + "Cmd+F6", + "{} make the open document after the current one in tab order the current one, as a click on its tab does (after the last document it goes to the first, and from the Home screen to the first); returns {index}", + ), + ( + "window.previousDocument", + "Previous Document", + "Cmd+Shift+F6", + "{} make the open document before the current one in tab order the current one, as a click on its tab does (before the first document it goes to the last, and from the Home screen to the last); returns {index}", + ), ("tool.select", "Select Tool", "", "{tool: id} (see tools)"), ( "tool.setOption", diff --git a/docs/mcp.md b/docs/mcp.md index 77490dc8a..315e60393 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3270,6 +3270,11 @@ each range, and a size of 0 or less is read as 1 pt. The desktop app's `file.newDialog` (Cmd/Ctrl+N) opens the New Document dialog on the first Recent entry, or on the Print category's Letter while Recent is empty. `file.newWithoutDialog` (Cmd/Ctrl+Alt+N) runs `file.new` with those settings and the next untitled name, without opening the dialog, and returns what `file.new` returns. +`window.nextDocument` (Cmd/Ctrl+F6) and `window.previousDocument` (Cmd/Ctrl+Shift+F6) make the next and the previous +open document in tab order the current one, as a click on its tab does, and return `{index}`. After the last document +comes the first, and before the first document comes the last. From the Home screen they go to the first and the last +document. With no document open they fail with "no document open". `document.activate {index}` makes a given document +the current one. ```json {"name":"run_command","arguments":{"command":"file.new","params":{"preset":"A4","orientation":"landscape","artboards":4,"artboardLayout":{"columns":2},"bleed":9}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 07b31b249..73f40f91f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -623,6 +623,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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 making a 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 making a new document with no 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. + - Cmd/Ctrl+F6 (`window.nextDocument`) and Cmd/Ctrl+Shift+F6 (`window.previousDocument`) make the next and the previous open document the current one, in tab order and as a click on its tab does, 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 navigating to the next and the previous document. After the last document comes the first, and before the first document comes the last. From the Home screen the keys go to the first and the last document. Keyboard Shortcuts lists both under Window. - Cmd/Ctrl+Alt+Shift+U (`document.switchUnits`) switches the document to the next unit of measurement, in one undo step, 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 switching units of measurement. The page does not give an order. VectorCraft steps through the units in the order a right-click on a ruler lists them (Points, Picas, Inches, Millimeters, Centimeters, Pixels, Feet & Inches, Meters, Yards, Feet) and goes from Feet back to Points. - 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.*`). - On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one.