Commit Graph
80 Commits
Author SHA1 Message Date
BFlat d89f593bfa Merge pull request #7380 from ncic/pencil-cursors
Pencil: shows where a stroke continues, connects and closes
2026-11-15 00:38:01 +07:00
ncic fc47e4782b Pencil: Use Precise Cursors shows the plain crosshair again; a far close is a straight segment 2026-11-14 13:23:22 +01:00
ncic cbd1c142dc Merge remote-tracking branch 'upstream/main' into pencil-cursors
# Conflicts:
#	docs/target-app-parity.md
2026-11-14 09:05:01 +01:00
dotCooCoo b4a7f106ac Merge main into fix/smooth-keeps-closed-start 2026-11-13 22:58:44 -08:00
dotCooCoo 0263b4af13 Merge main into fix/smooth-keeps-closed-start 2026-11-13 20:38:29 -08:00
ncic 5a55fdce0e Merge upstream main into pencil-cursors
# Conflicts:
#	docs/mcp.md
#	docs/target-app-parity.md
2026-11-14 03:17:11 +01:00
ncic b48688cb1c Pencil: a closed stroke closes with a straight segment
A closed freehand stroke is fitted as the open stroke would be and closed by a
straight segment from its end back to its start. Fitted as one loop, the
closing segment curved on past both ends and back, like a hat's brim.
2026-11-14 02:10:48 +01:00
BFlat be1810e0c9 Merge pull request #7263 from dotCooCoo/feat/smooth-free-end-tangent
Smooth tool: an open path's end turns with the smoothing when a drag reaches it
2026-11-14 06:26:31 +07:00
dotCooCoo 380f03345f Merge main into feat/smooth-free-end-tangent 2026-11-13 14:01:37 -08:00
dotCooCoo cccc17d8f5 Merge main into fix/smooth-keeps-closed-start 2026-11-13 13:43:28 -08:00
ncic b67a08dc0a Pencil: shows where a stroke continues, connects and closes
The continuation cursor (a short rising line) shows over an open end of a
selected path before any press, freehand too, and while a continuing stroke
nears an open end of another selected path it connects to. The straight-line
cursor becomes a short level line. A stroke continuing a path, a straight line
too, shows the closing ring within Close Paths When Ends Are Within of the
path's other end, and the release closes it; path.freehand takes the tool's
closed for extend instead of its own four-Fidelity distance.
2026-11-13 22:29:09 +01:00
dotCooCoo 4ea2f2e9c6 Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 13:03:29 -08:00
dotCooCoo 1567957482 Smooth tool: an open path's end turns with the smoothing when a drag reaches it
When a Smooth tool drag refits the stretch at the first or last anchor
of an open path, the curve there now leaves or arrives along the
smoothed path within the tool's reach instead of along the first or
last segment, so a jagged end no longer keeps its jag. The end point
stays where it is. An end where the smoothed path turns back by 90
degrees or more keeps its segment's direction, and a stretch that ends
inside the path keeps the direction of the part outside the drag.

- pathops: SimplifyOptions gains free_start and free_end, off by
  default; closed subpaths and simplify_part ignore them, so Simplify
  and Object > Path > Smooth give the same output as before.
- engine: path.smoothRegion sets them for the ends of an open path
  that a run's refit reaches.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 11:51:02 -08:00
dotCooCoo fb476ca64d Smooth tool: smoothing over a closed path's first anchor keeps its start
When a drag reached the first anchor of a closed path, the smoothed path
started at the first anchor past the drag, and a drag over the whole
path could start it at a corner the refit kept. Dashes and type on the
path then started somewhere else.

The smoothed path now starts at its point nearest where the first anchor
was: at an anchor there, or at a new anchor split into the segment
there, which leaves the outline as it is. Of points equally near, the
one earliest along the path before smoothing, from its first anchor,
is used. A drag that does not reach the first anchor leaves it first,
as before.

Part of #2224.
2026-11-13 11:49:35 -08:00
dotCooCoo d1f76ede8a Smooth: a drag along a segment smooths it when its anchors are out of reach
A Smooth drag along the middle of a curved segment smoothed nothing when
neither of the segment's anchors was within the reach, and paths of two
anchors were never smoothed.

- path.smoothRegion searches each curved segment where it can come
  within the reach, halving it down to pieces no longer than half the
  reach, and adds an anchor in the middle of each stretch in reach; the
  existing reach split then cuts the segment where the drag enters and
  leaves the reach. A drag that only grazes a curve finds the place on
  it nearest the drag. The search is bounded per drag and shared
  equally by the segments in reach.
- Paths of two anchors are smoothed, open or closed.
- Alt with the Paintbrush smooths a selected two-anchor curve; a
  two-anchor straight line still gets the closed stroke. Both sides use
  one segment_is_straight test.

Part of #2224
2026-11-13 10:44:19 -08:00
dotCooCoo 1b935b6d0a Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 10:13:12 -08:00
BFlat 4f0d3f1eb3 Merge pull request #7111 from dotCooCoo/fix/freehand-tolerance-low-zoom
Pencil: Fidelity keeps its screen size at low zoom
2026-11-14 00:50:47 +07:00
dotCooCoo d92e63257b Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 08:44:25 -08:00
BFlat e74c2d04df Merge pull request #7083 from dotCooCoo/fix/simplify-line-keeps-tangent
Simplify: a refitted curve becomes a line only when its joins stay smooth
2026-11-13 23:31:37 +07:00
dotCooCoo 137bc11a2c Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 08:28:42 -08:00
BFlat 43d0b50760 Merge pull request #7157 from dotCooCoo/fix/remove-anchors-one-pass
Paths: Remove Anchor Points removes many anchors of a subpath in one pass
2026-11-13 23:15:10 +07:00
dotCooCoo 3348b41796 Merge main into fix/freehand-tolerance-low-zoom 2026-11-13 08:12:01 -08:00
BFlat d26413c3fb Merge pull request #7117 from dotCooCoo/fix/smooth-tool-smooths
Smooth tool: each drag smooths the path under it, and repeated drags keep smoothing
2026-11-13 23:04:21 +07:00
dotCooCoo 5f78e1fca7 Merge main into fix/simplify-line-keeps-tangent 2026-11-13 07:41:05 -08:00
dotCooCoo fad839f843 Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 07:29:34 -08:00
dotCooCoo 4fae51023e Paths: Remove Anchor Points removes many anchors of a subpath in one pass
Object > Path > Remove Anchor Points removed each selected anchor with
its own Vec::remove, shifting the rest of the subpath every time, so
removing k anchors from an n-anchor subpath took O(k*n) moves.

- pathops: remove_anchors removes a subpath's anchors the last first,
  reading each anchor's current neighbors, handles, length and closed
  state as removing them one at a time does, and compacts the anchor
  list once. remove_anchor and remove_anchors share one refit, so the
  result is bit for bit the same.
- engine: the command groups the selected anchors by subpath, works out
  the next selected anchor with each subpath reading only its own
  selected anchors, and takes emptied paths out of the selection
  together (Selection::remove_all).
- Tests compare remove_anchors bit for bit with remove_anchor over 600
  generated subpaths and focused cases, count the anchors moved (2001
  anchors, every other one removed: at most one move per anchor left,
  500,500 one at a time), and compare the next-anchor selection and
  the command on many paths and subpaths with removing one at a time.
2026-11-13 06:54:55 -08:00
dotCooCoo 349034dda2 Smooth tool: each drag smooths the path under it, and repeated drags keep smoothing
Part of #2224.

A Smooth tool drag only refitted the anchors in its reach within
Fidelity, so a wobble wider than Fidelity stayed, and a second drag over
the same place changed nothing. Repeated refits also drifted away from
the path, and a drag over a whole closed path left a corner at its first
anchor.

- path.smoothRegion now smooths the stretch in reach along its length
  with a Gaussian whose spread is a third of the reach, then refits it
  within Fidelity. The run's outer end points and handles stay fixed.
- Each refit stays within twice the distance the smoothing moved the
  path (at least a tenth of Fidelity), so repeated drags do not drift
  (SimplifyOptions::smooth_moved_only).
- A whole closed path is smoothed across its start as a loop, so a
  bump at the first anchor is smoothed like any other and no corner is
  left. Object > Path > Smooth smooths a closed path across its start
  the same way.
2026-11-13 04:44:12 -08:00
dotCooCoo 6e361b0bec Pencil: Fidelity keeps its screen size at low zoom
The Pencil and the Paintbrush send their Fidelity in points at the
current zoom, but path.freehand read at most 100 pt. At 3.13% zoom a
20 px Fidelity (639 pt) was fitted at 100 pt, the same as 3.13 px on
screen, so a stroke drawn zoomed out kept far more anchors than the
same stroke at 100%.

- path.freehand, and brush.freehand, which runs it, take a Fidelity
  from MIN_FIT_TOLERANCE to MAX_FIT_TOLERANCE (100 / 0.0313 pt, the
  100 px field maximum at the lowest zoom). A value outside that range
  is now rejected with an error instead of being read as the nearer
  end. The default stays 1.5 pt and path.blob keeps its 100 pt bound.
- The Pencil and the Paintbrush keep the value they send within that
  range, so a stroke draws at any zoom.
- An extending stroke still closes the path within four Fidelities of
  its other end, at most 400 pt, as before.

It conflicts with open #7089 in crates/tools/src/draw2.rs.

Docs: docs/mcp.md, docs/target-app-parity.md, path.freehand params.
2026-11-13 04:35:29 -08:00
dotCooCoo 1c0310a1d5 Smooth: Fidelity is in screen pixels, so a drag smooths as much at any zoom
The Smooth tool, and an Alt smoothing drag with the Pencil, Paintbrush or
Blob Brush, now send path.smoothRegion their Fidelity divided by the zoom,
as the Pencil and the Paintbrush do since #5748. Smooth Tool Options shows
Fidelity in px. A saved Fidelity keeps its number, so it smooths as before
at 100% zoom.

path.smoothRegion now refits at tolerances from 0.05 px at the highest zoom
to 100 px at the lowest (0.000078125 to about 3195 pt) instead of clamping
to 0.05 to 100 pt, so the setting keeps scaling across the whole zoom range.
2026-11-13 01:18:06 -08:00
dotCooCoo bc92def89e Smooth tool: smoothing a path deselects its direct-selected anchors
path.smoothRegion replaced the anchors of each path it smoothed but left
the selection alone, so anchor and segment refs selected with Direct
Selection still named the old indices: after a smooth that removed
anchors, a ref could point past the end of the path or at another
anchor. The edit now drops those refs for each path it changes, so the
path stays selected whole. Selected paths the drag does not change keep
their anchors, and undo restores the earlier selection.
2026-11-13 01:12:38 -08:00
dotCooCoo eee96556e6 Simplify: a refitted curve becomes a line only when its joins stay smooth
Simplify, Object > Path > Smooth, the Smooth tool and freehand strokes
turned every refitted curve whose handles lay near its chord into a
straight line. A short curve whose end directions were off the chord
passed that test, and the anchor between it and the next curve became a
corner the path did not have: a Smooth tool drag along a line jagged by
up to 6 pt left corners of 7.7 to 40.6 degrees.

A curve now becomes a line only when the line runs within 0.5 degrees
of the directions the path arrives and leaves along. A nearly straight
piece that would turn more stays a flat curve. Convert to Straight Lines
is unchanged.
2026-11-13 01:09:38 -08:00
dotCooCoo 1587cb84f7 Pen: Space held during a closing drag moves the first anchor
Space held while dragging out a new anchor already moved it, handles and
all, but the closing drag skipped it, so the closing anchor could not be
repositioned. The closing drag now moves the first anchor by as far as the
pointer moves while Space is held, its handles with it, and the drag
shapes the handles round the new place once Space is released.

path.close takes `at: [x, y]`, which moves the first anchor, its handles
with it, before `in` and `out` are placed. docs/mcp.md and the Pen line
in docs/target-app-parity.md describe it.
2026-11-12 18:55:10 -08:00
dotCooCoo e6add18663 Fix #6634: Pen closing drag no longer gives the first anchor symmetric handles
Closing a path by dragging on its first anchor made that anchor smooth
with symmetric handles, so the first segment bent along with the closing
one. The drag now sets the first anchor's incoming handle to the mirror
of the pointer. Its outgoing handle stays where it is on a corner, so
the first segment keeps its shape, and on a smooth anchor it turns to
stay in line at its own length. Alt makes the anchor a corner and leaves
the outgoing handle where it was when Alt went down. A click on the
first anchor closes the path with the incoming handle retracted and the
anchor a corner, so the closing segment comes in straight.

The Smart Pen's closing drag follows the same rule.

path.close's `in` now places the handle as path.setHandle does, and a
new `out` param places the outgoing handle and makes the anchor a
corner.
2026-11-11 15:33:09 -08:00
dotCooCoo f4496d61aa Paths: Add Anchor Point keeps the same anchors and segments selected
path.insertAnchor (the Add Anchor Point tool, the Pen's Auto Add, the
Smart Pen's Alt-click on a segment and Alt-click with the Scissors)
added the anchor without changing the selection, so the direct-selected
anchors and selected segments after it on the subpath kept their old
numbers and the selection shifted onto the next points along. It now
renumbers them with renumber_after_insert, the helper path.reshape
uses. A selected segment that the new anchor splits stays selected as
its two halves, and the new anchor is direct-selected with them. Undo
restores the path and the selection in one step.

Docs: params text, docs/mcp.md, docs/target-app-parity.md.
2026-11-10 22:17:08 -08:00
dotCooCoo 7eb1006abb Scissors: a cut leaves one of the two new end points selected
After a Scissors cut (`path.split`) the whole path stayed selected: both
pieces of an open path, or the opened path of a closed one. A Direct
Selection drag from the cut then moved every anchor, so an ellipse cut
at an anchor could not be pulled open by dragging its end.

- `path.split` selects the first anchor of the piece after the cut and
  nothing else, as `path.cutAtAnchors` does: anchor 0 of an opened
  closed subpath, of the new path when an open path becomes two, or of
  the subpath added to a path of several subpaths.
- A Direct Selection press on anchors of a selected path that lie on top
  of each other takes the selected one. It took the first in path order,
  which in a path of several subpaths was the unselected end.

Docs: docs/mcp.md, docs/target-app-parity.md, the `path.split` params
text.
2026-11-10 15:04:50 -08:00
dotCooCoo cb086ef1b7 Part of #6361: Cut takes direct-selected anchors out of the path
With anchors picked with Direct Selection, Edit > Cut removed only the
segments between them, so the picked anchors stayed in the path. Cut now
removes picked anchors and the segments attached to them from a path or
a compound path's member, as Clear does. Picked segments still lose only
those segments. When a path has picked segments, Copy and Cut leave out
anchors picked on its other contours, as Clear does.

Docs: docs/target-app-parity.md.
2026-11-09 17:03:43 -08:00
ruchirguitarandCursor 3deac491ca M2.1: merge current main and keep path-deletion evidence (#3380)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 09:03:17 +05:30
ruchirguitarandCursor 48fa6179b4 M2.1: keep the stronger Pen Cut split checks while merging the fork (#3380)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 09:02:03 +05:30
ruchirguitarandCursor 8782c14e59 M2.1: expect a cut Pen segment to leave independent path pieces (#3380)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 07:35:23 +05:30
ruchirguitar bcde7594fb M2.1: integrate current main and Pen Cut regression (#3380) 2026-11-09 04:40:11 +05:30
ruchirguitarandCursor 46cc28b299 M2.1: merge current main and keep path-deletion evidence (#3380)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 04:09:05 +05:30
dotCooCoo f9940486a8 Merge main into fix/spiral-decay-per-wind 2026-11-08 14:03:31 -08:00
Sven Lafebre afe8294c7d Test: anchors picked with a Cmd-lent Direct Selection over other tools are what Copy takes (#6123)
With the Rectangle and Ellipse tools, Cmd lends the selection tool chosen
last, and Copy already took the anchors Direct Selection picked. The
Curvature tool, which Cmd lends Direct Selection, copied the whole path
before this PR, as the Pen did.
2026-11-09 02:03:05 +07:00
Sven Lafebre 748ac5cf6e Pen: keep a lent pick across tool resets; a gesture that picks nothing keeps the Pen's anchor (#6123)
The remembered selection is now tied to the document it was made in, by its
uid, instead of being cleared on every tool reset: another view of the same
document, or closing an inactive tab, kept the anchors picked but made Copy
take the whole path.

A lent gesture that leaves the selection as it found it (a Cmd+Shift marquee
over nothing) is no longer recorded as a pick, so the Pen's last anchor stays
its own and Copy takes the whole path.

Also records #6123 in target-app-parity.md.
2026-11-09 02:03:05 +07:00
Sven Lafebre 91a0ba4231 Pen: Copy and Cut take the anchors a Cmd-lent Direct Selection picked (#6123)
With the Pen or Curvature tool, Copy and Cut ignore the anchor selection,
so that the last anchor the Pen leaves selected doesn't make Copy take just
that anchor (#2122). Anchors picked with the Direct Selection that Cmd/Ctrl
lends those tools were ignored too, and the whole path was copied.

The session now remembers the selection the lent tool leaves when it gives
the tool back. While that is still the selection, Copy and Cut take the
picked anchors and segments, as with Direct Selection chosen.
2026-11-09 02:03:05 +07:00
dotCooCoo 975c67681c Merge main into fix/spiral-decay-per-wind 2026-11-08 08:13:27 -08:00
ruchirguitar c232919dd3 M2.1: clarify deletion evidence and function documentation (#3380) 2026-11-08 02:02:31 +05:30
ruchirguitar e6a899857f M2.1: split disconnected path pieces after deletion (#3380) 2026-11-07 15:20:14 +05:30
Don Omondi 483de72dbb Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box (#3901)
Squashed from #5851 onto main.
2026-11-06 05:33:00 +07:00
Don Omondi b976de560d Join tool: scrubbing the ends of two crossing paths trims them and joins the paths at the crossing (#5844)
The Join tool drew a segment between the open ends of two crossing paths
instead of trimming what overhangs the crossing. path.joinScrub now looks for
two crossing open subpaths whose overhanging ends the scrub runs over, cuts
both at the crossing and joins the kept parts there with a corner, in one undo
step. Without a crossing the endpoint join is unchanged.
2026-11-06 05:32:24 +07:00