UI and application enhancements
This commit is contained in:
@@ -92,8 +92,6 @@
|
||||
const showAllSprites = ref(false);
|
||||
|
||||
const spritePositions = computed(() => {
|
||||
if (!canvasRef.value) return [];
|
||||
|
||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||
|
||||
return props.sprites.map((sprite, index) => {
|
||||
@@ -119,22 +117,38 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Cache last known max dimensions to avoid collapsing cells while images are loading
|
||||
const lastMaxWidth = ref(1);
|
||||
const lastMaxHeight = ref(1);
|
||||
|
||||
const calculateMaxDimensions = () => {
|
||||
let maxWidth = 0;
|
||||
let maxHeight = 0;
|
||||
|
||||
props.sprites.forEach(sprite => {
|
||||
maxWidth = Math.max(maxWidth, sprite.width);
|
||||
maxHeight = Math.max(maxHeight, sprite.height);
|
||||
const img = sprite.img as HTMLImageElement | undefined;
|
||||
const w = Math.max(0, sprite.width || (img ? img.naturalWidth || img.width || 0 : 0));
|
||||
const h = Math.max(0, sprite.height || (img ? img.naturalHeight || img.height || 0 : 0));
|
||||
maxWidth = Math.max(maxWidth, w);
|
||||
maxHeight = Math.max(maxHeight, h);
|
||||
});
|
||||
|
||||
// Add some padding to ensure sprites have room to move
|
||||
return { maxWidth: maxWidth, maxHeight: maxHeight };
|
||||
// Keep dimensions at least as large as last known to prevent temporary collapse during loading
|
||||
maxWidth = Math.max(1, maxWidth, lastMaxWidth.value);
|
||||
maxHeight = Math.max(1, maxHeight, lastMaxHeight.value);
|
||||
|
||||
lastMaxWidth.value = maxWidth;
|
||||
lastMaxHeight.value = maxHeight;
|
||||
|
||||
return { maxWidth, maxHeight };
|
||||
};
|
||||
|
||||
const startDrag = (event: MouseEvent) => {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
// Ignore non-left mouse buttons (but allow touch-generated events without a button prop)
|
||||
if ('button' in event && (event as MouseEvent).button !== 0) return;
|
||||
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
const scaleX = canvasRef.value.width / rect.width;
|
||||
const scaleY = canvasRef.value.height / rect.height;
|
||||
@@ -287,11 +301,8 @@
|
||||
const handleTouchStart = (event: TouchEvent) => {
|
||||
// Don't prevent default to allow scrolling
|
||||
if (event.touches.length === 1) {
|
||||
if (!canvasRef.value) return;
|
||||
const touch = event.touches[0];
|
||||
const rect = canvasRef.value?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
// Adjust for zoom
|
||||
const mouseEvent = {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
@@ -322,12 +333,9 @@
|
||||
// Search in reverse order to get the topmost sprite first
|
||||
for (let i = spritePositions.value.length - 1; i >= 0; i--) {
|
||||
const pos = spritePositions.value[i];
|
||||
const sprite = props.sprites.find(s => s.id === pos.id);
|
||||
|
||||
if (!sprite) continue;
|
||||
|
||||
if (x >= pos.canvasX && x <= pos.canvasX + sprite.width && y >= pos.canvasY && y <= pos.canvasY + sprite.height) {
|
||||
return sprite;
|
||||
if (x >= pos.canvasX && x <= pos.canvasX + pos.width && y >= pos.canvasY && y <= pos.canvasY + pos.height) {
|
||||
return props.sprites.find(s => s.id === pos.id) || null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -339,7 +347,7 @@
|
||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||
|
||||
// Set canvas size
|
||||
const rows = Math.ceil(props.sprites.length / props.columns);
|
||||
const rows = Math.max(1, Math.ceil(props.sprites.length / props.columns));
|
||||
canvasRef.value.width = maxWidth * props.columns;
|
||||
canvasRef.value.height = maxHeight * rows;
|
||||
|
||||
@@ -380,7 +388,7 @@
|
||||
props.sprites.forEach((sprite, spriteIndex) => {
|
||||
if (spriteIndex !== cellIndex) {
|
||||
// Don't draw the cell's own sprite with transparency
|
||||
ctx.value?.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
|
||||
ctx.value?.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
|
||||
}
|
||||
});
|
||||
ctx.value.globalAlpha = 1.0;
|
||||
@@ -401,7 +409,7 @@
|
||||
const cellY = Math.floor(row * maxHeight);
|
||||
|
||||
// Draw sprite using integer positions for pixel-perfect rendering
|
||||
ctx.value?.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
|
||||
ctx.value?.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
|
||||
});
|
||||
|
||||
// Draw ghost sprite if we're dragging between cells
|
||||
@@ -430,12 +438,32 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Track which images already have listeners
|
||||
const imagesWithListeners = new WeakSet<HTMLImageElement>();
|
||||
|
||||
const attachImageListeners = () => {
|
||||
props.sprites.forEach(sprite => {
|
||||
const img = sprite.img as HTMLImageElement | undefined;
|
||||
if (img && !imagesWithListeners.has(img)) {
|
||||
imagesWithListeners.add(img);
|
||||
if (!img.complete) {
|
||||
// Redraw when the image loads or errors (to reflect updated dimensions)
|
||||
img.addEventListener('load', handleForceRedraw, { once: true } as AddEventListenerOptions);
|
||||
img.addEventListener('error', handleForceRedraw, { once: true } as AddEventListenerOptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (canvasRef.value) {
|
||||
ctx.value = canvasRef.value.getContext('2d');
|
||||
drawCanvas();
|
||||
}
|
||||
|
||||
// Attach listeners for current sprites
|
||||
attachImageListeners();
|
||||
|
||||
// Listen for forceRedraw event from App.vue
|
||||
window.addEventListener('forceRedraw', handleForceRedraw);
|
||||
});
|
||||
@@ -459,17 +487,18 @@
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.sprites, drawCanvas, { deep: true });
|
||||
watch(
|
||||
() => props.sprites,
|
||||
() => {
|
||||
attachImageListeners();
|
||||
drawCanvas();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(() => props.columns, drawCanvas);
|
||||
watch(() => settingsStore.pixelPerfect, drawCanvas);
|
||||
watch(() => settingsStore.darkMode, drawCanvas);
|
||||
watch(showAllSprites, drawCanvas);
|
||||
|
||||
// Add scale computed property
|
||||
const scale = computed(() => {
|
||||
if (!canvasRef.value) return 1;
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
return rect.width / canvasRef.value.width;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user