diff --git a/ROADMAP.md b/ROADMAP.md index ffeada5b..f510d5bc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ VectorCraft is a clean-room, open-source, pure-Rust reimplementation of the Adob This file tracks **how far we are and what's left**. Time estimates are wall-clock hours of continuous Claude Opus 5.5 agent work (including builds and the CI gate), given both for **one agent** and for **4–6 parallel agents** on disjoint crates. They are counted from the remaining work (see [Parity estimate](#parity-estimate)), calibrated against measured throughput, and updated as work lands. -_Last updated: 2026-10-09 (after M4.14–M4.98, M8.1–M8.20, M14.4–M14.7, the MCP prompts, resource templates, completions and logging, the 2026-10-07 issue fixes, the Layers panel rework and the 2026-10-08 community contributions: Pen and shape-tool modifiers, path editing, Layers, Artboards, Swatches/Brushes and dialogs, Japanese composition and PDF text, Hebrew/Arabic type, interface languages (Spanish among them), the font menu, saved selections, MCP and EPS, the missing-font export warning (#319), open paths in the Shape Builder (#374), PDF/`.ai` import keeping strokes live and hidden and nested layers (#372), per-corner Live Corners (#381), SVG type opening at the size it draws at (#396), the Wayland clipboard with copied-file paste (#398), ruler guides that can be selected, moved and deleted (#414), two batches of Preferences options that were stored but unused (#394), skeleton reads with `document.find` for large documents over MCP (#416) and anchors dragged with Direct Selection snapping to points, segments and smart guides, clipped SVG layers opening as clipping layers (#420), PDF/`.ai` layers read on pages of tens of millions of operators (#419), Document Color Mode putting RGB greys on the black plate on request and converting pattern tiles (#421) and movable dialogs with Offset Path's Joins dropdown (#423) and numeric fields scrubbed by dragging their label (#400), stroke weight presets in the stroke unit (#426), the Appearance panel's Opacity popup (#430), type on a path brackets, flip and Type on a Path Options that keep the type in place (#429), Layers rows dragged with a ghost and dropped on the trash or New Layer (#435), tool flyouts opening on a right or corner press (#436), tool flyouts torn off into floating toolbars (#437), the Shape Builder's erase mode deleting edges too (#439), installed fonts registered outside the font folders and fonts installed while the app runs listed in the font menus (#443), alike circular corners on rectangles that aren't square (#442), tool cursors as OS cursors so the pointer no longer trails over the canvas (#444), artboards copied and pasted with their art (#446), guides dragged out of the rulers snapping to the art's side midpoints and the artboards, artboard guides (#451), the toolbar's double arrow and Window › Toolbars › Double Column switching between one and two columns in any window height (#455), the Smart Guides display options and Snapping Tolerance (#394), Font Size and Leading combo boxes with preset lists in the Character and Properties panels and the Control bar (#458), screenshots pasted from the clipboard on macOS, Windows and the web (#457), the Properties panel's Fill and Stroke matching the Control bar's, Illustrator's Window menu panel keys (Shift+F7 for Align…) and Alt-wheel zoom whatever the wheel preference (Discord feedback), the point/area type widget (Discord feedback), groups in legacy Illustrator .ai files (Discord feedback), a rotating desktop log file (#465), image controls in the Control bar and Properties panel: Image Trace with its presets, Mask, Crop Image, Embed/Unembed, and an Image Trace object's preset, Expand and Release (Discord feedback), the Pen working as the Anchor Point tool with Alt held over a selected path's handles and anchors, and anchor buttons that convert, connect and cut paths at the selected anchors (Discord feedback), Japanese hanging punctuation (burasagari, #463), Escape leaving Presentation Mode with no screen mode saved across restarts, documents dropped on the desktop app opening as tabs of their own and a note that an `.ai` file's art off its artboards isn't in the part read (#472), Add/Delete Anchor Point tool icons drawn from the Pen tool's (#479), EPS import running more of PostScript Level 3 (patterns, Type 3 fonts, mesh shadings, masked images, VM and resource probes), and a preview that stands in saying why (Discord feedback, #466, #469, #474), chords with modifiers recorded in Edit › Keyboard Shortcuts (#487), Type Object Selection by Path Only (#394), Anti-aliased Artwork (#394), Show Pixel Grid (#394), pen and touch taps pressed and lifted within one frame clicking where they tapped and the Linux/Wayland pen-tablet workaround (#491), bounding-box resizes landing on the other objects' anchors and centres with labels, and the Rotate, Scale, Shear and Reflect tools snapping their drags (#482), Shift-drag marquees that toggle the objects or anchors they reach and a Lasso whose Alt subtraction keeps the rest of the selection (#483), the mouse wheel stepping the focused numeric field, the dialogs' and preferences' drag values made such fields (#485), Show The Home Screen When No Documents Are Open (#394), Numbers Without Units Are Points (#394), Auto-Collapse Iconic Panels (#394), the Contextual Task Bar moved by its handle and its More Options menu pinning and resetting its position, a designed macOS DMG window (#493), panels, panel groups and the Tools panel undocking into floating panels that move, stack and dock again (#495), the 0.5.0 startup crash on dual-GPU Linux desktops under Wayland fixed: the desktop's own GPU by default and a restart on the next GPU when one can't show the window (#502), path editing while drawing: Cmd/Ctrl with the Pen borrowing Direct Selection and drawing on afterwards, Pen+Alt reshaping segments, handles of a whole selected path dragging, and handle drags no longer re-gathering snap targets every move (#494), the Control bar's and Properties panel's Convert buttons for a path selected as a whole with Direct Selection, the Pen and the anchor tools (#504), the Pen closing a path dragged out of its first anchor every time, without flicker (#501), the Paste menu checking the system clipboard on a background thread on Linux so an X11 clipboard owner that never answers no longer freezes the window (#510), PDF/`.ai` import keeping a type object's outline stroke on the type, wrapped lines as area type, barcode digits in place and layers in the file's stacking order (#508), the Italian interface (#514), EPS images with several data sources read in turn (#498), `restore` undoing dictionary changes in EPS import (part of #505), the macOS menu bar laid out as a Mac app's (the VectorCraft menu with Settings, Language and Appearance, the system's Hide, Services and Window items; owner's request), Object › Image Trace › Make tracing with the Default preset instead of asking for a preset's name (#543), the rulers' unit menu (#528), Live Corners on stars, polygons and pen paths (#511), the drawing tools snapping to Smart Guides as the Selection tool does, with Construction Guides (#506), the in-window menus' Select All, Cut, Copy and Paste acting on the text field they were opened from (#516), Windows rendering through DX12 before Vulkan (#545), Image Trace tracing colour layers in parallel and refusing traces too large for memory, with the canvas outlining huge selections by their bounds (#525), SVGs of chosen artboards holding only the art over each artboard (#550), the font menu scrolling only its list, under a search field and filters that stay put (#555), a click on an object of a selection making it the key object that Align aligns to and Distribute Spacing spaces from, drawn with a thicker outline (#541); see [Honest assessment](#honest-assessment-2026-10-05))._ +_Last updated: 2026-10-09 (after M4.14–M4.98, M8.1–M8.20, M14.4–M14.7, the MCP prompts, resource templates, completions and logging, the 2026-10-07 issue fixes, the Layers panel rework and the 2026-10-08 community contributions: Pen and shape-tool modifiers, path editing, Layers, Artboards, Swatches/Brushes and dialogs, Japanese composition and PDF text, Hebrew/Arabic type, interface languages (Spanish among them), the font menu, saved selections, MCP and EPS, the missing-font export warning (#319), open paths in the Shape Builder (#374), PDF/`.ai` import keeping strokes live and hidden and nested layers (#372), per-corner Live Corners (#381), SVG type opening at the size it draws at (#396), the Wayland clipboard with copied-file paste (#398), ruler guides that can be selected, moved and deleted (#414), two batches of Preferences options that were stored but unused (#394), skeleton reads with `document.find` for large documents over MCP (#416) and anchors dragged with Direct Selection snapping to points, segments and smart guides, clipped SVG layers opening as clipping layers (#420), PDF/`.ai` layers read on pages of tens of millions of operators (#419), Document Color Mode putting RGB greys on the black plate on request and converting pattern tiles (#421) and movable dialogs with Offset Path's Joins dropdown (#423) and numeric fields scrubbed by dragging their label (#400), stroke weight presets in the stroke unit (#426), the Appearance panel's Opacity popup (#430), type on a path brackets, flip and Type on a Path Options that keep the type in place (#429), Layers rows dragged with a ghost and dropped on the trash or New Layer (#435), tool flyouts opening on a right or corner press (#436), tool flyouts torn off into floating toolbars (#437), the Shape Builder's erase mode deleting edges too (#439), installed fonts registered outside the font folders and fonts installed while the app runs listed in the font menus (#443), alike circular corners on rectangles that aren't square (#442), tool cursors as OS cursors so the pointer no longer trails over the canvas (#444), artboards copied and pasted with their art (#446), guides dragged out of the rulers snapping to the art's side midpoints and the artboards, artboard guides (#451), the toolbar's double arrow and Window › Toolbars › Double Column switching between one and two columns in any window height (#455), the Smart Guides display options and Snapping Tolerance (#394), Font Size and Leading combo boxes with preset lists in the Character and Properties panels and the Control bar (#458), screenshots pasted from the clipboard on macOS, Windows and the web (#457), the Properties panel's Fill and Stroke matching the Control bar's, Illustrator's Window menu panel keys (Shift+F7 for Align…) and Alt-wheel zoom whatever the wheel preference (Discord feedback), the point/area type widget (Discord feedback), groups in legacy Illustrator .ai files (Discord feedback), a rotating desktop log file (#465), image controls in the Control bar and Properties panel: Image Trace with its presets, Mask, Crop Image, Embed/Unembed, and an Image Trace object's preset, Expand and Release (Discord feedback), the Pen working as the Anchor Point tool with Alt held over a selected path's handles and anchors, and anchor buttons that convert, connect and cut paths at the selected anchors (Discord feedback), Japanese hanging punctuation (burasagari, #463), Escape leaving Presentation Mode with no screen mode saved across restarts, documents dropped on the desktop app opening as tabs of their own and a note that an `.ai` file's art off its artboards isn't in the part read (#472), Add/Delete Anchor Point tool icons drawn from the Pen tool's (#479), EPS import running more of PostScript Level 3 (patterns, Type 3 fonts, mesh shadings, masked images, VM and resource probes), and a preview that stands in saying why (Discord feedback, #466, #469, #474), chords with modifiers recorded in Edit › Keyboard Shortcuts (#487), Type Object Selection by Path Only (#394), Anti-aliased Artwork (#394), Show Pixel Grid (#394), pen and touch taps pressed and lifted within one frame clicking where they tapped and the Linux/Wayland pen-tablet workaround (#491), bounding-box resizes landing on the other objects' anchors and centres with labels, and the Rotate, Scale, Shear and Reflect tools snapping their drags (#482), Shift-drag marquees that toggle the objects or anchors they reach and a Lasso whose Alt subtraction keeps the rest of the selection (#483), the mouse wheel stepping the focused numeric field, the dialogs' and preferences' drag values made such fields (#485), Show The Home Screen When No Documents Are Open (#394), Numbers Without Units Are Points (#394), Auto-Collapse Iconic Panels (#394), the Contextual Task Bar moved by its handle and its More Options menu pinning and resetting its position, a designed macOS DMG window (#493), panels, panel groups and the Tools panel undocking into floating panels that move, stack and dock again (#495), the 0.5.0 startup crash on dual-GPU Linux desktops under Wayland fixed: the desktop's own GPU by default and a restart on the next GPU when one can't show the window (#502), path editing while drawing: Cmd/Ctrl with the Pen borrowing Direct Selection and drawing on afterwards, Pen+Alt reshaping segments, handles of a whole selected path dragging, and handle drags no longer re-gathering snap targets every move (#494), the Control bar's and Properties panel's Convert buttons for a path selected as a whole with Direct Selection, the Pen and the anchor tools (#504), the Pen closing a path dragged out of its first anchor every time, without flicker (#501), the Paste menu checking the system clipboard on a background thread on Linux so an X11 clipboard owner that never answers no longer freezes the window (#510), PDF/`.ai` import keeping a type object's outline stroke on the type, wrapped lines as area type, barcode digits in place and layers in the file's stacking order (#508), the Italian interface (#514), EPS images with several data sources read in turn (#498), `restore` undoing dictionary changes in EPS import (part of #505), the macOS menu bar laid out as a Mac app's (the VectorCraft menu with Settings, Language and Appearance, the system's Hide, Services and Window items; owner's request), Object › Image Trace › Make tracing with the Default preset instead of asking for a preset's name (#543), the rulers' unit menu (#528), Live Corners on stars, polygons and pen paths (#511), the drawing tools snapping to Smart Guides as the Selection tool does, with Construction Guides (#506), the in-window menus' Select All, Cut, Copy and Paste acting on the text field they were opened from (#516), Windows rendering through DX12 before Vulkan (#545), Image Trace tracing colour layers in parallel and refusing traces too large for memory, with the canvas outlining huge selections by their bounds (#525), SVGs of chosen artboards holding only the art over each artboard (#550), the font menu scrolling only its list, under a search field and filters that stay put (#555), a click on an object of a selection making it the key object that Align aligns to and Distribute Spacing spaces from, drawn with a thicker outline (#541), Edit Artboards showing the active artboard in the Properties panel with New Artboard, Delete Artboard and Exit (#530); see [Honest assessment](#honest-assessment-2026-10-05))._ ## Where we are diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index a2fc2160..c6192492 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1492,6 +1492,8 @@ New Art Brush from Selection Nuevo pincel de arte a partir de la selección New Art Has Basic Appearance La nueva ilustración tiene apariencia básica New Artboard Nueva mesa de trabajo + Artboard Mesa de trabajo + Exit Salir New Bristle Brush Nuevo pincel de cerdas New Brush… Nuevo pincel… New Calligraphic Brush Nuevo pincel caligráfico diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 8ea33549..1d0271ac 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1492,6 +1492,8 @@ New Art Brush from Selection Nuovo pennello artistico dalla selezione New Art Has Basic Appearance La nuova grafica ha aspetto di base New Artboard Nuova tavola da disegno + Artboard Tavola da disegno + Exit Esci New Bristle Brush Nuovo pennello a setole New Brush… Nuovo pennello… New Calligraphic Brush Nuovo pennello calligrafico diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1acb91c0..4db1d739 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1635,6 +1635,8 @@ New Art Brush from Selection 選択範囲から新規アートブラシ New Art Has Basic Appearance 新規アートに基本アピアランスを使用 New Artboard 新規アートボード + Artboard アートボード + Exit 終了 New Bristle Brush 新規剛毛ブラシ New Brush… 新規ブラシ… New Calligraphic Brush 新規絵筆ブラシ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index ec6f085f..4b50d741 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1469,6 +1469,7 @@ New Art Brush from Selection 从选区新建艺术画笔 New Art Has Basic Appearance 新建图稿使用基本外观 New Artboard 新建画板 + Exit 退出 New Bristle Brush 新建鬃毛画笔 New Brush… 新建画笔… New Calligraphic Brush 新建书法画笔 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 737cb4e9..f02152f3 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1509,6 +1509,8 @@ New Art Brush from Selection 從選取範圍新增藝術筆刷 New Art Has Basic Appearance 新圖稿具有基本外觀 New Artboard 新增工作區域 + Artboard 工作區域 + Exit 結束 New Bristle Brush 新增鬃毛筆刷 New Brush… 新增筆刷… New Calligraphic Brush 新增書法筆刷 diff --git a/crates/ui-egui/src/panels/artboards.rs b/crates/ui-egui/src/panels/artboards.rs index 3dacd3f5..6eda05a3 100644 --- a/crates/ui-egui/src/panels/artboards.rs +++ b/crates/ui-egui/src/panels/artboards.rs @@ -13,13 +13,13 @@ use crate::widgets::{self, menu_item}; use crate::{VectorcraftApp, icons}; /// The active artboard (of `n`). -fn selected(app: &VectorcraftApp, n: usize) -> usize { +pub(crate) fn selected(app: &VectorcraftApp, n: usize) -> usize { app.view().map_or(0, |v| v.artboard).min(n.saturating_sub(1)) } /// Make artboard `i` the active one, leaving the view where it is: the navigator's and, while it /// is the tool, the Artboard tool's. -fn select(app: &mut VectorcraftApp, i: usize) { +pub(crate) fn select(app: &mut VectorcraftApp, i: usize) { if let Some(v) = app.view_mut() { v.artboard = i; } diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 158e3317..b74bc2ba 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -18,7 +18,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let n_sel = st.selection.len(); let anchors = crate::chrome::anchor_controls(app); let first = first_selected(app); + // Edit Artboards (the Artboard tool): the active artboard, whatever is selected. + let editing_artboards = app.session.tool_id() == "artboard"; let label = match (&first, n_sel) { + _ if editing_artboards => tl!("Artboard").to_string(), (None, _) => tl!("Document").to_string(), (_, n) if n > 1 => crate::i18n::tn(n as u64, "{n} Object", "{n} Objects"), (Some(n), _) if crate::panels::image_trace::is_trace(n) => tl!("Image Tracing").to_string(), @@ -26,6 +29,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { }; ui.label(egui::RichText::new(label).size(11.5).color(t.text_dim)); ui.add_space(4.0); + if editing_artboards { + artboard_sections(app, ui); + return; + } if first.is_none() { document_sections(app, ui); return; @@ -188,6 +195,48 @@ fn multi_color(app: &mut VectorcraftApp, ctx: &egui::Context) -> bool { } } +/// Edit Artboards (the Artboard tool): the active artboard's name, position and size, New Artboard +/// and Delete Artboard, and Exit back to the Selection tool (#530). +fn artboard_sections(app: &mut VectorcraftApp, ui: &mut Ui) { + let units = app.session.general_unit(); + let n = app.session.active().map_or(0, |d| d.doc.artboards.len()); + let i = super::artboards::selected(app, n); + if let Some(ab) = app.session.active().and_then(|d| d.doc.artboards.get(i)).cloned() { + // Its name as it is (names are never translated). + ui.label(egui::RichText::new(&ab.name).size(13.0).color(Tokens::get(ui.ctx()).text)); + let fw = ((ui.available_width() - 50.0) / 2.0).clamp(60.0, 110.0); + egui::Grid::new("ab-grid").num_columns(4).spacing([4.0, 6.0]).min_col_width(0.0).show(ui, |ui| { + let r = ab.rect; + for (row, [(l1, k1, v1), (l2, k2, v2)]) in + [[("X:", "x", r.x0), ("W:", "width", r.width())], [("Y:", "y", r.y0), ("H:", "height", r.height())]].into_iter().enumerate() + { + for (label, key, v) in [(l1, k1, v1), (l2, k2, v2)] { + dim_label(ui, label); + if let Some(v) = widgets::num_field(ui, ("ab", key, row), Some(v), units, fw) { + app.run("artboard.setProps", json!({"index": i, key: v})).ok(); + } + } + ui.end_row(); + } + }); + } + ui.add_space(4.0); + let w = (ui.available_width() - 6.0) / 2.0; + ui.horizontal(|ui| { + if widgets::flat_button(ui, tl!("New Artboard"), w).clicked() && app.run("artboard.new", json!({})).is_ok() { + super::artboards::select(app, n); + } + if ui.add_enabled_ui(n > 1, |ui| widgets::flat_button(ui, tl!("Delete Artboard"), w)).inner.clicked() { + app.run("artboard.delete", json!({"index": i})).ok(); + } + }); + divider(ui); + section_header(ui, tl!("Quick Actions")); + if widgets::flat_button(ui, tl!("Exit"), w).clicked() { + app.select_tool("selection"); + } +} + fn document_sections(app: &mut VectorcraftApp, ui: &mut Ui) { let units = app.session.general_unit(); section_header(ui, tl!("Document")); @@ -416,3 +465,58 @@ pub fn type_sections(app: &mut VectorcraftApp, ui: &mut Ui) { } }); } + +#[cfg(test)] +mod tests { + use egui::{Event, PointerButton, Pos2, Rect, vec2}; + use vectorcraft_engine::Session; + + use super::*; + + /// One frame of the panel with `events` → the texts painted, with their rects. + fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec) -> Vec<(String, Rect)> { + let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(300.0, 700.0))), events, ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| show(app, ui)); + out.textures_delta.clear(); + out.shapes + .iter() + .filter_map(|s| { + if let egui::Shape::Text(t) = &s.shape { + Some((t.galley.text().to_string(), Rect::from_min_size(t.pos, t.galley.size()))) + } else { + None + } + }) + .collect() + } + + fn click(app: &mut VectorcraftApp, ctx: &egui::Context, texts: &[(String, Rect)], label: &str) { + let at = texts.iter().find(|(t, _)| t == label).map(|(_, r)| r.center()).unwrap_or_else(|| panic!("no {label:?} in {texts:?}")); + let b = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() }; + frame(app, ctx, vec![Event::PointerMoved(at), b(true)]); + frame(app, ctx, vec![b(false)]); + } + + /// #530: Edit Artboards shows the active artboard, with New Artboard, Delete Artboard and Exit. + #[test] + fn edit_artboards_adds_artboards_and_exits() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + let ctx = egui::Context::default(); + let texts = frame(&mut app, &ctx, vec![]); + click(&mut app, &ctx, &texts, "Edit Artboards"); + assert_eq!(app.session.tool_id(), "artboard"); + let texts = frame(&mut app, &ctx, vec![]); + assert!(texts.iter().any(|(t, _)| t == "Artboard 1") && texts.iter().any(|(t, _)| t == "200 pt"), "{texts:?}"); + click(&mut app, &ctx, &texts, "New Artboard"); + let boards = |app: &VectorcraftApp| app.session.active().unwrap().doc.artboards.len(); + assert_eq!(boards(&app), 2); + let texts = frame(&mut app, &ctx, vec![]); + assert!(texts.iter().any(|(t, _)| t == "Artboard 2"), "the new artboard is the active one: {texts:?}"); + click(&mut app, &ctx, &texts, "Delete Artboard"); + assert_eq!(boards(&app), 1); + let texts = frame(&mut app, &ctx, vec![]); + click(&mut app, &ctx, &texts, "Exit"); + assert_eq!(app.session.tool_id(), "selection"); + } +}