Fifteen Preferences options were stored but read by nothing. They now act,
through the existing plumbing (ToolContext for the tools, the prefs read by the
canvas for the view), and reach agents through prefs.set:
- General: Show Tool Tips (off: no widget's tool tip comes due), Double Click
To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer,
Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways), Use Precise
Cursors (Pen, Eyedropper, Slice and Blend pointers become a crosshair).
- Selection & Anchor Display: Tolerance (objects, anchors and handles, in
place of the fixed 3 and 4 px), Object Selection by Path Only (compound
paths too), Command Click to Select Objects Behind (cycles down the objects
under the pointer; not when Cmd only borrows the selection tool), Zoom to
Selection (Zoom In/Out centre the selection), anchor Size, Highlight anchors
on mouse over (Direct Selection).
- Type: Number of Recent Fonts (up to 15 Recent Fonts slots).
- Guides & Grid: guide and grid Color and Style (lines or dots), Grids In Back.
The canvas's context menu, panel drops and hover highlight pick objects like
the selection tools. The control channel gets ui.wheel (with modifiers held
like ui.click) so agents can turn the wheel. The rest of #394 stays open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live rectangle stored four corner radii but drew them all with the largest,
and the corner widgets always rounded all four corners, so one corner could not
be rounded on its own.
- geom: rectangle_with_corners draws each corner with its own radius and kind
(Round, Inverted Round, Chamfer); a sharp corner is one anchor, a cut one two.
Uniform corners keep the exact rounded-rectangle anchors.
- doc: LiveShape::Rectangle gains `kinds` (saved only when a corner isn't round;
older files read as round), plus helpers mapping anchors to corners.
- engine: object.setLiveShape takes `corners` and `kind`; without `corners` it
edits the Direct-Selected corners, else all four, and keeps the selected
corners selected as their anchors change.
- tools: with Direct Selection, only the selected corners show widgets and a
drag rounds just those; Alt-click cycles the corner kind; double-click opens
the new Corners dialog (also `ui.corners`).
- ui: Properties/Transform radius fields show and set the selected corners;
CSS export keeps per-corner radii and skips cut-in corners.
- docs: mcp.md (Live Corners), control-protocol.md (Corners dialog), ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Shape Builder only used the filled areas of the selected paths, so a
stroke-only line or open path across a shape did nothing. Like the
reference app's Shape Builder, open paths now act as cutting edges:
- pathops::shape_builder: with an open path in the selection, the
regions are the Live Paint planar map's faces (every path an edge), so a
line across a shape splits it and lines can enclose a region of their
own; it also returns each open path cut where other paths meet it. Closed
shapes alone, more than SHAPE_BUILDER_MAX_SEGMENTS segments, or a map
that can't be built fall back to the filled-area arrangement.
- Filled open paths count as closed (an invisible closing edge) where that
encloses an area; unfilled open paths and straight lines stay edges.
- shapeBuilder.merge: merging or deleting regions on both sides of a line
piece removes that piece (the line is split, its other pieces kept); an
area only lines enclose takes the current fill and the front-most
object's look. Alt (erase) within `tolerance` of a line piece deletes it.
Returns `lines` (pieces deleted).
- shapeBuilder.regions also lists the line pieces.
- The tool shares BuilderMap/Touched with the command, highlights the line
piece under the pointer while Alt is held and passes its tolerance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Alt down at the press, the four transform tools opened their
dialog at once, so an Alt-drag did nothing: no transform and no copy.
They now decide on release: a click made with Alt still sets the
reference point and opens the dialog, and a drag made with Alt
transforms a copy about the reference point, as it does in
Illustrator.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With Alt held, a press on a path's stroke selects the whole path and
an Alt-drag copies it, as before #363 and as Group Selection does in
Illustrator; only a plain press on a segment selects its anchors and
reshapes it. Records the segment drag in ROADMAP.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any hit on a path's stroke took the "select the whole path" branch, so
dragging a segment moved the whole path. Now a press on the stroke of
a path that isn't selected as a whole selects the segment's two
anchors (unless they already are), and the drag bends the segment
with path.reshapeSegment when it's curved, else moves the selected
anchors (a straight edge moves, the rest stays), as in the reference
app. The fill, Shift-click and a path selected as a whole keep their
behaviour.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Unicode bidi (unicode-bidi, unicode-script, unicode-general-category):
each paragraph's direction from its first strong character, Hebrew and
Arabic shaped right to left, embedded Latin and numbers keep theirs,
lines wrapped in logical order and reordered per line by whole
clusters; source text, undo and style ranges stay logical.
- Caret, hit testing, selection and the arrow keys follow the visual
order.
- New text aligns automatically: right-to-left paragraphs right, others
left (Justify::Auto, text.setStyle {justify: "auto"}); the Paragraph
panel highlights the resolved alignment and has an Automatic alignment
toggle. Explicit alignments, and older documents', are kept.
- SVG export outlines bidirectional text (with a warning) until it can
be written as directional live text.
Add Snapping to the Artboard Tool
Merged with the Artboard tool's Alt-drag duplicate (#348): a copy snaps too, and to the artboard
and art it leaves behind (a move skips them).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid was drawn whenever its `visible` flag was set or a perspective
tool was active, and choosing the Perspective Grid tool (Shift+P) didn't set
the flag. So with the tool chosen, View > Perspective Grid > Hide Grid
(perspective.grid.show) turned the hidden flag on instead of off and the
grid stayed on screen however it was toggled.
Choosing a perspective tool now shows the grid (sets `visible`, view state,
no undo step), and the grid, its widget and the Perspective Grid tool's
handles are drawn only while it is visible. Hide Grid hides it with the
tool still chosen; choosing the tool again shows it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Parity: Object › Path › Remove Anchor Points now runs the new command (`path.removeAnchors`,
the menu's feature name) instead of `path.deleteAnchors`, which deleted the anchors with their
segments and opened closed paths; that stays the Delete key's command ("Delete Anchor Points",
off the menu). No second "Smart" item.
- Geometry: the refit moves to `vectorcraft_pathops::remove_anchor` on the existing least-squares
fitter (fixed end tangents, a golden-section search for where the curve passes the removed
point) instead of a ~1,000-candidate grid search in geom (about 4 ms per anchor, now well under
0.1 ms). Two straight sides stay one straight side (a rectangle loses a corner and becomes a
triangle), as Illustrator does; non-finite input leaves the handles alone.
- The Delete Anchor Point tool (`path.removeAnchor`) uses the same function, replacing its
heuristic handle stretch; the explicit id/anchor and `fit` params of the new command are dropped
as duplicates of it. The command is enabled by direct-selected anchors (`has_anchors`), not a
UI override.
- The Control bar shows an icon button (the Delete Anchor Point icon, tooltip "Remove Anchor
Points") instead of a 204 pt text button; the labels reuse the translated "Remove Anchor
Points"; zh-hant gains "Delete Anchor Points". The Pen test that pinned Alt-click as plain Pen
(Illustrator's Alt over an anchor is Convert Anchor Point) is dropped.
- Tests for the refit, closed paths, straight sides and bad input; docs/mcp.md and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
path.smartRemoveAnchor stays. Object › Path, the Control bar, the contextual task bar, Properties, and the canvas context menu run it on direct-selected anchors. The Pen no longer uses Alt/Option for this, so that key stays free for Convert Anchor Point.
- A dragged handle no longer snaps to its own path's bounds and centre, which move with the
handle; the path's anchors are point and alignment targets instead.
- Shift steps 45° from the Constrain Angle preference, like the cut and gradient tools.
- snap_draw and snap_handle share one pixel/grid/smart-guides switch.
- A test that a smooth anchor's opposite handle turns with a Shift-dragged handle and that Alt
still breaks it, and the tools' module docs (both from #338).
Co-authored-by: theironchef <6278036+theironchef@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging a direction handle with Direct Selection or the Anchor Point tool
sent the pointer position straight to path.setHandle: Shift did nothing and
the handle never snapped, while the Pen already constrains the handles it
pulls out.
guides::snap_handle places a dragged handle: with Shift at a multiple of
45° from its anchor (the Pen's constraint, keeping the dragged length),
otherwise snapped as a drawn point is (smart guides, the grid, pixels).
Both tools use it and show the smart guides while the handle moves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alt-dragging an artboard moved it like a plain drag. artboard.move now
takes copy: it leaves the artboard (and the art it carries) where it
is and moves a copy, named "<name> copy", with copies of that art. The
Artboard tool previews it while Alt is held, as "Duplicate Artboard"
(Alt pressed or released mid-drag switches between copy and move),
and makes the copy the active artboard, as in the reference app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Line Segment family (Arc, Spiral, Rectangular Grid, Polar Grid)
ignored Space while drawing, as the shape tools did before #302. Both
tools now share shape::space_moves. A headless canvas test presses Space
for real in the middle of a Rectangle and a Pen drag: the shape and the
anchor move and the view doesn't pan. Adds the ROADMAP entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shape tools (Rectangle, Rounded Rectangle, Ellipse, Polygon, Star,
Line Segment) ignored Space during a drag. While Space is held once the
drag has begun, the drag's start now moves with the pointer, so the
shape being drawn moves at its current size; letting go of Space resumes
sizing from there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Pen's Auto Add/Delete had its own copy of the anchor and segment hit
tests and of the path.insertAnchor / path.removeAnchor actions the Add
and Delete Anchor Point tools build. draw2 now exposes anchor_in /
segment_in (the hit tests over a given set of paths) and insert_anchor /
remove_anchor, which both tools use. Adds the ROADMAP entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between paths, the Pen now adds an anchor where a click lands on a
segment of a selected path and deletes the anchor a click lands on,
with the add and delete cursors while hovering, as in the reference
app. The ends of a selected open path still continue it. Shift held,
or General > Disable Auto Add/Delete (a stored preference nothing read
until now, passed to tools as ToolContext::auto_add_delete), starts a
new path instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While drawing, a click on the last anchor appended a second anchor at
the same spot, keeping the old outgoing handle, so the path got a
zero-length curve that showed as a spike. Now a click there retracts
the anchor's outgoing handle (the next segment leaves it as a corner)
and a drag pulls a new outgoing handle out on its own, with a convert
cursor (^) over it, as in the reference app. A one-anchor path no
longer closes on itself when its anchor is clicked again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Pen ignored Space during a drag, so the anchor being placed stayed
where the button went down and its handles swung round it. Space held
now moves the anchor, handles and all, as far as the pointer moves
(the handle Alt kept moves along too); releasing Space goes back to
shaping the handles round the new place, as in the reference app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pressing Alt while dragging out a smooth point set its incoming handle to
the anchor itself, so the segment just shaped turned straight. The Pen
now remembers the incoming handle it last previewed and keeps it while
Alt is held, so only the outgoing handle follows the pointer: a corner
with curves on both sides, as in the reference app. Alt held from the
start of the drag still gives an outgoing handle only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The resize snap skipped a bounding box standing at an angle. It now works
on the page whatever the angle: a corner handle takes the target's x, y or
both, and a side handle (or a proportional corner) slides along its own
direction until the handle or an end of its side is on a target. A target
the side runs almost along is skipped, so the handle stays near the pointer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j16ENkbsNYyHSCDhxYkYR
Dragging a bounding-box handle with the Selection or Free Transform tool
now lands the edges it moves on the edges and centres of the other objects
and artboards, with the guide line, as a move already did. A proportional
scale snaps the closer axis only; a turned bounding box doesn't snap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j16ENkbsNYyHSCDhxYkYR
A live rectangle kept every transform in its `xf`, so a non-uniform scale
(a bounding-box drag, Object > Transform > Scale) stretched the rounded
corners built in untransformed space into elliptical arcs. Scale Corners
off divided the radii by the mean scale, which only compensated uniform
scales.
LiveShape::transform now folds a scale along the rectangle's own sides
(any rotation or reflection, no shear) into w/h and keeps only rotation,
reflection and position in `xf`, so the radii are document lengths and the
path is regenerated with circular corners. Radii scale by the mean scale
(sqrt|det|), which keep_corners undoes for Scale Corners off. Shears and
plain moves keep composing into `xf` as before, so files saved with a
scale in `xf` keep their exact geometry until next scaled or rotated.
A corner-widget drag starts from the radius as drawn (clamped to fit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging a bounding-box handle of area type with the Selection tool now
resizes the type area: the text reflows at its own size (threads too)
instead of being scaled, and History says "Resize Type Area". With other
objects selected they scale as before while the area type resizes its
frame. Direct Selection picks the frame's corners and edges and reshapes
the area ("Reshape Type Area", new command text.reshapeArea), and the
selected frame's anchors and outline show. Area Type Options gains Width
and Height. Point type, rotation, Object > Transform > Scale, the Scale
tool and the Transform panel still transform the type.
Root cause: every handle drag previewed object.transform, which folds the
matrix into the text's xf (TextObject::transform), so area type scaled its
glyphs; and Direct Selection's anchor hit test only looked at paths, so the
frame's corners could not be picked. object.transform now takes
typeAreas, which reshapes the frame in text space (xf^-1 * m * xf), and
the tools use it and text.reshapeArea.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tool chose scale, distort, perspective or shear from the modifiers at
the press only, but a Cmd press on the canvas is the temporary Selection
tool, and the documented gesture is to start dragging and then hold Cmd.
A handle drag now follows the modifiers held during the drag; when the
operation changes, the interaction restarts so the undo step is named
after what was done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Type tool passed `y + 12` to `text.create` for a dragged frame, and
`text.create` puts the frame's top-left at (x, y), so every frame landed
12 pt below the marquee the user dragged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press only started a selection inside the text being edited; on other
type it waited for the release, which placed the caret at the press and
ignored the drag, so the first drag across a word never selected it.
A press on type now starts editing it there (the same code a click used
on release), so the drag selects at once; a plain click still just
places the caret.
Fixes#245.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pressing with Shift on an object that was already selected toggled it
out of the selection at once and went idle, so the drag that followed
moved nothing. The toggle now waits for the release: a Shift-click
still takes the object out of the selection, while a Shift-drag moves
the selection constrained, as it already did for unselected objects
and when Shift was pressed mid-drag.
Fixes#224.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9TrG4w49oSRYbP3cqxA3D
The Live Paint Bucket now fills areas enclosed by any paths. Crossing
lines enclose faces it can fill, a line across a shape splits that shape's
face in two, and an area enclosed by paths becomes a face even when nothing
fills it (it starts unfilled). Edges are the paths cut wherever another path
meets them, keeping their stroke, so Shift-click paints each piece. Closed
shapes give the same faces as before.
Root cause: Live Paint built its faces from the Pathfinder regions, which
divide the filled areas of closed shapes. An open path counted only as the
area it would fill if closed, which for a straight line is nothing, so lines
divided nothing and the bucket found no face to fill. A new planar map in
vectorcraft-pathops (live_paint) sweeps every path as an edge: an open path
goes in traced there and back, with crossing counts that never cancel; faces
are walked on the resulting graph (holes included), and their fills and the
edges' sources are found geometrically. The sweep runs in an exactly sheared
frame, because it mishandles a horizontal line traced there and back.
Make, Merge, the bucket and Live Paint Selection all use it;
interior_point moves to vectorcraft-pathops with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds on the vertical type from #145 (same data, commands and tools).
- Kinsoku (the strict set, horizontal Japanese too): no line starts with a
closing bracket, 、。, a middle dot, colon, !?, the long vowel mark,
iteration marks or small kana; no line ends with an opening bracket.
Hyphenation breaks obey it too.
- Tate-chu-yoko: a run of two or three half-width digits (10月, 第100回)
stands upright across the column as one block one em long, squeezed to
the em when wider; longer numbers stay on their side.
- Upright or on its side by Unicode's Vertical_Orientation: CJK and
symbols upright, marks that need a vertical form on their side when the
font has none; upright glyphs advance at least one em, centred, and turn
about the ideographic em box centre so they sit on the column's centre
line. Point type keeps its anchor on the first column's centre line.
- `vrt2` is no longer requested: its glyphs are already on their side, so
with the layout turning Latin and digits too they came out upside down
(and tate-chu-yoko blocks broke) in fonts that have it, e.g. Hiragino.
- The line→text mapping is one affine (`TextLayout::line_xf`, an exact
quarter turn plus the point-type offset); `logical_point` and
`physical_point` use it. Pixel snapping skips vertical type.
- Tests: kinsoku in both directions, tate-chu-yoko blocks, upright vs
sideways, anchor on the centre line, arrows along columns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Typing with an input method (the macOS Japanese IME and the like) used to
insert the raw keys (romaji came in as ASCII): egui's Ime events weren't
handled.
- Tools gain `ime_preedit` / `composing` / `ime_caret`; the Type tool lays
the marked text out in place inside its typing session, underlined (the
clause being converted thicker), and commits it through `text_input`.
Character ranges from the IME convert to byte ranges in one place. A
cancelled composition leaves no empty undo step; an empty clear with
nothing marked never deletes the selection.
- The UI sends Text and Ime events to the tool in order, sends IMEOutput
every frame while text is edited (what lets egui-winit enable the IME
and place its candidate window at the caret, or the converting clause),
and holds back keys, Enter/Escape, clipboard, single-key shortcuts and
menu commands (Undo, Cut, Select All… also from the native menu) while
composing. A click, a tool switch or a style change ends the
composition with the marked text kept as typed; on macOS the IME is
told to discard it (NSTextInputContext.discardMarkedText) so it doesn't
come back in the next input.
- Undo during a typing session also ends the Type tool's session, so its
caret and marked text follow the document.
- Tests: tools (char → byte ranges, underlines and candidate position),
engine (compose → commit is one step, cancel leaves no step, a click
commits, Undo mid-composition is safe), ui-egui (synthetic Ime events:
compose, commit, ⌘Z held back, IME output, click and app switch).
Checked by hand on macOS 27 with the Japanese IME (romaji input, convert,
commit, cancel, Undo/Redo after commit, click away, Latin mode, app
switch). Not checked: Windows and Linux IMEs, the web build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Edit Contents: while an envelope's contents are edited, clicks hit the
content where it sits undistorted and the Selection tool selects it; the
menu item reads Edit Envelope then. No default shortcut: the reference
app's (Shift+Cmd+V) is Paste in Place here.
- A selected envelope shows its mesh (a warp's surface grid, a mesh's own,
a top object's outline) instead of its content's outlines.
- Mesh envelope points get bezier handles (append-only; none stored is the
smooth Catmull-Rom mesh, which the first handle edit fills in exactly
along the grid lines). setMeshPoint takes `handle`; object.mesh.movePoint,
addLine and deletePoint edit mesh envelopes too. The Mesh tool and Direct
Selection drag points and the clicked point's handles (shared MeshEdit);
the Mesh tool adds rows and columns inside mesh envelopes. Reset with
Mesh keeps the shape along the grid with handles; Release returns the
exact mesh.
- Control bar: Edit Envelope / Edit Contents, warp style, orientation,
bend and distortions or mesh rows and columns, Reset, Envelope Options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Perspective Grid tool's left and right ground-level points move the whole
grid; the left and right extents are separate (extentRight; Alt drags both,
Shift goes by whole cells); a cell size widget sits up the line where the planes
meet; Shift keeps a vanishing point on the horizon and the ground points and
origin on one axis. The Plane Switching Widget stays put in a corner of the
document window (ViewInfo/ToolContext carry the window as a ScreenFrame), takes
its clicks with any tool while the grid shows (the Perspective Selection tool
too while it's hidden), and 1-4 pick the left, horizontal, right and no plane.
Double-clicking the Perspective Grid tool opens Perspective Grid Options
(show the widget, its corner; perspective.widget.options, kept with the
preferences).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Type and symbol instances attached to a plane keep their art and carry a projection
(Node.perspective.projection, append-only); the canvas and every export draw their outlines
through it (reshape/bake), so they are foreshortened and stay type and symbol instances. Bounds,
hit testing and transforms follow the projection. Object › Perspective › Edit Text
(perspective.editText, or double-clicking with the Perspective Selection tool) shows the type flat
where it is drawn in isolation mode; exiting isolation projects it again. Release with
Perspective keeps their look. Copies, pastes, duplicates and Selection-tool moves keep the
attachment (the art moves as dragged). Flare, spiral and polar grid draw on the active plane, and
so do the click-to-size shape dialogs (perspective.draw `at`: sizes in plane units).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Images distort as a raster mesh warp: cut by Fidelity into triangles,
each a clipped image under the affine map of its corners (pieces overlap
slightly, no seams; the renderer paints them without clip layers; SVG
writes the pixels once and <use>s them).
- Symbol instances distort as their symbol's art (the renderer's cache
follows edits to the symbol).
- Distort Appearance bakes strokes (filled outlines) and geometry effects
before bending; Distort Linear Gradients bends linear gradients; Distort
Pattern Fills bends the tiles. Opacity masks of the content bend too.
- Bug fix: a rotated, sheared or reflected warp envelope re-warped along
the page axes. Envelopes now keep their own frame (append-only model
field), so the warp turns with the art; move and scale keep it square.
- Live evaluation shares one set of hooks (vectorcraft_doc::live::Hooks,
vectorcraft_effects::{expand_live, expanded_live_group,
expand_live_deep}) between the canvas, Expand and every exporter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A compound path's stroke lives on the compound: the tool edits it through
any member, and stroke.widthPoint.* / stroke.widthProfile.set given a member
edit the compound's profile.
- Width points show on every subpath the stroke runs along (not just the
first) for the hovered and the selected stroke.
- Cursors: the Width tool's own glyphs off a stroke, over a stroke (a drag
adds a point) and over a width point or handle (a drag moves or widens it).
- Hint-bar text for the tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Holding the brush still keeps Twirl, Pucker and Bloat applying, scaled by the
time held: a new Tool::tick (time supplied by the host: canvas frames, or
`holdMs` on ui.pointer / pointer_gesture events) repeats the last point every
0.1 s, one more dab there. Liquify never touches graphs, meshes, type, symbols,
images, or envelopes, repeats and blends with their contents, nor guides in
selection mode; those under the brush come back in `skipped` with a `warning`
that the status bar shows (UiRequest::Status). A drag applies only the dabs
each new sample adds (the stroke's state is kept between previews), giving the
same result as the whole stroke, so journal replay still reproduces it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View > Perspective Grid gains Show/Hide Rulers, Snap to Grid (on by default),
Lock/Unlock Grid and Lock Station Point, with flipping labels and checkmarks (and
Show/Hide Grid's label). Lock Grid stops the grid widgets from dragging; Lock
Station Point swings the other vanishing point around the station point; Snap to
Grid lands drawn corners and moved edges on gridlines within a quarter cell
(perspective.draw/move take snap?); Show Rulers draws a ruler up the true height
line in the grid's units. Gridlines draw in the Define Grid colours and opacity.
New commands perspective.grid.lock/lockStation/snap/rulers (view state).
Existing tests that measure exact perspective moves (two in tests_distort.rs,
the persp_session helper of tests_persp_select.rs) turn snapping off first (it is on by
default and would move their exact corners onto gridlines).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid keeps per-plane offsets (leftOffset, rightOffset, groundOffset). Each plane has a widget
(a diamond in its colour) that the Perspective Grid and Perspective Selection tools drag: plain
moves the plane, Shift moves the objects on it too, Alt copies them (perspective.plane.move).
Double-clicking a widget, or ui.perspectivePlane, opens the plane's options dialog (location; Do
Not Move / Move All Objects / Copy All Objects). Object › Perspective › Move Plane to Match Object
(perspective.plane.matchObject). New objects attach where the plane is. Grid edits leave
attached art in place as the reference app does; perspective.grid.set {reproject: true} moves it
with the grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pins show as soon as the tool has art selected: automatic ones at the
centre and the middle of each limb's end (at least three), until pins are
placed. They stay across tool switches while the selection is unchanged and
a new selection starts afresh.
- The pins live in the document (Document.puppet, never saved) with the rest
shape they started from: every warp starts from it, so warps don't stack and
dragging a pin back restores the original; Undo/Redo take the pins back with
the art. Adding and deleting pins are undo steps too.
- object.puppetWarp gains rest: true (the tool's pins); the new query
object.puppetWarp.pins reads them. A click off the mesh adds nothing and
says so (not-allowed cursor, canvas label); the command refuses such pins.
- The mesh grid covers the expanded art, so tiny art with a wide Expand no
longer allocates millions of cells.
- Tests on groups, compound paths and open strokes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>