diff --git a/crates/engine/src/tests/prefs.rs b/crates/engine/src/tests/prefs.rs index 8ae55a084..d2123ef87 100644 --- a/crates/engine/src/tests/prefs.rs +++ b/crates/engine/src/tests/prefs.rs @@ -1616,6 +1616,23 @@ fn real_time_drawing_off_skips_tool_previews_until_commit() { assert!(s.doc().unwrap().revision > rev, "on again: previews run while dragging"); } +/// #4646: with Real-time Drawing off, a ruler guide moved with the Selection tool still moves +/// while it is dragged (the guide is all the drag shows). +#[test] +fn real_time_drawing_off_still_shows_a_dragged_guide() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + s.execute("guide.add", &json!({"vertical": true, "pos": 100})).unwrap(); + let v = ViewInfo::default(); + s.select_tool("selection", v).unwrap(); + set_pref(&mut s, "realTimeDrawing", json!(false)); + s.pointer(&PointerEvent::new(PointerKind::Down, 100.0, 200.0), v).unwrap(); + s.pointer(&PointerEvent::new(PointerKind::Drag, 150.0, 200.0), v).unwrap(); + assert_eq!(s.doc().unwrap().doc.guides[0].pos, 150.0, "the guide moves while dragged"); + s.pointer(&PointerEvent::new(PointerKind::Up, 150.0, 200.0), v).unwrap(); + assert_eq!((s.doc().unwrap().doc.guides[0].pos, s.doc().unwrap().history.undo.last().map(|e| e.label.as_str())), (150.0, Some("Move Guide"))); +} + /// Real-time Drawing turned on mid-drag still applies the deferred preview on commit. #[test] fn real_time_drawing_turned_on_mid_drag_still_commits_deferred_preview() { diff --git a/crates/engine/src/tests/save.rs b/crates/engine/src/tests/save.rs index 842aec4aa..e562482d0 100644 --- a/crates/engine/src/tests/save.rs +++ b/crates/engine/src/tests/save.rs @@ -562,3 +562,25 @@ fn eps_save_bounds_large_page_preview_without_cropping_vector_or_editing_data() assert_eq!(native.artboards, s.doc().unwrap().doc.artboards); assert_eq!(native.layers, s.doc().unwrap().doc.layers); } + +/// #4646: a new document's units and ruler guides survive Save, Close and Open in every format +/// that carries the whole document (an SVG keeps them only with Preserve Editing). +#[test] +fn units_and_ruler_guides_survive_save_and_reopen() { + let d = dir("units-guides"); + for ext in ["vectorcraft", "pdf", "ai", "eps"] { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 100, "units": "Millimeters"})).unwrap(); + s.execute("guide.add", &json!({"vertical": true, "pos": 50})).unwrap(); + s.execute("guide.add", &json!({"vertical": false, "pos": 30})).unwrap(); + rect(&mut s); + let p = path(&d, &format!("a.{ext}")); + s.execute("document.save", &json!({"path": p})).unwrap(); + s.execute("file.close", &json!({})).unwrap(); + s.execute("document.open", &json!({"path": p})).unwrap(); + let doc = &s.doc().unwrap().doc; + let guides: Vec<_> = doc.guides.iter().map(|g| (g.vertical, g.pos)).collect(); + assert_eq!((doc.units, guides), (vectorcraft_doc::Unit::Millimeters, vec![(true, 50.0), (false, 30.0)]), "{ext}"); + } + let _ = std::fs::remove_dir_all(d); +} diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index b991ad219..13a7c2862 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -743,8 +743,9 @@ impl Session { Action::Begin(label) => self.begin_interaction(&label)?, Action::Preview(cmd, p) => { // Performance › Real-time Drawing and Editing (#394): off, remember the latest - // params and apply them once on Commit instead of every move. - if !self.prefs.real_time_drawing { + // params and apply them once on Commit instead of every move. A ruler guide + // being dragged still shows: the guide is all there is to see (#4646). + if !self.prefs.real_time_drawing && !matches!(cmd.as_str(), "guide.add" | "guide.move") { self.clear_deferred_overlay_cache(); let st = self.doc_mut()?; if let Some(it) = &mut st.interaction { diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 110e3bd5c..c82c528cd 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -1032,6 +1032,39 @@ fn a_drag_from_a_ruler_onto_the_canvas_makes_a_guide() { assert_eq!(app.session.active().unwrap().doc.art_bounds(), None, "the tool drew nothing"); } +/// #4646: a guide dragged out of a ruler shows as a line across the canvas while it is dragged, +/// also with Preferences › Performance › Real-time Drawing and Editing off (only its position +/// label showed then). +#[test] +fn a_guide_dragged_out_of_a_ruler_shows_while_dragged() { + for real_time in [true, false] { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + app.ui.view.rulers = true; + app.ui.view.smart_guides = false; + app.ui.view.snap_to_point = false; + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + let rect = app.canvas_rect.unwrap(); + let zoom = app.view().unwrap().zoom as f32; + let from = pos2(rect.center().x, rect.top() - RULER / 2.0); + let at = pos2(rect.center().x, rect.top() + 120.0); + let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + frame(&mut app, &ctx, vec![egui::Event::PointerMoved(from)]); + frame(&mut app, &ctx, vec![button(from, true)]); + frame(&mut app, &ctx, vec![egui::Event::PointerMoved(at)]); + // A horizontal line across the canvas at the pointer (snapped to a whole point at most). + let across = |s: &Shape| { + matches!(s, Shape::LineSegment { points, .. } + if (points[0].y - at.y).abs() <= zoom && points[0].y == points[1].y && (points[1].x - points[0].x).abs() >= rect.width() - 0.5) + }; + assert!(shapes(&mut app, &ctx).iter().any(across), "real-time drawing {real_time}: the guide shows while dragged"); + frame(&mut app, &ctx, vec![button(at, false)]); + assert_eq!(app.session.active().unwrap().doc.guides.len(), 1, "real-time drawing {real_time}: released, it is made"); + } +} + #[test] fn new_ruler_guides_follow_alt_through_preview_release_and_undo() { use egui::{Event, Modifiers}; diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 3f16df15a..f8493a0de 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Direct Selection: a marquee over part of a path segment, with neither of its anchors inside, selects the segment (#4654); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (UI chrome: with Real-time Drawing and Editing off, a ruler guide shows while it is dragged (#4646); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -601,6 +601,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). Five stored options still do nothing yet (the audit below). - Preferences › Type › Highlight Substituted Fonts (on by default) paints pink behind text drawn in a substitute for a missing font in every document, and turning it off removes that highlight everywhere (`prefs.set highlightSubstitutedFonts`); Document Setup keeps Highlight Substituted Glyphs ([help](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/edit-text-without-replacing-missing-fonts.html)). - Preferences audit (2026-10-30, #394): every Preferences dialog option is read by shipped code, by its field or its key, except User Interface › Open Documents As Tabs, Units › East Asian Type, Hyphenation › Default Language, Plug-ins & Scratch Disks › Primary and Secondary, and Clipboard Handling › Legacy vector clipboard and its mode, which are stored only; [gaps.md G12](gaps.md#g12-preferences-and-panels-that-dont-act-yet) says what each waits on. + - Performance › Real-time Drawing and Editing off defers a drag's edits to its release, but a ruler guide dragged out of a ruler or moved still shows as it is dragged, where only its position label showed (#4646; `a_guide_dragged_out_of_a_ruler_shows_while_dragged`, `real_time_drawing_off_still_shows_a_dragged_guide`). - Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423). - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once. - While Alt (Option on macOS) is held, the Flare Tool Options' Cancel button reads Reset, which puts every option back to the Flare tool's default and leaves the dialog open (`ui.dialogReset`). @@ -1207,6 +1208,7 @@ applies to fills). Tests: `color::gradient::tests` (perceptual midpoint, expansi | Date | Change | Summary | |---|---|---| +| 2026-10-30 | minor | UI chrome: with Preferences › Performance › Real-time Drawing and Editing off, a ruler guide dragged out of a ruler or moved shows while it is dragged (#4646); evidence added; score and hours unchanged (estimated). | | 2026-10-30 | minor | Direct Selection: a marquee over part of a path segment, with neither of its anchors inside, selects the segment (#4654); scores unchanged | | 2026-10-30 | minor | Tools: Object › Path › Average & Join… (`path.averageJoin`, Cmd+Alt+Shift+J) moves two open path ends to their midpoint and joins them there, the joined anchor a corner or a smooth point as its dialog's Points choice asks, in one undo step; evidence added; scores and hours unchanged (estimated). | | 2026-10-30 | minor | UI chrome: G12: Preferences audit (#394): 5 options are stored but not acted on (Open Documents As Tabs, Units › East Asian Type, Hyphenation › Default Language, Scratch Disks, Legacy vector clipboard), each waiting on a subsystem; Highlight Alternate Glyphs, Real-time Drawing and the Devices options act; evidence added; score and hours unchanged (estimated). |