| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function computeSnap(layer, nx, ny, ctx) { |
| const SNAP_PX = 6 / Math.max(ctx.zoom, 0.0001); |
| const cw = ctx.canvasW, ch = ctx.canvasH; |
| const w = layer.canvas.width, h = layer.canvas.height; |
|
|
| const vTargets = [ |
| { x: 0, label: 'canvas-l' }, |
| { x: cw, label: 'canvas-r' }, |
| { x: cw / 2, label: 'canvas-cx' }, |
| ]; |
| const hTargets = [ |
| { y: 0, label: 'canvas-t' }, |
| { y: ch, label: 'canvas-b' }, |
| { y: ch / 2, label: 'canvas-cy' }, |
| ]; |
| const otherLayers = Array.isArray(ctx.otherLayers) ? ctx.otherLayers : []; |
| for (const other of otherLayers) { |
| if (!other.visible || other.id === layer.id) continue; |
| const o = other.offset || { x: 0, y: 0 }; |
| const ow = other.canvas.width, oh = other.canvas.height; |
| vTargets.push({ x: o.x, label: 'layer-l' }); |
| vTargets.push({ x: o.x + ow, label: 'layer-r' }); |
| vTargets.push({ x: o.x + ow / 2, label: 'layer-cx' }); |
| hTargets.push({ y: o.y, label: 'layer-t' }); |
| hTargets.push({ y: o.y + oh, label: 'layer-b' }); |
| hTargets.push({ y: o.y + oh / 2, label: 'layer-cy' }); |
| } |
|
|
| const myEdgesX = { l: nx, cx: nx + w / 2, r: nx + w }; |
| const myEdgesY = { t: ny, cy: ny + h / 2, b: ny + h }; |
| let bestX = null, bestDx = Infinity; |
| let bestY = null, bestDy = Infinity; |
| for (const [src, val] of Object.entries(myEdgesX)) { |
| for (const t of vTargets) { |
| const d = Math.abs(t.x - val); |
| if (d < SNAP_PX && d < bestDx) { |
| bestDx = d; |
| bestX = { snapTo: t.x, src, target: t }; |
| } |
| } |
| } |
| for (const [src, val] of Object.entries(myEdgesY)) { |
| for (const t of hTargets) { |
| const d = Math.abs(t.y - val); |
| if (d < SNAP_PX && d < bestDy) { |
| bestDy = d; |
| bestY = { snapTo: t.y, src, target: t }; |
| } |
| } |
| } |
|
|
| const guides = []; |
| let snappedX = nx, snappedY = ny; |
| if (bestX) { |
| if (bestX.src === 'l') snappedX = bestX.snapTo; |
| else if (bestX.src === 'cx') snappedX = bestX.snapTo - w / 2; |
| else snappedX = bestX.snapTo - w; |
| guides.push({ vertical: true, x: bestX.snapTo }); |
| } |
| if (bestY) { |
| if (bestY.src === 't') snappedY = bestY.snapTo; |
| else if (bestY.src === 'cy') snappedY = bestY.snapTo - h / 2; |
| else snappedY = bestY.snapTo - h; |
| guides.push({ vertical: false, y: bestY.snapTo }); |
| } |
| return { x: snappedX, y: snappedY, guides }; |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| export function cursorForHandle(id) { |
| switch (id) { |
| case 'tl': case 'br': return 'nwse-resize'; |
| case 'tr': case 'bl': return 'nesw-resize'; |
| case 'rot': return 'grab'; |
| default: return 'default'; |
| } |
| } |
|
|