diff --git a/src/index.html b/src/index.html index 7d8a8d2..8d3a3ff 100644 --- a/src/index.html +++ b/src/index.html @@ -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'; } } }