diff --git a/crates/engine/src/cmd/menucmds.rs b/crates/engine/src/cmd/menucmds.rs index 1c2fb440..a6ebd243 100644 --- a/crates/engine/src/cmd/menucmds.rs +++ b/crates/engine/src/cmd/menucmds.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use serde_json::{Value, json}; use vectorcraft_color::{Color, Paint}; use vectorcraft_doc::marks::{MarkStyle, TrimMarks}; -use vectorcraft_doc::{Appearance, ImageObject, LiveShape, Node, NodeId, NodeKind, RasterColorModel, RasterEffectsSettings}; +use vectorcraft_doc::{Appearance, ImageObject, LiveShape, Node, NodeId, NodeKind, RasterColorModel, RasterEffectsSettings, Scaling}; use vectorcraft_geom::{Affine, Anchor, PathData, Point, Rect, Vec2}; use vectorcraft_render::AntiAlias; use vectorcraft_render::encode::{RasterExportOptions, RasterFormat}; @@ -156,7 +156,7 @@ pub fn specs() -> Vec { "Rearrange All Artboards…", ["Object", "Artboards"], None, - "{layout?: gridByRow|gridByColumn|row|column (gridByRow; byColumn?: true is gridByColumn), order?: leftToRight|rightToLeft (leftToRight: right-to-left mirrors the order along the rows), columns?: n (2; the rows for gridByColumn; row and column ignore it), spacing?: pt (20), moveArtwork?: true (locked and hidden art only with prefs moveLockedWithArtboard)} lay the artboards out in their order, the grid's top-left at the first artboard's → {artboards, rows, columns}", + "{layout?: gridByRow|gridByColumn|row|column (gridByRow; byColumn?: true is gridByColumn), order?: leftToRight|rightToLeft (leftToRight: right-to-left mirrors the order along the rows), columns?: n (2; the rows for gridByColumn; row and column ignore it), spacing?: pt (20), moveArtwork?: true (locked and hidden art only with prefs moveLockedWithArtboard), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles)} lay the artboards out in their order, the grid's top-left at the first artboard's → {artboards, rows, columns}", has_doc, rearrange_artboards ), @@ -870,35 +870,40 @@ fn rearrange_artboards(s: &mut Session, p: &Value) -> Result { let spacing = f64_or(p, "spacing", 20.0).clamp(-1.0e5, 1.0e5); let move_art = bool_or(p, "moveArtwork", true); let locked_and_hidden = s.prefs.move_locked_with_artboard; - let scale_strokes = false; - // How far each artboard moves: the grid's top-left is at the first artboard's. - let rects: Vec = s.doc()?.doc.artboards.iter().map(|a| a.rect).collect(); + let d = &s.doc()?.doc; + let rects: Vec = d.artboards.iter().map(|a| a.rect).collect(); let sizes: Vec<(f64, f64)> = rects.iter().map(|r| (r.width(), r.height())).collect(); - let origin = rects.first().map_or(Point::ZERO, |r| r.origin()); - let origins = super::newdoc::grid_origins_rc(&sizes, origin, (rows, cols), spacing, layout == L::GridByColumn, rtl); + let first = rects.first().map_or(Point::ZERO, |r| r.origin()); + let origins = super::newdoc::grid_origins_rc(&sizes, first, (rows, cols), spacing, layout == L::GridByColumn, rtl); let deltas: Vec = rects.iter().zip(origins).map(|(r, o)| o - r.origin()).collect(); // When no artboard moves more than 1e-9 pt on either axis, or there is none, the document and // its undo history stay as they are. if deltas.iter().all(|dl| dl.x.abs() <= 1e-9 && dl.y.abs() <= 1e-9) { return Ok(json!({ "artboards": count, "rows": rows, "columns": cols })); } + // With moveArtwork, each top-level object moves with the artboard its center is on; locked and + // hidden art moves only when prefs moveLockedWithArtboard is on. + let art: Vec<(NodeId, Vec2)> = if move_art { + d.layers + .iter() + .filter(|l| l.rides_with_artboard(locked_and_hidden)) + .flat_map(|l| l.children().into_iter().flatten()) + .filter(|n| n.rides_with_artboard(locked_and_hidden)) + .filter_map(|n| { + let c = n.geometric_bounds()?.center(); + let dl = *rects.iter().position(|r| r.contains(c)).and_then(|i| deltas.get(i))?; + (dl != Vec2::ZERO).then_some((n.id, dl)) + }) + .collect() + } else { + vec![] + }; + let ids: Vec = art.iter().map(|(id, _)| *id).collect(); + let sc = Scaling { patterns: super::object::transform_patterns(s, p, &ids)?, ..Scaling::default() }; s.edit("Rearrange Artboards", |d, _| { - if move_art { - let tops: Vec<(NodeId, Point)> = d - .layers - .iter() - .filter(|l| l.rides_with_artboard(locked_and_hidden)) - .flat_map(|l| l.children().into_iter().flatten()) - .filter(|n| n.rides_with_artboard(locked_and_hidden)) - .filter_map(|n| Some((n.id, n.geometric_bounds()?.center()))) - .collect(); - for (id, c) in tops { - if let Some(&dl) = rects.iter().position(|r| r.contains(c)).and_then(|i| deltas.get(i)) - && dl != Vec2::ZERO - && let Some(n) = d.node_mut(id) - { - n.transform(Affine::translate(dl), scale_strokes); - } + for (id, dl) in &art { + if let Some(n) = d.node_mut(*id) { + n.transform(Affine::translate(*dl), sc); } } let mut moved = vec![]; diff --git a/crates/engine/src/cmd/panelcmds.rs b/crates/engine/src/cmd/panelcmds.rs index 561ae395..360c2b9f 100644 --- a/crates/engine/src/cmd/panelcmds.rs +++ b/crates/engine/src/cmd/panelcmds.rs @@ -2,7 +2,7 @@ //! Character/Paragraph attributes. use serde_json::{Value, json}; -use vectorcraft_doc::{Composer, NodeKind}; +use vectorcraft_doc::{Composer, NodeKind, Scaling}; use vectorcraft_geom::{Rect, Vec2}; use super::*; @@ -16,7 +16,7 @@ pub fn specs() -> Vec { "Duplicate Artboard", ["Window", "Artboards"], None, - "{index?: n (default: the Artboard tool's artboard), art?: bool (default: the Artboard tool's moveArt option)} copy placed right of the last artboard, with copies of the art fully inside it when `art` (locked and hidden art only with prefs moveLockedWithArtboard) → {index, ids: the art's copies}", + "{index?: n (default: the Artboard tool's artboard), art?: bool (default: the Artboard tool's moveArt option), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles)} copy placed right of the last artboard, with copies of the art fully inside it when `art` (locked and hidden art only with prefs moveLockedWithArtboard) → {index, ids: the art's copies}", has_doc, artboard_duplicate ), @@ -108,10 +108,10 @@ pub(crate) fn beside_artboards(d: &vectorcraft_doc::Document, rect: Rect) -> Vec fn artboard_duplicate(s: &mut Session, p: &Value) -> Result { let (i, art) = board_and_art(s, p); let art = artboard_art(s, i, art)?; - let scale_strokes = s.prefs.scale_strokes; + let sc = Scaling { patterns: super::object::transform_patterns(s, p, &art)?, ..Scaling::default() }; let (index, ids) = s.edit("Duplicate Artboard", |d, _| { let dv = d.artboards.get(i).map(|a| beside_artboards(d, a.rect)).unwrap_or_default(); - copy_artboard(d, i, dv, &art, scale_strokes) + copy_artboard(d, i, dv, &art, sc) })?; Ok(json!({"index": index, "ids": ids.iter().map(|i| i.0).collect::>()})) } @@ -152,15 +152,9 @@ pub(crate) fn artboard_copy(s: &mut Session, p: &Value, cut: bool) -> Result Result<(usize, Vec)> { +/// above its original) moved along → (the copy's index, the art's copies). When `sc.patterns`, the +/// pattern tiles of the art's copies move with them. Duplicate Artboards and Alt-drag. +pub(crate) fn copy_artboard(d: &mut vectorcraft_doc::Document, i: usize, dv: Vec2, art: &[NodeId], sc: Scaling) -> Result<(usize, Vec)> { let src = d.artboards.get(i).cloned().ok_or_else(|| EngineError::Other("no such artboard".into()))?; let index = push_artboard_copy(d, &src, src.rect + dv); // Its guides come along. @@ -171,7 +165,7 @@ pub(crate) fn copy_artboard( for id in art { let (Some((parent, at, _)), Some(n)) = (d.position(*id), d.node(*id).cloned()) else { continue }; let mut c = d.reid(&n); - c.transform(Affine::translate(dv), scale_strokes); + c.transform(Affine::translate(dv), sc); copies.push(d.insert(parent, at + 1, c)?); } Ok((index, copies)) diff --git a/crates/engine/src/cmd/xform.rs b/crates/engine/src/cmd/xform.rs index b7168688..e937c1d0 100644 --- a/crates/engine/src/cmd/xform.rs +++ b/crates/engine/src/cmd/xform.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use serde_json::{Value, json}; use vectorcraft_color::Paint; -use vectorcraft_doc::{Node, NodeKind}; +use vectorcraft_doc::{Node, NodeKind, Scaling}; use vectorcraft_geom::{Affine, Point, Rect, Vec2}; use super::edit::selected_roots; @@ -45,7 +45,7 @@ pub fn specs() -> Vec { "Move Artboard", [], None, - "{index | indices: [index…], dx, dy, moveArt?: bool, copy?: bool} move artboards (and the art fully inside them; locked and hidden art only with prefs moveLockedWithArtboard), several in one step; `copy` (Alt-drag) leaves them and moves copies → {index (the first), indices: the artboards moved or the copies, moved: the art moved or the copies}", + "{index | indices: [index…], dx, dy, moveArt?: bool, copy?: bool, patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles)} move artboards (and the art fully inside them; locked and hidden art only with prefs moveLockedWithArtboard), several in one step; `copy` (Alt-drag) leaves them and moves copies → {index (the first), indices: the artboards moved or the copies, moved: the art moved or the copies}", has_doc, artboard_move ), @@ -376,14 +376,15 @@ fn artboard_move(s: &mut Session, p: &Value) -> Result { let art = art.into_iter().filter(|id| !arts.iter().any(|(_, a)| a.contains(id))).collect(); arts.push((i, art)); } - let scale_strokes = s.prefs.scale_strokes; + let ids: Vec = arts.iter().flat_map(|(_, a)| a.iter().copied()).collect(); + let sc = Scaling { patterns: super::object::transform_patterns(s, p, &ids)?, ..Scaling::default() }; let copy = bool_or(p, "copy", false); let (index, moved) = s.edit(if copy { "Duplicate Artboard" } else { "Move Artboard" }, |d, _| { let (mut index, mut moved) = (vec![], vec![]); for (i, art) in &arts { // A copy keeps the artboard (and its art) where they are and moves duplicates instead. if copy { - let (at, copies) = super::panelcmds::copy_artboard(d, *i, dv, art, scale_strokes)?; + let (at, copies) = super::panelcmds::copy_artboard(d, *i, dv, art, sc)?; index.push(at); moved.extend(copies); continue; @@ -394,7 +395,7 @@ fn artboard_move(s: &mut Session, p: &Value) -> Result { d.move_artboard_guides(id, dv); for id in art { if let Some(n) = d.node_mut(*id) { - n.transform(Affine::translate(dv), scale_strokes); + n.transform(Affine::translate(dv), sc); } } index.push(*i); diff --git a/crates/engine/src/tests_prefs.rs b/crates/engine/src/tests_prefs.rs index 617eaa70..9121e4a2 100644 --- a/crates/engine/src/tests_prefs.rs +++ b/crates/engine/src/tests_prefs.rs @@ -513,6 +513,61 @@ fn align_and_distribute_follow_transform_pattern_tiles() { } } +/// General › Transform Pattern Tiles (#394) applies to art moved or duplicated with its artboard +/// as it does to the transforms: off, the tiles of a pattern-filled square stay in place; on, they +/// move with the square or its copy, and `patterns: false` keeps them in place for one call. +#[test] +fn art_moved_with_its_artboard_follows_transform_pattern_tiles() { + use vectorcraft_color::Paint; + let mut s = new_doc(); + let plain = square(&mut s, 500.0, 0.0); + s.execute("select.set", &json!({"ids": [plain.0]})).unwrap(); + s.execute("object.pattern.make", &json!({"name": "Dots", "width": 20, "height": 20})).unwrap(); + s.execute("object.pattern.done", &json!({})).unwrap(); + s.execute("artboard.new", &json!({"x": 1000, "y": 0, "width": 400, "height": 400})).unwrap(); + let filled = square(&mut s, 1100.0, 100.0); + s.execute("paint.setFill", &json!({"ids": [filled.0], "swatch": "Dots"})).unwrap(); + let tiles = |s: &Session, id: NodeId| match s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint() { + Paint::Pattern { xf, .. } => xf.as_coeffs(), + p => panic!("not a pattern: {p:?}"), + }; + // Artboard 1 starts at x 1000 and holds the square; artboard 0 holds no art, and `plain` is on + // no artboard. Duplicate Artboard puts the copy 20 pt right of the last artboard, artboard 1, so + // the art's copy is 420 pt right of the square. Rearranged in a row, artboard 1 moves to x 420, + // 20 pt right of artboard 0, and `artboard.setProps` puts it at x 1030. `moved` and `ids` name + // the art moved or its copies. + for (cmd, p, key, d) in [ + ("artboard.move", json!({"index": 1, "dx": 30, "dy": 0, "moveArt": true}), Some("moved"), [30.0, 0.0]), + ("artboard.move", json!({"indices": [0, 1], "dx": 30, "dy": 0, "moveArt": true}), Some("moved"), [30.0, 0.0]), + ("artboard.move", json!({"index": 1, "dx": 0, "dy": 500, "moveArt": true, "copy": true}), Some("moved"), [0.0, 500.0]), + ("artboard.duplicate", json!({"index": 1, "art": true}), Some("ids"), [420.0, 0.0]), + ("artboard.rearrange", json!({"layout": "row"}), None, [-580.0, 0.0]), + ("artboard.setProps", json!({"index": 1, "x": 1030, "moveArt": true}), None, [30.0, 0.0]), + ] { + let run = |s: &mut Session, p: &Value| { + let r = s.execute(cmd, p).unwrap(); + let ids = key.and_then(|k| r[k].as_array().cloned()).unwrap_or_else(|| vec![json!(filled.0)]); + assert_eq!(ids.len(), 1, "{cmd}: the square alone goes with the artboard"); + NodeId(ids[0].as_u64().unwrap()) + }; + let id = run(&mut s, &p); + assert_eq!(tiles(&s, id), [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], "{cmd}: off, the tiles stay"); + assert_eq!(s.journal.last().unwrap().1["patterns"], json!(false), "{cmd}: the journal keeps the choice"); + s.execute("edit.undo", &json!({})).unwrap(); + set_pref(&mut s, "transformPatternTiles", json!(true)); + let id = run(&mut s, &p); + assert_eq!(tiles(&s, id), [1.0, 0.0, 0.0, 1.0, d[0], d[1]], "{cmd}: on, they move with the square"); + assert_eq!(s.journal.last().unwrap().1["patterns"], json!(true), "{cmd}: the journal keeps the choice"); + s.execute("edit.undo", &json!({})).unwrap(); + let mut p = p; + p["patterns"] = json!(false); + let id = run(&mut s, &p); + assert_eq!(tiles(&s, id), [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], "{cmd}: the param wins"); + s.execute("edit.undo", &json!({})).unwrap(); + set_pref(&mut s, "transformPatternTiles", json!(false)); + } +} + /// General › Select Same Tint % (#394): off, Select › Same › Fill Color takes every tint of the /// swatch; on, only the same tint. #[test] diff --git a/docs/mcp.md b/docs/mcp.md index 6868103c..8e6b51c9 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1555,7 +1555,9 @@ The Selection & Anchor Display and General preferences apply to `pointer_gesture - `transformPatternTiles` (off by default): the default of the transforms' `patterns` param (`object.transform`, `object.move`, `object.rotate`, `object.scale`, `object.reflect`, `object.shear`, `object.transformEach`, `object.align`, `object.distribute`, `object.distributeSpacing`, the Selection and transform tools, the dialogs' - Transform Patterns): pattern fills and strokes move with the art. + Transform Patterns) and of the same param on the commands that move or duplicate art with an artboard (`artboard.move` + and the Artboard tool, `artboard.duplicate`, `artboard.rearrange`, `artboard.setProps`): pattern fills and strokes + move with the art. - `selectSameTintPercent` (off by default): `select.same.fillColor`, `strokeColor` and `fillAndStroke` take every tint of a global or spot swatch; on, only the same tint. @@ -1834,7 +1836,8 @@ step (`edit.pasteInPlace`, `edit.pasteInFront` and `edit.pasteInBack` put it whe pastes only the art), in this document or another; the result's `artboard` is the new artboard's index. `artboard.duplicate {index?, art?}` (Window › Artboards › Duplicate Artboards, or a row dragged onto New Artboard) does the same in one step → `{index, ids}`, and `artboard.move {copy: true, moveArt: true}` is the Artboard tool's -Alt-drag. +Alt-drag. These two commands also move the pattern tiles of the copies when `patterns` is true (default: prefs +`transformPatternTiles`); `edit.paste` leaves the tiles of a pasted artboard's art where they were. Scale Artwork with Artboard (#602): `artboard.setProps {index, x?, y?, width?, height?, scaleArt: true}` resizes an artboard and scales the art fully inside it, and its artboard guides, from the old rectangle onto the new one, each side diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index b2f9e60c..0b5cb3c4 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -385,7 +385,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - A canvas context menu: right-click selects the object under the pointer and lists what applies to the selection (Undo/Redo, clipboard, group, isolation, join, Remove Anchor Points when anchors are selected, masks, compound paths, guides, Transform, Arrange, Select, Export Selection), or the view commands on empty canvas. - Save / Don't Save / Cancel before closing or quitting with unsaved documents (tabs, Close, Close All, Quit, window close). - Four brightness themes, persistent preferences. - - 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, and Align and Distribute) 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). About 17 stored options still do nothing yet (the rest of #394: Highlight Alternate Glyphs, Identify Objects By, East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Appearance of Black, Touch Workspace). + - 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, and art moved or duplicated with an artboard) 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). About 17 stored options still do nothing yet (the rest of #394: Highlight Alternate Glyphs, Identify Objects By, East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Appearance of Black, Touch Workspace). - 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…). 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. - Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels. Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off.