From 8bcebf0e48f1bea27168bacdbdb798aa8f0fb45f Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 16 Oct 2026 10:50:58 -0700 Subject: [PATCH] Artboards: Alt-click on New Artboard opens Artboard Options for the new artboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Alt-click (Option-click on macOS) on the Artboards panel's New Artboard used to add an artboard at once, as a plain click does. It now opens Artboard Options for the new artboard, filled in with the name and size that artboard.new {} would give it, and adds nothing yet. OK adds the artboard with the fields where artboard.new {} would place it (20 pt to the right of the last artboard) in one undo step, and makes it the active artboard; Cancel adds no artboard. A plain click and a row dropped on the button work as before. The new UI command ui.newArtboard ("New Artboard Options…") opens the dialog. It is listed after ui.newLayer, and its label has a row in each catalog. Docs: docs/mcp.md, docs/target-app-parity.md. --- .../ui-egui/src/dialogs/artboard_options.rs | 36 ++++++++ crates/ui-egui/src/dialogs/tests.rs | 1 + crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/menus.rs | 8 ++ crates/ui-egui/src/panels/artboards.rs | 90 ++++++++++++++++++- docs/mcp.md | 4 +- docs/target-app-parity.md | 1 + 25 files changed, 155 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/dialogs/artboard_options.rs b/crates/ui-egui/src/dialogs/artboard_options.rs index 9c0eecdc6..6a340b9e3 100644 --- a/crates/ui-egui/src/dialogs/artboard_options.rs +++ b/crates/ui-egui/src/dialogs/artboard_options.rs @@ -29,6 +29,9 @@ pub fn open(app: &mut VectorcraftApp) -> Result { } fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { + if d.bool("new") { + return confirm_new(app, d); + } let mut p = Value::Object(d.fields.clone()); for k in ["x", "y", "width", "height"] { if d.fields.contains_key(k) { @@ -38,3 +41,36 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { p["moveArt"] = json!(crate::panels::artboards::move_art(app)); run_and_close(app, "artboard.setProps", p) } + +/// `ui.newArtboard` (Alt-click on the Artboards panel's New Artboard): Artboard Options for a new +/// artboard (field `new`), filled in as `artboard.new {}` would make it: named Artboard N (N one +/// more than the number of artboards), as large as the last artboard and 20 pt to the right of it. +pub fn open_new(app: &mut VectorcraftApp) -> Result { + let st = app.session.active().ok_or("no document")?; + let abs = &st.doc.artboards; + let last = abs.last().map_or(vectorcraft_geom::Rect::new(0.0, 0.0, 612.0, 792.0), |a| a.rect); + let fields = json!({ + "new": true, "name": format!("Artboard {}", abs.len() + 1), + "x": last.x1 + 20.0, "y": last.y0, "width": last.width(), "height": last.height(), + }); + app.ui.dialog = Some(Dialog::new(KIND, fields)); + Ok(json!({ "dialog": KIND })) +} + +/// OK for a new artboard: `artboard.new` with the fields (an empty name gives the default one), +/// and the new artboard becomes the active one. +fn confirm_new(app: &mut VectorcraftApp, d: &Dialog) -> Result { + let mut p = json!({}); + for k in ["x", "y", "width", "height"] { + p[k] = json!(d.f64(k, 0.0)); + } + let name = d.str("name"); + if !name.trim().is_empty() { + p["name"] = json!(name); + } + let r = run_and_close(app, "artboard.new", p)?; + if let Some(i) = r["index"].as_u64().and_then(|i| usize::try_from(i).ok()) { + crate::panels::artboards::select(app, i); + } + Ok(r) +} diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index b427bbbfc..fdd8309cb 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -358,6 +358,7 @@ fn dialogs_do_not_stretch_to_the_screen() { ("ui.swatchOptions", json!({"name": "White"})), ("ui.colorPicker", json!({})), ("ui.graphicStyleOptions", json!({})), + ("ui.newArtboard", json!({})), ("ui.brushOptions", json!({"name": "3 pt. Round"})), ("ui.brushOptions", json!({"name": "Bristle Round"})), ("ui.colorGuideOptions", json!({})), diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index f428a745c..e4882df93 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1798,6 +1798,7 @@ New Graphic Style Нов графичен стил New Graphic Style (Alt-click to name it) Нов графичен стил (кликнете с Alt, за да го наименувате) New Graphic Style… Нов графичен стил… + New Artboard Options… Опции за нова работна област… New Layer Options… Опции за нов слой… New Layer… Нов слой… New Opacity Masks Are Clipping Новите маски за непрозрачност са изрязващи diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9291096a7..2add60334 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -879,6 +879,7 @@ lighting Light Světlo Menu Dialog Dialogové okno nabídky Merge Graphic Styles… Sloučit grafické styly… New Color Group… Nová skupina barev… + New Artboard Options… Možnosti nové pracovní plochy… New Layer Options… Možnosti nové vrstvy… New Swatch… Nová položka vzorníku… No saved views (View → New View…) Žádná uložená zobrazení (Zobrazení → Nové zobrazení…) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 6dfe3c698..a0a16f593 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1802,6 +1802,7 @@ file-filter Templates Vorlagen New Graphic Style Neuer Grafikstil New Graphic Style (Alt-click to name it) Neuer Grafikstil (Alt-Klick, um ihn zu benennen) New Graphic Style… Neuer Grafikstil… + New Artboard Options… Optionen für neue Zeichenfläche… New Layer Options… Optionen für neue Ebene… New Layer… Neue Ebene… New Opacity Masks Are Clipping Neue Deckkraftmasken sind Schnittmasken diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index f9e32919f..76628ab93 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2510,6 +2510,7 @@ theme Appearance Εμφάνιση New Graphic Style Νέο στυλ γραφικών New Graphic Style (Alt-click to name it) Νέο στυλ γραφικών (Alt-κλικ για ονομασία) New Graphic Style… Νέο στυλ γραφικών… + New Artboard Options… Επιλογές νέου πίνακα σχεδίασης… New Layer Options… Επιλογές νέου επιπέδου… New Opacity Masks Are Clipping Οι νέες μάσκες αδιαφάνειας εφαρμόζουν αποκοπή New Opacity Masks Are Inverted Οι νέες μάσκες αδιαφάνειας είναι ανεστραμμένες diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 00d9b6d8d..2dad776d6 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1841,6 +1841,7 @@ New Graphic Style Nuevo estilo gráfico New Graphic Style (Alt-click to name it) Nuevo estilo gráfico (Alt+clic para darle nombre) New Graphic Style… Nuevo estilo gráfico… + New Artboard Options… Opciones de mesa de trabajo nueva… New Layer Options… Opciones de capa nueva… New Layer… Nueva capa… New Opacity Masks Are Clipping Las nuevas máscaras de opacidad recortan diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 3c8b7c130..d1ea2c832 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -858,6 +858,7 @@ lighting Light Valo Menu Dialog Valikkovalintaikkuna Merge Graphic Styles… Yhdistä grafiikkatyylit… New Color Group… Uusi väriryhmä… + New Artboard Options… Uuden piirtoalustan asetukset… New Layer Options… Uuden tason asetukset… New Swatch… Uusi värimalli… No saved views (View → New View…) Ei tallennettuja näkymiä (Näytä → Uusi näkymä…) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index b8e18521e..5c616a773 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1838,6 +1838,7 @@ New Graphic Style Nouveau style graphique New Graphic Style (Alt-click to name it) Nouveau style graphique (Alt+clic pour le nommer) New Graphic Style… Nouveau style graphique… + New Artboard Options… Options du nouveau plan de travail… New Layer Options… Options du nouveau calque… New Layer… Nouveau calque… New Opacity Masks Are Clipping Les nouveaux masques d’opacité sont des masques d’écrêtage diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 1c28d5cd6..a922a6865 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2272,6 +2272,7 @@ file-filter Templates Sablonok New Graphic Style… Új grafikai stílus… New Group Új csoport New Group… Új csoport… + New Artboard Options… Új rajztábla beállításai… New Layer Options… Új réteg beállításai… New Layer… Új réteg… New Library Group Új könyvtárcsoport diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 8e81b3fee..743f12bbc 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1853,6 +1853,7 @@ file-filter Templates Modelli New Graphic Style Nuovo stile grafico New Graphic Style (Alt-click to name it) Nuovo stile grafico (Alt+clic per assegnare un nome) New Graphic Style… Nuovo stile grafico… + New Artboard Options… Opzioni nuova tavola da disegno… New Layer Options… Opzioni nuovo livello… New Layer… Nuovo livello… New Opacity Masks Are Clipping Le nuove maschere di opacità ritagliano diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 3f30506bc..b90ca4736 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1962,6 +1962,7 @@ New Graphic Style 新規グラフィックスタイル New Graphic Style (Alt-click to name it) 新規グラフィックスタイル (Alt+クリックで名前を指定) New Graphic Style… 新規グラフィックスタイル… + New Artboard Options… 新規アートボードオプション… New Layer Options… 新規レイヤーオプション… New Layer… 新規レイヤー… New Opacity Masks Are Clipping 新規不透明マスクをクリップに設定 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 3d43b0bc3..b3a7655c8 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1957,6 +1957,7 @@ New Graphic Style 새 그래픽 스타일 New Graphic Style (Alt-click to name it) 새 그래픽 스타일 (Alt 클릭으로 이름 지정) New Graphic Style… 새 그래픽 스타일… + New Artboard Options… 새 아트보드 옵션… New Layer Options… 새 레이어 옵션… New Layer… 새 레이어… New Opacity Masks Are Clipping 새 불투명 마스크를 클리핑으로 설정 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 8ef8cefe3..7dcbc942b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -816,6 +816,7 @@ lighting Light Światło Menu Dialog Okno menu Merge Graphic Styles… Scal style graficzne… New Color Group… Nowa grupa kolorów… + New Artboard Options… Opcje nowego obszaru kompozycji… New Layer Options… Opcje nowej warstwy… New Swatch… Nowa próbka… No saved views (View → New View…) Brak zapisanych widoków (Widok → Nowy widok…) diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 08c059363..615e4685b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3055,6 +3055,7 @@ lighting Light Luz Hue Shift Deslocamento de matiz Key Color: Cor-chave: Shape: Forma: + New Artboard Options… Opções de nova mesa de trabalho… New Layer Options… Opções de nova camada… No matching fonts Nenhuma fonte correspondente Create: Criar: diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index f62ba11c5..99624b452 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1849,6 +1849,7 @@ file-filter Templates Шаблоны New Graphic Style Новый графический стиль New Graphic Style (Alt-click to name it) Новый графический стиль (Alt+щелчок, чтобы задать имя) New Graphic Style… Новый графический стиль… + New Artboard Options… Параметры новой монтажной области… New Layer Options… Параметры нового слоя… New Layer… Новый слой… New Opacity Masks Are Clipping Новые непрозрачные маски обрезают diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f95092ebb..e7ccfdacd 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1798,6 +1798,7 @@ New Graphic Style Ny grafikstil New Graphic Style (Alt-click to name it) Ny grafikstil (Alt-klicka för att namnge den) New Graphic Style… Ny grafikstil… + New Artboard Options… Alternativ för ny rityta… New Layer Options… Alternativ för nytt lager… New Layer… Nytt lager… New Opacity Masks Are Clipping Nya opacitetsmasker är klippande diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 65033a930..70d9c6529 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1845,6 +1845,7 @@ file-filter Templates Шаблони New Graphic Style Новий графічний стиль New Graphic Style (Alt-click to name it) Новий графічний стиль (Alt+клацання, щоб задати ім’я) New Graphic Style… Новий графічний стиль… + New Artboard Options… Параметри нової монтажної області… New Layer Options… Параметри нового шару… New Layer… Новий шар… New Opacity Masks Are Clipping Нові маски непрозорості відсікають diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index c3618f4ec..ff91b0957 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2066,6 +2066,7 @@ file-filter Templates Mẫu New Graphic Style… Kiểu đồ họa mới… New Group Nhóm mới New Group… Nhóm mới… + New Artboard Options… Tùy chọn bảng vẽ mới… New Layer Options… Tùy chọn lớp mới… New Layer… Lớp mới… New Library Group Nhóm thư viện mới diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 17bd30ed9..f40e78807 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4396,6 +4396,7 @@ imageTrace Outline 轮廓 Lock Others 锁定其他 Merge Selected 合并所选图层 Move Rows 移动行 + New Artboard Options… 新建画板选项… New Layer Options… 新建图层选项… Options for Selection… 所选项的选项… Outline Others 轮廓化其他 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 1bcfcf028..1044fea0d 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1824,6 +1824,7 @@ file-filter Templates 範本 New Graphic Style 新增繪圖樣式 New Graphic Style (Alt-click to name it) 新增繪圖樣式 (Alt+點擊可命名) New Graphic Style… 新增繪圖樣式… + New Artboard Options… 新工作區域選項… New Layer Options… 新圖層選項… New Layer… 新增圖層… New Opacity Masks Are Clipping 新的不透明度遮色片為剪裁 diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 85af590b5..ecb43118a 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -537,6 +537,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{sublayer?: bool} open Layer Options for a new layer (or a sublayer of the current layer), named and coloured as it would be: dialog `layerOptions` with mode new|newSublayer; OK runs layer.new or layer.newSublayer with the fields", ), + ( + "ui.newArtboard", + "New Artboard Options…", + "", + "{} open Artboard Options for a new artboard, filled in as artboard.new {} would make it (Artboard N, the last artboard's size, 20 pt to its right): dialog `artboardOptions` with new: true, fields name, x, y, width, height; OK runs artboard.new with the fields and makes the new artboard the active one; Cancel adds nothing", + ), ( "ui.layersPanelOptions", "Panel Options…", @@ -1506,6 +1512,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option crate::dialogs::tile_edge_color::open(app), "ui.layerOptions" => crate::dialogs::layer_options::open(app, p), "ui.newLayer" => crate::dialogs::layer_options::open_new(app, p), + "ui.newArtboard" => crate::dialogs::artboard_options::open_new(app), "ui.layersPanelOptions" => crate::dialogs::layers_panel_options::open(app), "ui.layersExpand" => crate::panels::layers::expand(app, p), "ui.layersFilter" => crate::panels::layers::set_filter(app, p), @@ -2204,6 +2211,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool { "ui.findFontsInFolder" => crate::picks::can(app, &crate::picks::PickRequest::Folder) && app.session.active().is_some(), "ui.swatchOptions" | "ui.newSwatch" | "ui.newColorGroup" => app.session.active().is_some(), "ui.graphicStyleOptions" => app.session.active().is_some(), + "ui.newArtboard" => app.session.active().is_some(), "ui.sampleScreenColor" => crate::screen_color::available(app), "ui.brushOptions" => crate::dialogs::brush_options::available(app), "ui.hyphenation" => crate::panels::character::text_style(app).is_some(), diff --git a/crates/ui-egui/src/panels/artboards.rs b/crates/ui-egui/src/panels/artboards.rs index 3282c3421..b54aff2dc 100644 --- a/crates/ui-egui/src/panels/artboards.rs +++ b/crates/ui-egui/src/panels/artboards.rs @@ -1,5 +1,6 @@ -//! Artboards panel: numbered list with inline rename (double-click the name), move up / down, new, -//! delete; a row dragged onto New Artboard duplicates its artboard (as Duplicate Artboards does, with +//! Artboards panel: numbered list with inline rename (double-click the name), move up / down, new +//! (Alt-click: Artboard Options for the new artboard first), delete; a row dragged onto New +//! Artboard duplicates its artboard (as Duplicate Artboards does, with //! its art when the Artboard tool's Move/Copy Artwork with Artboard is on). The highlighted row is the active artboard, the one the status bar's navigator shows and //! Fit Artboard in Window fits: clicking a row makes it active, double-clicking its number also //! fits it in the window. The options icon at a row's right end opens Artboard Options: a click on @@ -8,7 +9,7 @@ use egui::{Sense, Ui, pos2, vec2}; use serde_json::{Value, json}; -use super::{pstate, set_pstate}; +use super::{alt_held, pstate, set_pstate}; use crate::theme::Tokens; use crate::widgets::{self, menu_item}; use crate::{VectorcraftApp, icons}; @@ -327,6 +328,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { } if let Some(row) = new.dnd_release_payload::() { duplicate(app, row.0); + } else if new.clicked() && alt_held(ui) { + // Alt-click: Artboard Options for the new artboard, which OK adds. + app.run("ui.newArtboard", json!({})).ok(); } else if new.clicked() && app.run("artboard.new", json!({})).is_ok() { select(app, n); } @@ -1027,4 +1031,84 @@ mod tests { assert_eq!((d.artboards.len(), d.layers[0].children().unwrap().len()), (2, 2), "the artboard and its art"); assert_eq!(app.view().unwrap().artboard, 1); } + + /// Alt-click on New Artboard opens Artboard Options for the new artboard and adds nothing yet: + /// the fields are those a plain click's artboard gets. Cancel adds nothing; OK adds the artboard + /// with the fields in one undo step and makes it the active one. + #[test] + fn alt_clicking_new_artboard_opens_artboard_options_for_the_new_artboard() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 200, "height": 150})).unwrap(); + app.session.execute("artboard.new", &json!({"x": 400, "y": 300, "width": 100, "height": 50})).unwrap(); + select(&mut app, 0); + let ctx = egui::Context::default(); + let screen = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(240.0, 400.0))); + let frame = |app: &mut VectorcraftApp, time: f64, events: Vec| { + let mut out = ctx.run_ui(egui::RawInput { events, time: Some(time), screen_rect: screen, ..Default::default() }, |ui| show(app, ui)); + out.textures_delta.clear(); + out.shapes + }; + // The bottom bar's buttons sit under its divider, New Artboard second from the right. + let bar = frame(&mut app, 0.0, vec![]) + .iter() + .find_map(|c| match &c.shape { + egui::Shape::Rect(r) if r.fill == Tokens::get(&ctx).divider => Some(r.rect), + _ => None, + }) + .unwrap(); + let new = pos2(bar.right() - 24.0 - 4.0 - 12.0, bar.bottom() + 2.0 + 12.0); + let click = |modifiers: egui::Modifiers| { + let b = |pressed| egui::Event::PointerButton { pos: new, button: egui::PointerButton::Primary, pressed, modifiers }; + vec![egui::Event::ModifiersChanged(modifiers), egui::Event::PointerMoved(new), b(true), b(false)] + }; + let count = |app: &VectorcraftApp| app.session.active().unwrap().doc.artboards.len(); + let fields = |app: &VectorcraftApp| { + let d = app.ui.dialog.as_ref().unwrap(); + assert_eq!(d.kind, crate::dialogs::artboard_options::KIND); + (d.str("name"), ["x", "y", "width", "height"].map(|k| d.f64(k, -1.0))) + }; + // Alt-click: the dialog for Artboard 3, 20 pt right of Artboard 2 and as large; no artboard yet. + frame(&mut app, 1.0, click(egui::Modifiers::ALT)); + assert_eq!(fields(&app), ("Artboard 3".to_string(), [520.0, 300.0, 100.0, 50.0])); + assert_eq!(count(&app), 2, "no artboard is added before OK"); + // A plain click adds the artboard the dialog showed, with no dialog. + crate::dialogs::cancel(&mut app); + frame(&mut app, 2.0, click(egui::Modifiers::NONE)); + let ab = app.session.active().unwrap().doc.artboards[2].clone(); + assert_eq!((ab.name.as_str(), ab.rect), ("Artboard 3", vectorcraft_geom::Rect::new(520.0, 300.0, 620.0, 350.0))); + assert!(app.ui.dialog.is_none()); + app.run("edit.undo", json!({})).unwrap(); + select(&mut app, 0); + // Cancel adds nothing. + frame(&mut app, 3.0, click(egui::Modifiers::ALT)); + crate::dialogs::cancel(&mut app); + assert_eq!((count(&app), app.view().unwrap().artboard), (2, 0)); + // OK adds it with the fields, in one undo step, and makes it the active artboard. + frame(&mut app, 4.0, click(egui::Modifiers::ALT)); + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.insert("name".into(), json!("Cover")); + d.fields.insert("width".into(), json!(300)); + let undo = app.session.doc().unwrap().history.undo.len(); + crate::dialogs::confirm(&mut app).unwrap(); + let ab = app.session.active().unwrap().doc.artboards[2].clone(); + assert_eq!((ab.name.as_str(), ab.rect), ("Cover", vectorcraft_geom::Rect::new(520.0, 300.0, 820.0, 350.0))); + assert_eq!((app.view().unwrap().artboard, app.session.doc().unwrap().history.undo.len()), (2, undo + 1)); + assert!(app.ui.dialog.is_none()); + } + + /// `ui.newArtboard` answers with the dialog's kind. A name left empty gives the new artboard + /// its default name, and with the Artboard tool in use the tool moves to the new artboard. + #[test] + fn new_artboard_options_with_an_empty_name_add_the_default_name() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + assert!(app.run("ui.newArtboard", json!({})).is_err(), "no document"); + app.session.execute("file.new", &json!({"width": 200, "height": 150})).unwrap(); + app.select_tool("artboard"); + assert_eq!(app.run("ui.newArtboard", json!({})).unwrap(), json!({"dialog": "artboardOptions"})); + app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!(" ")); + crate::dialogs::confirm(&mut app).unwrap(); + let names: Vec = app.session.active().unwrap().doc.artboards.iter().map(|a| a.name.clone()).collect(); + assert_eq!(names, ["Artboard 1", "Artboard 2"]); + assert_eq!((selected(&app, 2), app.session.tool_options()["active"].clone()), (1, json!(1))); + } } diff --git a/docs/mcp.md b/docs/mcp.md index 790c4acf7..74700e46e 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3662,7 +3662,9 @@ same dialog, and its OK also draws a flare there. The Artboard tool's are the ac (dialog `artboardOptions`; the Artboard tool's active artboard while it is in use, else the window's), a graph tool's the selected graph's Graph Type (an error without one), a Symbolism tool's the Symbolism Tools Options (dialog `symbolismOptions`: `diameter` in points, `intensity` and `density` 1–10, one brush for the eight tools) and the -Magic Wand tool's its panel. +Magic Wand tool's its panel. `ui.newArtboard` (Alt-click on the Artboards panel's New Artboard) opens Artboard Options +for a new artboard, with `new: true` and the name, position and size that `artboard.new {}` would give it. OK runs +`artboard.new` with the fields and makes the new artboard the active one, and Cancel adds no artboard. ```json {"name":"run_command","arguments":{"command":"tool.setOption","params":{"tool":"flare","values":{"rays":24,"ringsOn":false}}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 58335bb85..594f3eea5 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -538,6 +538,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Align to Artboard uses the active artboard, the one the Artboards panel highlights (`object.align {artboard}`). A press with a selection tool inside an artboard makes that artboard active; with the Selection tool, a Shift-click on an empty spot inside an artboard makes it active and keeps the selection. One selected object is aligned to the active artboard too, whatever Align To says (a lone object is never the key object). When an action plays, its Align to Artboard, All on Active Artboard, Paste in Place, Paste in Front and Paste in Back steps that name no artboard use the artboard that is active then. Choosing the Artboard tool (Shift+O, its toolbar button, or Edit Artboards in Properties or Document Setup) starts it on the active artboard: the canvas outlines that artboard, and Delete, Cut, Copy, the Transform panel and Artboard Options act on it. In the Artboards panel, a click on the active artboard's options icon, or a double-click on another row's icon, opens Artboard Options for that row's artboard, and the panel menu's Artboard Options… opens it for the active artboard (part of #1876). Both used to choose the Artboard tool instead. Opened while another tool is in use, the dialog moves the art with a new X or Y when the Artboard tool's Move Artwork with Artboard option is on. + Alt-clicking New Artboard in the Artboards panel opens Artboard Options for the new artboard, and OK adds it and makes it the active artboard (`ui.newArtboard`; behavior reference: [Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), Use panels). A plain click still adds the artboard at once. With Align to Artboard chosen, the Align panel's Distribute Objects and Distribute Spacing buttons use the active artboard (`object.distribute` and `object.distributeSpacing` with `to: "artboard"` and `artboard`). The first object's left or top edge goes to the artboard's left or top edge, and the last object's right or bottom edge goes to the artboard's right or bottom edge. Distribute Objects spaces the other objects evenly by its button's line, and two objects are enough. Distribute Spacing makes the gaps between the objects equal. With anchor points selected, the buttons space the points among themselves and do not use the artboard. When an action plays, its Distribute Objects and Distribute Spacing steps with Align to Artboard use the artboard that is active then. With a key object, the Align panel's Distribute Objects buttons use the Distribute Spacing value (`object.distribute {spacing}`): the key object, or the compound path or selected group that contains it, stays in place, and the objects' left edges, centers or right edges (top edges, centers or bottom edges) end up that far apart, outward from the key object's, in their order. With the field's default value of 0, a click puts every object on the key object's line (behavior reference: [Ask Mordy: Distributing Objects](http://rwillustrator.blogspot.com/2008/01/ask-mordy-distribute.html)). Choosing the Artboard tool (Shift+O, its toolbar button, Edit Artboards in Properties or Document Setup, or the Artboards panel menu's Artboard Options…) starts it on the active artboard: the canvas outlines that artboard, and Delete, Cut, Copy, the Transform panel and Artboard Options act on it.