diff --git a/src/components/PreviewModal.vue b/src/components/PreviewModal.vue index ab60efe..67a2c3c 100644 --- a/src/components/PreviewModal.vue +++ b/src/components/PreviewModal.vue @@ -80,7 +80,7 @@
- Position: {{ Math.round(spriteOffset.x) }}px, {{ Math.round(spriteOffset.y) }}px (drag to move within cell) + Position: {{ Math.round(spriteOffset?.x ?? 0) }}px, {{ Math.round(spriteOffset?.y ?? 0) }}px (drag to move within cell)
(null); + // Add this constant for pan amount + const panAmount = 10; // pixels to pan per keypress + const isModalOpen = computed({ get: () => store.isModalOpen.value, set: value => { @@ -150,20 +153,18 @@ get: () => { // Get the offset for the current frame const frameOffset = store.getSpriteOffset(currentFrame.value); - // Update the current offset for UI display - store.currentSpriteOffset.x = frameOffset.x; - store.currentSpriteOffset.y = frameOffset.y; - return store.currentSpriteOffset; + // Return the frame-specific offset directly + return frameOffset; }, set: val => { - // Update both the current offset and the frame-specific offset - store.currentSpriteOffset.x = val.x; - store.currentSpriteOffset.y = val.y; - - // Get the frame-specific offset and update it + // Update the frame-specific offset directly const frameOffset = store.getSpriteOffset(currentFrame.value); frameOffset.x = val.x; frameOffset.y = val.y; + + // Also update the current offset for UI consistency + store.currentSpriteOffset.x = val.x; + store.currentSpriteOffset.y = val.y; }, }); const isCanvasDragging = ref(false); @@ -184,7 +185,7 @@ // Computed property to check if sprite has been moved from original position const hasSpriteOffset = computed(() => { - return spriteOffset.x !== 0 || spriteOffset.y !== 0; + return spriteOffset.value.x !== 0 || spriteOffset.value.y !== 0; }); // applyOffsetsToMainView function removed @@ -298,10 +299,6 @@ frameOffset.y = centerY; } - // Update the current offset for UI display - store.currentSpriteOffset.x = frameOffset.x; - store.currentSpriteOffset.y = frameOffset.y; - // Render with the frame-specific offset store.renderAnimationFrame(0, showAllSprites.value, frameOffset); } @@ -357,9 +354,8 @@ animation.value.currentFrame = currentFrame.value; animation.value.manualUpdate = true; - // Get the frame-specific offset - const frameOffset = store.getSpriteOffset(currentFrame.value); - store.renderAnimationFrame(currentFrame.value, showAllSprites.value, frameOffset); + // Use the computed spriteOffset directly + store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value); }; const handleFrameRateChange = () => { @@ -438,14 +434,8 @@ const centerX = Math.max(0, Math.floor((store.cellSize.width - currentSprite.width) / 2)); const centerY = Math.max(0, Math.floor((store.cellSize.height - currentSprite.height) / 2)); - // Reset the sprite offset for the current frame to the center position - const frameOffset = store.getSpriteOffset(currentFrame.value); - frameOffset.x = centerX; - frameOffset.y = centerY; - - // Also update the current offset - store.currentSpriteOffset.x = centerX; - store.currentSpriteOffset.y = centerY; + // Update the sprite offset using the computed property setter + spriteOffset.value = { x: centerX, y: centerY }; // Update the frame to reflect the change updateFrame(); @@ -497,9 +487,6 @@ // Only move when delta exceeds the threshold for one pixel movement at current zoom const pixelThreshold = previewZoom.value; // One pixel at current zoom level - // Get the frame-specific offset - const frameOffset = store.getSpriteOffset(currentFrame.value); - // Calculate the maximum allowed offset const maxOffsetX = Math.max(0, store.cellSize.width - currentSprite.width); const maxOffsetY = Math.max(0, store.cellSize.height - currentSprite.height); @@ -507,8 +494,8 @@ // Move one pixel at a time when threshold is reached if (Math.abs(deltaX) >= pixelThreshold) { const pixelsToMove = Math.sign(deltaX); - const newX = frameOffset.x + pixelsToMove; - frameOffset.x = Math.max(0, Math.min(maxOffsetX, newX)); + const newX = spriteOffset.value.x + pixelsToMove; + spriteOffset.value.x = Math.max(0, Math.min(maxOffsetX, newX)); // Reset the start X position for next pixel move canvasDragStart.value.x = e.clientX; @@ -516,17 +503,13 @@ if (Math.abs(deltaY) >= pixelThreshold) { const pixelsToMove = Math.sign(deltaY); - const newY = frameOffset.y + pixelsToMove; - frameOffset.y = Math.max(0, Math.min(maxOffsetY, newY)); + const newY = spriteOffset.value.y + pixelsToMove; + spriteOffset.value.y = Math.max(0, Math.min(maxOffsetY, newY)); // Reset the start Y position for next pixel move canvasDragStart.value.y = e.clientY; } - // Update the current offset to match - store.currentSpriteOffset.x = frameOffset.x; - store.currentSpriteOffset.y = frameOffset.y; - // Update the frame updateFrame(); @@ -651,7 +634,7 @@ if (isModalOpen.value && newSprites.length > 0) { updateCanvasSize(); updateCanvasContainerSize(); - store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value); + updateFrame(); } }, { deep: true } @@ -670,7 +653,7 @@ () => previewBorder.value, () => { if (isModalOpen.value && sprites.value.length > 0) { - store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value); + updateFrame(); } }, { deep: true } @@ -681,7 +664,7 @@ () => showAllSprites.value, () => { if (isModalOpen.value && sprites.value.length > 0) { - store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value); + updateFrame(); } } ); diff --git a/src/composables/useSpritesheetStore.ts b/src/composables/useSpritesheetStore.ts index b8f944a..e8f68ac 100644 --- a/src/composables/useSpritesheetStore.ts +++ b/src/composables/useSpritesheetStore.ts @@ -259,9 +259,21 @@ export function useSpritesheetStore() { // Get the frame-specific offset for this sprite const frameOffset = getSpriteOffset(index); - // Apply the frame-specific offset to the sprite position - const finalX = x + frameOffset.x; - const finalY = y + frameOffset.y; + // Calculate the maximum allowed offset based on sprite and cell size + const maxOffsetX = Math.max(0, cellSize.width - sprite.width); + const maxOffsetY = Math.max(0, cellSize.height - sprite.height); + + // Constrain the offset to prevent out-of-bounds positioning + const constrainedOffsetX = Math.max(0, Math.min(maxOffsetX, frameOffset.x)); + const constrainedOffsetY = Math.max(0, Math.min(maxOffsetY, frameOffset.y)); + + // Update the frame offset with the constrained values + frameOffset.x = constrainedOffsetX; + frameOffset.y = constrainedOffsetY; + + // Apply the constrained offset to the sprite position + const finalX = x + constrainedOffsetX; + const finalY = y + constrainedOffsetY; // Draw the image at its final position with pixel-perfect rendering ctx.value!.imageSmoothingEnabled = false; // Keep pixel art sharp @@ -277,9 +289,21 @@ export function useSpritesheetStore() { // Get the frame-specific offset for this sprite const frameOffset = getSpriteOffset(index); - // Apply the frame-specific offset to the sprite position - const finalX = x + frameOffset.x; - const finalY = y + frameOffset.y; + // Calculate the maximum allowed offset based on sprite and cell size + const maxOffsetX = Math.max(0, cellSize.width - sprite.width); + const maxOffsetY = Math.max(0, cellSize.height - sprite.height); + + // Constrain the offset to prevent out-of-bounds positioning + const constrainedOffsetX = Math.max(0, Math.min(maxOffsetX, frameOffset.x)); + const constrainedOffsetY = Math.max(0, Math.min(maxOffsetY, frameOffset.y)); + + // Update the frame offset with the constrained values + frameOffset.x = constrainedOffsetX; + frameOffset.y = constrainedOffsetY; + + // Apply the constrained offset to the sprite position + const finalX = x + constrainedOffsetX; + const finalY = y + constrainedOffsetY; ctx.value.imageSmoothingEnabled = false; // Keep pixel art sharp ctx.value.drawImage(sprite.img, finalX, finalY, sprite.width, sprite.height); @@ -432,11 +456,33 @@ export function useSpritesheetStore() { // Ensure pixel art remains sharp in the downloaded file tempCtx.imageSmoothingEnabled = false; - sprites.value.forEach(sprite => { - // Use rounded coordinates for pixel-perfect rendering - const x = Math.round(sprite.x); - const y = Math.round(sprite.y); - tempCtx.drawImage(sprite.img, x, y); + sprites.value.forEach((sprite, index) => { + // Get the frame-specific offset for this sprite + const frameOffset = getSpriteOffset(index); + + // Calculate the cell coordinates for this sprite + const cellX = Math.floor(sprite.x / cellSize.width); + const cellY = Math.floor(sprite.y / cellSize.height); + + // Calculate the base position within the cell + const baseX = cellX * cellSize.width; + const baseY = cellY * cellSize.height; + + // Calculate the maximum allowed offset based on sprite and cell size + // This prevents sprites from going out of bounds + const maxOffsetX = Math.max(0, cellSize.width - sprite.width); + const maxOffsetY = Math.max(0, cellSize.height - sprite.height); + + // Constrain the offset to prevent out-of-bounds positioning + const constrainedOffsetX = Math.max(0, Math.min(maxOffsetX, frameOffset.x)); + const constrainedOffsetY = Math.max(0, Math.min(maxOffsetY, frameOffset.y)); + + // Apply the constrained offset to the base position + const finalX = baseX + constrainedOffsetX; + const finalY = baseY + constrainedOffsetY; + + // Draw the sprite at the calculated position + tempCtx.drawImage(sprite.img, finalX, finalY, sprite.width, sprite.height); }); const link = document.createElement('a');