From 49b67dd10a261018d038d33cf3b7afb3a064fc56 Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Sat, 5 Apr 2025 13:50:07 +0200 Subject: [PATCH] Work --- src/application/canvasOperations.ts | 45 ++++++++++-- src/application/spriteOperations.ts | 29 +++++++- src/components/MainContent.vue | 102 ++++++++++++++++------------ 3 files changed, 125 insertions(+), 51 deletions(-) diff --git a/src/application/canvasOperations.ts b/src/application/canvasOperations.ts index c8871a5..49e8f59 100644 --- a/src/application/canvasOperations.ts +++ b/src/application/canvasOperations.ts @@ -120,11 +120,48 @@ export function renderSpritesheetPreview(showGrid = true) { return; } - // Check if sprite is within canvas bounds - if (sprite.x >= 0 && sprite.y >= 0 && sprite.x + sprite.width <= canvas.value!.width && sprite.y + sprite.height <= canvas.value!.height) { - renderSpriteOnCanvas(sprite, 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 cell boundaries + const cellLeft = cellX * cellSize.width; + const cellTop = cellY * cellSize.height; + + // Get the frame-specific offset for this sprite + const frameOffset = getSpriteOffset(index); + + // 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)); + + // Calculate final position ensuring sprite stays within cell bounds + const finalX = Math.max(cellLeft, Math.min(cellLeft + maxOffsetX, cellLeft + constrainedOffsetX)); + const finalY = Math.max(cellTop, Math.min(cellTop + maxOffsetY, cellTop + constrainedOffsetY)); + + // Update sprite position to stay within bounds + sprite.x = finalX; + sprite.y = finalY; + + // Update the frame offset with the constrained values + frameOffset.x = finalX - cellLeft; + frameOffset.y = finalY - cellTop; + + // Draw the image at its final position with pixel-perfect rendering + if (isImageReady(sprite.img)) { + ctx.value.imageSmoothingEnabled = false; // Keep pixel art sharp + ctx.value.drawImage(sprite.img, finalX, finalY, sprite.width, sprite.height); } else { - logger.warn(`Sprite at index ${index} is outside canvas bounds: sprite(${sprite.x},${sprite.y}) canvas(${canvas.value!.width},${canvas.value!.height})`); + logger.warn(`Sprite image ${index} not fully loaded, setting onload handler`); + sprite.img.onload = () => { + if (ctx.value && canvas.value) { + renderSpriteOnCanvas(sprite, index); + } + }; } } catch (spriteError) { logger.error(`Error rendering sprite at index ${index}:`, spriteError); diff --git a/src/application/spriteOperations.ts b/src/application/spriteOperations.ts index 59ad62b..e1c5cc9 100644 --- a/src/application/spriteOperations.ts +++ b/src/application/spriteOperations.ts @@ -30,6 +30,8 @@ export function addSprites(newSprites: Sprite[]) { sprites.value.push(...validSprites); sprites.value.sort((a, b) => a.uploadOrder - b.uploadOrder); + + // Update cell size before arranging sprites updateCellSize(); autoArrangeSprites(); } catch (error) { @@ -49,8 +51,9 @@ export function updateCellSize() { let maxWidth = 0; let maxHeight = 0; + // Find the maximum dimensions across all sprites sprites.value.forEach(sprite => { - if (sprite.width <= 0 || sprite.height <= 0) { + if (!sprite.img || sprite.width <= 0 || sprite.height <= 0) { logger.warn('Sprite with invalid dimensions detected', sprite); return; } @@ -63,10 +66,30 @@ export function updateCellSize() { return; } - cellSize.width = maxWidth; - cellSize.height = maxHeight; + // Add a small buffer to ensure sprites fit completely (optional) + const buffer = 0; // Increase if you want padding between sprites + cellSize.width = maxWidth + buffer; + cellSize.height = maxHeight + buffer; + + // Ensure all sprites are within their cell bounds after resize + sprites.value.forEach((sprite, index) => { + const column = index % columns.value; + const row = Math.floor(index / columns.value); + + // Calculate base position for the sprite's cell + const cellX = column * cellSize.width; + const cellY = row * cellSize.height; + + // Center the sprite within its cell if smaller than cell size + const offsetX = Math.floor((cellSize.width - sprite.width) / 2); + const offsetY = Math.floor((cellSize.height - sprite.height) / 2); + + sprite.x = cellX + offsetX; + sprite.y = cellY + offsetY; + }); updateCanvasSize(); + renderSpritesheetPreview(); } catch (error) { logger.error('Error updating cell size:', error); } diff --git a/src/components/MainContent.vue b/src/components/MainContent.vue index ca5d52b..16971c5 100644 --- a/src/components/MainContent.vue +++ b/src/components/MainContent.vue @@ -125,6 +125,9 @@ store.renderSpritesheetPreview(); }; + /** + * Handle mouse down to detect which sprite was clicked + */ const handleMouseDown = (e: MouseEvent) => { if (!canvasEl.value || store.sprites.value.length === 0) return; @@ -133,10 +136,11 @@ const x = (e.clientX - rect.left) / store.zoomLevel.value; const y = (e.clientY - rect.top) / store.zoomLevel.value; - // Find which sprite was clicked + // Find which sprite was clicked - start from top (last rendered) for (let i = store.sprites.value.length - 1; i >= 0; i--) { const sprite = store.sprites.value[i]; - if (x >= sprite.x && x <= sprite.x + store.cellSize.width && y >= sprite.y && y <= sprite.y + store.cellSize.height) { + // Check if click is within the actual sprite bounds, not just the cell + if (x >= sprite.x && x <= sprite.x + sprite.width && y >= sprite.y && y <= sprite.y + sprite.height) { store.draggedSprite.value = sprite; store.dragOffset.x = x - sprite.x; store.dragOffset.y = y - sprite.y; @@ -175,14 +179,14 @@ // Handle sprite dragging if (store.draggedSprite.value) { - if (store.isShiftPressed.value) { - // Free positioning within the cell bounds when shift is pressed - const cellX = Math.floor(store.draggedSprite.value.x / store.cellSize.width); - const cellY = Math.floor(store.draggedSprite.value.y / store.cellSize.height); + const sprite = store.draggedSprite.value; + const spriteIndex = store.sprites.value.findIndex(s => s.id === sprite.id); - // Calculate new position - const newX = x - store.dragOffset.x; - const newY = y - store.dragOffset.y; + if (store.isShiftPressed.value) { + // --- FREE POSITIONING WITHIN CELL --- + // Determine the current cell the sprite is in + const cellX = Math.floor(sprite.x / store.cellSize.width); + const cellY = Math.floor(sprite.y / store.cellSize.height); // Calculate cell boundaries const cellLeft = cellX * store.cellSize.width; @@ -190,20 +194,27 @@ const cellRight = cellLeft + store.cellSize.width; const cellBottom = cellTop + store.cellSize.height; - // Calculate maximum allowed position to keep sprite within cell - const maxX = cellRight - store.draggedSprite.value.width; - const maxY = cellBottom - store.draggedSprite.value.height; + // Calculate new position based on mouse movement + const newX = x - store.dragOffset.x; + const newY = y - store.dragOffset.y; - // Constrain position to stay within the cell - store.draggedSprite.value.x = Math.max(cellLeft, Math.min(newX, maxX)); - store.draggedSprite.value.y = Math.max(cellTop, Math.min(newY, maxY)); + // Calculate maximum allowed position to keep sprite strictly within cell + // This ensures the sprite cannot extend beyond its assigned cell + const maxX = cellLeft + (store.cellSize.width - sprite.width); + const maxY = cellTop + (store.cellSize.height - sprite.height); - // Calculate the offset within the cell - const offsetX = store.draggedSprite.value.x - cellLeft; - const offsetY = store.draggedSprite.value.y - cellTop; + // Constrain position to keep sprite fully within the cell + const constrainedX = Math.max(cellLeft, Math.min(newX, maxX)); + const constrainedY = Math.max(cellTop, Math.min(newY, maxY)); + + // Update sprite position + sprite.x = constrainedX; + sprite.y = constrainedY; + + // Calculate and update the offset within the cell + const offsetX = sprite.x - cellLeft; + const offsetY = sprite.y - cellTop; - // Update the sprite offset in the store for the current sprite - const spriteIndex = store.sprites.value.findIndex(s => s.id === store.draggedSprite.value.id); if (spriteIndex !== -1) { const frameOffset = store.getSpriteOffset(spriteIndex); frameOffset.x = offsetX; @@ -214,35 +225,38 @@ store.currentSpriteOffset.y = offsetY; } } else { - // Calculate new position based on grid cells (snap to grid) - const newCellX = Math.floor((x - store.dragOffset.x) / store.cellSize.width); - const newCellY = Math.floor((y - store.dragOffset.y) / store.cellSize.height); + // --- GRID SNAPPING MODE --- + if (!canvasEl.value) return; - // Make sure we stay within bounds - if (canvasEl.value) { - const maxCellX = Math.floor(canvasEl.value.width / store.cellSize.width) - 1; - const maxCellY = Math.floor(canvasEl.value.height / store.cellSize.height) - 1; + // Calculate target cell coordinates based on mouse position + const targetCellX = Math.floor((x - store.dragOffset.x) / store.cellSize.width); + const targetCellY = Math.floor((y - store.dragOffset.y) / store.cellSize.height); - const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX)); - const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY)); + // Calculate how many cells the sprite occupies + const spriteCellsWide = Math.ceil(sprite.width / store.cellSize.width); + const spriteCellsHigh = Math.ceil(sprite.height / store.cellSize.height); - // Update the sprite position with pixel-perfect coordinates - const newX = boundedCellX * store.cellSize.width; - const newY = boundedCellY * store.cellSize.height; - store.draggedSprite.value.x = newX; - store.draggedSprite.value.y = newY; + // Calculate maximum valid cell position to prevent overflow + const maxValidCellX = Math.floor(canvasEl.value.width / store.cellSize.width) - spriteCellsWide; + const maxValidCellY = Math.floor(canvasEl.value.height / store.cellSize.height) - spriteCellsHigh; - // When snapping to grid, reset any offsets for this sprite - const spriteIndex = store.sprites.value.findIndex(s => s.id === store.draggedSprite.value.id); - if (spriteIndex !== -1) { - const frameOffset = store.getSpriteOffset(spriteIndex); - frameOffset.x = 0; - frameOffset.y = 0; + // Ensure we don't place sprites where they would extend beyond canvas bounds + const boundedCellX = Math.max(0, Math.min(targetCellX, maxValidCellX)); + const boundedCellY = Math.max(0, Math.min(targetCellY, maxValidCellY)); - // Also update current offset for UI consistency - store.currentSpriteOffset.x = 0; - store.currentSpriteOffset.y = 0; - } + // Update sprite position to align with cell grid + sprite.x = boundedCellX * store.cellSize.width; + sprite.y = boundedCellY * store.cellSize.height; + + // Reset any offsets for grid-snapped sprites + if (spriteIndex !== -1) { + const frameOffset = store.getSpriteOffset(spriteIndex); + frameOffset.x = 0; + frameOffset.y = 0; + + // Also update current offset for UI consistency + store.currentSpriteOffset.x = 0; + store.currentSpriteOffset.y = 0; } }