mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 09:45:24 -04:00
Odysseus v1.0
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Clone tool — Alt-click (desktop) or double-tap (mobile) sets the
|
||||
* sample source; a regular click+drag stamps from that source onto the
|
||||
* active layer. The source point moves WITH the brush so the offset
|
||||
* stays constant across the stroke.
|
||||
*
|
||||
* begin() handles the source-pick and stroke-start branches; the
|
||||
* actual per-sample stamping continues through the shared stroke
|
||||
* pipeline (`_strokeTo`) which knows about clone-mode internally.
|
||||
*
|
||||
* @param {{
|
||||
* activeLayer: () => object | null,
|
||||
* saveState: (label?: string) => void,
|
||||
* strokeTo: (x: number, y: number) => void,
|
||||
* showToast: (msg: string) => void,
|
||||
* }} deps
|
||||
*/
|
||||
import { state } from '../state.js';
|
||||
import { canvasCoords } from '../canvas-coords.js';
|
||||
|
||||
export function createCloneTool({ activeLayer, saveState, strokeTo, showToast }) {
|
||||
return {
|
||||
begin(e) {
|
||||
const layer = activeLayer();
|
||||
const coords = canvasCoords(e, state.mainCanvas);
|
||||
// Mobile equivalent of Alt-click: double-tap in screen pixels.
|
||||
// Wider tolerances (500 ms, 40 px) than desktop because finger
|
||||
// taps drift more than mouse clicks.
|
||||
const isTouchEvt = e.type && e.type.startsWith('touch');
|
||||
let isDoubleTap = false;
|
||||
if (isTouchEvt) {
|
||||
const t = e.touches ? e.touches[0] : null;
|
||||
const cx = t ? t.clientX : 0;
|
||||
const cy = t ? t.clientY : 0;
|
||||
const now = Date.now();
|
||||
const dt = now - state.cloneLastTapTime;
|
||||
const dx = cx - state.cloneLastTapX;
|
||||
const dy = cy - state.cloneLastTapY;
|
||||
if (dt < 500 && Math.hypot(dx, dy) < 40) {
|
||||
isDoubleTap = true;
|
||||
state.cloneLastTapTime = 0; // consume the pair
|
||||
} else {
|
||||
state.cloneLastTapTime = now;
|
||||
state.cloneLastTapX = cx;
|
||||
state.cloneLastTapY = cy;
|
||||
}
|
||||
}
|
||||
if (e.altKey || isDoubleTap) {
|
||||
state.cloneSourceX = coords.x;
|
||||
state.cloneSourceY = coords.y;
|
||||
state.cloneSourceLayerId = (layer && layer.id) || state.activeLayerId;
|
||||
state.cloneSourceSnapshot = null; // captured at first stroke
|
||||
showToast('Clone source set');
|
||||
return;
|
||||
}
|
||||
if (state.cloneSourceX === null || state.cloneSourceY === null) {
|
||||
showToast(isTouchEvt
|
||||
? 'Double-tap first to set a clone source'
|
||||
: 'Alt-click first to set a clone source');
|
||||
return;
|
||||
}
|
||||
if (!layer || layer.locked) return;
|
||||
saveState('Clone stroke');
|
||||
// Snapshot the source layer's pixels at stroke-start so the
|
||||
// brush samples clean source pixels even after it has painted
|
||||
// over them. Otherwise we'd cascade-clone the same ring.
|
||||
const srcLayer = state.layers.find(l => l.id === state.cloneSourceLayerId) || layer;
|
||||
const snap = document.createElement('canvas');
|
||||
snap.width = srcLayer.canvas.width;
|
||||
snap.height = srcLayer.canvas.height;
|
||||
snap.getContext('2d').drawImage(srcLayer.canvas, 0, 0);
|
||||
state.cloneSourceSnapshot = snap;
|
||||
state.cloneStrokeStartX = coords.x;
|
||||
state.cloneStrokeStartY = coords.y;
|
||||
state.drawing = true;
|
||||
state.lastX = coords.x;
|
||||
state.lastY = coords.y;
|
||||
strokeTo(coords.x, coords.y);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user