[FEAT] Add pixel editor
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Canvas Grid Settings (Editor only) -->
|
||||
<section v-if="activeTab === 'canvas'">
|
||||
<section v-if="activeTab === 'canvas' || activeTab === 'preview'">
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Grid Layout</h3>
|
||||
<div class="card p-3 bg-gray-50/50 dark:bg-gray-800/40 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -112,7 +112,7 @@
|
||||
</section>
|
||||
|
||||
<!-- View Options (Editor only) -->
|
||||
<section v-if="activeTab === 'canvas'">
|
||||
<section v-if="activeTab === 'canvas' || activeTab === 'preview'">
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">View Options</h3>
|
||||
<div class="card p-2 bg-gray-50/50 dark:bg-gray-800/40 grid grid-cols-2 gap-2">
|
||||
<Tooltip text="Disable anti-aliasing for crisp pixel art rendering">
|
||||
@@ -173,7 +173,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Tools (Editor only) -->
|
||||
<section v-if="activeTab === 'canvas'">
|
||||
<section v-if="activeTab === 'canvas' || activeTab === 'preview'">
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Tools</h3>
|
||||
<div class="card p-2 bg-gray-50/50 dark:bg-gray-800/40 space-y-2">
|
||||
<div class="flex gap-2">
|
||||
@@ -243,6 +243,13 @@
|
||||
>
|
||||
<i class="fas fa-play mr-2"></i>Preview
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'draw'"
|
||||
:class="activeTab === 'draw' ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'"
|
||||
class="px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-200"
|
||||
>
|
||||
<i class="fas fa-paint-brush mr-2"></i>Draw
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Background Color (Compact) -->
|
||||
@@ -303,9 +310,10 @@
|
||||
@rotate-sprite="rotateSprite"
|
||||
@flip-sprite="flipSprite"
|
||||
@copy-sprite-to-frame="copySpriteToFrame"
|
||||
@open-pixel-editor="openPixelEditor"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'preview'" class="h-full flex items-center justify-center">
|
||||
<div v-else-if="activeTab === 'preview'" class="h-full flex items-center justify-center">
|
||||
<sprite-preview
|
||||
:layers="layers"
|
||||
:active-layer-id="activeLayerId"
|
||||
@@ -317,6 +325,15 @@
|
||||
@flip-sprite="flipSprite"
|
||||
@copy-sprite-to-frame="copySpriteToFrame"
|
||||
@replace-sprite="replaceSprite"
|
||||
@open-pixel-editor="openPixelEditor"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'draw'" class="h-full">
|
||||
<draw-tab
|
||||
:layers="layers"
|
||||
:initial-frame="pixelEditorFrame"
|
||||
@save-frame="handleSaveFrame"
|
||||
@close="pixelEditorFrame = null"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -340,6 +357,7 @@
|
||||
import FileUploader from '@/components/FileUploader.vue';
|
||||
import SpriteCanvas from '@/components/SpriteCanvas.vue';
|
||||
import SpritePreview from '@/components/SpritePreview.vue';
|
||||
import DrawTab from '@/components/DrawTab.vue';
|
||||
import SpritesheetSplitter from '@/components/SpritesheetSplitter.vue';
|
||||
import GifFpsModal from '@/components/GifFpsModal.vue';
|
||||
import ShareModal from '@/components/ShareModal.vue';
|
||||
@@ -442,7 +460,8 @@
|
||||
};
|
||||
|
||||
const cellSize = computed(getCellSize);
|
||||
const activeTab = ref<'canvas' | 'preview'>('canvas');
|
||||
const activeTab = ref<'canvas' | 'preview' | 'draw'>('canvas');
|
||||
const pixelEditorFrame = ref<{ layerId: string; frameIndex: number } | null>(null);
|
||||
|
||||
const isSpritesheetSplitterOpen = ref(false);
|
||||
const isGifFpsModalOpen = ref(false);
|
||||
@@ -631,6 +650,46 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveFrame = (layerId: string, frameIndex: number, file: File) => {
|
||||
const layer = layers.value.find(l => l.id === layerId);
|
||||
if (!layer) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const url = e.target?.result as string;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const oldSprite = layer.sprites[frameIndex];
|
||||
const sprite = {
|
||||
id: oldSprite?.id || crypto.randomUUID(),
|
||||
file,
|
||||
img,
|
||||
url,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: oldSprite?.x || 0,
|
||||
y: oldSprite?.y || 0,
|
||||
rotation: oldSprite?.rotation || 0,
|
||||
flipX: oldSprite?.flipX || false,
|
||||
flipY: oldSprite?.flipY || false,
|
||||
};
|
||||
|
||||
if (frameIndex < layer.sprites.length) {
|
||||
layer.sprites[frameIndex] = sprite;
|
||||
}
|
||||
};
|
||||
img.src = url;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
pixelEditorFrame.value = null;
|
||||
};
|
||||
|
||||
const openPixelEditor = (layerId: string, frameIndex: number) => {
|
||||
pixelEditorFrame.value = { layerId, frameIndex };
|
||||
activeTab.value = 'draw';
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const id = route.params.id as string;
|
||||
if (id) {
|
||||
|
||||
Reference in New Issue
Block a user