[FEAT] add grid metrics

This commit is contained in:
2026-01-01 17:14:21 +01:00
parent 281a37fa7e
commit 1f9fc4d5bb
5 changed files with 352 additions and 128 deletions

View File

@@ -204,6 +204,8 @@
import type { Sprite } from '@/types/sprites';
import { useDragSprite } from '@/composables/useDragSprite';
import { useFileDrop } from '@/composables/useFileDrop';
import { useGridMetrics } from '@/composables/useGridMetrics';
import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
import type { Layer } from '@/types/sprites';
@@ -315,8 +317,16 @@
}
);
// Grid metrics
const gridMetrics = computed(() => calculateMaxDimensions());
// Use the new useGridMetrics composable for consistent calculations
const { gridMetrics: gridMetricsRef, getCellPosition: getCellPositionHelper } = useGridMetrics({
layers: toRef(props, 'layers'),
negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'),
manualCellSizeEnabled: toRef(settingsStore, 'manualCellSizeEnabled'),
manualCellWidth: toRef(settingsStore, 'manualCellWidth'),
manualCellHeight: toRef(settingsStore, 'manualCellHeight'),
});
const gridMetrics = gridMetricsRef;
const totalCells = computed(() => {
// Use all layers regardless of visibility to keep canvas size stable
@@ -335,47 +345,20 @@
});
const getCellPosition = (index: number) => {
const col = index % props.columns;
const row = Math.floor(index / props.columns);
return {
x: Math.round(col * gridMetrics.value.maxWidth),
y: Math.round(row * gridMetrics.value.maxHeight),
};
return getCellPositionHelper(index, props.columns, gridMetrics.value);
};
const getCellBackground = () => {
const bg = settingsStore.backgroundColor;
if (bg === 'transparent') {
return 'transparent';
}
return bg;
};
// Use the new useBackgroundStyles composable for consistent background styling
const { backgroundColor: cellBackgroundColor, backgroundImage: cellBackgroundImage, backgroundSize: cellBackgroundSize, backgroundPosition: cellBackgroundPosition } = useBackgroundStyles({
backgroundColor: toRef(settingsStore, 'backgroundColor'),
checkerboardEnabled: toRef(settingsStore, 'checkerboardEnabled'),
darkMode: toRef(settingsStore, 'darkMode'),
});
const getCellBackgroundImage = () => {
const bg = settingsStore.backgroundColor;
if (bg === 'transparent' && settingsStore.checkerboardEnabled) {
// Checkerboard pattern for transparent backgrounds (dark mode friendly)
const color = settingsStore.darkMode ? '#4b5563' : '#d1d5db';
return `linear-gradient(45deg, ${color} 25%, transparent 25%), linear-gradient(-45deg, ${color} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${color} 75%), linear-gradient(-45deg, transparent 75%, ${color} 75%)`;
}
return 'none';
};
const getCellBackgroundSize = () => {
const bg = settingsStore.backgroundColor;
if (bg === 'transparent') {
return '20px 20px';
}
return 'auto';
};
const getCellBackgroundPosition = () => {
const bg = settingsStore.backgroundColor;
if (bg === 'transparent') {
return '0 0, 0 10px, 10px -10px, -10px 0px';
}
return '0 0';
};
const getCellBackground = () => cellBackgroundColor.value;
const getCellBackgroundImage = () => cellBackgroundImage.value;
const getCellBackgroundSize = () => cellBackgroundSize.value;
const getCellBackgroundPosition = () => cellBackgroundPosition.value;
const startDrag = (event: MouseEvent) => {
// If the click originated from an interactive element (button, link, input), ignore drag handling

View File

@@ -73,9 +73,9 @@
width: `${cellDimensions.cellWidth}px`,
height: `${cellDimensions.cellHeight}px`,
backgroundColor: settingsStore.backgroundColor === 'transparent' ? '#f9fafb' : settingsStore.backgroundColor,
backgroundImage: getPreviewBackgroundImage(),
backgroundSize: settingsStore.backgroundColor === 'transparent' ? '20px 20px' : 'auto',
backgroundPosition: settingsStore.backgroundColor === 'transparent' ? '0 0, 0 10px, 10px -10px, -10px 0px' : '0 0',
backgroundImage: previewBackgroundImage,
backgroundSize: previewBackgroundSize,
backgroundPosition: previewBackgroundPosition,
border: `1px solid ${settingsStore.darkMode ? '#4b5563' : '#e5e7eb'}`,
}"
@dragover.prevent="onDragOver"
@@ -302,13 +302,13 @@
</template>
<script setup lang="ts">
import { ref, onMounted, watch, onUnmounted, computed } from 'vue';
import { ref, onMounted, watch, onUnmounted, computed, toRef } from 'vue';
import { useSettingsStore } from '@/stores/useSettingsStore';
import type { Layer, Sprite } from '@/types/sprites';
import { getMaxDimensionsAcrossLayers } from '@/composables/useLayers';
import { useZoom } from '@/composables/useZoom';
import { useAnimationFrames } from '@/composables/useAnimationFrames';
import { calculateNegativeSpacing } from '@/composables/useNegativeSpacing';
import { useGridMetrics } from '@/composables/useGridMetrics';
import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
const props = defineProps<{
layers: Layer[];
@@ -426,38 +426,30 @@
return layer.sprites[currentFrameIndex.value] || null;
});
// Computed cell dimensions
const cellDimensions = computed(() => {
// Use ALL layers (regardless of visibility) to keep preview size stable
const allLayers = props.layers;
// If manual cell size is enabled, use manual values
if (settingsStore.manualCellSizeEnabled) {
return {
cellWidth: Math.round(settingsStore.manualCellWidth),
cellHeight: Math.round(settingsStore.manualCellHeight),
negativeSpacing: 0,
};
}
// Otherwise, calculate from sprite dimensions across ALL layers
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(allLayers);
const allSprites = allLayers.flatMap(l => l.sprites);
const negativeSpacing = Math.round(calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled));
return {
cellWidth: Math.round(maxWidth + negativeSpacing),
cellHeight: Math.round(maxHeight + negativeSpacing),
negativeSpacing,
};
// Use the new useGridMetrics composable for consistent calculations
const { gridMetrics } = useGridMetrics({
layers: toRef(props, 'layers'),
negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'),
manualCellSizeEnabled: toRef(settingsStore, 'manualCellSizeEnabled'),
manualCellWidth: toRef(settingsStore, 'manualCellWidth'),
manualCellHeight: toRef(settingsStore, 'manualCellHeight'),
});
// Helper for background image (dark mode friendly)
const getPreviewBackgroundImage = () => {
if (settingsStore.backgroundColor === 'transparent' && settingsStore.checkerboardEnabled) {
const color = settingsStore.darkMode ? '#4b5563' : '#d1d5db';
return `linear-gradient(45deg, ${color} 25%, transparent 25%), linear-gradient(-45deg, ${color} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${color} 75%), linear-gradient(-45deg, transparent 75%, ${color} 75%)`;
}
return 'none';
};
// Computed cell dimensions (for backward compatibility with existing code)
const cellDimensions = computed(() => ({
cellWidth: gridMetrics.value.maxWidth,
cellHeight: gridMetrics.value.maxHeight,
negativeSpacing: gridMetrics.value.negativeSpacing,
}));
// Use the new useBackgroundStyles composable for consistent background styling
const { backgroundImage: previewBackgroundImage, backgroundSize: previewBackgroundSize, backgroundPosition: previewBackgroundPosition } = useBackgroundStyles({
backgroundColor: toRef(settingsStore, 'backgroundColor'),
checkerboardEnabled: toRef(settingsStore, 'checkerboardEnabled'),
darkMode: toRef(settingsStore, 'darkMode'),
});
const getPreviewBackgroundImage = () => previewBackgroundImage.value;
// Dragging state
const isDragging = ref(false);