Continuation of separting logic into domain specific composables
This commit is contained in:
@@ -157,10 +157,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, onUnmounted, computed } from 'vue';
|
||||
import { ref, onMounted, watch, onUnmounted } from 'vue';
|
||||
import { useSettingsStore } from '@/stores/useSettingsStore';
|
||||
import type { Sprite } from '@/types/sprites';
|
||||
import { getMaxDimensions } from '@/composables/useSprites';
|
||||
import { useCanvas2D } from '@/composables/useCanvas2D';
|
||||
import { useZoom } from '@/composables/useZoom';
|
||||
import { useAnimationFrames } from '@/composables/useAnimationFrames';
|
||||
|
||||
const props = defineProps<{
|
||||
sprites: Sprite[];
|
||||
@@ -172,17 +175,43 @@
|
||||
}>();
|
||||
|
||||
const previewCanvasRef = ref<HTMLCanvasElement | null>(null);
|
||||
const ctx = ref<CanvasRenderingContext2D | null>(null);
|
||||
|
||||
// Get settings from store
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Initialize composables
|
||||
const canvas2D = useCanvas2D(previewCanvasRef);
|
||||
|
||||
const { zoom, increase: increaseZoom, decrease: decreaseZoom } = useZoom({
|
||||
allowedValues: [0.5, 1, 2, 3, 4, 5],
|
||||
initial: 1,
|
||||
});
|
||||
|
||||
const {
|
||||
currentFrameIndex,
|
||||
isPlaying,
|
||||
fps,
|
||||
hiddenFrames,
|
||||
visibleFrames,
|
||||
visibleFramesCount,
|
||||
visibleFrameIndex,
|
||||
visibleFrameNumber,
|
||||
togglePlayback,
|
||||
nextFrame,
|
||||
previousFrame,
|
||||
handleSliderInput,
|
||||
toggleHiddenFrame,
|
||||
showAllFrames,
|
||||
hideAllFrames,
|
||||
stopAnimation,
|
||||
} = useAnimationFrames({
|
||||
sprites: props.sprites,
|
||||
onDraw: drawPreviewCanvas,
|
||||
});
|
||||
|
||||
// Preview state
|
||||
const currentFrameIndex = ref(0);
|
||||
const isPlaying = ref(false);
|
||||
const fps = ref(12);
|
||||
const zoom = ref(1);
|
||||
const isDraggable = ref(false);
|
||||
const showAllSprites = ref(false);
|
||||
const animationFrameId = ref<number | null>(null);
|
||||
const lastFrameTime = ref(0);
|
||||
|
||||
// Dragging state
|
||||
const isDragging = ref(false);
|
||||
@@ -191,128 +220,44 @@
|
||||
const dragStartY = ref(0);
|
||||
const spritePosBeforeDrag = ref({ x: 0, y: 0 });
|
||||
|
||||
// Add this after other refs
|
||||
const hiddenFrames = ref<number[]>([]);
|
||||
|
||||
// Get settings from store
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Add these computed properties
|
||||
const visibleFrames = computed(() => props.sprites.filter((_, index) => !hiddenFrames.value.includes(index)));
|
||||
const visibleFramesCount = computed(() => visibleFrames.value.length);
|
||||
const visibleFrameIndex = computed(() => {
|
||||
return visibleFrames.value.findIndex((_, idx) => idx === visibleFrames.value.findIndex(s => s === props.sprites[currentFrameIndex.value]));
|
||||
});
|
||||
const visibleFrameNumber = computed(() => visibleFrameIndex.value + 1);
|
||||
|
||||
// Canvas drawing
|
||||
|
||||
const drawPreviewCanvas = () => {
|
||||
if (!previewCanvasRef.value || !ctx.value || props.sprites.length === 0) return;
|
||||
function drawPreviewCanvas() {
|
||||
if (!previewCanvasRef.value || !canvas2D.ctx.value || props.sprites.length === 0) return;
|
||||
|
||||
const currentSprite = props.sprites[currentFrameIndex.value];
|
||||
if (!currentSprite) return;
|
||||
|
||||
const { maxWidth, maxHeight } = getMaxDimensions(props.sprites);
|
||||
|
||||
// Apply pixel art optimization consistently from store
|
||||
ctx.value.imageSmoothingEnabled = !settingsStore.pixelPerfect;
|
||||
// Apply pixel art optimization
|
||||
canvas2D.applySmoothing();
|
||||
|
||||
// Set canvas size to just fit one sprite cell
|
||||
previewCanvasRef.value.width = maxWidth;
|
||||
previewCanvasRef.value.height = maxHeight;
|
||||
canvas2D.setCanvasSize(maxWidth, maxHeight);
|
||||
|
||||
// Clear canvas
|
||||
ctx.value.clearRect(0, 0, previewCanvasRef.value.width, previewCanvasRef.value.height);
|
||||
canvas2D.clear();
|
||||
|
||||
// Draw grid background (cell)
|
||||
ctx.value.fillStyle = '#f9fafb';
|
||||
ctx.value.fillRect(0, 0, maxWidth, maxHeight);
|
||||
|
||||
// Keep pixel art optimization consistent throughout all drawing operations
|
||||
ctx.value.imageSmoothingEnabled = !settingsStore.pixelPerfect;
|
||||
canvas2D.fillRect(0, 0, maxWidth, maxHeight, '#f9fafb');
|
||||
|
||||
// Draw all sprites with transparency if enabled
|
||||
if (showAllSprites.value && props.sprites.length > 1) {
|
||||
ctx.value.globalAlpha = 0.3;
|
||||
props.sprites.forEach((sprite, index) => {
|
||||
if (index !== currentFrameIndex.value && !hiddenFrames.value.includes(index)) {
|
||||
// Use Math.floor for pixel-perfect positioning
|
||||
ctx.value?.drawImage(sprite.img, Math.floor(sprite.x), Math.floor(sprite.y));
|
||||
canvas2D.drawImage(sprite.img, sprite.x, sprite.y, 0.3);
|
||||
}
|
||||
});
|
||||
ctx.value.globalAlpha = 1.0;
|
||||
}
|
||||
|
||||
// Draw current sprite with integer positions for pixel-perfect rendering
|
||||
ctx.value.drawImage(currentSprite.img, Math.floor(currentSprite.x), Math.floor(currentSprite.y));
|
||||
// Draw current sprite
|
||||
canvas2D.drawImage(currentSprite.img, currentSprite.x, currentSprite.y);
|
||||
|
||||
// Draw cell border
|
||||
ctx.value.strokeStyle = '#e5e7eb';
|
||||
ctx.value.lineWidth = 1;
|
||||
ctx.value.strokeRect(0, 0, maxWidth, maxHeight);
|
||||
};
|
||||
canvas2D.strokeRect(0, 0, maxWidth, maxHeight, '#e5e7eb', 1);
|
||||
}
|
||||
|
||||
// Animation control
|
||||
const togglePlayback = () => {
|
||||
isPlaying.value = !isPlaying.value;
|
||||
|
||||
if (isPlaying.value) {
|
||||
startAnimation();
|
||||
} else {
|
||||
stopAnimation();
|
||||
}
|
||||
};
|
||||
|
||||
const startAnimation = () => {
|
||||
lastFrameTime.value = performance.now();
|
||||
animateFrame();
|
||||
};
|
||||
|
||||
const stopAnimation = () => {
|
||||
if (animationFrameId.value !== null) {
|
||||
cancelAnimationFrame(animationFrameId.value);
|
||||
animationFrameId.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const animateFrame = () => {
|
||||
const now = performance.now();
|
||||
const elapsed = now - lastFrameTime.value;
|
||||
const frameTime = 1000 / fps.value;
|
||||
|
||||
if (elapsed >= frameTime) {
|
||||
lastFrameTime.value = now - (elapsed % frameTime);
|
||||
nextFrame();
|
||||
}
|
||||
|
||||
animationFrameId.value = requestAnimationFrame(animateFrame);
|
||||
};
|
||||
|
||||
const nextFrame = () => {
|
||||
if (visibleFrames.value.length === 0) return;
|
||||
|
||||
const currentVisibleIndex = visibleFrameIndex.value;
|
||||
const nextVisibleIndex = (currentVisibleIndex + 1) % visibleFrames.value.length;
|
||||
currentFrameIndex.value = props.sprites.indexOf(visibleFrames.value[nextVisibleIndex]);
|
||||
drawPreviewCanvas();
|
||||
};
|
||||
|
||||
const previousFrame = () => {
|
||||
if (visibleFrames.value.length === 0) return;
|
||||
|
||||
const currentVisibleIndex = visibleFrameIndex.value;
|
||||
const prevVisibleIndex = (currentVisibleIndex - 1 + visibleFrames.value.length) % visibleFrames.value.length;
|
||||
currentFrameIndex.value = props.sprites.indexOf(visibleFrames.value[prevVisibleIndex]);
|
||||
drawPreviewCanvas();
|
||||
};
|
||||
|
||||
// Add this method to handle slider input
|
||||
const handleSliderInput = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const index = parseInt(target.value);
|
||||
currentFrameIndex.value = props.sprites.indexOf(visibleFrames.value[index]);
|
||||
};
|
||||
|
||||
// Drag functionality
|
||||
const startDrag = (event: MouseEvent) => {
|
||||
@@ -372,22 +317,6 @@
|
||||
activeSpriteId.value = null;
|
||||
};
|
||||
|
||||
// Add helper methods for mobile zoom controls
|
||||
const increaseZoom = () => {
|
||||
const zoomValues = [0.5, 1, 2, 3, 4];
|
||||
const currentIndex = zoomValues.indexOf(Number(zoom.value));
|
||||
if (currentIndex < zoomValues.length - 1) {
|
||||
zoom.value = zoomValues[currentIndex + 1];
|
||||
}
|
||||
};
|
||||
|
||||
const decreaseZoom = () => {
|
||||
const zoomValues = [0.5, 1, 2, 3, 4];
|
||||
const currentIndex = zoomValues.indexOf(Number(zoom.value));
|
||||
if (currentIndex > 0) {
|
||||
zoom.value = zoomValues[currentIndex - 1];
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchStart = (event: TouchEvent) => {
|
||||
if (!isDraggable.value) return;
|
||||
@@ -421,10 +350,8 @@
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
if (previewCanvasRef.value) {
|
||||
ctx.value = previewCanvasRef.value.getContext('2d');
|
||||
drawPreviewCanvas();
|
||||
}
|
||||
canvas2D.initContext();
|
||||
drawPreviewCanvas();
|
||||
|
||||
// Listen for forceRedraw event from App.vue
|
||||
window.addEventListener('forceRedraw', handleForceRedraw);
|
||||
@@ -437,17 +364,9 @@
|
||||
|
||||
// Handler for force redraw event
|
||||
const handleForceRedraw = () => {
|
||||
// Ensure we're using integer positions for pixel-perfect rendering
|
||||
props.sprites.forEach(sprite => {
|
||||
sprite.x = Math.floor(sprite.x);
|
||||
sprite.y = Math.floor(sprite.y);
|
||||
});
|
||||
|
||||
// Force a redraw with the correct image smoothing settings
|
||||
if (ctx.value) {
|
||||
ctx.value.imageSmoothingEnabled = !settingsStore.pixelPerfect;
|
||||
drawPreviewCanvas();
|
||||
}
|
||||
canvas2D.ensureIntegerPositions(props.sprites);
|
||||
canvas2D.applySmoothing();
|
||||
drawPreviewCanvas();
|
||||
};
|
||||
|
||||
// Watchers
|
||||
@@ -464,41 +383,6 @@
|
||||
drawPreviewCanvas();
|
||||
}
|
||||
|
||||
const toggleHiddenFrame = (index: number) => {
|
||||
const currentIndex = hiddenFrames.value.indexOf(index);
|
||||
if (currentIndex === -1) {
|
||||
// Adding to hidden frames
|
||||
hiddenFrames.value.push(index);
|
||||
|
||||
// If we're hiding the current frame, switch to the next visible frame
|
||||
if (index === currentFrameIndex.value) {
|
||||
const nextVisibleSprite = props.sprites.findIndex((_, i) => i !== index && !hiddenFrames.value.includes(i));
|
||||
if (nextVisibleSprite !== -1) {
|
||||
currentFrameIndex.value = nextVisibleSprite;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Removing from hidden frames
|
||||
hiddenFrames.value.splice(currentIndex, 1);
|
||||
}
|
||||
|
||||
// Force a redraw
|
||||
drawPreviewCanvas();
|
||||
};
|
||||
|
||||
const showAllFrames = () => {
|
||||
hiddenFrames.value = [];
|
||||
drawPreviewCanvas();
|
||||
};
|
||||
|
||||
const hideAllFrames = () => {
|
||||
hiddenFrames.value = props.sprites.map((_, index) => index);
|
||||
// Keep at least one frame visible
|
||||
if (hiddenFrames.value.length > 0) {
|
||||
hiddenFrames.value.splice(currentFrameIndex.value, 1);
|
||||
}
|
||||
drawPreviewCanvas();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user