fix(UI.TRACE): expose trace adjustments and reliably apply and undo them

This commit is contained in:
RICHRAN2030
2026-10-16 01:36:25 +07:00
committed by bflatastic
parent ce133351fd
commit 313b1a52b9
7 changed files with 250 additions and 12 deletions
+196 -5
View File
@@ -133,8 +133,22 @@ pub(crate) fn view_row(app: &mut VectorcraftApp, ui: &mut Ui, current: TraceView
/// Trace the selected image, or trace the selected Image Trace object again, with built-in `preset`.
fn trace_with(app: &mut VectorcraftApp, preset: &str) {
// A failure is reported in the status bar.
let _ = app.run("imageTrace.make", json!({ "preset": preset }));
// Only reveal the controls after a successful trace; failures stay in the status bar.
if app.run("imageTrace.make", json!({ "preset": preset })).is_ok() {
open_settings(app);
}
}
/// Reveal the editable tracing controls, including fidelity, in the panel's current location.
pub(crate) fn open_settings(app: &mut VectorcraftApp) {
// Icon panels toggle when activated, so an already open flyout must stay open.
if app.ui.open_panel.as_deref() != Some("imageTrace")
&& let Err(e) = app.run("window.panel.activate", json!({ "panel": "imageTrace" }))
{
app.ui.status = e;
return;
}
app.ui.image_trace_advanced = true;
}
/// The built-in presets to choose from (`current` highlighted) → the one chosen. Listed only
@@ -187,7 +201,7 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) -> bool {
preset_dropdown(app, ui, &preset, 150.0);
view_row(app, ui, view, 190.0);
if widgets::icon_button(ui, "image", tl!("Image Trace"), false, 24.0).clicked() {
app.ui.open_panel = Some("imageTrace".into());
open_settings(app);
}
if widgets::flat_button(ui, tl!("Expand"), 64.0).clicked() {
app.run("imageTrace.expand", json!({})).ok();
@@ -199,7 +213,14 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) -> bool {
fn trace(app: &mut VectorcraftApp, st: &mut TraceUi) {
let preset = if st.preset == "Custom" { "Default" } else { st.preset.as_str() };
match app.run("imageTrace.make", json!({ "preset": preset, "params": st.params, "view": st.view.id() })) {
Ok(r) => st.info = Some((r["paths"].as_u64().unwrap_or(0), r["anchors"].as_u64().unwrap_or(0), r["colors"].as_u64().unwrap_or(0))),
Ok(r) => {
st.info = Some((r["paths"].as_u64().unwrap_or(0), r["anchors"].as_u64().unwrap_or(0), r["colors"].as_u64().unwrap_or(0)));
// Adopt the committed state immediately: Undo before the next panel frame must
// still be recognized as a change, even when it returns to the previous settings.
if let Some(stored) = target(app).2 {
st.synced = stored;
}
}
Err(e) => app.ui.status = e,
}
}
@@ -213,6 +234,9 @@ fn slider(ui: &mut Ui, label: &str, v: &mut f64, range: std::ops::RangeInclusive
ui.visuals_mut().widgets.inactive.bg_fill = t.input_border;
ui.visuals_mut().selection.bg_fill = t.accent;
let r = ui.add(egui::Slider::new(v, range).suffix(suffix).integer().trailing_fill(true));
if cfg!(test) {
ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new(("trace-slider", label)), r.rect));
}
out = (r.changed(), r.drag_stopped() || (r.changed() && !r.dragged()));
});
out
@@ -308,6 +332,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let (changed, release) = slider(ui, label, &mut v, range, suffix);
if changed {
st.params[key] = if key == "colorDetail" { json!(v.round()) } else { json!(v.round() as u64) };
}
// Keep the preset's Enhanced row (and widget identities) until the drag finishes.
// Switching to Custom mid-drag removes that row and loses the slider's release event.
if release {
st.preset = "Custom".into();
}
retrace |= release;
@@ -325,6 +353,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let (changed, release) = slider(ui, label, &mut v, range, suffix);
if changed {
st.params[key] = if key == "noise" { json!(v.round() as u64) } else { json!(v.round()) };
}
if release {
st.preset = "Custom".into();
}
retrace |= release;
@@ -359,6 +389,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let (changed, release) = ui.add_enabled_ui(strokes, |ui| slider(ui, tl!("Stroke"), &mut v, 1.0..=100.0, " px")).inner;
if changed {
st.params["strokeWidth"] = json!(v.round());
}
if release {
st.preset = "Custom".into();
}
retrace |= release;
@@ -468,9 +500,13 @@ mod tests {
}
fn traced_app() -> VectorcraftApp {
traced_app_with_ink(0)
}
fn traced_app_with_ink(ink: u8) -> VectorcraftApp {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
let img = image::RgbaImage::from_fn(20, 20, |x, _| image::Rgba(if x < 10 { [0, 0, 0, 255] } else { [255, 255, 255, 255] }));
let img = image::RgbaImage::from_fn(20, 20, |x, _| image::Rgba(if x < 10 { [ink, ink, ink, 255] } else { [255, 255, 255, 255] }));
let mut png = vec![];
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
let data = vectorcraft_format::base64_encode(&png);
@@ -479,6 +515,161 @@ mod tests {
app
}
#[test]
fn sketch_quick_trace_reveals_all_adjustments_and_keeps_the_source() {
let mut app = traced_app();
app.run("imageTrace.release", json!({})).unwrap();
app.ui.image_trace_advanced = false;
trace_with(&mut app, "Sketched Art");
assert_eq!(app.ui.open_panel.as_deref(), Some("imageTrace"));
assert!(app.ui.image_trace_advanced);
assert_eq!(selected_trace(&app).unwrap().0, "Sketched Art");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let texts = frame(&mut app, &ctx, vec![]);
for label in ["Threshold", "Paths", "Corners", "Noise", "Method:", "Fills", "Strokes", "Ignore Color"] {
assert!(texts.iter().any(|(t, _)| t == label), "missing {label}: {texts:?}");
}
let n = first_selected(&app).unwrap();
assert!(is_trace(&n));
assert!(matches!(n.children().unwrap()[0].kind, NodeKind::Image(_)));
// Opening controls again neither closes the flyout nor adds a document undo step.
let steps = app.session.active().unwrap().history.undo.len();
open_settings(&mut app);
assert_eq!(app.ui.open_panel.as_deref(), Some("imageTrace"));
assert_eq!(app.session.active().unwrap().history.undo.len(), steps);
// Optional local visual QA; no reference images or generated captures are committed.
if let Some(path) = std::env::var_os("VECTORCRAFT_TRACE_CAPTURE") {
let mut ready = false;
let mut harness = egui_kittest::Harness::builder().with_size(vec2(340.0, 820.0)).wgpu().build_ui_state(
move |ui, app: &mut VectorcraftApp| {
if !ready {
egui_extras::install_image_loaders(ui.ctx());
crate::theme::install_fonts(ui.ctx());
crate::theme::apply(ui.ctx(), crate::theme::Brightness::MediumDark);
ready = true;
return;
}
show(app, ui);
},
app,
);
harness.run_steps(4);
harness.render().unwrap().save(path).unwrap();
}
}
#[test]
fn a_failed_quick_trace_does_not_open_settings() {
let mut app = traced_app();
app.run("select.none", json!({})).unwrap();
app.ui.open_panel = None;
app.ui.image_trace_advanced = false;
trace_with(&mut app, "Sketched Art");
assert!(app.ui.open_panel.is_none());
assert!(!app.ui.image_trace_advanced);
}
#[test]
fn quick_trace_activates_the_existing_floating_panel() {
let mut app = traced_app();
app.run("window.panel.float", json!({"panel": "imageTrace"})).unwrap();
let before = app.ui.docking.clone().unwrap();
trace_with(&mut app, "Sketched Art");
let after = app.ui.docking.as_ref().unwrap();
assert_eq!(after.floating.len(), before.floating.len());
assert!(after.floating.iter().any(|g| g.panels == ["imageTrace"]));
assert!(app.ui.open_panel.is_none(), "the existing floating group owns the panel");
}
#[test]
fn adjusting_sketch_threshold_changes_the_vector_result_and_can_be_undone() {
let mut app = traced_app_with_ink(100);
trace_with(&mut app, "Sketched Art");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
frame(&mut app, &ctx, vec![]);
let rail = ctx.data(|d| d.get_temp::<Rect>(egui::Id::new(("trace-slider", "Threshold")))).unwrap();
let at = rail.left_center() + vec2(20.0, 0.0);
assert_eq!(first_selected(&app).unwrap().children().unwrap().len(), 2, "source and ink path");
for pressed in [true, false] {
frame(
&mut app,
&ctx,
vec![
Event::PointerMoved(at),
Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() },
],
);
}
assert_eq!(first_selected(&app).unwrap().children().unwrap().len(), 1, "only the source remains below the ink's luminance");
app.run("edit.undo", json!({})).unwrap();
assert_eq!(first_selected(&app).unwrap().children().unwrap().len(), 2);
frame(&mut app, &ctx, vec![]);
let state: TraceUi = pstate(&ctx, "image-trace");
assert_eq!(state.params["threshold"], 150);
}
#[test]
fn sketch_adjustment_sliders_retrace_on_release() {
let mut app = traced_app();
trace_with(&mut app, "Sketched Art");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
for (label, key) in [("Threshold", "threshold"), ("Paths", "paths"), ("Corners", "corners"), ("Noise", "noise")] {
frame(&mut app, &ctx, vec![]);
let rail = ctx.data(|d| d.get_temp::<Rect>(egui::Id::new(("trace-slider", label)))).unwrap();
let at = rail.left_center() + vec2(20.0, 0.0);
let press = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() };
let before = target(&app).2.unwrap()["params"][key].clone();
let preset_before = pstate::<TraceUi>(&ctx, "image-trace").preset;
let steps = app.session.active().unwrap().history.undo.len();
frame(&mut app, &ctx, vec![Event::PointerMoved(at), press(true)]);
assert_eq!(app.session.active().unwrap().history.undo.len(), steps, "{label} waits for release");
let draft: TraceUi = pstate(&ctx, "image-trace");
assert_ne!(draft.params[key], before, "{label} updates while held");
assert_eq!(draft.preset, preset_before, "the preset row stays in place during {label}");
frame(&mut app, &ctx, vec![press(false)]);
let stored = target(&app).2.unwrap();
assert_ne!(
stored["params"][key],
before,
"{label} reaches the tracer; rail {rail:?}, draft {:?}",
pstate::<TraceUi>(&ctx, "image-trace").params
);
assert_eq!(stored["preset"], "Custom");
assert_eq!(app.session.active().unwrap().history.undo.len(), steps + 1, "one undo step for {label}");
frame(&mut app, &ctx, vec![]);
let state: TraceUi = pstate(&ctx, "image-trace");
assert_eq!(state.params, stored["params"], "{label} survives the next frame");
}
}
#[test]
fn properties_can_reopen_the_trace_controls() {
let mut app = traced_app();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut at = None;
for _ in 0..2 {
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| super::super::properties::show(&mut app, ui));
let texts = crate::tests_removeanchors::shapes_text(&out.shapes.iter().map(|c| c.shape.clone()).collect::<Vec<_>>());
// The first Image Trace text is the section heading, the last is the button.
at = texts.iter().rev().find(|(t, _)| t == "Image Trace").map(|(_, r)| r.center());
out.textures_delta.clear();
}
let at = at.unwrap();
for pressed in [true, false] {
let events = vec![
Event::PointerMoved(at),
Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() },
];
let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| super::super::properties::show(&mut app, ui));
out.textures_delta.clear();
}
assert_eq!(app.ui.open_panel.as_deref(), Some("imageTrace"));
}
/// #1358: in Properties the View row fits the panel, so the panel doesn't widen and clip its
/// labels (the row took the panel's whole width for its menu, beside its label).
#[test]
+4 -1
View File
@@ -163,7 +163,7 @@ fn image_section(app: &mut VectorcraftApp, ui: &mut Ui) {
}
/// The one selected Image Trace object: its preset (choosing another traces it again with that
/// one), view, Expand and Release.
/// one), view, a button to reveal the tracing controls, Expand and Release.
fn trace_section(app: &mut VectorcraftApp, ui: &mut Ui, preset: &str, view: vectorcraft_doc::TraceView) {
section_header(ui, tl!("Image Trace"));
ui.horizontal(|ui| {
@@ -171,6 +171,9 @@ fn trace_section(app: &mut VectorcraftApp, ui: &mut Ui, preset: &str, view: vect
crate::panels::image_trace::preset_dropdown(app, ui, preset, ui.available_width());
});
ui.horizontal(|ui| crate::panels::image_trace::view_row(app, ui, view, ui.available_width()));
if widgets::flat_button(ui, tl!("Image Trace"), ui.available_width()).clicked() {
crate::panels::image_trace::open_settings(app);
}
let w = (ui.available_width() - 6.0) / 2.0;
ui.horizontal(|ui| {
for (label, id) in [(tl!("Expand"), "imageTrace.expand"), (tl!("Release"), "imageTrace.release")] {
+4 -1
View File
File diff suppressed because one or more lines are too long
+37
View File
@@ -0,0 +1,37 @@
# Adjusting an Image Trace
Select a placed raster image, then choose **Image Trace** or a preset from its arrow
in Properties or the Control bar. A successful trace opens the Image Trace panel with
**Advanced** expanded, including when choosing **Sketched Art**. The original image stays
inside the tracing object so that subsequent adjustments always use the original pixels.
If the panel was closed, select the tracing object and click **Image Trace** in Properties
or the image icon in the Control bar. An existing floating or docked panel is activated in
its current location. Opening the controls does not add a document undo step.
| Control | Range | Effect |
|---|---|---|
| Threshold (Black and White) | 0–255 | Higher values include lighter pixels as black ink. |
| Paths | 0–100% | Higher values fit outlines more closely to the image. |
| Corners | 0–100% | Higher values preserve more sharp turns. |
| Noise | 1–100 px | Suppresses small connected regions; higher values remove more small details. |
| Method | Abutting / Overlapping | Chooses shared boundaries or stacked overlapping paths. |
| Create | Fills / Strokes | Creates filled areas, stroked lines, or both. |
| Ignore Color | Color picker | Leaves out the selected color (white by default). |
Sliders apply on release, and their numeric fields can be edited directly. Each committed
adjustment re-traces the selected object as one undo step and changes the preset to Custom.
While dragging, the preset and Enhanced row stay in place until release so the
slider retains its active interaction. Committed settings are synchronized immediately,
so Undo also restores the panel values before its next frame. View can show the source image, the result, outlines, or combinations without re-tracing.
Use **Expand** when finished to turn the result into ordinary editable vector objects;
after expanding, the tracing settings are no longer available. **Release** restores the source.
These controls use VectorCraft's existing tracing algorithm. Parameter effects follow its
implementation; they do not claim pixel-identical results with another application.
The terminology and general control behavior are described in the
[public Image Trace panel documentation](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/image-trace-panel-options.html).
Regression coverage: `crates/ui-egui/src/panels/image_trace.rs` tests panel discovery,
failure handling, repeated opening, floating ownership, slider commits, visible threshold
changes, undo, and the Properties entry point. Tracer tests live in `crates/trace/src/tests.rs`.
+3 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (later 2026-10-14 merges: macOS save sheet Format menu back to missing, #1855, #866 reopened; anchor X/Y/W/H in the Transform panel, Properties and Control bar, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar labels, columns and the All Tools drawer, #1751; Actions panel selection, #1629 in part; scores unchanged; 2026-10-14 merges: anchor Align and Distribute, #1636; Delete on closed paths and whole objects, #1674; selection inside isolation, #1666; Escape in a Direct Selection drag and while typing, #1743, #1686; Pen clicks under 3 px, #1663; [ and ] on more tools, #1661 in part; fields commit on click away, #1787; command search keys, #1757; a guide's position in the Transform panel is a convenience with no parity claim, #1659; scores unchanged; intersect Smart Guides for drawn points and dragged anchors, #1602 in part; a ruler guide's position in the Transform panel, #1597 in part; Symbol Options before New Symbol, #1560; tool flyout widths, #1616; Pathfinder Trim, Merge and Crop strokes, #1570; scores unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; Join merges anchors lying on one another, #1533; correction: Average and Join with a corner or smooth choice is missing, not done; guide objects take the guide colour and style and Lock Guides, #1534; Keyboard Shortcuts lists commands outside the menus, #1539; round corners next to curves and the Smooth tool's reach, #1512; Make Compound Path on a group, #1537; dialog slider rails, #1483; template layers stay in Preview in Outline view, #1486; Direct Selection takes the selected object's anchor where anchors coincide, #1508; Tabs panel stop drags, #1458; Split Into Grid dialog, #1462; Transform Objects and Reflect's angle, #1448 and #1460; Pathfinder panel on a group, #1450; value fields: readable selection, #1465 in part, and a focused field follows its value, #1457; scores unchanged; Transform dialogs have a live Preview and step with the arrow keys, #1412; transform tools, dialogs and Move act on direct-selected anchors, #1408; the bounding box hides during a corner drag, #1229; scores unchanged; dialogs focus their first field on every open, #1367, and a first field that starts greyed out once it is enabled, #1395; scores unchanged; Rectangular Grid and Polar Grid click dialogs take the skew, frame, compound path and Fill Grid options, #1377 in part, score unchanged; Live Corners: a Direct Selection click on a widget selects its corner, #1239; custom ruler origin, #1212; Artboard tool clicks fit the topmost editable object in one undo step (#1227); scores unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; DXF control-point SPLINE closure read, #1299 follow-up; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Simplify preview/counts and valid rewritten-path selection (#1272); parity remains partial; Texture-effect drag redraws optimized (#1257); idle Windows interaction still unverified; scores unchanged; Curvature point display improved (#1236, in part); scores unchanged; Direct Selection: no-op anchor Align preserves Undo, Redo and saved state (#1180 follow-up); Eyedropper cursor distinguished from precise crosshair (#1269); scores unchanged; Eyedropper can read visible locked and outside-isolation sources (#1251); scores unchanged; Direct Selection: independent segment picks and Delete (#1278); scores unchanged; Adding paint to an unpainted zero-width stroke restores the 1 pt default (#1253); scores unchanged; macOS save sheets offer a Format menu, #866; Attributes fill-rule controls edit the compound when a member is selected, #1197; a marquee needs to touch a path, not just lie inside its fill, #1103; earlier: Live Corners round corners between curved sides, #1144) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (Image Trace adjustment discovery; later 2026-10-14 merges: macOS save sheet Format menu back to missing, #1855, #866 reopened; anchor X/Y/W/H in the Transform panel, Properties and Control bar, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar labels, columns and the All Tools drawer, #1751; Actions panel selection, #1629 in part; scores unchanged; 2026-10-14 merges: anchor Align and Distribute, #1636; Delete on closed paths and whole objects, #1674; selection inside isolation, #1666; Escape in a Direct Selection drag and while typing, #1743, #1686; Pen clicks under 3 px, #1663; [ and ] on more tools, #1661 in part; fields commit on click away, #1787; command search keys, #1757; a guide's position in the Transform panel is a convenience with no parity claim, #1659; scores unchanged; intersect Smart Guides for drawn points and dragged anchors, #1602 in part; a ruler guide's position in the Transform panel, #1597 in part; Symbol Options before New Symbol, #1560; tool flyout widths, #1616; Pathfinder Trim, Merge and Crop strokes, #1570; scores unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; Join merges anchors lying on one another, #1533; correction: Average and Join with a corner or smooth choice is missing, not done; guide objects take the guide colour and style and Lock Guides, #1534; Keyboard Shortcuts lists commands outside the menus, #1539; round corners next to curves and the Smooth tool's reach, #1512; Make Compound Path on a group, #1537; dialog slider rails, #1483; template layers stay in Preview in Outline view, #1486; Direct Selection takes the selected object's anchor where anchors coincide, #1508; Tabs panel stop drags, #1458; Split Into Grid dialog, #1462; Transform Objects and Reflect's angle, #1448 and #1460; Pathfinder panel on a group, #1450; value fields: readable selection, #1465 in part, and a focused field follows its value, #1457; scores unchanged; Transform dialogs have a live Preview and step with the arrow keys, #1412; transform tools, dialogs and Move act on direct-selected anchors, #1408; the bounding box hides during a corner drag, #1229; scores unchanged; dialogs focus their first field on every open, #1367, and a first field that starts greyed out once it is enabled, #1395; scores unchanged; Rectangular Grid and Polar Grid click dialogs take the skew, frame, compound path and Fill Grid options, #1377 in part, score unchanged; Live Corners: a Direct Selection click on a widget selects its corner, #1239; custom ruler origin, #1212; Artboard tool clicks fit the topmost editable object in one undo step (#1227); scores unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; DXF control-point SPLINE closure read, #1299 follow-up; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Simplify preview/counts and valid rewritten-path selection (#1272); parity remains partial; Texture-effect drag redraws optimized (#1257); idle Windows interaction still unverified; scores unchanged; Curvature point display improved (#1236, in part); scores unchanged; Direct Selection: no-op anchor Align preserves Undo, Redo and saved state (#1180 follow-up); Eyedropper cursor distinguished from precise crosshair (#1269); scores unchanged; Eyedropper can read visible locked and outside-isolation sources (#1251); scores unchanged; Direct Selection: independent segment picks and Delete (#1278); scores unchanged; Adding paint to an unpainted zero-width stroke restores the 1 pt default (#1253); scores unchanged; macOS save sheets offer a Format menu, #866; Attributes fill-rule controls edit the compound when a member is selected, #1197; a marquee needs to touch a path, not just lie inside its fill, #1103; earlier: Live Corners round corners between curved sides, #1144) · **Target:** Adobe Illustrator 2026 (30.x)
How VectorCraft's tools, handles, snapping and panels behave next to Illustrator's. A vector illustrator's
trust is in the details: where a handle lands, what Shift and Alt do mid-drag, what a smart guide snaps to,
@@ -190,6 +190,7 @@ panel-menu commands such as Break Link to Symbol), so they can be given shortcut
| Floating groups stack into sets; sets dock as extra dock columns (a multi-column dock); groups drop anywhere in a set or column, or onto its tabs; a column taller than the window scrolls | done | `window.panel.float {below, above}`, `window.panel.dock {column}` |
| Panels outside the app window (another monitor) | missing | single OS window |
| Edit Toolbar (•••): the All Tools drawer opens beside the toolbar by its button (Escape or a click outside closes it), dims the tools already in the toolbar; while it is open, tools drag from it onto the toolbar (between two tools, or onto one to join its group), from the toolbar into it, and within the toolbar; each toolbar keeps its edits | done | #1018; `window.editToolbar`, `toolbar.addTool`/`removeTool`/`reset`; the drawer's Reset (Illustrator has it in a drawer menu); dragging a toolbar button moves the tool it shows, not its whole group; New/Manage Toolbars missing; tool flyouts are as wide as their longest translated name and shortcut need (#1616) |
| Quick Image Trace presets reveal editable controls; Properties reopens them | done | Threshold, Paths, Corners and Noise; `panels/image_trace.rs` headless interaction tests. No algorithm parity claim. |
| Toolbar without group labels by default, one or two columns; All Tools drawer by flyout group with shortcuts, its menu (Reset, Basic, Advanced) and Show section | done | #1751; a preference brings the labels back; `toolbar.show` sets the Fill/Stroke, Coloring, Draw Mode and Screen Mode controls |
| Actions panel: a clicked action stays selected for Play Current Selection and Delete Selection | partial | #1853 (part of #1629); editing actions and action shortcuts remain |
| Properties panel per context (every selection type's Quick Actions) | partial | |
@@ -223,6 +224,7 @@ re-measure of the panels added since. Slider rails show on Medium Dark (#1808, #
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | Quick Image Trace presets reveal the adjustment panel with Advanced open; Properties reopens it. Sliders keep their preset row until release so the re-trace is committed. UI regressions added; scores and remaining estimates unchanged. |
| 2026-10-14 | minor | The macOS save sheet's Format menu goes back to missing: #1855 withdrew it to restore saving (#866 reopened). Rows added for the X/Y/W/H of direct-selected anchors (#1839, fixes #1824), the toolbar and All Tools drawer (#1751) and the Actions panel selection (#1853, part of #1629); the snapping row notes Snap to Grid over Snap to Pixel (#1840). Scores unchanged (estimated) |
| 2026-10-14 | minor | Align and Distribute act on the anchor points selected with Direct Selection: the points of one path align to each other, not to the artboard, and Distribute spaces them (#1641, fixes #1636; aligning to a key anchor is still missing); Delete removes every selected anchor of a closed path (it used to remove the wrong ones), removes objects selected whole along with the selected anchors of others, and keeps the remainder selected so a second Delete removes it (#1674); In isolation mode Direct Selection and Group Selection pick only inside the isolated object (#1666); Escape cancels a Direct Selection drag (#1803, #1743); Escape while typing ends the edit, keeps the type selected and switches to the Selection tool (#1802, #1686, #1656); A filled Pencil path keeps a stroke of None (#1804, part of #1679); With the Pen, a press that moves under 3 screen pixels is a click (a corner without handles), and a Precise Cursors command toggles the existing preference and can take a shortcut (#1663); The [ and ] keys reach the Blob Brush, the Eraser and the Symbolism tools in the app (#1734, part of #1661); In the Pathfinder panel a group selected alone counts its nested shapes, so an expanded stroked object keeps the Shape Modes enabled (#1642, #1635); Object › Expand turns radial, grid and mirror repeats into groups of their instances (#1765, #1756); Select › Same matches type on its characters' fill and stroke (#1746, #1737); Fields commit what was typed when you click away or move to another field, as Enter does; Escape still discards it (#1787); Up and Down move through the command search results and Enter runs the highlighted one (#1798, #1757); Slider rails show on Medium Dark (#1808, #1662); On Windows the custom title bar's window stays truly borderless, so the pointer lines up with what is drawn on systems where the hidden caption offset it (#1791, #1758; awaiting the reporter's confirmation); Tool cursors are painted into the window on Wayland (#1652; not run on Linux, #1645 stays open until confirmed); the guide-position row (#1659) is now "not scored": the documentation found doesn't establish it, and it is kept as a convenience pending the owner's decision; scores unchanged |
| 2026-10-13 | minor | Pathfinder's Trim, Merge and Crop remove the objects' strokes, and Merge joins same-fill objects whatever their strokes were, as the public Pathfinder page describes (#1572, fixes #1570); Offset Path with an inset larger than the shapes says the offset leaves nothing, instead of asking to select paths (#1591, fixes #1590); The Symbols panel's New Symbol opens Symbol Options to name the symbol first, and Alt-click skips it (#1566, fixes #1560); Tool flyouts are as wide as their longest translated name and shortcut need, so names no longer run into shortcuts in Spanish and other languages (#1650, fixes #1616); Smart Guides: a point drawn with the Pen or a shape tool, and an anchor dragged with Direct Selection, snaps to where paths and ruler guides cross, labelled "intersect" (#1658, part of #1602; a moved selection's own anchors don't); The Transform panel shows a selected ruler guide's X or Y, and typing a value moves it (#1659, part of #1597). This is not established by the documentation we could find: the public help pages on guides, rulers and the Transform, Control and Properties panels (read again on 2026-10-14) describe selecting and dragging unlocked guides and the panels' X and Y for a selection, not a guide's position in a panel, and the one public statement of it is a user's [community answer](https://community.adobe.com/questions-652/adding-a-new-custom-guide-785586), which is not documentation. It is kept as a convenience pending the owner's decision, with no parity claim and nothing counted towards a score; Select Behind (Ctrl/Cmd-click) tests each object once, so a deep stack of overlapping objects no longer slows it; same objects in the same order (#1571); Commands that take `ids` run on the objects given with nothing selected, over MCP, the control channel and the CLI too (#1577, fixes #1575); scores unchanged (estimated) |