POC
This commit is contained in:
205
src/composables/useLayers.ts
Normal file
205
src/composables/useLayers.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import type { Layer, Sprite } from '@/types/sprites';
|
||||
import { getMaxDimensions as getMaxDimensionsSingle, useSprites as useSpritesSingle } from './useSprites';
|
||||
|
||||
export const createEmptyLayer = (name: string): Layer => ({
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
sprites: [],
|
||||
visible: true,
|
||||
locked: false,
|
||||
});
|
||||
|
||||
export const useLayers = () => {
|
||||
const layers = ref<Layer[]>([createEmptyLayer('Base')]);
|
||||
const activeLayerId = ref<string>(layers.value[0].id);
|
||||
const columns = ref(4);
|
||||
|
||||
watch(columns, val => {
|
||||
const num = typeof val === 'number' ? val : parseInt(String(val));
|
||||
const safe = Number.isFinite(num) && num >= 1 ? Math.min(num, 10) : 1;
|
||||
if (safe !== columns.value) columns.value = safe;
|
||||
});
|
||||
|
||||
const activeLayer = computed(() => layers.value.find(l => l.id === activeLayerId.value) || layers.value[0]);
|
||||
|
||||
const getMaxDimensions = (sprites: Sprite[]) => getMaxDimensionsSingle(sprites);
|
||||
|
||||
const updateSpritePosition = (id: string, x: number, y: number) => {
|
||||
const l = activeLayer.value;
|
||||
if (!l) return;
|
||||
const i = l.sprites.findIndex(s => s.id === id);
|
||||
if (i !== -1) {
|
||||
l.sprites[i].x = Math.floor(x);
|
||||
l.sprites[i].y = Math.floor(y);
|
||||
}
|
||||
};
|
||||
|
||||
const alignSprites = (position: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
|
||||
const l = activeLayer.value;
|
||||
if (!l || !l.sprites.length) return;
|
||||
const { maxWidth, maxHeight } = getMaxDimensions(l.sprites);
|
||||
l.sprites = l.sprites.map(sprite => {
|
||||
let x = sprite.x;
|
||||
let y = sprite.y;
|
||||
switch (position) {
|
||||
case 'left':
|
||||
x = 0;
|
||||
break;
|
||||
case 'center':
|
||||
x = Math.floor((maxWidth - sprite.width) / 2);
|
||||
break;
|
||||
case 'right':
|
||||
x = Math.floor(maxWidth - sprite.width);
|
||||
break;
|
||||
case 'top':
|
||||
y = 0;
|
||||
break;
|
||||
case 'middle':
|
||||
y = Math.floor((maxHeight - sprite.height) / 2);
|
||||
break;
|
||||
case 'bottom':
|
||||
y = Math.floor(maxHeight - sprite.height);
|
||||
break;
|
||||
}
|
||||
return { ...sprite, x: Math.floor(x), y: Math.floor(y) };
|
||||
});
|
||||
};
|
||||
|
||||
const updateSpriteCell = (id: string, newIndex: number) => {
|
||||
const l = activeLayer.value;
|
||||
if (!l) return;
|
||||
const currentIndex = l.sprites.findIndex(s => s.id === id);
|
||||
if (currentIndex === -1 || currentIndex === newIndex) return;
|
||||
const next = [...l.sprites];
|
||||
if (newIndex < next.length) {
|
||||
const moving = { ...next[currentIndex] };
|
||||
const target = { ...next[newIndex] };
|
||||
next[currentIndex] = target;
|
||||
next[newIndex] = moving;
|
||||
} else {
|
||||
const [moved] = next.splice(currentIndex, 1);
|
||||
next.splice(newIndex, 0, moved);
|
||||
}
|
||||
l.sprites = next;
|
||||
};
|
||||
|
||||
const removeSprite = (id: string) => {
|
||||
const l = activeLayer.value;
|
||||
if (!l) return;
|
||||
const i = l.sprites.findIndex(s => s.id === id);
|
||||
if (i === -1) return;
|
||||
const s = l.sprites[i];
|
||||
if (s.url && s.url.startsWith('blob:')) {
|
||||
try {
|
||||
URL.revokeObjectURL(s.url);
|
||||
} catch {}
|
||||
}
|
||||
l.sprites.splice(i, 1);
|
||||
};
|
||||
|
||||
const replaceSprite = (id: string, file: File) => {
|
||||
const l = activeLayer.value;
|
||||
if (!l) return;
|
||||
const i = l.sprites.findIndex(s => s.id === id);
|
||||
if (i === -1) return;
|
||||
const old = l.sprites[i];
|
||||
if (old.url && old.url.startsWith('blob:')) {
|
||||
try {
|
||||
URL.revokeObjectURL(old.url);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
l.sprites[i] = { id: old.id, file, img, url, width: img.width, height: img.height, x: old.x, y: old.y };
|
||||
};
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
const addSprite = (file: File) => addSpriteWithResize(file);
|
||||
|
||||
const addSpriteWithResize = (file: File) => {
|
||||
const l = activeLayer.value;
|
||||
if (!l) return;
|
||||
const url = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const next: Sprite = {
|
||||
id: crypto.randomUUID(),
|
||||
file,
|
||||
img,
|
||||
url,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
l.sprites = [...l.sprites, next];
|
||||
};
|
||||
img.onerror = () => URL.revokeObjectURL(url);
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
const processImageFiles = async (files: File[]) => {
|
||||
for (const f of files) addSpriteWithResize(f);
|
||||
};
|
||||
|
||||
const addLayer = (name?: string) => {
|
||||
const l = createEmptyLayer(name || `Layer ${layers.value.length + 1}`);
|
||||
layers.value.push(l);
|
||||
activeLayerId.value = l.id;
|
||||
};
|
||||
|
||||
const removeLayer = (id: string) => {
|
||||
if (layers.value.length === 1) return;
|
||||
const idx = layers.value.findIndex(l => l.id === id);
|
||||
if (idx === -1) return;
|
||||
layers.value.splice(idx, 1);
|
||||
if (activeLayerId.value === id) activeLayerId.value = layers.value[0].id;
|
||||
};
|
||||
|
||||
const moveLayer = (id: string, direction: 'up' | 'down') => {
|
||||
const idx = layers.value.findIndex(l => l.id === id);
|
||||
if (idx === -1) return;
|
||||
if (direction === 'up' && idx > 0) {
|
||||
const [l] = layers.value.splice(idx, 1);
|
||||
layers.value.splice(idx - 1, 0, l);
|
||||
}
|
||||
if (direction === 'down' && idx < layers.value.length - 1) {
|
||||
const [l] = layers.value.splice(idx, 1);
|
||||
layers.value.splice(idx + 1, 0, l);
|
||||
}
|
||||
};
|
||||
|
||||
const visibleLayers = computed(() => layers.value.filter(l => l.visible));
|
||||
|
||||
return {
|
||||
layers,
|
||||
visibleLayers,
|
||||
activeLayerId,
|
||||
activeLayer,
|
||||
columns,
|
||||
getMaxDimensions,
|
||||
updateSpritePosition,
|
||||
updateSpriteCell,
|
||||
removeSprite,
|
||||
replaceSprite,
|
||||
addSprite,
|
||||
addSpriteWithResize,
|
||||
processImageFiles,
|
||||
alignSprites,
|
||||
addLayer,
|
||||
removeLayer,
|
||||
moveLayer,
|
||||
};
|
||||
};
|
||||
|
||||
export const getMaxDimensionsAcrossLayers = (layers: Layer[]) => {
|
||||
const sprites = layers.flatMap(l => l.visible ? l.sprites : []);
|
||||
return getMaxDimensionsSingle(sprites);
|
||||
};
|
||||
Reference in New Issue
Block a user