[FEAT] UX enhancements
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-6 relative">
|
||||
<div class="bg-cyan-500 dark:bg-cyan-600 rounded-xl p-4 shadow-lg border border-cyan-400/50 dark:border-cyan-500/50">
|
||||
<div class="flex items-start gap-3">
|
||||
<i class="fas fa-lightbulb text-yellow-300 text-xl mt-0.5"></i>
|
||||
@@ -104,7 +104,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showContextMenu" class="fixed bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-600 rounded-xl shadow-2xl z-50 py-2 min-w-[200px] overflow-hidden" :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }">
|
||||
<div v-if="showContextMenu" class="absolute bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-600 rounded-xl shadow-2xl z-50 py-2 min-w-[200px] overflow-hidden" :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }">
|
||||
<button @click="addSprite" class="w-full px-5 py-3 text-left hover:bg-blue-50 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-plus text-blue-600 dark:text-blue-400"></i>
|
||||
<span>Add Sprite</span>
|
||||
@@ -310,8 +310,17 @@
|
||||
|
||||
const clickedSprite = findSpriteAtPosition(pos.x, pos.y);
|
||||
contextMenuSpriteId.value = clickedSprite?.id || null;
|
||||
contextMenuX.value = event.clientX;
|
||||
contextMenuY.value = event.clientY;
|
||||
|
||||
// Get the root component element to calculate offset
|
||||
const rootElement = canvasRef.value.closest('.space-y-6') as HTMLElement;
|
||||
if (!rootElement) return;
|
||||
|
||||
const rootRect = rootElement.getBoundingClientRect();
|
||||
|
||||
// Position relative to the component root
|
||||
contextMenuX.value = event.clientX - rootRect.left;
|
||||
contextMenuY.value = event.clientY - rootRect.top;
|
||||
|
||||
showContextMenu.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,6 +103,11 @@
|
||||
<span class="text-sm dark:text-gray-200">Reposition</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer ml-4" :class="{ 'opacity-50 cursor-not-allowed': !isDraggable }">
|
||||
<input type="checkbox" v-model="repositionAllLayers" class="w-4 h-4 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" :disabled="!isDraggable" />
|
||||
<span class="text-sm dark:text-gray-200">All layers</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" v-model="showAllSprites" class="w-4 h-4 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" />
|
||||
<span class="text-sm dark:text-gray-200">Compare sprites</span>
|
||||
@@ -170,6 +175,7 @@
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'updateSprite', id: string, x: number, y: number): void;
|
||||
(e: 'updateSpriteInLayer', layerId: string, spriteId: string, x: number, y: number): void;
|
||||
}>();
|
||||
|
||||
const previewCanvasRef = ref<HTMLCanvasElement | null>(null);
|
||||
@@ -207,6 +213,7 @@
|
||||
|
||||
// Preview state
|
||||
const isDraggable = ref(false);
|
||||
const repositionAllLayers = ref(false);
|
||||
const showAllSprites = ref(false);
|
||||
|
||||
const compositeFrames = computed<Sprite[]>(() => {
|
||||
@@ -238,6 +245,7 @@
|
||||
const dragStartX = ref(0);
|
||||
const dragStartY = ref(0);
|
||||
const spritePosBeforeDrag = ref({ x: 0, y: 0 });
|
||||
const allSpritesPosBeforeDrag = ref<Map<string, { x: number; y: number }>>(new Map());
|
||||
|
||||
// Canvas drawing
|
||||
|
||||
@@ -319,19 +327,47 @@
|
||||
const mouseX = ((event.clientX - rect.left) / zoom.value) * scaleX;
|
||||
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
|
||||
|
||||
const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value];
|
||||
const { negativeSpacing } = getCellDimensions();
|
||||
|
||||
// Check if click is on sprite (accounting for negative spacing offset)
|
||||
if (activeSprite) {
|
||||
const spriteCanvasX = negativeSpacing + activeSprite.x;
|
||||
const spriteCanvasY = negativeSpacing + activeSprite.y;
|
||||
if (mouseX >= spriteCanvasX && mouseX <= spriteCanvasX + activeSprite.width && mouseY >= spriteCanvasY && mouseY <= spriteCanvasY + activeSprite.height) {
|
||||
isDragging.value = true;
|
||||
activeSpriteId.value = activeSprite.id;
|
||||
dragStartX.value = mouseX;
|
||||
dragStartY.value = mouseY;
|
||||
spritePosBeforeDrag.value = { x: activeSprite.x, y: activeSprite.y };
|
||||
if (repositionAllLayers.value) {
|
||||
// Check if click is on any sprite from any visible layer
|
||||
const visibleLayers = getVisibleLayers();
|
||||
for (const layer of visibleLayers) {
|
||||
const sprite = layer.sprites[currentFrameIndex.value];
|
||||
if (!sprite) continue;
|
||||
|
||||
const spriteCanvasX = negativeSpacing + sprite.x;
|
||||
const spriteCanvasY = negativeSpacing + sprite.y;
|
||||
if (mouseX >= spriteCanvasX && mouseX <= spriteCanvasX + sprite.width && mouseY >= spriteCanvasY && mouseY <= spriteCanvasY + sprite.height) {
|
||||
isDragging.value = true;
|
||||
activeSpriteId.value = 'ALL_LAYERS'; // Special marker for all layers
|
||||
dragStartX.value = mouseX;
|
||||
dragStartY.value = mouseY;
|
||||
|
||||
// Store initial positions for all sprites in this frame from all visible layers
|
||||
allSpritesPosBeforeDrag.value.clear();
|
||||
visibleLayers.forEach(layer => {
|
||||
const s = layer.sprites[currentFrameIndex.value];
|
||||
if (s) {
|
||||
allSpritesPosBeforeDrag.value.set(s.id, { x: s.x, y: s.y });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only check active layer sprite
|
||||
const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value];
|
||||
if (activeSprite) {
|
||||
const spriteCanvasX = negativeSpacing + activeSprite.x;
|
||||
const spriteCanvasY = negativeSpacing + activeSprite.y;
|
||||
if (mouseX >= spriteCanvasX && mouseX <= spriteCanvasX + activeSprite.width && mouseY >= spriteCanvasY && mouseY <= spriteCanvasY + activeSprite.height) {
|
||||
isDragging.value = true;
|
||||
activeSpriteId.value = activeSprite.id;
|
||||
dragStartX.value = mouseX;
|
||||
dragStartY.value = mouseY;
|
||||
spritePosBeforeDrag.value = { x: activeSprite.x, y: activeSprite.y };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -349,21 +385,45 @@
|
||||
const deltaX = Math.round(mouseX - dragStartX.value);
|
||||
const deltaY = Math.round(mouseY - dragStartY.value);
|
||||
|
||||
const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value];
|
||||
if (!activeSprite || activeSprite.id !== activeSpriteId.value) return;
|
||||
|
||||
const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
|
||||
|
||||
// Calculate new position with constraints and round to integers
|
||||
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
|
||||
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
|
||||
if (activeSpriteId.value === 'ALL_LAYERS') {
|
||||
// Move all sprites in current frame from all visible layers
|
||||
const visibleLayers = getVisibleLayers();
|
||||
visibleLayers.forEach(layer => {
|
||||
const sprite = layer.sprites[currentFrameIndex.value];
|
||||
if (!sprite) return;
|
||||
|
||||
// Constrain movement within expanded cell (allow negative values up to -negativeSpacing)
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - activeSprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - activeSprite.height, newY));
|
||||
const originalPos = allSpritesPosBeforeDrag.value.get(sprite.id);
|
||||
if (!originalPos) return;
|
||||
|
||||
emit('updateSprite', activeSpriteId.value, newX, newY);
|
||||
drawPreviewCanvas();
|
||||
// Calculate new position with constraints
|
||||
let newX = Math.round(originalPos.x + deltaX);
|
||||
let newY = Math.round(originalPos.y + deltaY);
|
||||
|
||||
// Constrain movement within expanded cell
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - sprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - sprite.height, newY));
|
||||
|
||||
emit('updateSpriteInLayer', layer.id, sprite.id, newX, newY);
|
||||
});
|
||||
drawPreviewCanvas();
|
||||
} else {
|
||||
// Move only the active layer sprite
|
||||
const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value];
|
||||
if (!activeSprite || activeSprite.id !== activeSpriteId.value) return;
|
||||
|
||||
// Calculate new position with constraints and round to integers
|
||||
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
|
||||
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
|
||||
|
||||
// Constrain movement within expanded cell (allow negative values up to -negativeSpacing)
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - activeSprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - activeSprite.height, newY));
|
||||
|
||||
emit('updateSprite', activeSpriteId.value, newX, newY);
|
||||
drawPreviewCanvas();
|
||||
}
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
|
||||
Reference in New Issue
Block a user