PageSourceSearch

https://jayreddinjr-jr-odysseus-hf.hf.space/static/js/editor/tools/clone.js

js jayreddinjr-jr-odysseus-hf.hf.space collected 2026-10-04 17:01:02 UTC 3,223 bytes, 81 lines download raw bytes

1/**
2 * Clone tool — Alt-click (desktop) or double-tap (mobile) sets the
3 * sample source; a regular click+drag stamps from that source onto the
4 * active layer. The source point moves WITH the brush so the offset
5 * stays constant across the stroke.
6 *
7 * begin() handles the source-pick and stroke-start branches; the
8 * actual per-sample stamping continues through the shared stroke
9 * pipeline (`_strokeTo`) which knows about clone-mode internally.
10 *
11 * @param {{
12 *   activeLayer: () => object | null,
13 *   saveState:   (label?: string) => void,
14 *   strokeTo:    (x: number, y: number) => void,
15 *   showToast:   (msg: string) => void,
16 * }} deps
17 */
18import { state } from '../state.js';
19import { canvasCoords } from '../canvas-coords.js';
20
21export function createCloneTool({ activeLayer, saveState, strokeTo, showToast }) {
22  return {
23    begin(e) {
24      const layer = activeLayer();
25      const coords = canvasCoords(e, state.mainCanvas);
26      // Mobile equivalent of Alt-click: double-tap in screen pixels.
27      // Wider tolerances (500 ms, 40 px) than desktop because finger
28      // taps drift more than mouse clicks.
29      const isTouchEvt = e.type && e.type.startsWith('touch');
30      let isDoubleTap = false;
31      if (isTouchEvt) {
32        const t = e.touches ? e.touches[0] : null;
33        const cx = t ? t.clientX : 0;
34        const cy = t ? t.clientY : 0;
35        const now = Date.now();
36        const dt = now - state.cloneLastTapTime;
37        const dx = cx - state.cloneLastTapX;
38        const dy = cy - state.cloneLastTapY;
39        if (dt < 500 && Math.hypot(dx, dy) < 40) {
40          isDoubleTap = true;
41          state.cloneLastTapTime = 0; // consume the pair
42        } else {
43          state.cloneLastTapTime = now;
44          state.cloneLastTapX = cx;
45          state.cloneLastTapY = cy;
46        }
47      }
48      if (e.altKey || isDoubleTap) {
49        state.cloneSourceX = coords.x;
50        state.cloneSourceY = coords.y;
51        state.cloneSourceLayerId = (layer && layer.id) || state.activeLayerId;
52        state.cloneSourceSnapshot = null; // captured at first stroke
53        showToast('Clone source set');
54        return;
55      }
56      if (state.cloneSourceX === null || state.cloneSourceY === null) {
57        showToast(isTouchEvt
58          ? 'Double-tap first to set a clone source'
59          : 'Alt-click first to set a clone source');
60        return;
61      }
62      if (!layer || layer.locked) return;
63      saveState('Clone stroke');
64      // Snapshot the source layer's pixels at stroke-start so the
65      // brush samples clean source pixels even after it has painted
66      // over them. Otherwise we'd cascade-clone the same ring.
67      const srcLayer = state.layers.find(l => l.id === state.cloneSourceLayerId) || layer;
68      const snap = document.createElement('canvas');
69      snap.width = srcLayer.canvas.width;
70      snap.height = srcLayer.canvas.height;
71      snap.getContext('2d').drawImage(srcLayer.canvas, 0, 0);
72      state.cloneSourceSnapshot = snap;
73      state.cloneStrokeStartX = coords.x;
74      state.cloneStrokeStartY = coords.y;
75      state.drawing = true;
76      state.lastX = coords.x;
77      state.lastY = coords.y;
78      strokeTo(coords.x, coords.y);
79    },
80  };
81}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.