mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-04 04:30:40 +00:00
pen tool: auto-add / auto-delete + convert + handle-break (Illustrator parity)
- Pen tool over an existing segment now auto-inserts an anchor at the click with no modifier (was Alt-only). Pen over an anchor deletes it on click — Illustrator's default "Auto Add/Delete Anchor" behavior. - Drag (past a small threshold) on an existing anchor with the Pen tool converts the anchor on the fly: pulls symmetric handles from the drag, promoting corner → smooth. Alt-drag pulls only the outgoing handle for an asymmetric corner. - Alt-click an anchor in Pen mode still toggles corner ↔ smooth (drops or pairs handles without a drag), preserving the existing modifier. - Alt-drag a handle in Direct Selection breaks the smooth-anchor's handle symmetry: the node becomes a corner so the opposite handle stays put while the dragged one moves independently. - Endpoint-of-open-path still wins over auto-delete: hovering the start or last anchor of an open path shows the "continue" pointer cue, and clicking resumes drawing rather than deleting. - Hover cursors updated: − (delete) over a deletable anchor, + (add) over a segment in Pen mode, grab over a segment in Direct Select.
This commit is contained in:
1 parent
1d64306a33
commit
0fc45ae9fc
1 file changed
+103
-24
+103
-24
@@ -9747,28 +9747,32 @@
|
||||
if (editLayer) {
|
||||
const hit = hitTestPenEdit(editLayer, evt);
|
||||
if (hit) {
|
||||
if (hit.part === 'anchor' && evt.altKey) {
|
||||
// Alt-click an anchor → toggle corner / smooth.
|
||||
togglePenAnchorKind(editLayer, hit.nodeIndex);
|
||||
return;
|
||||
}
|
||||
if (hit.part === 'segment') {
|
||||
// Click on a segment (between anchors) → insert anchor + grab it.
|
||||
// Pen over a segment: auto-insert anchor at click + drag
|
||||
// (Illustrator's default — no modifier needed).
|
||||
insertPenAnchorAt(editLayer, hit.segIndex, hit.t);
|
||||
// Start dragging the new anchor immediately for ergonomics.
|
||||
startPenEditDrag(editLayer, { nodeIndex: hit.segIndex + 1, part: 'anchor' }, evt);
|
||||
return;
|
||||
}
|
||||
// Pen tool + plain click on an endpoint of an open path →
|
||||
// resume drawing from that endpoint. Illustrator parity.
|
||||
if (hit.part === 'anchor' && !evt.shiftKey && !evt.altKey
|
||||
&& editLayer.shape?.pen?.closed === false) {
|
||||
const last = editLayer.shape.pen.nodes.length - 1;
|
||||
if (hit.nodeIndex === 0 || hit.nodeIndex === last) {
|
||||
startPenContinuation(editLayer, hit.nodeIndex);
|
||||
return;
|
||||
if (hit.part === 'anchor') {
|
||||
// Endpoint of an open path → resume drawing from there.
|
||||
// Takes priority over auto-delete (Illustrator shows the
|
||||
// continue cursor on endpoint hover, not the − cursor).
|
||||
if (!evt.shiftKey && !evt.altKey
|
||||
&& editLayer.shape?.pen?.closed === false) {
|
||||
const last = editLayer.shape.pen.nodes.length - 1;
|
||||
if (hit.nodeIndex === 0 || hit.nodeIndex === last) {
|
||||
startPenContinuation(editLayer, hit.nodeIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Interior anchor: click → auto-delete; drag → convert
|
||||
// (pull symmetric handles, Alt-drag for asymmetric).
|
||||
startPenAnchorClickOrConvert(editLayer, hit.nodeIndex, evt);
|
||||
return;
|
||||
}
|
||||
// Handle hit (hIn / hOut): drag to edit the tangent; Alt
|
||||
// breaks symmetry. Handled inside startPenEditDrag.
|
||||
startPenEditDrag(editLayer, hit, evt);
|
||||
return;
|
||||
}
|
||||
@@ -10107,6 +10111,11 @@
|
||||
}
|
||||
} else {
|
||||
node[hit.part] = { x: local.x, y: local.y };
|
||||
// Alt-drag a handle in Direct Selection breaks symmetry:
|
||||
// node becomes corner so the opposite handle stays put.
|
||||
// Illustrator parity (the Convert Anchor Point tool's
|
||||
// Alt-drag-handle behavior).
|
||||
if (e.altKey) node.kind = 'corner';
|
||||
// Mirror the opposite handle if the anchor is smooth, so
|
||||
// editing one side of a curve keeps the curve smooth on
|
||||
// the other side too.
|
||||
@@ -10135,6 +10144,68 @@
|
||||
_penOverlay.addEventListener('pointercancel', up);
|
||||
}
|
||||
|
||||
// Pen tool on an interior anchor: disambiguate click vs drag.
|
||||
// Click without dragging deletes the anchor (auto-delete, the
|
||||
// Pen tool's default Illustrator behavior over an existing
|
||||
// anchor). Drag past a small threshold promotes the anchor to
|
||||
// smooth and pulls symmetric handles, converting a corner into
|
||||
// a smooth point on the fly. Alt-click toggles corner/smooth
|
||||
// without a drag; Alt-drag pulls a single (outgoing) handle and
|
||||
// leaves the incoming side empty, giving an asymmetric corner.
|
||||
function startPenAnchorClickOrConvert(layer, idx, evt) {
|
||||
const node = layer.shape.pen.nodes[idx];
|
||||
if (!node) return;
|
||||
const alt = !!evt.altKey;
|
||||
try { _penOverlay.setPointerCapture(evt.pointerId); } catch {}
|
||||
const startX = evt.clientX, startY = evt.clientY;
|
||||
const THRESHOLD = 4;
|
||||
let dragging = false;
|
||||
let historyPushed = false;
|
||||
const promote = () => {
|
||||
if (historyPushed) return;
|
||||
pushHistory('convert pen anchor');
|
||||
historyPushed = true;
|
||||
};
|
||||
const move = (e) => {
|
||||
if (!dragging) {
|
||||
const dx = e.clientX - startX, dy = e.clientY - startY;
|
||||
if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
|
||||
dragging = true;
|
||||
promote();
|
||||
}
|
||||
const pt = overlayPointToInches(e);
|
||||
if (!pt) return;
|
||||
const local = inchToPenNode(layer, pt);
|
||||
node.hOut = { x: local.x, y: local.y };
|
||||
if (alt) {
|
||||
// Asymmetric: only the outgoing handle is pulled.
|
||||
node.hIn = null;
|
||||
node.kind = 'corner';
|
||||
} else {
|
||||
node.hIn = { x: 2 * node.x - local.x, y: 2 * node.y - local.y };
|
||||
node.kind = 'smooth';
|
||||
}
|
||||
refitPenLayerIfNeeded(layer);
|
||||
regenPenLayerD(layer);
|
||||
renderLayers();
|
||||
renderPenEditHandles(layer);
|
||||
};
|
||||
const up = () => {
|
||||
_penOverlay.removeEventListener('pointermove', move);
|
||||
_penOverlay.removeEventListener('pointerup', up);
|
||||
_penOverlay.removeEventListener('pointercancel', up);
|
||||
if (!dragging) {
|
||||
if (alt) togglePenAnchorKind(layer, idx);
|
||||
else deletePenAnchor(layer, idx);
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
};
|
||||
_penOverlay.addEventListener('pointermove', move);
|
||||
_penOverlay.addEventListener('pointerup', up);
|
||||
_penOverlay.addEventListener('pointercancel', up);
|
||||
}
|
||||
|
||||
// Drag a curve segment body to bow it. Distributes the drag
|
||||
// delta equally across the two inner control points (a.hOut /
|
||||
// b.hIn) so the curve at parameter t tracks the cursor. Lines
|
||||
@@ -10785,19 +10856,27 @@
|
||||
const hit = hitTestPenEdit(editLayer, evt);
|
||||
if (hit) {
|
||||
if (hit.part === 'anchor') {
|
||||
// Pen tool's "click anchor" deletes; DSel's "click anchor" selects/drags.
|
||||
cursor = (t === 'pen') ? 'crosshair' : 'pointer';
|
||||
// Endpoint of an open path in pen mode → "continue from
|
||||
// here" affordance. Clicking seeds a draft from the
|
||||
// existing nodes so the next click extends the path.
|
||||
if (t === 'pen' && !editLayer.shape.pen.closed) {
|
||||
const lastIdx = editLayer.shape.pen.nodes.length - 1;
|
||||
if (hit.nodeIndex === 0 || hit.nodeIndex === lastIdx) cursor = 'pointer';
|
||||
if (t === 'pen') {
|
||||
// Pen over anchor = auto-delete (− cursor). The
|
||||
// 'not-allowed' built-in cursor is the closest
|
||||
// platform-agnostic "−" hint; the alternative would be a
|
||||
// custom SVG cursor.
|
||||
cursor = 'not-allowed';
|
||||
// Endpoint of an open path → "continue from here". Wins
|
||||
// over auto-delete since clicking there resumes drawing.
|
||||
if (!editLayer.shape.pen.closed) {
|
||||
const lastIdx = editLayer.shape.pen.nodes.length - 1;
|
||||
if (hit.nodeIndex === 0 || hit.nodeIndex === lastIdx) cursor = 'pointer';
|
||||
}
|
||||
} else {
|
||||
cursor = 'pointer';
|
||||
}
|
||||
} else if (hit.part === 'hIn' || hit.part === 'hOut') {
|
||||
cursor = 'move';
|
||||
} else if (hit.part === 'segment') {
|
||||
cursor = (t === 'pen') ? 'copy' : 'default'; // 'copy' is browsers' "+"-style cursor
|
||||
// Pen over segment: + cursor (auto-add). Direct Select:
|
||||
// grab to bow the curve.
|
||||
cursor = (t === 'pen') ? 'copy' : 'grab';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user