185 lines
4.2 KiB
TypeScript
185 lines
4.2 KiB
TypeScript
/**
|
|
* 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<DragEvent>) {
|
|
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<DragEvent>) {
|
|
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<DragEvent>) {
|
|
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;
|
|
}
|