mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-22 19:08:39 +00:00
Move dialog opens on the last drag or Move, with its last checkboxes
This commit is contained in:
1 parent
b9309f7985
commit
6daf74fb6d
8 files changed
+203
-12
No files matched your search
@@ -99,7 +99,7 @@ pub mod text_import;
|
||||
mod tiff_bmp_tga;
|
||||
pub mod tile_edge_color;
|
||||
mod tools;
|
||||
mod transform;
|
||||
pub(crate) mod transform;
|
||||
pub mod transform_each;
|
||||
pub mod variables;
|
||||
mod warp_effect;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
//! Transform dialogs: Move, Rotate, Scale, Reflect and Shear (with Transform Objects, Transform
|
||||
//! Patterns, Copy and Preview; Scale also has Uniform, Scale Corners and Scale Strokes & Effects;
|
||||
//! Reflect's axis is Horizontal, Vertical or an angle). With Preview on
|
||||
//! (`preview`, off when the dialog opens), the transform shows on the canvas as the values change.
|
||||
//! Reflect's axis is Horizontal, Vertical or an angle). With Preview on (`preview`), the transform
|
||||
//! shows on the canvas as the values change.
|
||||
//!
|
||||
//! Move opens on the offset moved last, by a drag or by itself, and with the checkboxes (Preview
|
||||
//! too) it was last confirmed with, so Enter, Enter repeats a move or a copy at the same spacing.
|
||||
//! The other dialogs open with Preview off.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -72,6 +76,42 @@ fn clean(v: f64) -> f64 {
|
||||
(v * 1e6).round() / 1e6 + 0.0
|
||||
}
|
||||
|
||||
/// The Move dialog's fields as it opens: Horizontal and Vertical are the offset moved last, and
|
||||
/// Copy, Transform Objects and Preview are as it was last confirmed with (Transform Patterns is
|
||||
/// General › Transform Pattern Tiles, which its OK sets).
|
||||
pub(crate) fn move_fields(app: &VectorcraftApp) -> Value {
|
||||
let (dx, dy) = last_move(app).unwrap_or_default();
|
||||
let mut fields = json!({"dx": format!("{} pt", clean(dx)), "dy": format!("{} pt", clean(dy))});
|
||||
let kept = &app.ui.move_options;
|
||||
let kept_bool = |k: &str| kept.get(k).and_then(Value::as_bool);
|
||||
for k in ["copy", "objects", "preview"] {
|
||||
if let Some(on) = kept_bool(k) {
|
||||
fields[k] = json!(on);
|
||||
}
|
||||
}
|
||||
fields
|
||||
}
|
||||
|
||||
/// The offset of the last move the session's journal keeps: a drag that moved the selection (a
|
||||
/// Transform that only translates) or Move.
|
||||
fn last_move(app: &VectorcraftApp) -> Option<(f64, f64)> {
|
||||
let length = |v: Option<&Value>| match v {
|
||||
Some(Value::Number(n)) => n.as_f64(),
|
||||
Some(Value::String(s)) => vectorcraft_doc::Unit::Points.parse(s),
|
||||
_ => Some(0.0),
|
||||
};
|
||||
let finite = |(dx, dy): (f64, f64)| (dx.is_finite() && dy.is_finite()).then_some((dx, dy));
|
||||
app.session.journal.iter().rev().find_map(|(id, p)| match id.as_str() {
|
||||
"object.move" => finite((length(p.get("dx"))?, length(p.get("dy"))?)),
|
||||
"object.transform" => {
|
||||
let [a, b, c, d, e, f] = vectorcraft_engine::cmd::matrix_param(p, "matrix")?.as_coeffs();
|
||||
let translates = (a, b, c, d) == (1.0, 0.0, 0.0, 1.0) && (e, f) != (0.0, 0.0);
|
||||
translates.then_some((e, f)).and_then(finite)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Transform Patterns: the dialog's field, else General › Transform Pattern Tiles.
|
||||
fn transform_patterns(app: &VectorcraftApp, d: &Dialog) -> bool {
|
||||
d.fields.get("patterns").and_then(Value::as_bool).unwrap_or(app.session.prefs.transform_pattern_tiles)
|
||||
@@ -171,7 +211,16 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
if d.kind == "scale" {
|
||||
params = with_scale_options(app, d, params)?;
|
||||
}
|
||||
run_and_close(app, id, params)
|
||||
let r = run_and_close(app, id, params)?;
|
||||
if d.kind == "move" {
|
||||
app.ui.move_options = json!({"copy": d.bool("copy"), "objects": transform_objects(d), "preview": d.bool("preview")});
|
||||
// Move's Transform Patterns stays as confirmed: it is the preference it starts from.
|
||||
let patterns = transform_patterns(app, d);
|
||||
if patterns != app.session.prefs.transform_pattern_tiles {
|
||||
app.run("prefs.set", json!({"key": "transformPatternTiles", "value": patterns}))?;
|
||||
}
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -336,6 +385,75 @@ mod tests {
|
||||
assert_eq!((b.x0, b.y0), (100.0, 90.0));
|
||||
}
|
||||
|
||||
/// Move opens on the offset of the last drag or Move, and with the checkboxes it was last
|
||||
/// confirmed with: Enter, Enter makes copies at the same spacing.
|
||||
#[test]
|
||||
fn move_opens_on_the_last_move_and_checkboxes() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 300})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let open = |app: &mut VectorcraftApp| {
|
||||
crate::toolbar::open_options(app, "selection").unwrap();
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::dialogs::show(app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
([d.f64("dx", f64::NAN), d.f64("dy", f64::NAN)], ["copy", "objects", "preview"].map(|k| d.bool(k)))
|
||||
};
|
||||
let lefts = |app: &VectorcraftApp| {
|
||||
let kids = app.session.doc().unwrap().doc.layers[0].children().unwrap();
|
||||
kids.iter().map(|n| n.geometric_bounds().unwrap().x0).collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(open(&mut app), ([0.0, 0.0], [false, true, false]), "nothing moved yet");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
// A drag: its offset, and a rotation since doesn't count.
|
||||
app.session.begin_interaction("Move").unwrap();
|
||||
app.session.preview("object.transform", &json!({"matrix": [1, 0, 0, 1, 20, 5]})).unwrap();
|
||||
app.session.commit_interaction().unwrap();
|
||||
app.run("object.rotate", json!({"angle": 90})).unwrap();
|
||||
app.run("object.rotate", json!({"angle": -90})).unwrap();
|
||||
assert_eq!(open(&mut app).0, [20.0, 5.0]);
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.extend([("dx".to_string(), json!(30)), ("dy".to_string(), json!(0)), ("copy".to_string(), json!(true))]);
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(open(&mut app), ([30.0, 0.0], [true, true, false]));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(lefts(&app), [20.0, 50.0, 80.0], "a copy at 30 pt each time");
|
||||
// A drag moves the next one by its offset instead; the checkboxes stay.
|
||||
app.session.begin_interaction("Move").unwrap();
|
||||
app.session.preview("object.transform", &json!({"matrix": [1, 0, 0, 1, 0, 12]})).unwrap();
|
||||
app.session.commit_interaction().unwrap();
|
||||
assert_eq!(open(&mut app), ([0.0, 12.0], [true, true, false]));
|
||||
crate::dialogs::cancel(&mut app);
|
||||
}
|
||||
|
||||
/// The Move dialog's Transform Patterns is as it was last confirmed with: its OK sets General ›
|
||||
/// Transform Pattern Tiles, which it starts from.
|
||||
#[test]
|
||||
fn move_keeps_transform_patterns_in_the_preference() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 300})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let open = |app: &mut VectorcraftApp| {
|
||||
crate::menus::invoke(app, "object.move", json!({}));
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::dialogs::show(app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
app.ui.dialog.as_ref().unwrap().bool("patterns")
|
||||
};
|
||||
app.run("prefs.set", json!({"key": "transformPatternTiles", "value": false})).unwrap();
|
||||
assert!(!open(&mut app));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("patterns".into(), json!(true));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert!(open(&mut app) && app.session.prefs.transform_pattern_tiles, "as confirmed");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
app.run("prefs.set", json!({"key": "transformPatternTiles", "value": false})).unwrap();
|
||||
assert!(!open(&mut app), "the preference");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
}
|
||||
|
||||
/// Transform Patterns starts from General › Transform Pattern Tiles (#394) and goes with the
|
||||
/// command.
|
||||
#[test]
|
||||
|
||||
@@ -3299,7 +3299,8 @@ fn queried_dialog(id: &str) -> Option<(&'static str, &'static [&'static str])> {
|
||||
/// The dialog (kind, fields) the menu item of command `id` opens, as the reference app's does.
|
||||
fn menu_dialog(app: &VectorcraftApp, id: &str) -> Option<(&'static str, Value)> {
|
||||
Some(match id {
|
||||
"object.move" => ("move", json!({"dx": "0 pt", "dy": "0 pt"})),
|
||||
// Move opens on the offset moved last and the checkboxes it was last confirmed with.
|
||||
"object.move" => ("move", crate::dialogs::transform::move_fields(app)),
|
||||
"object.rotate" => ("rotate", json!({"angle": 0})),
|
||||
"object.scale" => ("scale", json!({"sx": 100, "sy": 100, "uniform": true})),
|
||||
"object.transformEach" => (crate::dialogs::transform_each::KIND, crate::dialogs::transform_each::fields()),
|
||||
@@ -3376,8 +3377,9 @@ pub fn invoke(app: &mut VectorcraftApp, id: &str, p: Value) {
|
||||
if waits_for_ime(app, id) {
|
||||
return;
|
||||
}
|
||||
if let Some((kind, fields)) = menu_dialog(app, id)
|
||||
&& p.as_object().is_none_or(|o| o.is_empty())
|
||||
// Params first: some dialogs' fields take a look through the session (Move's last move).
|
||||
if p.as_object().is_none_or(|o| o.is_empty())
|
||||
&& let Some((kind, fields)) = menu_dialog(app, id)
|
||||
{
|
||||
app.ui.dialog = Some(crate::state::Dialog::new(kind, fields));
|
||||
return;
|
||||
|
||||
@@ -1171,6 +1171,67 @@ mod tests {
|
||||
assert_eq!(kind(&app), None, "another tool has its own Enter");
|
||||
}
|
||||
|
||||
/// A drag with the Selection tool, then Enter: the Move dialog opens on the drag's offset, and
|
||||
/// Enter is its OK. With Copy ticked once, each Enter, Enter after that makes another copy at
|
||||
/// the same spacing.
|
||||
#[test]
|
||||
fn enter_after_a_drag_repeats_its_offset() {
|
||||
use vectorcraft_tools::{PointerEvent, PointerKind};
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 800, "height": 300})).unwrap();
|
||||
app.session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let enter = || egui::Event::Key { key: Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE };
|
||||
let frame = |app: &mut VectorcraftApp, events: Vec<egui::Event>| {
|
||||
let screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1280.0, 800.0)));
|
||||
let mut out = ctx.run_ui(egui::RawInput { events, screen_rect, ..Default::default() }, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
};
|
||||
let lefts = |app: &VectorcraftApp| {
|
||||
let kids = app.session.doc().unwrap().doc.layers[0].children().unwrap();
|
||||
kids.iter().map(|n| n.geometric_bounds().unwrap().x0.round()).collect::<Vec<_>>()
|
||||
};
|
||||
app.select_tool("selection");
|
||||
let view = app.view_info();
|
||||
for (kind, x, y) in
|
||||
[(PointerKind::Down, 20.0, 20.0), (PointerKind::Drag, 40.0, 20.0), (PointerKind::Drag, 60.0, 20.0), (PointerKind::Up, 60.0, 20.0)]
|
||||
{
|
||||
app.session.pointer(&PointerEvent::new(kind, x, y), view).unwrap();
|
||||
}
|
||||
let [moved] = lefts(&app)[..] else { panic!("one rectangle") };
|
||||
let step = moved - 10.0;
|
||||
assert!(step > 0.0);
|
||||
// Keys reach the canvas from the second frame on.
|
||||
frame(&mut app, vec![]);
|
||||
frame(&mut app, vec![enter()]);
|
||||
let d = app.ui.dialog.as_mut().expect("Enter opens Move");
|
||||
assert_eq!((d.kind.as_str(), d.f64("dx", f64::NAN).round(), d.f64("dy", f64::NAN)), ("move", step, 0.0));
|
||||
d.fields.insert("copy".into(), json!(true));
|
||||
frame(&mut app, vec![enter()]);
|
||||
for _ in 0..2 {
|
||||
frame(&mut app, vec![enter()]);
|
||||
frame(&mut app, vec![enter()]);
|
||||
}
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(lefts(&app), (0..4).map(|i| moved + step * f64::from(i)).collect::<Vec<_>>());
|
||||
// With Preview on too, each Enter, Enter still makes one copy, and one undo step.
|
||||
frame(&mut app, vec![enter()]);
|
||||
app.ui.dialog.as_mut().expect("Move").fields.insert("preview".into(), json!(true));
|
||||
frame(&mut app, vec![enter()]);
|
||||
let steps = app.session.active().unwrap().history.undo.len();
|
||||
for _ in 0..2 {
|
||||
frame(&mut app, vec![enter()]);
|
||||
assert!(app.ui.dialog.as_ref().is_some_and(|d| d.bool("preview") && d.bool("copy")) && app.session.in_interaction());
|
||||
frame(&mut app, vec![enter()]);
|
||||
assert!(app.ui.dialog.is_none() && !app.session.in_interaction());
|
||||
}
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), steps + 2);
|
||||
assert_eq!(lefts(&app), (0..7).map(|i| moved + step * f64::from(i)).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
/// #1661: typed `[` and `]` reach the tool that takes them: the Blob Brush's and the Eraser's
|
||||
/// size (Option or AltGr brackets too), the stroke weight left as it is; other tools leave them
|
||||
/// alone.
|
||||
|
||||
@@ -452,6 +452,10 @@ pub struct UiState {
|
||||
/// dialog opens on them (#1152).
|
||||
#[serde(default)]
|
||||
pub offset_path: Value,
|
||||
/// The Move dialog's checkboxes as it was last confirmed with (copy, objects, preview; null:
|
||||
/// never used): it opens with them.
|
||||
#[serde(default)]
|
||||
pub move_options: Value,
|
||||
/// The DXF Import Options chosen last (fit, scaleLineweights, center, mergeLayers; null:
|
||||
/// never used).
|
||||
#[serde(default)]
|
||||
@@ -781,6 +785,7 @@ impl Default for UiState {
|
||||
dxf_options: Value::Null,
|
||||
eps_options: Value::Null,
|
||||
offset_path: Value::Null,
|
||||
move_options: Value::Null,
|
||||
dxf_import: Value::Null,
|
||||
home: None,
|
||||
layers_panel: Default::default(),
|
||||
|
||||
@@ -177,7 +177,9 @@ for `eyedropper` it opens Eyedropper Options, an `eyedropperOptions` dialog (fie
|
||||
`appearance.copyFrom` copies). For `hand` it fits the artboard in the window (`view.fitArtboard`) and for `zoom` it
|
||||
shows 100% (`view.actualSize`). For `rotate`, `scale`, `reflect` and `shear` it opens the same dialog as Object ›
|
||||
Transform (dialog kind = the tool id), or fails with `nothing selected`. For `selection`, `directSelection` and
|
||||
`groupSelection` it opens the Move dialog (kind `move`), with the same failure. `ui.key` Enter with one of those seven
|
||||
`groupSelection` it opens the Move dialog (kind `move`), with the same failure; Move opens on the offset of the last
|
||||
drag or Move (`dx`, `dy`) and with the `copy`, `objects` and `preview` it was last confirmed with (its `patterns` is
|
||||
the `transformPatternTiles` preference, which its OK sets). `ui.key` Enter with one of those seven
|
||||
tools opens its dialog too (nothing happens without a selection), with the transform tools' `origin` at their reference
|
||||
point. Gradient tool handles snap to anchors, edges and smart guides; Shift constrains them to 45° steps from the
|
||||
`constrainAngle` preference.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 30/57, 27 remaining (22–38 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; sLine truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 30/57, 27 remaining (22–38 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correctioLine truncated
|
||||
|
||||
The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is
|
||||
the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md),
|
||||
@@ -519,7 +519,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Swatches panel clicks:** Ctrl/Cmd-clicking New Swatch makes a spot color, and Ctrl/Cmd+Shift-clicking it makes a global process color.
|
||||
- **Panel drag and drop:** art dragged off the canvas onto the Symbols panel becomes a symbol, onto the Swatches panel a pattern swatch and onto the Brushes panel an Art brush; a symbol dragged onto the canvas places an instance where it is dropped, and a brush dragged onto a path is applied to it.
|
||||
In the Swatches panel, Alt-dragging a swatch onto another swatch of its kind (not a tint swatch) replaces that swatch's color, gradient or pattern, and the swatch keeps its name and place.
|
||||
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, the Selection, Direct Selection and Group Selection tools the Move dialog (Enter opens these seven tools' dialogs too), Gradient, Eyedropper, Blend and the Liquify tools their panels or options, the Pencil, Paintbrush, Smooth, Blob Brush and Eraser tools their Tool Options (Fidelity, fill, Keep selected, the tolerances, the brush size), and (#812) the Flare tool its Flare Tool Options, the Artboard tool the active artboard's Artboard Options, the graph tools Graph Type for the selected graph, the Symbolism tools the Symbolism Tools Options (Diameter, Intensity, Symbol Set Density) and the Magic Wand tool its panel.
|
||||
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, the Selection, Direct Selection and Group Selection tools the Move dialog (Enter opens these seven tools' dialogs too; Move opens on the offset of the last drag or Move and with the checkboxes it was last confirmed with, so Enter, Enter repeats a move or a copy at the same spacing, as [an Adobe Community thread](https://community.adobe.com/questions-652/move-tool-771221) describes), Gradient, Eyedropper, Blend and the Liquify tools their panels or options, the Pencil, Paintbrush, Smooth, Blob Brush and Eraser tools their Tool Options (Fidelity, fill, Keep selected, the tolerances, the brush size), and (#812) the Flare tool its Flare Tool Options, the Artboard tool the active artboard's Artboard Options, the graph tools Graph Type for the selected graph, the Symbolism tools the Symbolism Tools Options (Diameter, Intensity, Symbol Set Density) and the Magic Wand tool its panel.
|
||||
Keep selected in the Pencil and Paintbrush Tool Options is on by default; while it is off, the path a stroke draws or continues is not selected afterwards and the rest of the selection stays as it was (`path.freehand` `keepSelected`).
|
||||
The Blob Brush Tool Options have Keep selected and Merge only with selection, both off by default: while Keep selected is off, the blob a stroke draws or merges into is not selected afterwards, and while Merge only with selection is on, a stroke merges only with selected blobs of its color that it touches (`path.blob` `keepSelected` and `mergeOnlySelection`).
|
||||
- **Ruler guides as layer objects:** a guide goes on the current layer, shows, hides, locks and is deleted with it, is listed in the Layers panel (its row selects it) and moves to another layer (`guide.setLayer`, or its row dragged onto that layer's rows); guides on a hidden or locked layer can't be selected, moved or deleted, Duplicate copies a layer's guides and Merge Selected and Flatten Artwork keep them on the merged layer; guides from files saved before guides had layers go on the top visible, unlocked layer when the document opens. Not done: guides stacked among the art of their layer (they are listed above it), a guide row's own eye and lock. Lock Guides also locks the objects made into guides: they can't be selected, moved or deleted (#1543); they are drawn in the guide colour and style of Preferences, and a dragged selection snaps to ruler guides with Smart Guides on (#1583, fixes #1534). 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.
|
||||
@@ -730,6 +730,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-16 | minor | The Move dialog opens on the offset of the last drag or Move and with the Copy, Transform Objects and Preview it was last confirmed with (Transform Patterns sets the preference it starts from), so Enter, Enter repeats a move or a copy; scores and hours unchanged |
|
||||
| 2026-10-16 | minor | Image Trace: a loop or junction-returning piece whose fit collapsed to one cubic keeps the fewest segments that hold its area and course instead of every positioned point, so a higher Paths no longer gives fewer segments on code-drawn noisy images (six falling runs to none; one fall with another cause remains). Scores and hours unchanged. |
|
||||
| 2026-10-16 | minor | Image Trace: a notch corner is cut only when the straight edges beside it lie in the shared piece, so both regions of the piece get the same points (code-drawn noisy colour images: 93 uncovered sample points to 0). Scores and hours unchanged. |
|
||||
| 2026-10-16 | minor | Image Trace: a crack's sub-pixel shift is the mean of the readings from its two sides, exactly antisymmetric, so abutting regions get the same points on thin slivers (a code-drawn noisy ripple went from 482–1,228 uncovered sample points per trace to 0). Scores and hours unchanged. |
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (transparency grid visibility saved in native documents, #1968; Live Corners radius up to the neighbouring cuts, #2332; Image Trace quick presets: wording corrected to the shipped behaviour, the Image Trace button opens the controls; batch exporting, #1935; document window scroll bars, #2322; Anchor Point tool: a click on a handle end retracts that handle only, #2267; scores unchanged; Smart Guides: the dragged end of a straight segment keeps to its extension at any angle, #2120; Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; 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 unLine truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (the Move dialog opens on the last move and its last checkboxes; scores unchanged; transparency grid visibility saved in native documents, #1968; Live Corners radius up to the neighbouring cuts, #2332; Image Trace quick presets: wording corrected to the shipped behaviour, the Image Trace button opens the controls; batch exporting, #1935; document window scroll bars, #2322; Anchor Point tool: a click on a handle end retracts that handle only, #2267; scores unchanged; Smart Guides: the dragged end of a straight segment keeps to its extension at any angle, #2120; Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; 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 unLine truncated
|
||||
|
||||
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,
|
||||
@@ -127,8 +127,9 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Free Transform, Scale and Reflect along a rotated box | missing | |
|
||||
| Free Transform widget: constrain, free distort, perspective distort; the key-held variants | done | |
|
||||
| Transform tools: click sets the reference point (snaps to anchors/centres), Alt-click opens the dialog, Alt-release copies | done | with anchors selected with Direct Selection, the tools, the dialogs and Move transform those anchors alone, about the centre of their box by default (#1408; the default point and whole-object copies are our choices) |
|
||||
| Move, Rotate, Scale, Reflect and Shear dialogs: live Preview; Up/Down step angles and percentages by 1, Shift by 10 | done | #1412; Preview starts off (our choice: the public documentation gives no default); OK is one undo step and Transform Again repeats it |
|
||||
| Move, Rotate, Scale, Reflect and Shear dialogs: live Preview; Up/Down step angles and percentages by 1, Shift by 10 | done | #1412; Preview starts off (our choice: the public documentation gives no default), Move's as it was last confirmed; OK is one undo step and Transform Again repeats it |
|
||||
| Transform Again, Transform Each, Scale Strokes & Effects, Scale Corners | done | |
|
||||
| Move opens on the offset of the last move (a drag of the selection or Move) and with the Copy, Transform Objects and Preview it was last confirmed with, so Enter, Enter repeats a move or a copy | done | the public source: [an Adobe Community thread](https://community.adobe.com/questions-652/move-tool-771221) (the dialog shows the last move, drags included); that the checkboxes are kept, Preview among them, and that a nudge with the arrow keys is not a move here are our choices; Transform Patterns is General › Transform Pattern Tiles, which Move's OK sets; the offset is the session's (not kept between launches) |
|
||||
| Transform Objects beside Transform Patterns in the Move, Rotate, Scale, Reflect and Shear dialogs and Transform Each; Reflect across an angle | done | #1448, #1460; off, only the pattern tiles transform; the checkboxes are always enabled (our choice); the link that keeps Transform Each's two scales equal is missing (not documented) |
|
||||
| Split Into Grid dialog: number, height or width, gutter and total for rows and columns, Add Guides, Preview | done | #1462; opens at the selection's size; several paths become one grid painted as the topmost one; a typed height or width keeps the gutter and changes the total (our choice) |
|
||||
| Reference point locator (nine squares) in Control bar and Transform panel | done | |
|
||||
@@ -239,6 +240,7 @@ re-measure of the panels added since. Slider rails show on Medium Dark (#1808, #
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-16 | minor | The Move dialog opens on the offset of the last drag or Move and with the checkboxes it was last confirmed with; scores unchanged |
|
||||
| 2026-10-16 | minor | Native documents restore transparency grid visibility (#1968); save/open regression covers on/off, old files and SVG; scores and hours unchanged (estimated) |
|
||||
| 2026-10-17 | minor | Group Selection picks one path of a compound path (the outer shape or a hole), and a second click adds the compound path (#2441, in part) |
|
||||
| 2026-10-16 | minor | Live Corners: a corner rounds until its cut reaches the neighbouring anchors or their cuts, so one corner of a 100 pt square alone reaches a 100 pt radius instead of 50 (#2332); score unchanged (estimated) |
|
||||
|
||||
Reference in new issue
Block a user