/** * Image dragging interactions for canvas * Handles dragging images to reposition them */ import Konva from 'konva'; import { selection } from '$lib/stores/selection'; import { get } from 'svelte/store'; export interface DragState { isDragging: boolean; startPos: { x: number; y: number } | null; draggedImageId: string | null; } const dragState: DragState = { isDragging: false, startPos: null, draggedImageId: null, }; /** * Setup drag interactions for an image */ export function setupImageDrag( image: Konva.Image | Konva.Group, imageId: string, onDragMove?: (imageId: string, x: number, y: number) => void, onDragEnd?: (imageId: string, x: number, y: number) => void ): () => void { /** * Handle drag start */ function handleDragStart(e: Konva.KonvaEventObject) { dragState.isDragging = true; dragState.startPos = { x: image.x(), y: image.y() }; dragState.draggedImageId = imageId; // If dragged image is not selected, select it const selectionState = get(selection); if (!selectionState.selectedIds.has(imageId)) { // Check if Ctrl/Cmd key is pressed if (e.evt.ctrlKey || e.evt.metaKey) { selection.addToSelection(imageId); } else { selection.selectOne(imageId); } } // Set dragging cursor const stage = image.getStage(); if (stage) { stage.container().style.cursor = 'grabbing'; } } /** * Handle drag move */ function handleDragMove(_e: Konva.KonvaEventObject) { if (!dragState.isDragging) return; const x = image.x(); const y = image.y(); // Call callback if provided if (onDragMove) { onDragMove(imageId, x, y); } // If multiple images are selected, move them together const selectionState = get(selection); if (selectionState.selectedIds.size > 1 && dragState.startPos) { const deltaX = x - dragState.startPos.x; const deltaY = y - dragState.startPos.y; // Update start position for next delta calculation dragState.startPos = { x, y }; // Dispatch custom event to move other selected images const stage = image.getStage(); if (stage) { stage.fire('multiDragMove', { draggedImageId: imageId, deltaX, deltaY, selectedIds: Array.from(selectionState.selectedIds), }); } } } /** * Handle drag end */ function handleDragEnd(_e: Konva.KonvaEventObject) { if (!dragState.isDragging) return; const x = image.x(); const y = image.y(); // Call callback if provided if (onDragEnd) { onDragEnd(imageId, x, y); } // Reset drag state dragState.isDragging = false; dragState.startPos = null; dragState.draggedImageId = null; // Reset cursor const stage = image.getStage(); if (stage) { stage.container().style.cursor = 'default'; } } // Enable dragging image.draggable(true); // Attach event listeners image.on('dragstart', handleDragStart); image.on('dragmove', handleDragMove); image.on('dragend', handleDragEnd); // Return cleanup function return () => { image.off('dragstart', handleDragStart); image.off('dragmove', handleDragMove); image.off('dragend', handleDragEnd); image.draggable(false); }; } /** * Move image to specific position (programmatic) */ export function moveImageTo( image: Konva.Image | Konva.Group, x: number, y: number, animate: boolean = false ): void { if (animate) { // TODO: Add animation support using Konva.Tween image.to({ x, y, duration: 0.3, easing: Konva.Easings.EaseOut, }); } else { image.position({ x, y }); } } /** * Move image by delta (programmatic) */ export function moveImageBy( image: Konva.Image | Konva.Group, deltaX: number, deltaY: number, animate: boolean = false ): void { const currentX = image.x(); const currentY = image.y(); moveImageTo(image, currentX + deltaX, currentY + deltaY, animate); } /** * Get current drag state (useful for debugging) */ export function getDragState(): DragState { return { ...dragState }; } /** * Check if currently dragging */ export function isDragging(): boolean { return dragState.isDragging; }