mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #1879 from RICHRAN2030/feature/image-trace-adjustments
Fix Image Trace adjustment discovery and slider commits
This commit is contained in:
@@ -133,10 +133,23 @@ 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.
|
||||
// A failure is reported in the status bar. The panel stays as it was: the Image Trace
|
||||
// button (Properties, Control bar) opens it.
|
||||
let _ = app.run("imageTrace.make", json!({ "preset": preset }));
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// while the menu is open.
|
||||
/// A preset's name as shown: translated as a preset's name where the catalog has it so (the
|
||||
@@ -187,7 +200,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 +212,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 +233,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 +331,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 +352,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 +388,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 +499,19 @@ mod tests {
|
||||
}
|
||||
|
||||
fn traced_app() -> VectorcraftApp {
|
||||
traced_app_with_ink(0)
|
||||
}
|
||||
|
||||
/// A quick trace, then the Image Trace button.
|
||||
fn trace_and_open(app: &mut VectorcraftApp, preset: &str) {
|
||||
trace_with(app, preset);
|
||||
open_settings(app);
|
||||
}
|
||||
|
||||
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 +520,162 @@ 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_and_open(&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_quick_trace_leaves_the_panel_as_it_was() {
|
||||
let mut app = traced_app();
|
||||
app.run("imageTrace.release", json!({})).unwrap();
|
||||
app.ui.open_panel = None;
|
||||
app.ui.image_trace_advanced = false;
|
||||
trace_with(&mut app, "Sketched Art");
|
||||
assert!(is_trace(&first_selected(&app).unwrap()));
|
||||
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_and_open(&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_and_open(&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_and_open(&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]
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
|
||||
# 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. The original image stays inside the tracing object so
|
||||
that subsequent adjustments always use the original pixels.
|
||||
|
||||
To adjust the result, select the tracing object and click **Image Trace** in Properties or the
|
||||
image icon in the Control bar: the Image Trace panel opens with **Advanced** expanded. 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 opening the panel,
|
||||
a quick trace leaving it as it was, 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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
-1
@@ -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) |
|
||||
|
||||
Reference in New Issue
Block a user