From 5dd9d1e7afe8a3dcbaaf6ba209cecfc273f9ddc2 Mon Sep 17 00:00:00 2001 From: Dennis Postma <dennis@directonline.io> Date: Fri, 31 Jan 2025 02:16:19 +0100 Subject: [PATCH] Added temp. offset logic for easier sprite management --- .../partials/sprite/SpriteDetails.vue | 27 +++++++++++++-- .../sprite/partials/SpriteImagesInput.vue | 33 +++++++++++++++++-- .../sprite/partials/SpritePreview.vue | 15 +++++++-- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue b/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue index 1b25bf4..705af93 100644 --- a/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue +++ b/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue @@ -48,12 +48,24 @@ <input v-model.number="action.frameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" /> </div> <div class="form-field-full"> - <SpriteActionsInput v-model="action.sprites" /> + <SpriteActionsInput + v-model="action.sprites" + @tempOffsetChange="(index, offset) => handleTempOffsetChange(action, index, offset)" + /> </div> </form> </template> </Accordion> - <SpritePreview v-if="selectedAction" :sprites="selectedAction.sprites" :frame-rate="selectedAction.frameRate" :is-modal-open="isModalOpen" @update:frame-rate="updateFrameRate" @update:is-modal-open="isModalOpen = $event" /> + <SpritePreview + v-if="selectedAction" + :sprites="selectedAction.sprites" + :frame-rate="selectedAction.frameRate" + :is-modal-open="isModalOpen" + :temp-offset-index="tempOffsetData.index" + :temp-offset="tempOffsetData.offset" + @update:frame-rate="updateFrameRate" + @update:is-modal-open="isModalOpen = $event" + /> </div> </div> </template> @@ -187,6 +199,17 @@ function updateFrameRate(value: number) { } } +const tempOffsetData = ref<{ index: number | undefined; offset: { x: number; y: number } | undefined }>({ + index: undefined, + offset: undefined +}) + +function handleTempOffsetChange(action: SpriteAction, index: number, offset: { x: number; y: number }) { + if (selectedAction.value === action) { + tempOffsetData.value = { index, offset } + } +} + watch(selectedSprite, (sprite: Sprite | null) => { if (!sprite) return spriteName.value = sprite.name diff --git a/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue b/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue index 6e8d7bb..96b6061 100644 --- a/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue +++ b/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue @@ -1,7 +1,10 @@ <template> <div class="flex flex-wrap gap-3"> <div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-300 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)"> - <img :src="image.url" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" /> + <img :src="image.url" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" @load="updateImageDimensions($event, index)" /> + <div v-if="image.dimensions" class="absolute bottom-1 right-1 bg-black/50 text-white text-xs px-1 py-0.5 rounded transition-opacity font-default"> + {{ image.dimensions.width }}x{{ image.dimensions.height }} + </div> <div class="absolute top-1 left-1 flex-row space-y-1"> <button @click.stop="deleteImage(index)" class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> @@ -50,7 +53,7 @@ <script setup lang="ts"> import Modal from '@/components/utilities/Modal.vue' -import { ref } from 'vue' +import { ref, watch } from 'vue' interface SpriteImage { url: string @@ -58,6 +61,10 @@ interface SpriteImage { x: number y: number } + dimensions?: { + width: number + height: number + } } interface Props { @@ -71,6 +78,7 @@ const props = withDefaults(defineProps<Props>(), { const emit = defineEmits<{ (e: 'update:modelValue', value: SpriteImage[]): void (e: 'close'): void + (e: 'tempOffsetChange', index: number, offset: { x: number, y: number }): void }>() const fileInput = ref<HTMLInputElement | null>(null) @@ -162,4 +170,25 @@ const saveOffset = (index: number) => { updateImages(newImages) closeOffsetModal() } + +const onOffsetChange = () => { + if (selectedImageIndex.value !== null) { + emit('tempOffsetChange', selectedImageIndex.value, tempOffset.value) + } +} + +watch(tempOffset, onOffsetChange, { deep: true }) + +const updateImageDimensions = (event: Event, index: number) => { + const img = event.target as HTMLImageElement + const newImages = [...props.modelValue] + newImages[index] = { + ...newImages[index], + dimensions: { + width: img.naturalWidth, + height: img.naturalHeight + } + } + updateImages(newImages) +} </script> diff --git a/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue b/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue index caac77c..c44e6ea 100644 --- a/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue +++ b/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue @@ -16,7 +16,7 @@ }" > <img - v-for="(sprite, index) in sprites" + v-for="(sprite, index) in spritesWithTempOffset" :key="index" :src="sprite.url" alt="Sprite" @@ -62,12 +62,14 @@ <script setup lang="ts"> import type { SpriteImage } from '@/application/types' import Modal from '@/components/utilities/Modal.vue' -import { onMounted, onUnmounted, ref, watch } from 'vue' +import { computed, onMounted, onUnmounted, ref, watch } from 'vue' const props = defineProps<{ sprites: SpriteImage[] frameRate: number isModalOpen?: boolean + tempOffsetIndex?: number + tempOffset?: { x: number, y: number } }>() const emit = defineEmits<{ @@ -82,6 +84,15 @@ const localFrameRate = ref(props.frameRate) const zoomLevel = ref(100) let animationInterval: number | null = null +const spritesWithTempOffset = computed(() => { + return props.sprites.map((sprite, index) => { + if (index === props.tempOffsetIndex && props.tempOffset) { + return { ...sprite, offset: props.tempOffset } + } + return sprite + }) +}) + function updateContainerSize(event: Event) { const img = event.target as HTMLImageElement maxWidth.value = Math.max(maxWidth.value, img.naturalWidth)