Initial pan/zoom implementation

This commit is contained in:
Austin Mroz
2025-12-23 17:52:18 -08:00
parent 1f17f5f1b5
commit a96e0dec0c
2 changed files with 56 additions and 6 deletions

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue'
const zoomPane = useTemplateRef('zoomPane')
const zoom = ref(1.0)
const panX = ref(0.0)
const panY = ref(0.0)
function handleWheel(e: WheelEvent) {
zoom.value -= e.deltaY
//TODO apply pan relative to mouse coords?
}
let dragging = false
function handleDown(e: PointerEvent) {
const zoomPaneEl = zoomPane.value
if (!zoomPaneEl) return
zoomPaneEl.setPointerCapture(e.pointerId)
dragging = true
}
function handleMove(e: PointerEvent) {
if (!dragging) return
panX.value += e.movementX
panY.value += e.movementY
}
const transform = computed(() => {
const scale = 1.1 ** (zoom.value / 30)
const matrix = [scale, 0, 0, scale, panX.value, panY.value]
return `matrix(${matrix.join(',')})`
})
</script>
<template>
<div
ref="zoomPane"
class="contain-content"
@wheel="handleWheel"
@pointerdown.prevent="handleDown"
@pointermove="handleMove"
@pointerup="dragging = false"
>
<slot :style="{ transform }" />
</div>
</template>

View File

@@ -14,6 +14,7 @@ import { computed, ref, shallowRef, useTemplateRef, watch } from 'vue'
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
import ZoomPane from '@/components/ui/ZoomPane.vue'
import Button from '@/components/ui/button/Button.vue'
import { safeWidgetMapper } from '@/composables/graph/useGraphNodeManager'
import { d, t } from '@/i18n'
@@ -267,6 +268,10 @@ function gotoPreviousOutput() {
}
function handleCenterWheel(e: WheelEvent) {
if (!e.ctrlKey && !e.metaKey) return
e.preventDefault()
e.stopPropagation()
//TODO roll in litegraph/CanvasPointer and give slight stickiness when on trackpad
if (e.deltaY > 0) gotoNextOutput()
else {
@@ -347,7 +352,7 @@ function handleCenterWheel(e: WheelEvent) {
<SplitterPanel
:size="98"
class="flex flex-col min-w-min gap-4 mx-12 my-8 relative"
@wheel="handleCenterWheel"
@wheel.capture="handleCenterWheel"
>
<div class="flex gap-4 text-muted-foreground h-14 w-full items-center">
<div
@@ -374,18 +379,19 @@ function handleCenterWheel(e: WheelEvent) {
<i class="icon-[lucide--ellipsis]" />
</Button>
</div>
<img
<ZoomPane
v-if="preview?.mediaType === 'images'"
class="object-contain flex-1 max-h-full"
:src="preview.url"
/>
v-slot="slotProps"
class="flex-1 w-full"
>
<img :src="preview.url" v-bind="slotProps" />
</ZoomPane>
<!--FIXME: core videos are type 'images', VHS still wrapped as 'gifs'-->
<video
v-else-if="preview?.mediaType === 'gifs'"
class="object-contain flex-1 min-h-0"
controls
:src="preview.url"
@wheel.prevent
/>
<img
v-else