Zoom fixes
This commit is contained in:
parent
319a052d48
commit
33efe08207
@ -11,16 +11,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div ref="containerEl" class="relative overflow-auto bg-gray-700 rounded border border-gray-600 h-96"
|
<div ref="containerEl" class="relative overflow-auto bg-gray-700 rounded border border-gray-600 h-96" :class="{ 'cursor-grab': !isPanning, 'cursor-grabbing': isPanning }">
|
||||||
:class="{ 'cursor-grab': !isPanning, 'cursor-grabbing': isPanning }">
|
|
||||||
<canvas
|
<canvas
|
||||||
ref="canvasEl"
|
ref="canvasEl"
|
||||||
class="block"
|
class="block"
|
||||||
:style="{
|
:style="{
|
||||||
transform: `scale(${store.zoomLevel.value})`,
|
transform: `scale(${store.zoomLevel.value})`,
|
||||||
transformOrigin: 'top left',
|
transformOrigin: 'top left',
|
||||||
width: zoomedWidth + 'px',
|
|
||||||
height: zoomedHeight + 'px'
|
|
||||||
}"
|
}"
|
||||||
></canvas>
|
></canvas>
|
||||||
</div>
|
</div>
|
||||||
@ -30,368 +27,368 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<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';
|
||||||
|
|
||||||
const store = useSpritesheetStore();
|
const store = useSpritesheetStore();
|
||||||
const canvasEl = ref<HTMLCanvasElement | null>(null);
|
const canvasEl = ref<HTMLCanvasElement | null>(null);
|
||||||
const containerEl = ref<HTMLDivElement | null>(null);
|
const containerEl = ref<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
// Panning state
|
// Panning state
|
||||||
const isPanning = ref(false);
|
const isPanning = ref(false);
|
||||||
const isAltPressed = ref(false);
|
const isAltPressed = ref(false);
|
||||||
const isMiddleMouseDown = ref(false);
|
const isMiddleMouseDown = ref(false);
|
||||||
const lastPosition = ref({ x: 0, y: 0 });
|
const lastPosition = ref({ x: 0, y: 0 });
|
||||||
|
|
||||||
// Tooltip state
|
// Tooltip state
|
||||||
const isTooltipVisible = ref(false);
|
const isTooltipVisible = ref(false);
|
||||||
const tooltipText = ref('');
|
const tooltipText = ref('');
|
||||||
const tooltipPosition = ref({ x: 0, y: 0 });
|
const tooltipPosition = ref({ x: 0, y: 0 });
|
||||||
|
|
||||||
// Responsive canvas sizing
|
// Responsive canvas sizing
|
||||||
const containerWidth = ref(0);
|
const containerWidth = ref(0);
|
||||||
const containerHeight = ref(0);
|
const containerHeight = ref(0);
|
||||||
const baseCanvasWidth = ref(0);
|
const baseCanvasWidth = ref(0);
|
||||||
const baseCanvasHeight = ref(0);
|
const baseCanvasHeight = ref(0);
|
||||||
|
|
||||||
// Computed properties for zoomed dimensions
|
// Computed properties for zoomed dimensions
|
||||||
const zoomedWidth = computed(() => {
|
const zoomedWidth = computed(() => {
|
||||||
return baseCanvasWidth.value * store.zoomLevel.value;
|
return baseCanvasWidth.value * store.zoomLevel.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const zoomedHeight = computed(() => {
|
const zoomedHeight = computed(() => {
|
||||||
return baseCanvasHeight.value * store.zoomLevel.value;
|
return baseCanvasHeight.value * store.zoomLevel.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tooltipStyle = computed(() => ({
|
const tooltipStyle = computed(() => ({
|
||||||
left: `${tooltipPosition.value.x + 15}px`,
|
left: `${tooltipPosition.value.x + 15}px`,
|
||||||
top: `${tooltipPosition.value.y + 15}px`,
|
top: `${tooltipPosition.value.y + 15}px`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Watch for zoom changes to update the container scroll position
|
// Watch for zoom changes to update the container scroll position
|
||||||
watch(() => store.zoomLevel.value, (newZoom, oldZoom) => {
|
watch(
|
||||||
if (!containerEl.value) return;
|
() => store.zoomLevel.value,
|
||||||
|
(newZoom, oldZoom) => {
|
||||||
|
if (!containerEl.value) return;
|
||||||
|
|
||||||
// Adjust scroll position to keep the center point consistent when zooming
|
// Adjust scroll position to keep the center point consistent when zooming
|
||||||
const centerX = containerEl.value.scrollLeft + containerEl.value.clientWidth / 2;
|
const centerX = containerEl.value.scrollLeft + containerEl.value.clientWidth / 2;
|
||||||
const centerY = containerEl.value.scrollTop + containerEl.value.clientHeight / 2;
|
const centerY = containerEl.value.scrollTop + containerEl.value.clientHeight / 2;
|
||||||
|
|
||||||
// Calculate new scroll position based on new zoom level
|
// Calculate new scroll position based on new zoom level
|
||||||
const scaleChange = newZoom / oldZoom;
|
const scaleChange = newZoom / oldZoom;
|
||||||
containerEl.value.scrollLeft = centerX * scaleChange - containerEl.value.clientWidth / 2;
|
containerEl.value.scrollLeft = centerX * scaleChange - containerEl.value.clientWidth / 2;
|
||||||
containerEl.value.scrollTop = centerY * scaleChange - containerEl.value.clientHeight / 2;
|
containerEl.value.scrollTop = centerY * scaleChange - containerEl.value.clientHeight / 2;
|
||||||
|
|
||||||
// Re-render the canvas with the new zoom level
|
// Re-render the canvas with the new zoom level
|
||||||
updateCanvasSize();
|
updateCanvasSize();
|
||||||
store.renderSpritesheetPreview();
|
store.renderSpritesheetPreview();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const setupCheckerboardPattern = () => {
|
const setupCheckerboardPattern = () => {
|
||||||
if (!canvasEl.value) return;
|
if (!canvasEl.value) return;
|
||||||
|
|
||||||
canvasEl.value.style.backgroundImage = `
|
canvasEl.value.style.backgroundImage = `
|
||||||
linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
|
linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
|
||||||
linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),
|
linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),
|
||||||
linear-gradient(45deg, transparent 75%, #1a1a1a 75%),
|
linear-gradient(45deg, transparent 75%, #1a1a1a 75%),
|
||||||
linear-gradient(-45deg, transparent 75%, #1a1a1a 75%)
|
linear-gradient(-45deg, transparent 75%, #1a1a1a 75%)
|
||||||
`;
|
`;
|
||||||
canvasEl.value.style.backgroundSize = '20px 20px';
|
canvasEl.value.style.backgroundSize = '20px 20px';
|
||||||
canvasEl.value.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px';
|
canvasEl.value.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px';
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCanvasSize = () => {
|
const updateCanvasSize = () => {
|
||||||
if (!canvasEl.value || !containerEl.value) return;
|
if (!canvasEl.value || !containerEl.value) return;
|
||||||
|
|
||||||
// Get the container dimensions
|
// Get the container dimensions
|
||||||
containerWidth.value = containerEl.value.clientWidth;
|
containerWidth.value = containerEl.value.clientWidth;
|
||||||
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
|
// These are the "unzoomed" dimensions
|
||||||
baseCanvasWidth.value = Math.max(containerWidth.value,
|
baseCanvasWidth.value = Math.max(containerWidth.value, store.cellSize.width * Math.ceil(containerWidth.value / store.cellSize.width));
|
||||||
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));
|
|
||||||
|
|
||||||
// Update the actual canvas element size
|
// Set the actual canvas dimensions - remove any zoom scaling here
|
||||||
canvasEl.value.width = baseCanvasWidth.value;
|
canvasEl.value.width = baseCanvasWidth.value;
|
||||||
canvasEl.value.height = baseCanvasHeight.value;
|
canvasEl.value.height = baseCanvasHeight.value;
|
||||||
|
|
||||||
// Trigger a re-render
|
|
||||||
store.renderSpritesheetPreview();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
|
||||||
if (!canvasEl.value || store.sprites.value.length === 0) return;
|
|
||||||
|
|
||||||
const rect = canvasEl.value.getBoundingClientRect();
|
|
||||||
// Adjust coordinates based on zoom level
|
|
||||||
const x = (e.clientX - rect.left) / store.zoomLevel.value;
|
|
||||||
const y = (e.clientY - rect.top) / store.zoomLevel.value;
|
|
||||||
|
|
||||||
// Find which sprite was clicked
|
|
||||||
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) {
|
|
||||||
store.draggedSprite.value = sprite;
|
|
||||||
store.dragOffset.x = x - sprite.x;
|
|
||||||
store.dragOffset.y = y - sprite.y;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
|
||||||
// Don't process sprite movement or tooltips while panning
|
|
||||||
if (isPanning.value) return;
|
|
||||||
|
|
||||||
if (!canvasEl.value) return;
|
|
||||||
|
|
||||||
const rect = canvasEl.value.getBoundingClientRect();
|
|
||||||
// Adjust coordinates for zoom
|
|
||||||
const x = (e.clientX - rect.left) / store.zoomLevel.value;
|
|
||||||
const y = (e.clientY - rect.top) / store.zoomLevel.value;
|
|
||||||
|
|
||||||
// Update tooltip
|
|
||||||
const cellX = Math.floor(x / store.cellSize.width);
|
|
||||||
const cellY = Math.floor(y / 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;
|
|
||||||
tooltipText.value = `Cell: (${cellX}, ${cellY})`;
|
|
||||||
tooltipPosition.value.x = e.pageX;
|
|
||||||
tooltipPosition.value.y = e.pageY;
|
|
||||||
} else {
|
|
||||||
isTooltipVisible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the sprite if we're dragging one
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Calculate new position with constraints to stay within the cell
|
|
||||||
const newX = x - store.dragOffset.x;
|
|
||||||
const newY = y - store.dragOffset.y;
|
|
||||||
|
|
||||||
// Calculate cell boundaries
|
|
||||||
const cellLeft = cellX * store.cellSize.width;
|
|
||||||
const cellTop = cellY * store.cellSize.height;
|
|
||||||
const cellRight = cellLeft + store.cellSize.width - store.draggedSprite.value.img.width;
|
|
||||||
const cellBottom = cellTop + store.cellSize.height - store.draggedSprite.value.img.height;
|
|
||||||
|
|
||||||
// Constrain position to stay within the cell
|
|
||||||
store.draggedSprite.value.x = Math.max(cellLeft, Math.min(newX, cellRight));
|
|
||||||
store.draggedSprite.value.y = Math.max(cellTop, Math.min(newY, cellBottom));
|
|
||||||
} 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);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX));
|
|
||||||
const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY));
|
|
||||||
|
|
||||||
store.draggedSprite.value.x = boundedCellX * store.cellSize.width;
|
|
||||||
store.draggedSprite.value.y = boundedCellY * store.cellSize.height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger a re-render
|
// Trigger a re-render
|
||||||
store.renderSpritesheetPreview();
|
store.renderSpritesheetPreview();
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
store.draggedSprite.value = null;
|
if (!canvasEl.value || store.sprites.value.length === 0) return;
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseOut = () => {
|
const rect = canvasEl.value.getBoundingClientRect();
|
||||||
isTooltipVisible.value = false;
|
// Adjust coordinates based on zoom level
|
||||||
store.draggedSprite.value = null;
|
const x = (e.clientX - rect.left) / store.zoomLevel.value;
|
||||||
};
|
const y = (e.clientY - rect.top) / store.zoomLevel.value;
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
// Find which sprite was clicked
|
||||||
if (e.key === 'Shift') {
|
for (let i = store.sprites.value.length - 1; i >= 0; i--) {
|
||||||
store.isShiftPressed.value = true;
|
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) {
|
||||||
|
store.draggedSprite.value = sprite;
|
||||||
if (e.key === 'Alt') {
|
store.dragOffset.x = x - sprite.x;
|
||||||
e.preventDefault(); // Prevent browser from focusing address bar
|
store.dragOffset.y = y - sprite.y;
|
||||||
isAltPressed.value = true;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add keyboard shortcuts for zooming
|
|
||||||
if (e.ctrlKey || e.metaKey) {
|
|
||||||
if (e.key === '=' || e.key === '+') {
|
|
||||||
e.preventDefault();
|
|
||||||
store.zoomIn();
|
|
||||||
} else if (e.key === '-') {
|
|
||||||
e.preventDefault();
|
|
||||||
store.zoomOut();
|
|
||||||
} else if (e.key === '0') {
|
|
||||||
e.preventDefault();
|
|
||||||
store.resetZoom();
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyUp = (e: KeyboardEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (e.key === 'Shift') {
|
// Don't process sprite movement or tooltips while panning
|
||||||
store.isShiftPressed.value = false;
|
if (isPanning.value) return;
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'Alt') {
|
if (!canvasEl.value) return;
|
||||||
isAltPressed.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCanvasMouseDown = (e: MouseEvent) => {
|
const rect = canvasEl.value.getBoundingClientRect();
|
||||||
// Middle mouse button or Alt + left click for panning
|
// Adjust coordinates for zoom
|
||||||
if (e.button === 1 || (e.button === 0 && isAltPressed.value)) {
|
const x = (e.clientX - rect.left) / store.zoomLevel.value;
|
||||||
e.preventDefault();
|
const y = (e.clientY - rect.top) / store.zoomLevel.value;
|
||||||
isPanning.value = true;
|
|
||||||
lastPosition.value = { x: e.clientX, y: e.clientY };
|
|
||||||
} else {
|
|
||||||
// Regular sprite dragging
|
|
||||||
handleMouseDown(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCanvasMouseMove = (e: MouseEvent) => {
|
// Update tooltip
|
||||||
if (isPanning.value && containerEl.value) {
|
const cellX = Math.floor(x / store.cellSize.width);
|
||||||
e.preventDefault();
|
const cellY = Math.floor(y / store.cellSize.height);
|
||||||
const dx = e.clientX - lastPosition.value.x;
|
|
||||||
const dy = e.clientY - lastPosition.value.y;
|
|
||||||
|
|
||||||
// Scroll the container in the opposite direction of the mouse movement
|
if (canvasEl.value && cellX >= 0 && cellX < canvasEl.value.width / store.cellSize.width && cellY >= 0 && cellY < canvasEl.value.height / store.cellSize.height) {
|
||||||
containerEl.value.scrollLeft -= dx;
|
isTooltipVisible.value = true;
|
||||||
containerEl.value.scrollTop -= dy;
|
tooltipText.value = `Cell: (${cellX}, ${cellY})`;
|
||||||
|
tooltipPosition.value.x = e.pageX;
|
||||||
// Update the last position for the next movement
|
tooltipPosition.value.y = e.pageY;
|
||||||
lastPosition.value = { x: e.clientX, y: e.clientY };
|
} else {
|
||||||
} else {
|
isTooltipVisible.value = false;
|
||||||
// Handle regular mouse move for sprites and tooltip
|
|
||||||
handleMouseMove(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCanvasMouseUp = () => {
|
|
||||||
isPanning.value = false;
|
|
||||||
handleMouseUp();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCanvasMouseLeave = () => {
|
|
||||||
isPanning.value = false;
|
|
||||||
handleMouseOut();
|
|
||||||
};
|
|
||||||
|
|
||||||
const preventContextMenu = (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
const setupCanvasEvents = () => {
|
|
||||||
if (!canvasEl.value) return;
|
|
||||||
|
|
||||||
// Set up mouse events for the canvas
|
|
||||||
canvasEl.value.addEventListener('mousedown', handleCanvasMouseDown);
|
|
||||||
canvasEl.value.addEventListener('mousemove', handleCanvasMouseMove);
|
|
||||||
canvasEl.value.addEventListener('mouseup', handleCanvasMouseUp);
|
|
||||||
canvasEl.value.addEventListener('mouseleave', handleCanvasMouseLeave);
|
|
||||||
canvasEl.value.addEventListener('contextmenu', preventContextMenu);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle window resize to update canvas dimensions
|
|
||||||
const handleResize = () => {
|
|
||||||
updateCanvasSize();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
// Set up global event listeners
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
window.addEventListener('keyup', handleKeyUp);
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
|
|
||||||
// Initialize the canvas
|
|
||||||
await nextTick();
|
|
||||||
initializeCanvas();
|
|
||||||
|
|
||||||
// Observe container size changes
|
|
||||||
if ('ResizeObserver' in window) {
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
updateCanvasSize();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (containerEl.value) {
|
|
||||||
resizeObserver.observe(containerEl.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const initializeCanvas = () => {
|
|
||||||
if (!canvasEl.value || !containerEl.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const context = canvasEl.value.getContext('2d');
|
|
||||||
if (!context) {
|
|
||||||
console.error('Failed to get 2D context from canvas');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set canvas and context in the store
|
// Move the sprite if we're dragging one
|
||||||
store.canvas.value = canvasEl.value;
|
if (store.draggedSprite.value) {
|
||||||
store.ctx.value = context;
|
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);
|
||||||
|
|
||||||
// Set up the checkerboard pattern
|
// Calculate new position with constraints to stay within the cell
|
||||||
setupCheckerboardPattern();
|
const newX = x - store.dragOffset.x;
|
||||||
|
const newY = y - store.dragOffset.y;
|
||||||
|
|
||||||
// Set up canvas mouse events
|
// Calculate cell boundaries
|
||||||
setupCanvasEvents();
|
const cellLeft = cellX * store.cellSize.width;
|
||||||
|
const cellTop = cellY * store.cellSize.height;
|
||||||
|
const cellRight = cellLeft + store.cellSize.width - store.draggedSprite.value.img.width;
|
||||||
|
const cellBottom = cellTop + store.cellSize.height - store.draggedSprite.value.img.height;
|
||||||
|
|
||||||
// Set the initial canvas size based on container
|
// Constrain position to stay within the cell
|
||||||
updateCanvasSize();
|
store.draggedSprite.value.x = Math.max(cellLeft, Math.min(newX, cellRight));
|
||||||
|
store.draggedSprite.value.y = Math.max(cellTop, Math.min(newY, cellBottom));
|
||||||
|
} 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);
|
||||||
|
|
||||||
// Update sprites if there are any loaded
|
// Make sure we stay within bounds
|
||||||
if (store.sprites.value.length > 0) {
|
if (canvasEl.value) {
|
||||||
store.updateCellSize();
|
const maxCellX = Math.floor(canvasEl.value.width / store.cellSize.width) - 1;
|
||||||
store.autoArrangeSprites();
|
const maxCellY = Math.floor(canvasEl.value.height / store.cellSize.height) - 1;
|
||||||
|
|
||||||
|
const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX));
|
||||||
|
const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY));
|
||||||
|
|
||||||
|
store.draggedSprite.value.x = boundedCellX * store.cellSize.width;
|
||||||
|
store.draggedSprite.value.y = boundedCellY * store.cellSize.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger a re-render
|
||||||
store.renderSpritesheetPreview();
|
store.renderSpritesheetPreview();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
};
|
||||||
console.error('Error initializing canvas:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
const handleMouseUp = () => {
|
||||||
// Remove global event listeners
|
store.draggedSprite.value = null;
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
};
|
||||||
window.removeEventListener('keyup', handleKeyUp);
|
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
|
|
||||||
// Remove canvas event listeners
|
const handleMouseOut = () => {
|
||||||
if (canvasEl.value) {
|
isTooltipVisible.value = false;
|
||||||
canvasEl.value.removeEventListener('mousedown', handleCanvasMouseDown);
|
store.draggedSprite.value = null;
|
||||||
canvasEl.value.removeEventListener('mousemove', handleCanvasMouseMove);
|
};
|
||||||
canvasEl.value.removeEventListener('mouseup', handleCanvasMouseUp);
|
|
||||||
canvasEl.value.removeEventListener('mouseleave', handleCanvasMouseLeave);
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
canvasEl.value.removeEventListener('contextmenu', preventContextMenu);
|
if (e.key === 'Shift') {
|
||||||
}
|
store.isShiftPressed.value = true;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Alt') {
|
||||||
|
e.preventDefault(); // Prevent browser from focusing address bar
|
||||||
|
isAltPressed.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add keyboard shortcuts for zooming
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
if (e.key === '=' || e.key === '+') {
|
||||||
|
e.preventDefault();
|
||||||
|
store.zoomIn();
|
||||||
|
} else if (e.key === '-') {
|
||||||
|
e.preventDefault();
|
||||||
|
store.zoomOut();
|
||||||
|
} else if (e.key === '0') {
|
||||||
|
e.preventDefault();
|
||||||
|
store.resetZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Shift') {
|
||||||
|
store.isShiftPressed.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Alt') {
|
||||||
|
isAltPressed.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCanvasMouseDown = (e: MouseEvent) => {
|
||||||
|
// Middle mouse button or Alt + left click for panning
|
||||||
|
if (e.button === 1 || (e.button === 0 && isAltPressed.value)) {
|
||||||
|
e.preventDefault();
|
||||||
|
isPanning.value = true;
|
||||||
|
lastPosition.value = { x: e.clientX, y: e.clientY };
|
||||||
|
} else {
|
||||||
|
// Regular sprite dragging
|
||||||
|
handleMouseDown(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCanvasMouseMove = (e: MouseEvent) => {
|
||||||
|
if (isPanning.value && containerEl.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
const dx = e.clientX - lastPosition.value.x;
|
||||||
|
const dy = e.clientY - lastPosition.value.y;
|
||||||
|
|
||||||
|
// Scroll the container in the opposite direction of the mouse movement
|
||||||
|
containerEl.value.scrollLeft -= dx;
|
||||||
|
containerEl.value.scrollTop -= dy;
|
||||||
|
|
||||||
|
// Update the last position for the next movement
|
||||||
|
lastPosition.value = { x: e.clientX, y: e.clientY };
|
||||||
|
} else {
|
||||||
|
// Handle regular mouse move for sprites and tooltip
|
||||||
|
handleMouseMove(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCanvasMouseUp = () => {
|
||||||
|
isPanning.value = false;
|
||||||
|
handleMouseUp();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCanvasMouseLeave = () => {
|
||||||
|
isPanning.value = false;
|
||||||
|
handleMouseOut();
|
||||||
|
};
|
||||||
|
|
||||||
|
const preventContextMenu = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupCanvasEvents = () => {
|
||||||
|
if (!canvasEl.value) return;
|
||||||
|
|
||||||
|
// Set up mouse events for the canvas
|
||||||
|
canvasEl.value.addEventListener('mousedown', handleCanvasMouseDown);
|
||||||
|
canvasEl.value.addEventListener('mousemove', handleCanvasMouseMove);
|
||||||
|
canvasEl.value.addEventListener('mouseup', handleCanvasMouseUp);
|
||||||
|
canvasEl.value.addEventListener('mouseleave', handleCanvasMouseLeave);
|
||||||
|
canvasEl.value.addEventListener('contextmenu', preventContextMenu);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle window resize to update canvas dimensions
|
||||||
|
const handleResize = () => {
|
||||||
|
updateCanvasSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// Set up global event listeners
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
window.addEventListener('keyup', handleKeyUp);
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// Initialize the canvas
|
||||||
|
await nextTick();
|
||||||
|
initializeCanvas();
|
||||||
|
|
||||||
|
// Observe container size changes
|
||||||
|
if ('ResizeObserver' in window) {
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
updateCanvasSize();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (containerEl.value) {
|
||||||
|
resizeObserver.observe(containerEl.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const initializeCanvas = () => {
|
||||||
|
if (!canvasEl.value || !containerEl.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const context = canvasEl.value.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
console.error('Failed to get 2D context from canvas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set canvas and context in the store
|
||||||
|
store.canvas.value = canvasEl.value;
|
||||||
|
store.ctx.value = context;
|
||||||
|
|
||||||
|
// Set up the checkerboard pattern
|
||||||
|
setupCheckerboardPattern();
|
||||||
|
|
||||||
|
// Set up canvas mouse events
|
||||||
|
setupCanvasEvents();
|
||||||
|
|
||||||
|
// Set the initial canvas size based on container
|
||||||
|
updateCanvasSize();
|
||||||
|
|
||||||
|
// Update sprites if there are any loaded
|
||||||
|
if (store.sprites.value.length > 0) {
|
||||||
|
store.updateCellSize();
|
||||||
|
store.autoArrangeSprites();
|
||||||
|
store.renderSpritesheetPreview();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing canvas:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// Remove global event listeners
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
window.removeEventListener('keyup', handleKeyUp);
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// Remove canvas event listeners
|
||||||
|
if (canvasEl.value) {
|
||||||
|
canvasEl.value.removeEventListener('mousedown', handleCanvasMouseDown);
|
||||||
|
canvasEl.value.removeEventListener('mousemove', handleCanvasMouseMove);
|
||||||
|
canvasEl.value.removeEventListener('mouseup', handleCanvasMouseUp);
|
||||||
|
canvasEl.value.removeEventListener('mouseleave', handleCanvasMouseLeave);
|
||||||
|
canvasEl.value.removeEventListener('contextmenu', preventContextMenu);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.cursor-grab {
|
.cursor-grab {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-grabbing {
|
.cursor-grabbing {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fixed inset-0 flex items-center justify-center z-50" :class="{ 'pointer-events-none': !isModalOpen }">
|
<!-- Make the outer container always pointer-events-none so clicks pass through -->
|
||||||
|
<div class="fixed inset-0 flex items-center justify-center z-50 pointer-events-none">
|
||||||
|
<!-- Apply pointer-events-auto ONLY to the modal itself so it can be interacted with -->
|
||||||
<div class="bg-gray-800 rounded-lg max-w-4xl max-h-[90vh] overflow-auto scrollbar-hide shadow-lg pointer-events-auto" :class="{ invisible: !isModalOpen, visible: isModalOpen }" :style="{ transform: `translate3d(${position.x}px, ${position.y + (isModalOpen ? 0 : -20)}px, 0)` }">
|
<div class="bg-gray-800 rounded-lg max-w-4xl max-h-[90vh] overflow-auto scrollbar-hide shadow-lg pointer-events-auto" :class="{ invisible: !isModalOpen, visible: isModalOpen }" :style="{ transform: `translate3d(${position.x}px, ${position.y + (isModalOpen ? 0 : -20)}px, 0)` }">
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600 cursor-move" @mousedown="startDrag">
|
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600 cursor-move" @mousedown="startDrag">
|
||||||
<div class="flex items-center gap-2 text-lg font-semibold select-none">
|
<div class="flex items-center gap-2 text-lg font-semibold select-none">
|
||||||
@ -73,6 +75,9 @@
|
|||||||
const animation = computed(() => store.animation);
|
const animation = computed(() => store.animation);
|
||||||
|
|
||||||
const currentFrame = ref(0);
|
const currentFrame = ref(0);
|
||||||
|
const position = ref({ x: 0, y: 0 });
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const dragOffset = ref({ x: 0, y: 0 });
|
||||||
|
|
||||||
const currentFrameDisplay = computed(() => {
|
const currentFrameDisplay = computed(() => {
|
||||||
const totalFrames = Math.max(1, sprites.value.length);
|
const totalFrames = Math.max(1, sprites.value.length);
|
||||||
@ -117,7 +122,6 @@
|
|||||||
currentFrame.value = 0;
|
currentFrame.value = 0;
|
||||||
animation.value.currentFrame = 0;
|
animation.value.currentFrame = 0;
|
||||||
|
|
||||||
// Make sure the canvas is initialized
|
|
||||||
await initializeCanvas();
|
await initializeCanvas();
|
||||||
|
|
||||||
// Set modal open state if not already open
|
// Set modal open state if not already open
|
||||||
@ -129,18 +133,21 @@
|
|||||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
|
||||||
// Set proper canvas size before rendering
|
// Set proper canvas size before rendering
|
||||||
if (animCanvas.value && store.cellSize.width && store.cellSize.height) {
|
updateCanvasSize();
|
||||||
animCanvas.value.width = store.cellSize.width;
|
|
||||||
animCanvas.value.height = store.cellSize.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force render the first frame
|
// Force render the first frame
|
||||||
if (sprites.value.length > 0) {
|
if (sprites.value.length > 0) {
|
||||||
// Ensure we have the latest sprites
|
|
||||||
store.renderAnimationFrame(0);
|
store.renderAnimationFrame(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateCanvasSize = () => {
|
||||||
|
if (animCanvas.value && store.cellSize.width && store.cellSize.height) {
|
||||||
|
animCanvas.value.width = store.cellSize.width;
|
||||||
|
animCanvas.value.height = store.cellSize.height;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
store.isModalOpen.value = false;
|
store.isModalOpen.value = false;
|
||||||
|
|
||||||
@ -181,44 +188,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initializeCanvas();
|
|
||||||
|
|
||||||
// Setup keyboard shortcuts for the modal
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
});
|
|
||||||
|
|
||||||
const initializeCanvas = async () => {
|
|
||||||
if (!animCanvas.value) {
|
|
||||||
console.error('PreviewModal: Animation canvas not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get the 2D context
|
|
||||||
const context = animCanvas.value.getContext('2d');
|
|
||||||
if (!context) {
|
|
||||||
console.error('PreviewModal: Failed to get 2D context from animation canvas');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the store references
|
|
||||||
store.animation.canvas = animCanvas.value;
|
|
||||||
store.animation.ctx = context;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('PreviewModal: Error initializing animation canvas:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add these new refs for dragging functionality
|
|
||||||
const position = ref({ x: 0, y: 0 });
|
|
||||||
const isDragging = ref(false);
|
|
||||||
const dragOffset = ref({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
const startDrag = (e: MouseEvent) => {
|
const startDrag = (e: MouseEvent) => {
|
||||||
isDragging.value = true;
|
isDragging.value = true;
|
||||||
dragOffset.value = {
|
dragOffset.value = {
|
||||||
@ -248,6 +217,37 @@
|
|||||||
window.removeEventListener('mouseup', stopDrag);
|
window.removeEventListener('mouseup', stopDrag);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initializeCanvas = async () => {
|
||||||
|
if (!animCanvas.value) {
|
||||||
|
console.error('PreviewModal: Animation canvas not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const context = animCanvas.value.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
console.error('PreviewModal: Failed to get 2D context from animation canvas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.animation.canvas = animCanvas.value;
|
||||||
|
store.animation.ctx = context;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PreviewModal: Error initializing animation canvas:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initializeCanvas();
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
window.removeEventListener('mousemove', handleDrag);
|
||||||
|
window.removeEventListener('mouseup', stopDrag);
|
||||||
|
});
|
||||||
|
|
||||||
// Keep currentFrame in sync with animation.currentFrame
|
// Keep currentFrame in sync with animation.currentFrame
|
||||||
watch(
|
watch(
|
||||||
() => animation.value.currentFrame,
|
() => animation.value.currentFrame,
|
||||||
@ -261,12 +261,8 @@
|
|||||||
() => sprites.value,
|
() => sprites.value,
|
||||||
newSprites => {
|
newSprites => {
|
||||||
if (isModalOpen.value && newSprites.length > 0) {
|
if (isModalOpen.value && newSprites.length > 0) {
|
||||||
// Update the canvas with the new sprites
|
updateCanvasSize();
|
||||||
if (animCanvas.value && store.cellSize.width && store.cellSize.height) {
|
store.renderAnimationFrame(currentFrame.value);
|
||||||
animCanvas.value.width = store.cellSize.width;
|
|
||||||
animCanvas.value.height = store.cellSize.height;
|
|
||||||
store.renderAnimationFrame(currentFrame.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
@ -294,12 +290,12 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Optional: Prevent text selection while dragging */
|
/* Prevent text selection while dragging */
|
||||||
.cursor-move {
|
.cursor-move {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add these new styles */
|
/* Hide scrollbar styles */
|
||||||
.scrollbar-hide {
|
.scrollbar-hide {
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
|
@ -53,15 +53,9 @@
|
|||||||
<!-- Zoom Controls -->
|
<!-- Zoom Controls -->
|
||||||
<div class="flex flex-wrap gap-3 mb-6">
|
<div class="flex flex-wrap gap-3 mb-6">
|
||||||
<div class="text-sm font-medium text-gray-300 mb-2 w-full">Zoom Controls</div>
|
<div class="text-sm font-medium text-gray-300 mb-2 w-full">Zoom Controls</div>
|
||||||
<button @click="zoomIn" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500">
|
<button @click="zoomIn" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-search-plus"></i> Zoom In</button>
|
||||||
<i class="fas fa-search-plus"></i> Zoom In
|
<button @click="zoomOut" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-search-minus"></i> Zoom Out</button>
|
||||||
</button>
|
<button @click="resetZoom" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-sync-alt"></i> Reset Zoom</button>
|
||||||
<button @click="zoomOut" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500">
|
|
||||||
<i class="fas fa-search-minus"></i> Zoom Out
|
|
||||||
</button>
|
|
||||||
<button @click="resetZoom" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500">
|
|
||||||
<i class="fas fa-sync-alt"></i> Reset Zoom
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-3 mt-4">
|
<div class="flex flex-wrap gap-3 mt-4">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="sprites.length === 0" class="text-center text-gray-400 py-8">No sprites uploaded yet</div>
|
<div v-if="sprites.length === 0" class="text-center text-gray-400 py-8">No sprites uploaded yet</div>
|
||||||
|
|
||||||
<div v-else class="grid grid-cols-2 gap-3 max-h-72 overflow-y-auto pr-2">
|
<div v-else class="grid grid-cols-2 gap-3 max-h-72 overflow-y-auto p-4">
|
||||||
<div v-for="(sprite, index) in sprites" :key="sprite.id" @click="$emit('spriteClicked', sprite.id)" class="border border-gray-600 rounded bg-gray-700 p-2 text-center transition-all cursor-pointer hover:border-blue-500 hover:-translate-y-0.5 hover:shadow-md">
|
<div v-for="(sprite, index) in sprites" :key="sprite.id" @click="$emit('spriteClicked', sprite.id)" class="border border-gray-600 rounded bg-gray-700 p-2 text-center transition-all cursor-pointer hover:border-blue-500 hover:-translate-y-0.5 hover:shadow-md">
|
||||||
<img :src="sprite.img.src" :alt="sprite.name" class="max-w-full max-h-16 mx-auto mb-2 bg-black bg-opacity-20 rounded" />
|
<img :src="sprite.img.src" :alt="sprite.name" class="max-w-full max-h-16 mx-auto mb-2 bg-black bg-opacity-20 rounded" />
|
||||||
<div class="text-xs text-gray-400 truncate">{{ index + 1 }}. {{ truncateName(sprite.name) }}</div>
|
<div class="text-xs text-gray-400 truncate">{{ index + 1 }}. {{ truncateName(sprite.name) }}</div>
|
||||||
|
@ -146,9 +146,11 @@ export function useSpritesheetStore() {
|
|||||||
canvas.value.height = newHeight;
|
canvas.value.height = newHeight;
|
||||||
|
|
||||||
// Emit an event to update the wrapper dimensions
|
// Emit an event to update the wrapper dimensions
|
||||||
window.dispatchEvent(new CustomEvent('canvas-size-updated', {
|
window.dispatchEvent(
|
||||||
detail: { width: newWidth, height: newHeight }
|
new CustomEvent('canvas-size-updated', {
|
||||||
}));
|
detail: { width: newWidth, height: newHeight },
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Store: Error updating canvas size:', error);
|
console.error('Store: Error updating canvas size:', error);
|
||||||
}
|
}
|
||||||
@ -191,8 +193,6 @@ export function useSpritesheetStore() {
|
|||||||
function renderSpritesheetPreview(showGrid = true) {
|
function renderSpritesheetPreview(showGrid = true) {
|
||||||
if (!ctx.value || !canvas.value) {
|
if (!ctx.value || !canvas.value) {
|
||||||
console.error('Store: Canvas or context not available for rendering, will retry when ready');
|
console.error('Store: Canvas or context not available for rendering, will retry when ready');
|
||||||
|
|
||||||
// Set up a small delay and retry when elements might be ready
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (ctx.value && canvas.value) {
|
if (ctx.value && canvas.value) {
|
||||||
renderSpritesheetPreview(showGrid);
|
renderSpritesheetPreview(showGrid);
|
||||||
@ -201,26 +201,17 @@ export function useSpritesheetStore() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sprites.value.length === 0) {
|
if (sprites.value.length === 0) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make sure canvas dimensions are set correctly
|
|
||||||
updateCanvasSize();
|
|
||||||
|
|
||||||
// Clear the canvas
|
// Clear the canvas
|
||||||
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
|
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
|
||||||
|
|
||||||
// Apply zoom transformation
|
|
||||||
ctx.value.save();
|
|
||||||
ctx.value.scale(zoomLevel.value, zoomLevel.value);
|
|
||||||
|
|
||||||
if (showGrid) {
|
if (showGrid) {
|
||||||
drawGrid();
|
drawGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw each sprite
|
// Draw each sprite - remove the zoom scaling from context
|
||||||
sprites.value.forEach((sprite, index) => {
|
sprites.value.forEach((sprite, index) => {
|
||||||
try {
|
try {
|
||||||
if (!sprite.img) {
|
if (!sprite.img) {
|
||||||
@ -229,17 +220,13 @@ export function useSpritesheetStore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sprite.img.complete && sprite.img.naturalWidth !== 0) {
|
if (sprite.img.complete && sprite.img.naturalWidth !== 0) {
|
||||||
ctx.value!.drawImage(sprite.img, sprite.x, sprite.y);
|
// Draw the image at its original size
|
||||||
|
ctx.value!.drawImage(sprite.img, sprite.x, sprite.y, sprite.width, sprite.height);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Store: Sprite image ${index} not fully loaded, setting onload handler`);
|
console.warn(`Store: Sprite image ${index} not fully loaded, setting onload handler`);
|
||||||
|
|
||||||
// If image isn't loaded yet, set an onload handler
|
|
||||||
sprite.img.onload = () => {
|
sprite.img.onload = () => {
|
||||||
if (ctx.value && canvas.value) {
|
if (ctx.value && canvas.value) {
|
||||||
ctx.value.save();
|
ctx.value.drawImage(sprite.img, sprite.x, sprite.y, sprite.width, sprite.height);
|
||||||
ctx.value.scale(zoomLevel.value, zoomLevel.value);
|
|
||||||
ctx.value.drawImage(sprite.img, sprite.x, sprite.y);
|
|
||||||
ctx.value.restore();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -247,11 +234,8 @@ export function useSpritesheetStore() {
|
|||||||
console.error(`Store: Error rendering sprite at index ${index}:`, spriteError);
|
console.error(`Store: Error rendering sprite at index ${index}:`, spriteError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore the canvas state
|
|
||||||
ctx.value.restore();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Store: Error rendering spritesheet preview:', error);
|
console.error('Store: Error in renderSpritesheetPreview:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user