This commit is contained in:
Dennis Postma 2025-04-05 13:50:07 +02:00
parent 5a7f7032ef
commit 49b67dd10a
3 changed files with 125 additions and 51 deletions

View File

@ -120,11 +120,48 @@ export function renderSpritesheetPreview(showGrid = true) {
return; return;
} }
// Check if sprite is within canvas bounds // Calculate the cell coordinates for this sprite
if (sprite.x >= 0 && sprite.y >= 0 && sprite.x + sprite.width <= canvas.value!.width && sprite.y + sprite.height <= canvas.value!.height) { const cellX = Math.floor(sprite.x / cellSize.width);
renderSpriteOnCanvas(sprite, index); 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 { } 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) { } catch (spriteError) {
logger.error(`Error rendering sprite at index ${index}:`, spriteError); logger.error(`Error rendering sprite at index ${index}:`, spriteError);

View File

@ -30,6 +30,8 @@ export function addSprites(newSprites: Sprite[]) {
sprites.value.push(...validSprites); sprites.value.push(...validSprites);
sprites.value.sort((a, b) => a.uploadOrder - b.uploadOrder); sprites.value.sort((a, b) => a.uploadOrder - b.uploadOrder);
// Update cell size before arranging sprites
updateCellSize(); updateCellSize();
autoArrangeSprites(); autoArrangeSprites();
} catch (error) { } catch (error) {
@ -49,8 +51,9 @@ export function updateCellSize() {
let maxWidth = 0; let maxWidth = 0;
let maxHeight = 0; let maxHeight = 0;
// Find the maximum dimensions across all sprites
sprites.value.forEach(sprite => { 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); logger.warn('Sprite with invalid dimensions detected', sprite);
return; return;
} }
@ -63,10 +66,30 @@ export function updateCellSize() {
return; return;
} }
cellSize.width = maxWidth; // Add a small buffer to ensure sprites fit completely (optional)
cellSize.height = maxHeight; 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(); updateCanvasSize();
renderSpritesheetPreview();
} catch (error) { } catch (error) {
logger.error('Error updating cell size:', error); logger.error('Error updating cell size:', error);
} }

View File

@ -125,6 +125,9 @@
store.renderSpritesheetPreview(); store.renderSpritesheetPreview();
}; };
/**
* Handle mouse down to detect which sprite was clicked
*/
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
if (!canvasEl.value || store.sprites.value.length === 0) return; if (!canvasEl.value || store.sprites.value.length === 0) return;
@ -133,10 +136,11 @@
const x = (e.clientX - rect.left) / store.zoomLevel.value; const x = (e.clientX - rect.left) / store.zoomLevel.value;
const y = (e.clientY - rect.top) / 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--) { for (let i = store.sprites.value.length - 1; i >= 0; i--) {
const sprite = store.sprites.value[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.draggedSprite.value = sprite;
store.dragOffset.x = x - sprite.x; store.dragOffset.x = x - sprite.x;
store.dragOffset.y = y - sprite.y; store.dragOffset.y = y - sprite.y;
@ -175,14 +179,14 @@
// Handle sprite dragging // Handle sprite dragging
if (store.draggedSprite.value) { if (store.draggedSprite.value) {
if (store.isShiftPressed.value) { const sprite = store.draggedSprite.value;
// Free positioning within the cell bounds when shift is pressed const spriteIndex = store.sprites.value.findIndex(s => s.id === sprite.id);
const cellX = Math.floor(store.draggedSprite.value.x / store.cellSize.width);
const cellY = Math.floor(store.draggedSprite.value.y / store.cellSize.height);
// Calculate new position if (store.isShiftPressed.value) {
const newX = x - store.dragOffset.x; // --- FREE POSITIONING WITHIN CELL ---
const newY = y - store.dragOffset.y; // 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 // Calculate cell boundaries
const cellLeft = cellX * store.cellSize.width; const cellLeft = cellX * store.cellSize.width;
@ -190,20 +194,27 @@
const cellRight = cellLeft + store.cellSize.width; const cellRight = cellLeft + store.cellSize.width;
const cellBottom = cellTop + store.cellSize.height; const cellBottom = cellTop + store.cellSize.height;
// Calculate maximum allowed position to keep sprite within cell // Calculate new position based on mouse movement
const maxX = cellRight - store.draggedSprite.value.width; const newX = x - store.dragOffset.x;
const maxY = cellBottom - store.draggedSprite.value.height; const newY = y - store.dragOffset.y;
// Constrain position to stay within the cell // Calculate maximum allowed position to keep sprite strictly within cell
store.draggedSprite.value.x = Math.max(cellLeft, Math.min(newX, maxX)); // This ensures the sprite cannot extend beyond its assigned cell
store.draggedSprite.value.y = Math.max(cellTop, Math.min(newY, maxY)); const maxX = cellLeft + (store.cellSize.width - sprite.width);
const maxY = cellTop + (store.cellSize.height - sprite.height);
// Calculate the offset within the cell // Constrain position to keep sprite fully within the cell
const offsetX = store.draggedSprite.value.x - cellLeft; const constrainedX = Math.max(cellLeft, Math.min(newX, maxX));
const offsetY = store.draggedSprite.value.y - cellTop; 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) { if (spriteIndex !== -1) {
const frameOffset = store.getSpriteOffset(spriteIndex); const frameOffset = store.getSpriteOffset(spriteIndex);
frameOffset.x = offsetX; frameOffset.x = offsetX;
@ -214,26 +225,30 @@
store.currentSpriteOffset.y = offsetY; store.currentSpriteOffset.y = offsetY;
} }
} else { } else {
// Calculate new position based on grid cells (snap to grid) // --- GRID SNAPPING MODE ---
const newCellX = Math.floor((x - store.dragOffset.x) / store.cellSize.width); if (!canvasEl.value) return;
const newCellY = Math.floor((y - store.dragOffset.y) / store.cellSize.height);
// Make sure we stay within bounds // Calculate target cell coordinates based on mouse position
if (canvasEl.value) { const targetCellX = Math.floor((x - store.dragOffset.x) / store.cellSize.width);
const maxCellX = Math.floor(canvasEl.value.width / store.cellSize.width) - 1; const targetCellY = Math.floor((y - store.dragOffset.y) / store.cellSize.height);
const maxCellY = Math.floor(canvasEl.value.height / store.cellSize.height) - 1;
const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX)); // Calculate how many cells the sprite occupies
const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY)); 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 // Calculate maximum valid cell position to prevent overflow
const newX = boundedCellX * store.cellSize.width; const maxValidCellX = Math.floor(canvasEl.value.width / store.cellSize.width) - spriteCellsWide;
const newY = boundedCellY * store.cellSize.height; const maxValidCellY = Math.floor(canvasEl.value.height / store.cellSize.height) - spriteCellsHigh;
store.draggedSprite.value.x = newX;
store.draggedSprite.value.y = newY;
// When snapping to grid, reset any offsets for this sprite // Ensure we don't place sprites where they would extend beyond canvas bounds
const spriteIndex = store.sprites.value.findIndex(s => s.id === store.draggedSprite.value.id); const boundedCellX = Math.max(0, Math.min(targetCellX, maxValidCellX));
const boundedCellY = Math.max(0, Math.min(targetCellY, maxValidCellY));
// 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) { if (spriteIndex !== -1) {
const frameOffset = store.getSpriteOffset(spriteIndex); const frameOffset = store.getSpriteOffset(spriteIndex);
frameOffset.x = 0; frameOffset.x = 0;
@ -244,7 +259,6 @@
store.currentSpriteOffset.y = 0; store.currentSpriteOffset.y = 0;
} }
} }
}
// Trigger a re-render // Trigger a re-render
store.renderSpritesheetPreview(); store.renderSpritesheetPreview();