import { CanvasSmbl } from './canvas-smbl.js'; import { ProcessedSmbl } from './move-evt-proc.js'; import { listen, listenDel } from './util.js'; /** * Get point in canvas given the scale and position of the canvas * @param {{position:{x:number, y:number}, scale:number}} canvasData * @param {number} x, @param {number} y */ export const pointInCanvas = (canvasData, x, y) => ({ x: (x - canvasData.position.x) / canvasData.scale, y: (y - canvasData.position.y) / canvasData.scale }); /** * @param {Point} point * @param {number} cell */ export function placeToCell(point, cell) { const cellSizeHalf = cell / 2; function placeToCell(coordinate) { const coor = (Math.round(coordinate / cell) * cell); return (coordinate - coor > 0) ? coor + cellSizeHalf : coor - cellSizeHalf; } point.x = placeToCell(point.x); point.y = placeToCell(point.y); } /** @param { CanvasElement } canvas */ export function moveScaleApplay(canvas) { const canvasData = canvas[CanvasSmbl].data; const gripUpdate = applayGrid(canvas.ownerSVGElement, canvasData); function transform() { canvas.style.transform = `matrix(${canvasData.scale}, 0, 0, ${canvasData.scale}, ${canvasData.position.x}, ${canvasData.position.y})`; gripUpdate(); } /** * @param {number} nextScale * @param {Point} originPoint */ function scale(nextScale, originPoint) { if (nextScale < 0.25 || nextScale > 4) { return; } const divis = nextScale / canvasData.scale; canvasData.scale = nextScale; canvasData.position.x = divis * (canvasData.position.x - originPoint.x) + originPoint.x; canvasData.position.y = divis * (canvasData.position.y - originPoint.y) + originPoint.y; transform(); } // move, scale with fingers applayFingers(canvas.ownerSVGElement, canvasData, scale, transform); // scale with mouse wheel canvas.ownerSVGElement.addEventListener('wheel', /** @param {WheelEvent} evt */ evt => { evt.preventDefault(); const delta = evt.deltaY || evt.deltaX; const scaleStep = Math.abs(delta) < 50 ? 0.05 // trackpad pitch : 0.25; // mouse wheel scale( canvasData.scale + (delta < 0 ? scaleStep : -scaleStep), evtPoint(evt)); }); canvas[CanvasSmbl].move = function (x, y, scale) { canvasData.position.x = x; canvasData.position.y = y; canvasData.scale = scale; transform(); }; } /** * @param { SVGSVGElement } svg * @param { {position:Point, scale:number} } canvasData * @param { {(nextScale:number, originPoint:Point):void} } scaleFn * @param { {():void} } transformFn * @return */ function applayFingers(svg, canvasData, scaleFn, transformFn) { /** @type { Pointer } */ let firstPointer; /** @type { Pointer} */ let secondPointer; /** @type {number} */ let distance; /** @type {Point} */ let center; /** @param {PointerEvent} evt */ function cancel(evt) { distance = null; center = null; if (firstPointer?.id === evt.pointerId) { firstPointer = null; } if (secondPointer?.id === evt.pointerId) { secondPointer = null; } if (!firstPointer && !secondPointer) { listenDel(svg, 'pointermove', move); listenDel(svg, 'pointercancel', cancel); listenDel(svg, 'pointerup', cancel); } }; /** @param {PointerEvent} evt */ function move(evt) { if (evt[ProcessedSmbl]) { return; } if ((firstPointer && !secondPointer) || (!firstPointer && secondPointer)) { // move with one pointer canvasData.position.x = evt.clientX + (firstPointer || secondPointer).shift.x; canvasData.position.y = evt.clientY + (firstPointer || secondPointer).shift.y; transformFn(); return; } if (!secondPointer || !firstPointer || (secondPointer?.id !== evt.pointerId && firstPointer?.id !== evt.pointerId)) { return; } const distanceNew = Math.hypot(firstPointer.pos.x - secondPointer.pos.x, firstPointer.pos.y - secondPointer.pos.y); const centerNew = { x: (firstPointer.pos.x + secondPointer.pos.x) / 2, y: (firstPointer.pos.y + secondPointer.pos.y) / 2 }; // not first move if (distance) { canvasData.position.x = canvasData.position.x + centerNew.x - center.x; canvasData.position.y = canvasData.position.y + centerNew.y - center.y; scaleFn( canvasData.scale / distance * distanceNew, centerNew); } distance = distanceNew; center = centerNew; if (firstPointer.id === evt.pointerId) { firstPointer = evtPointer(evt, canvasData); } if (secondPointer.id === evt.pointerId) { secondPointer = evtPointer(evt, canvasData); } } listen(svg, 'pointerdown', /** @param {PointerEvent} evt */ evt => { if (evt[ProcessedSmbl] || (!firstPointer && !evt.isPrimary) || (firstPointer && secondPointer)) { return; } svg.setPointerCapture(evt.pointerId); if (!firstPointer) { listen(svg, 'pointermove', move); listen(svg, 'pointercancel', cancel); listen(svg, 'pointerup', cancel); } if (!firstPointer) { firstPointer = evtPointer(evt, canvasData); return; } if (!secondPointer) { secondPointer = evtPointer(evt, canvasData); } }); } /** * @param { SVGSVGElement } svg * @param { import('./canvas-smbl.js').CanvasData } canvasData */ function applayGrid(svg, canvasData) { let curOpacity; /** @param {number} opacity */ function backImg(opacity) { if (curOpacity !== opacity) { curOpacity = opacity; svg.style.backgroundImage = `radial-gradient(rgb(73 80 87 / ${opacity}) 1px, transparent 0)`; } } backImg(0.7); svg.style.backgroundSize = `${canvasData.cell}px ${canvasData.cell}px`; return function() { const size = canvasData.cell * canvasData.scale; if (canvasData.scale < 0.5) { backImg(0); } else if (canvasData.scale <= 0.9) { backImg(0.3); } else { backImg(0.7); } svg.style.backgroundSize = `${size}px ${size}px`; svg.style.backgroundPosition = `${canvasData.position.x}px ${canvasData.position.y}px`; }; } /** * @param {PointerEvent | MouseEvent} evt * @return {Point} */ function evtPoint(evt) { return { x: evt.clientX, y: evt.clientY }; } /** * @param { PointerEvent } evt * @param { {position:Point, scale:number} } canvasData * @return { Pointer } */ function evtPointer(evt, canvasData) { return { id: evt.pointerId, pos: evtPoint(evt), shift: { x: canvasData.position.x - evt.clientX, y: canvasData.position.y - evt.clientY } }; } /** @typedef { {x:number, y:number} } Point */ /** @typedef { {id:number, pos:Point, shift:Point} } Pointer */ /** @typedef { import("./move-evt-proc").ProcEvent } DgrmEvent */ /** @typedef { import('./canvas-smbl.js').CanvasData } CanvasData */ /** @typedef { import('./canvas-smbl.js').CanvasElement } CanvasElement */