[FEAT] Allow to adjust cell size

This commit is contained in:
2025-11-22 21:33:01 +01:00
parent 69fc4c4a7e
commit f8b4e98f9c
8 changed files with 210 additions and 50 deletions

View File

@@ -66,8 +66,30 @@
</div> </div>
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<span class="text-gray-700 dark:text-gray-200 font-medium">Cell size:</span> <label class="text-gray-700 dark:text-gray-200 font-medium flex items-center gap-1">
<span class="text-gray-600 dark:text-gray-300">{{ cellSize.width }} × {{ cellSize.height }}px</span> <input type="checkbox" v-model="settingsStore.manualCellSizeEnabled" class="w-4 h-4" />
Cell size:
</label>
<input
v-if="settingsStore.manualCellSizeEnabled"
type="number"
v-model.number="settingsStore.manualCellWidth"
min="1"
max="2048"
class="w-20 px-2 py-1 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent outline-none transition-all text-sm"
placeholder="Width"
/>
<span v-if="settingsStore.manualCellSizeEnabled" class="text-gray-600 dark:text-gray-300">×</span>
<input
v-if="settingsStore.manualCellSizeEnabled"
type="number"
v-model.number="settingsStore.manualCellHeight"
min="1"
max="2048"
class="w-20 px-2 py-1 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent outline-none transition-all text-sm"
placeholder="Height"
/>
<span v-if="!settingsStore.manualCellSizeEnabled" class="text-gray-600 dark:text-gray-300">{{ cellSize.width }} × {{ cellSize.height }}px</span>
</div> </div>
<!-- Add mass position buttons --> <!-- Add mass position buttons -->
@@ -183,10 +205,26 @@
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const { layers, visibleLayers, activeLayer, activeLayerId, columns, updateSpritePosition, updateSpriteCell, removeSprite, replaceSprite, addSprite, addSpriteWithResize, processImageFiles, alignSprites, addLayer, removeLayer, moveLayer } = useLayers(); const { layers, visibleLayers, activeLayer, activeLayerId, columns, updateSpritePosition, updateSpriteCell, removeSprite, replaceSprite, addSprite, addSpriteWithResize, processImageFiles, alignSprites, addLayer, removeLayer, moveLayer } = useLayers();
const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExportLayers(layers, columns, toRef(settingsStore, 'negativeSpacingEnabled'), activeLayerId, toRef(settingsStore, 'backgroundColor')); const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExportLayers(
layers,
columns,
toRef(settingsStore, 'negativeSpacingEnabled'),
activeLayerId,
toRef(settingsStore, 'backgroundColor'),
toRef(settingsStore, 'manualCellSizeEnabled'),
toRef(settingsStore, 'manualCellWidth'),
toRef(settingsStore, 'manualCellHeight')
);
const cellSize = computed(() => { const cellSize = computed(() => {
if (!layers.value.length) return { width: 0, height: 0 }; if (!layers.value.length) return { width: 0, height: 0 };
// If manual cell size is enabled, use the manual values
if (settingsStore.manualCellSizeEnabled) {
return { width: settingsStore.manualCellWidth, height: settingsStore.manualCellHeight };
}
// Otherwise, calculate based on max dimensions
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers.value); const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers.value);
const allSprites = visibleLayers.value.flatMap(l => l.sprites); const allSprites = visibleLayers.value.flatMap(l => l.sprites);
const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled); const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled);

View File

@@ -190,6 +190,9 @@
zoom, zoom,
allowCellSwap, allowCellSwap,
negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'), negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'),
manualCellSizeEnabled: toRef(settingsStore, 'manualCellSizeEnabled'),
manualCellWidth: toRef(settingsStore, 'manualCellWidth'),
manualCellHeight: toRef(settingsStore, 'manualCellHeight'),
getMousePosition: (event, z) => canvas2D.getMousePosition(event, z), getMousePosition: (event, z) => canvas2D.getMousePosition(event, z),
onUpdateSprite: (id, x, y) => emit('updateSprite', id, x, y), onUpdateSprite: (id, x, y) => emit('updateSprite', id, x, y),
onUpdateSpriteCell: (id, newIndex) => emit('updateSpriteCell', id, newIndex), onUpdateSpriteCell: (id, newIndex) => emit('updateSpriteCell', id, newIndex),
@@ -513,6 +516,9 @@
watch(() => settingsStore.darkMode, requestDraw); watch(() => settingsStore.darkMode, requestDraw);
watch(() => settingsStore.negativeSpacingEnabled, requestDraw); watch(() => settingsStore.negativeSpacingEnabled, requestDraw);
watch(() => settingsStore.backgroundColor, requestDraw); watch(() => settingsStore.backgroundColor, requestDraw);
watch(() => settingsStore.manualCellSizeEnabled, requestDraw);
watch(() => settingsStore.manualCellWidth, requestDraw);
watch(() => settingsStore.manualCellHeight, requestDraw);
watch(showAllSprites, requestDraw); watch(showAllSprites, requestDraw);
</script> </script>

View File

@@ -239,16 +239,34 @@
// Canvas drawing // Canvas drawing
const getCellDimensions = () => {
const visibleLayers = getVisibleLayers();
// If manual cell size is enabled, use manual values
if (settingsStore.manualCellSizeEnabled) {
return {
cellWidth: settingsStore.manualCellWidth,
cellHeight: settingsStore.manualCellHeight,
negativeSpacing: 0,
};
}
// Otherwise, calculate from sprite dimensions
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers);
const allSprites = visibleLayers.flatMap(l => l.sprites);
const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled);
return {
cellWidth: maxWidth + negativeSpacing,
cellHeight: maxHeight + negativeSpacing,
negativeSpacing,
};
};
function drawPreviewCanvas() { function drawPreviewCanvas() {
if (!previewCanvasRef.value || !canvas2D.ctx.value) return; if (!previewCanvasRef.value || !canvas2D.ctx.value) return;
const visibleLayers = getVisibleLayers(); const visibleLayers = getVisibleLayers();
if (!visibleLayers.length || !visibleLayers.some(l => l.sprites.length)) return; if (!visibleLayers.length || !visibleLayers.some(l => l.sprites.length)) return;
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const allSprites = visibleLayers.flatMap(l => l.sprites);
const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
// Apply pixel art optimization // Apply pixel art optimization
canvas2D.applySmoothing(); canvas2D.applySmoothing();
@@ -298,9 +316,7 @@
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY; const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value]; const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value];
const vLayers = getVisibleLayers(); const { negativeSpacing } = getCellDimensions();
const allSprites = vLayers.flatMap(l => l.sprites);
const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled);
// Check if click is on sprite (accounting for negative spacing offset) // Check if click is on sprite (accounting for negative spacing offset)
if (activeSprite) { if (activeSprite) {
@@ -332,12 +348,7 @@
const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value]; const activeSprite = props.layers.find(l => l.id === props.activeLayerId)?.sprites[currentFrameIndex.value];
if (!activeSprite || activeSprite.id !== activeSpriteId.value) return; if (!activeSprite || activeSprite.id !== activeSpriteId.value) return;
const vLayers = getVisibleLayers(); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(vLayers);
const allSprites = vLayers.flatMap(l => l.sprites);
const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
// Calculate new position with constraints and round to integers // Calculate new position with constraints and round to integers
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX); let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
@@ -417,6 +428,9 @@
watch(hiddenFrames, drawPreviewCanvas); watch(hiddenFrames, drawPreviewCanvas);
watch(() => settingsStore.pixelPerfect, drawPreviewCanvas); watch(() => settingsStore.pixelPerfect, drawPreviewCanvas);
watch(() => settingsStore.negativeSpacingEnabled, drawPreviewCanvas); watch(() => settingsStore.negativeSpacingEnabled, drawPreviewCanvas);
watch(() => settingsStore.manualCellSizeEnabled, drawPreviewCanvas);
watch(() => settingsStore.manualCellWidth, drawPreviewCanvas);
watch(() => settingsStore.manualCellHeight, drawPreviewCanvas);
// Initial draw // Initial draw
if (props.layers.some(l => l.sprites.length > 0)) { if (props.layers.some(l => l.sprites.length > 0)) {

View File

@@ -32,6 +32,9 @@ export interface DragSpriteOptions {
zoom?: Ref<number>; zoom?: Ref<number>;
allowCellSwap?: Ref<boolean>; allowCellSwap?: Ref<boolean>;
negativeSpacingEnabled?: Ref<boolean>; negativeSpacingEnabled?: Ref<boolean>;
manualCellSizeEnabled?: Ref<boolean>;
manualCellWidth?: Ref<number>;
manualCellHeight?: Ref<number>;
getMousePosition: (event: MouseEvent, zoom?: number) => { x: number; y: number } | null; getMousePosition: (event: MouseEvent, zoom?: number) => { x: number; y: number } | null;
onUpdateSprite: (id: string, x: number, y: number) => void; onUpdateSprite: (id: string, x: number, y: number) => void;
onUpdateSpriteCell?: (id: string, newIndex: number) => void; onUpdateSpriteCell?: (id: string, newIndex: number) => void;
@@ -47,6 +50,9 @@ export function useDragSprite(options: DragSpriteOptions) {
const getZoom = () => options.zoom?.value ?? 1; const getZoom = () => options.zoom?.value ?? 1;
const getAllowCellSwap = () => options.allowCellSwap?.value ?? false; const getAllowCellSwap = () => options.allowCellSwap?.value ?? false;
const getNegativeSpacingEnabled = () => options.negativeSpacingEnabled?.value ?? false; const getNegativeSpacingEnabled = () => options.negativeSpacingEnabled?.value ?? false;
const getManualCellSizeEnabled = () => options.manualCellSizeEnabled?.value ?? false;
const getManualCellWidth = () => options.manualCellWidth?.value ?? 64;
const getManualCellHeight = () => options.manualCellHeight?.value ?? 64;
// Drag state // Drag state
const isDragging = ref(false); const isDragging = ref(false);
@@ -69,9 +75,23 @@ export function useDragSprite(options: DragSpriteOptions) {
const calculateMaxDimensions = () => { const calculateMaxDimensions = () => {
const sprites = getSprites(); const sprites = getSprites();
const negativeSpacingEnabled = getNegativeSpacingEnabled(); const negativeSpacingEnabled = getNegativeSpacingEnabled();
const manualCellSizeEnabled = getManualCellSizeEnabled();
// If manual cell size is enabled, use manual dimensions
if (manualCellSizeEnabled) {
const maxWidth = getManualCellWidth();
const maxHeight = getManualCellHeight();
// When manual cell size is used, negative spacing is not applied
const negativeSpacing = 0;
// Don't update lastMaxWidth/lastMaxHeight when in manual mode
return { maxWidth, maxHeight, negativeSpacing };
}
// Otherwise, calculate based on sprite dimensions
const base = getMaxDimensions(sprites); const base = getMaxDimensions(sprites);
const baseMaxWidth = Math.max(1, base.maxWidth, lastMaxWidth.value); // When switching back from manual mode, reset to actual sprite dimensions
const baseMaxHeight = Math.max(1, base.maxHeight, lastMaxHeight.value); const baseMaxWidth = Math.max(1, base.maxWidth);
const baseMaxHeight = Math.max(1, base.maxHeight);
lastMaxWidth.value = baseMaxWidth; lastMaxWidth.value = baseMaxWidth;
lastMaxHeight.value = baseMaxHeight; lastMaxHeight.value = baseMaxHeight;

View File

@@ -6,17 +6,34 @@ import type { Sprite } from '../types/sprites';
import { getMaxDimensions } from './useSprites'; import { getMaxDimensions } from './useSprites';
import { calculateNegativeSpacing } from './useNegativeSpacing'; import { calculateNegativeSpacing } from './useNegativeSpacing';
export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, backgroundColor?: Ref<string>) => { export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, backgroundColor?: Ref<string>, manualCellSizeEnabled?: Ref<boolean>, manualCellWidth?: Ref<number>, manualCellHeight?: Ref<number>) => {
const getCellDimensions = () => {
// If manual cell size is enabled, use manual values
if (manualCellSizeEnabled?.value) {
return {
cellWidth: manualCellWidth?.value ?? 64,
cellHeight: manualCellHeight?.value ?? 64,
negativeSpacing: 0,
};
}
// Otherwise, calculate from sprite dimensions
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
const negativeSpacing = calculateNegativeSpacing(sprites.value, negativeSpacingEnabled.value);
return {
cellWidth: maxWidth + negativeSpacing,
cellHeight: maxHeight + negativeSpacing,
negativeSpacing,
};
};
const downloadSpritesheet = () => { const downloadSpritesheet = () => {
if (!sprites.value.length) { if (!sprites.value.length) {
alert('Please upload or import sprites before downloading the spritesheet.'); alert('Please upload or import sprites before downloading the spritesheet.');
return; return;
} }
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const negativeSpacing = calculateNegativeSpacing(sprites.value, negativeSpacingEnabled.value);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
const rows = Math.ceil(sprites.value.length / columns.value); const rows = Math.ceil(sprites.value.length / columns.value);
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
@@ -78,6 +95,9 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negative
columns: columns.value, columns: columns.value,
negativeSpacingEnabled: negativeSpacingEnabled.value, negativeSpacingEnabled: negativeSpacingEnabled.value,
backgroundColor: backgroundColor?.value || 'transparent', backgroundColor: backgroundColor?.value || 'transparent',
manualCellSizeEnabled: manualCellSizeEnabled?.value || false,
manualCellWidth: manualCellWidth?.value || 64,
manualCellHeight: manualCellHeight?.value || 64,
sprites: spritesData.filter(Boolean), sprites: spritesData.filter(Boolean),
}; };
const jsonString = JSON.stringify(jsonData, null, 2); const jsonString = JSON.stringify(jsonData, null, 2);
@@ -99,6 +119,9 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negative
if (jsonData.columns && typeof jsonData.columns === 'number') columns.value = jsonData.columns; if (jsonData.columns && typeof jsonData.columns === 'number') columns.value = jsonData.columns;
if (typeof jsonData.negativeSpacingEnabled === 'boolean') negativeSpacingEnabled.value = jsonData.negativeSpacingEnabled; if (typeof jsonData.negativeSpacingEnabled === 'boolean') negativeSpacingEnabled.value = jsonData.negativeSpacingEnabled;
if (typeof jsonData.backgroundColor === 'string' && backgroundColor) backgroundColor.value = jsonData.backgroundColor; if (typeof jsonData.backgroundColor === 'string' && backgroundColor) backgroundColor.value = jsonData.backgroundColor;
if (typeof jsonData.manualCellSizeEnabled === 'boolean' && manualCellSizeEnabled) manualCellSizeEnabled.value = jsonData.manualCellSizeEnabled;
if (typeof jsonData.manualCellWidth === 'number' && manualCellWidth) manualCellWidth.value = jsonData.manualCellWidth;
if (typeof jsonData.manualCellHeight === 'number' && manualCellHeight) manualCellHeight.value = jsonData.manualCellHeight;
// revoke existing blob urls // revoke existing blob urls
if (sprites.value.length) { if (sprites.value.length) {
@@ -149,10 +172,7 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negative
return; return;
} }
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const negativeSpacing = calculateNegativeSpacing(sprites.value, negativeSpacingEnabled.value);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
if (!ctx) return; if (!ctx) return;
@@ -192,10 +212,7 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negative
} }
const zip = new JSZip(); const zip = new JSZip();
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const negativeSpacing = calculateNegativeSpacing(sprites.value, negativeSpacingEnabled.value);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
if (!ctx) return; if (!ctx) return;

View File

@@ -6,10 +6,31 @@ import type { Layer, Sprite } from '@/types/sprites';
import { getMaxDimensionsAcrossLayers } from './useLayers'; import { getMaxDimensionsAcrossLayers } from './useLayers';
import { calculateNegativeSpacing } from './useNegativeSpacing'; import { calculateNegativeSpacing } from './useNegativeSpacing';
export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, activeLayerId?: Ref<string>, backgroundColor?: Ref<string>) => { export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, activeLayerId?: Ref<string>, backgroundColor?: Ref<string>, manualCellSizeEnabled?: Ref<boolean>, manualCellWidth?: Ref<number>, manualCellHeight?: Ref<number>) => {
const getVisibleLayers = () => layersRef.value.filter(l => l.visible); const getVisibleLayers = () => layersRef.value.filter(l => l.visible);
const getAllVisibleSprites = () => getVisibleLayers().flatMap(l => l.sprites); const getAllVisibleSprites = () => getVisibleLayers().flatMap(l => l.sprites);
const getCellDimensions = () => {
// If manual cell size is enabled, use manual values
if (manualCellSizeEnabled?.value) {
return {
cellWidth: manualCellWidth?.value ?? 64,
cellHeight: manualCellHeight?.value ?? 64,
negativeSpacing: 0,
};
}
// Otherwise, calculate from sprite dimensions
const visibleLayers = getVisibleLayers();
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers);
const negativeSpacing = calculateNegativeSpacing(getAllVisibleSprites(), negativeSpacingEnabled.value);
return {
cellWidth: maxWidth + negativeSpacing,
cellHeight: maxHeight + negativeSpacing,
negativeSpacing,
};
};
const drawCompositeCell = (ctx: CanvasRenderingContext2D, cellIndex: number, cellWidth: number, cellHeight: number, negativeSpacing: number) => { const drawCompositeCell = (ctx: CanvasRenderingContext2D, cellIndex: number, cellWidth: number, cellHeight: number, negativeSpacing: number) => {
ctx.clearRect(0, 0, cellWidth, cellHeight); ctx.clearRect(0, 0, cellWidth, cellHeight);
// Apply background color if not transparent // Apply background color if not transparent
@@ -32,10 +53,7 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
return; return;
} }
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const negativeSpacing = calculateNegativeSpacing(getAllVisibleSprites(), negativeSpacingEnabled.value);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
const maxLen = Math.max(...visibleLayers.map(l => l.sprites.length)); const maxLen = Math.max(...visibleLayers.map(l => l.sprites.length));
const rows = Math.ceil(maxLen / columns.value); const rows = Math.ceil(maxLen / columns.value);
@@ -104,6 +122,9 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
columns: columns.value, columns: columns.value,
negativeSpacingEnabled: negativeSpacingEnabled.value, negativeSpacingEnabled: negativeSpacingEnabled.value,
backgroundColor: backgroundColor?.value || 'transparent', backgroundColor: backgroundColor?.value || 'transparent',
manualCellSizeEnabled: manualCellSizeEnabled?.value || false,
manualCellWidth: manualCellWidth?.value || 64,
manualCellHeight: manualCellHeight?.value || 64,
layers: layersData, layers: layersData,
}; };
const jsonString = JSON.stringify(json, null, 2); const jsonString = JSON.stringify(json, null, 2);
@@ -140,6 +161,9 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
if (typeof data.columns === 'number') columns.value = data.columns; if (typeof data.columns === 'number') columns.value = data.columns;
if (typeof data.negativeSpacingEnabled === 'boolean') negativeSpacingEnabled.value = data.negativeSpacingEnabled; if (typeof data.negativeSpacingEnabled === 'boolean') negativeSpacingEnabled.value = data.negativeSpacingEnabled;
if (typeof data.backgroundColor === 'string' && backgroundColor) backgroundColor.value = data.backgroundColor; if (typeof data.backgroundColor === 'string' && backgroundColor) backgroundColor.value = data.backgroundColor;
if (typeof data.manualCellSizeEnabled === 'boolean' && manualCellSizeEnabled) manualCellSizeEnabled.value = data.manualCellSizeEnabled;
if (typeof data.manualCellWidth === 'number' && manualCellWidth) manualCellWidth.value = data.manualCellWidth;
if (typeof data.manualCellHeight === 'number' && manualCellHeight) manualCellHeight.value = data.manualCellHeight;
if (Array.isArray(data.layers)) { if (Array.isArray(data.layers)) {
const newLayers: Layer[] = []; const newLayers: Layer[] = [];
@@ -186,10 +210,7 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
return; return;
} }
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const negativeSpacing = calculateNegativeSpacing(getAllVisibleSprites(), negativeSpacingEnabled.value);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@@ -224,10 +245,7 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
} }
const zip = new JSZip(); const zip = new JSZip();
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(visibleLayers); const { cellWidth, cellHeight, negativeSpacing } = getCellDimensions();
const negativeSpacing = calculateNegativeSpacing(getAllVisibleSprites(), negativeSpacingEnabled.value);
const cellWidth = maxWidth + negativeSpacing;
const cellHeight = maxHeight + negativeSpacing;
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@@ -263,6 +281,9 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
columns: columns.value, columns: columns.value,
negativeSpacingEnabled: negativeSpacingEnabled.value, negativeSpacingEnabled: negativeSpacingEnabled.value,
backgroundColor: backgroundColor?.value || 'transparent', backgroundColor: backgroundColor?.value || 'transparent',
manualCellSizeEnabled: manualCellSizeEnabled?.value || false,
manualCellWidth: manualCellWidth?.value || 64,
manualCellHeight: manualCellHeight?.value || 64,
layers: layersPayload, layers: layersPayload,
}; };
const metaStr = JSON.stringify(meta, null, 2); const metaStr = JSON.stringify(meta, null, 2);

View File

@@ -1,6 +1,7 @@
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import type { Layer, Sprite } from '@/types/sprites'; import type { Layer, Sprite } from '@/types/sprites';
import { getMaxDimensions as getMaxDimensionsSingle, useSprites as useSpritesSingle } from './useSprites'; import { getMaxDimensions as getMaxDimensionsSingle, useSprites as useSpritesSingle } from './useSprites';
import { useSettingsStore } from '@/stores/useSettingsStore';
export const createEmptyLayer = (name: string): Layer => ({ export const createEmptyLayer = (name: string): Layer => ({
id: crypto.randomUUID(), id: crypto.randomUUID(),
@@ -14,6 +15,7 @@ export const useLayers = () => {
const layers = ref<Layer[]>([createEmptyLayer('Base')]); const layers = ref<Layer[]>([createEmptyLayer('Base')]);
const activeLayerId = ref<string>(layers.value[0].id); const activeLayerId = ref<string>(layers.value[0].id);
const columns = ref(4); const columns = ref(4);
const settingsStore = useSettingsStore();
watch(columns, val => { watch(columns, val => {
const num = typeof val === 'number' ? val : parseInt(String(val)); const num = typeof val === 'number' ? val : parseInt(String(val));
@@ -38,7 +40,22 @@ export const useLayers = () => {
const alignSprites = (position: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => { const alignSprites = (position: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
const l = activeLayer.value; const l = activeLayer.value;
if (!l || !l.sprites.length) return; if (!l || !l.sprites.length) return;
const { maxWidth, maxHeight } = getMaxDimensions(l.sprites);
// Determine the cell dimensions to align within
let cellWidth: number;
let cellHeight: number;
if (settingsStore.manualCellSizeEnabled) {
// Use manual cell size (without negative spacing)
cellWidth = settingsStore.manualCellWidth;
cellHeight = settingsStore.manualCellHeight;
} else {
// Use auto-calculated dimensions based on sprite sizes
const { maxWidth, maxHeight } = getMaxDimensions(l.sprites);
cellWidth = maxWidth;
cellHeight = maxHeight;
}
l.sprites = l.sprites.map(sprite => { l.sprites = l.sprites.map(sprite => {
let x = sprite.x; let x = sprite.x;
let y = sprite.y; let y = sprite.y;
@@ -47,19 +64,19 @@ export const useLayers = () => {
x = 0; x = 0;
break; break;
case 'center': case 'center':
x = Math.floor((maxWidth - sprite.width) / 2); x = Math.floor((cellWidth - sprite.width) / 2);
break; break;
case 'right': case 'right':
x = Math.floor(maxWidth - sprite.width); x = Math.floor(cellWidth - sprite.width);
break; break;
case 'top': case 'top':
y = 0; y = 0;
break; break;
case 'middle': case 'middle':
y = Math.floor((maxHeight - sprite.height) / 2); y = Math.floor((cellHeight - sprite.height) / 2);
break; break;
case 'bottom': case 'bottom':
y = Math.floor(maxHeight - sprite.height); y = Math.floor(cellHeight - sprite.height);
break; break;
} }
return { ...sprite, x: Math.floor(x), y: Math.floor(y) }; return { ...sprite, x: Math.floor(x), y: Math.floor(y) };

View File

@@ -5,6 +5,9 @@ const pixelPerfect = ref(true);
const darkMode = ref(false); const darkMode = ref(false);
const negativeSpacingEnabled = ref(false); const negativeSpacingEnabled = ref(false);
const backgroundColor = ref('transparent'); const backgroundColor = ref('transparent');
const manualCellSizeEnabled = ref(false);
const manualCellWidth = ref(64);
const manualCellHeight = ref(64);
// Initialize dark mode from localStorage or system preference // Initialize dark mode from localStorage or system preference
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@@ -61,16 +64,40 @@ export const useSettingsStore = defineStore('settings', () => {
backgroundColor.value = color; backgroundColor.value = color;
} }
function toggleManualCellSize() {
manualCellSizeEnabled.value = !manualCellSizeEnabled.value;
}
function setManualCellWidth(width: number) {
manualCellWidth.value = Math.max(1, Math.floor(width));
}
function setManualCellHeight(height: number) {
manualCellHeight.value = Math.max(1, Math.floor(height));
}
function setManualCellSize(width: number, height: number) {
setManualCellWidth(width);
setManualCellHeight(height);
}
return { return {
pixelPerfect, pixelPerfect,
darkMode, darkMode,
negativeSpacingEnabled, negativeSpacingEnabled,
backgroundColor, backgroundColor,
manualCellSizeEnabled,
manualCellWidth,
manualCellHeight,
togglePixelPerfect, togglePixelPerfect,
setPixelPerfect, setPixelPerfect,
toggleDarkMode, toggleDarkMode,
setDarkMode, setDarkMode,
toggleNegativeSpacing, toggleNegativeSpacing,
setBackgroundColor, setBackgroundColor,
toggleManualCellSize,
setManualCellWidth,
setManualCellHeight,
setManualCellSize,
}; };
}); });