This commit is contained in:
Dennis Postma 2025-04-05 13:30:37 +02:00
parent 3e849c0286
commit 5a7f7032ef
2 changed files with 82 additions and 49 deletions

View File

@ -113,7 +113,7 @@ export function createSpriteFromFile(file: File, index: number): Promise<Sprite>
/** /**
* Process multiple files and create sprites * Process multiple files and create sprites
*/ */
export async function processImageFiles(files: FileList): Promise<{ newSprites: Sprite[], errorCount: number }> { export async function processImageFiles(files: FileList): Promise<{ newSprites: Sprite[]; errorCount: number }> {
const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/')); const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
if (imageFiles.length === 0) { if (imageFiles.length === 0) {

View File

@ -46,6 +46,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'; import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore'; import { useSpritesheetStore } from '../composables/useSpritesheetStore';
import { logger, getPixelPerfectCoordinate } from '../application/utilities';
const store = useSpritesheetStore(); const store = useSpritesheetStore();
const canvasEl = ref<HTMLCanvasElement | null>(null); const canvasEl = ref<HTMLCanvasElement | null>(null);
@ -102,10 +103,9 @@
{ deep: true } { deep: true }
); );
const setupCheckerboardPattern = () => { /**
// Remove this function or leave it empty since we don't need it anymore * Update canvas size based on container dimensions
}; */
const updateCanvasSize = () => { const updateCanvasSize = () => {
if (!canvasEl.value || !containerEl.value) return; if (!canvasEl.value || !containerEl.value) return;
@ -114,11 +114,10 @@
containerHeight.value = containerEl.value.clientHeight; containerHeight.value = containerEl.value.clientHeight;
// Set the base canvas size to fill the container // Set the base canvas size to fill the container
// These are the "unzoomed" dimensions
baseCanvasWidth.value = Math.max(containerWidth.value, store.cellSize.width * Math.ceil(containerWidth.value / store.cellSize.width)); baseCanvasWidth.value = Math.max(containerWidth.value, store.cellSize.width * Math.ceil(containerWidth.value / store.cellSize.width));
baseCanvasHeight.value = Math.max(containerHeight.value, store.cellSize.height * Math.ceil(containerHeight.value / store.cellSize.height)); baseCanvasHeight.value = Math.max(containerHeight.value, store.cellSize.height * Math.ceil(containerHeight.value / store.cellSize.height));
// Set the actual canvas dimensions - remove any zoom scaling here // Set the actual canvas dimensions
canvasEl.value.width = baseCanvasWidth.value; canvasEl.value.width = baseCanvasWidth.value;
canvasEl.value.height = baseCanvasHeight.value; canvasEl.value.height = baseCanvasHeight.value;
@ -146,6 +145,9 @@
} }
}; };
/**
* Handle mouse movement for tooltips and sprite dragging
*/
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
// Don't process sprite movement or tooltips while panning // Don't process sprite movement or tooltips while panning
if (isPanning.value) return; if (isPanning.value) return;
@ -153,14 +155,15 @@
if (!canvasEl.value) return; if (!canvasEl.value) return;
const rect = canvasEl.value.getBoundingClientRect(); const rect = canvasEl.value.getBoundingClientRect();
// Adjust coordinates for zoom // Adjust coordinates for zoom level
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;
// Update tooltip // Update tooltip with cell coordinates
const cellX = Math.floor(x / store.cellSize.width); const cellX = Math.floor(x / store.cellSize.width);
const cellY = Math.floor(y / store.cellSize.height); const cellY = Math.floor(y / store.cellSize.height);
// Show tooltip if mouse is within canvas bounds
if (canvasEl.value && cellX >= 0 && cellX < canvasEl.value.width / store.cellSize.width && cellY >= 0 && cellY < canvasEl.value.height / store.cellSize.height) { if (canvasEl.value && cellX >= 0 && cellX < canvasEl.value.width / store.cellSize.width && cellY >= 0 && cellY < canvasEl.value.height / store.cellSize.height) {
isTooltipVisible.value = true; isTooltipVisible.value = true;
tooltipText.value = `Cell: (${cellX}, ${cellY})`; tooltipText.value = `Cell: (${cellX}, ${cellY})`;
@ -170,7 +173,7 @@
isTooltipVisible.value = false; isTooltipVisible.value = false;
} }
// Move the sprite if we're dragging one // Handle sprite dragging
if (store.draggedSprite.value) { if (store.draggedSprite.value) {
if (store.isShiftPressed.value) { if (store.isShiftPressed.value) {
// Free positioning within the cell bounds when shift is pressed // Free positioning within the cell bounds when shift is pressed
@ -223,12 +226,11 @@
const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX)); const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX));
const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY)); const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY));
const oldX = store.draggedSprite.value.x; // Update the sprite position with pixel-perfect coordinates
const oldY = store.draggedSprite.value.y; const newX = boundedCellX * store.cellSize.width;
const newY = boundedCellY * store.cellSize.height;
// Update the sprite position store.draggedSprite.value.x = newX;
store.draggedSprite.value.x = boundedCellX * store.cellSize.width; store.draggedSprite.value.y = newY;
store.draggedSprite.value.y = boundedCellY * store.cellSize.height;
// When snapping to grid, reset any offsets for this sprite // When snapping to grid, reset any offsets for this sprite
const spriteIndex = store.sprites.value.findIndex(s => s.id === store.draggedSprite.value.id); const spriteIndex = store.sprites.value.findIndex(s => s.id === store.draggedSprite.value.id);
@ -258,6 +260,9 @@
store.draggedSprite.value = null; store.draggedSprite.value = null;
}; };
/**
* Handle keyboard down events
*/
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Shift') { if (e.key === 'Shift') {
store.isShiftPressed.value = true; store.isShiftPressed.value = true;
@ -268,7 +273,7 @@
isAltPressed.value = true; isAltPressed.value = true;
} }
// Add keyboard shortcuts for zooming // Handle keyboard shortcuts for zooming
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
if (e.key === '=' || e.key === '+') { if (e.key === '=' || e.key === '+') {
e.preventDefault(); e.preventDefault();
@ -305,13 +310,18 @@
} }
}; };
/**
* Handle mouse movement on the canvas for panning and sprite interactions
*/
const handleCanvasMouseMove = (e: MouseEvent) => { const handleCanvasMouseMove = (e: MouseEvent) => {
if (isPanning.value && containerEl.value) { if (isPanning.value && containerEl.value) {
e.preventDefault(); e.preventDefault();
// Calculate the distance moved since last position
const dx = e.clientX - lastPosition.value.x; const dx = e.clientX - lastPosition.value.x;
const dy = e.clientY - lastPosition.value.y; const dy = e.clientY - lastPosition.value.y;
// Scroll the container in the opposite direction of the mouse movement // Scroll the container in the opposite direction of the mouse movement
// for natural panning behavior
containerEl.value.scrollLeft -= dx; containerEl.value.scrollLeft -= dx;
containerEl.value.scrollTop -= dy; containerEl.value.scrollTop -= dy;
@ -337,8 +347,14 @@
e.preventDefault(); e.preventDefault();
}; };
/**
* Set up all canvas event listeners
*/
const setupCanvasEvents = () => { const setupCanvasEvents = () => {
if (!canvasEl.value) return; if (!canvasEl.value) {
logger.warn('Cannot set up canvas events: canvas element not available');
return;
}
// Set up mouse events for the canvas // Set up mouse events for the canvas
canvasEl.value.addEventListener('mousedown', handleCanvasMouseDown); canvasEl.value.addEventListener('mousedown', handleCanvasMouseDown);
@ -353,35 +369,48 @@
updateCanvasSize(); updateCanvasSize();
}; };
/**
* Component lifecycle hook - setup
*/
onMounted(async () => { onMounted(async () => {
// Set up global event listeners try {
window.addEventListener('keydown', handleKeyDown); // Set up global event listeners
window.addEventListener('keyup', handleKeyUp); window.addEventListener('keydown', handleKeyDown);
window.addEventListener('resize', handleResize); window.addEventListener('keyup', handleKeyUp);
window.addEventListener('resize', handleResize);
// Initialize the canvas // Initialize the canvas after DOM is updated
await nextTick(); await nextTick();
initializeCanvas(); initializeCanvas();
// Observe container size changes // Set up ResizeObserver to handle container size changes
if ('ResizeObserver' in window) { if ('ResizeObserver' in window) {
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {
updateCanvasSize(); updateCanvasSize();
}); });
if (containerEl.value) { if (containerEl.value) {
resizeObserver.observe(containerEl.value); resizeObserver.observe(containerEl.value);
}
} }
} catch (error) {
logger.error('Error during component mount:', error);
} }
}); });
/**
* Initialize the canvas and set up required event handlers
*/
const initializeCanvas = () => { const initializeCanvas = () => {
if (!canvasEl.value || !containerEl.value) return; if (!canvasEl.value || !containerEl.value) {
logger.error('Canvas or container element not available');
return;
}
try { try {
const context = canvasEl.value.getContext('2d'); const context = canvasEl.value.getContext('2d');
if (!context) { if (!context) {
console.error('Failed to get 2D context from canvas'); logger.error('Failed to get 2D context from canvas');
return; return;
} }
@ -389,9 +418,6 @@
store.canvas.value = canvasEl.value; store.canvas.value = canvasEl.value;
store.ctx.value = context; store.ctx.value = context;
// Set up the checkerboard pattern
setupCheckerboardPattern();
// Set up canvas mouse events // Set up canvas mouse events
setupCanvasEvents(); setupCanvasEvents();
@ -405,23 +431,30 @@
store.renderSpritesheetPreview(); store.renderSpritesheetPreview();
} }
} catch (error) { } catch (error) {
console.error('Error initializing canvas:', error); logger.error('Error initializing canvas:', error);
} }
}; };
/**
* Component lifecycle hook - cleanup
*/
onBeforeUnmount(() => { onBeforeUnmount(() => {
// Remove global event listeners try {
window.removeEventListener('keydown', handleKeyDown); // Remove global event listeners
window.removeEventListener('keyup', handleKeyUp); window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('resize', handleResize); window.removeEventListener('keyup', handleKeyUp);
window.removeEventListener('resize', handleResize);
// Remove canvas event listeners // Remove canvas event listeners
if (canvasEl.value) { if (canvasEl.value) {
canvasEl.value.removeEventListener('mousedown', handleCanvasMouseDown); canvasEl.value.removeEventListener('mousedown', handleCanvasMouseDown);
canvasEl.value.removeEventListener('mousemove', handleCanvasMouseMove); canvasEl.value.removeEventListener('mousemove', handleCanvasMouseMove);
canvasEl.value.removeEventListener('mouseup', handleCanvasMouseUp); canvasEl.value.removeEventListener('mouseup', handleCanvasMouseUp);
canvasEl.value.removeEventListener('mouseleave', handleCanvasMouseLeave); canvasEl.value.removeEventListener('mouseleave', handleCanvasMouseLeave);
canvasEl.value.removeEventListener('contextmenu', preventContextMenu); canvasEl.value.removeEventListener('contextmenu', preventContextMenu);
}
} catch (error) {
logger.error('Error during component unmount:', error);
} }
}); });
</script> </script>