mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 13:59:54 +00:00
Initial pan/zoom implementation
This commit is contained in:
44
src/components/ui/ZoomPane.vue
Normal file
44
src/components/ui/ZoomPane.vue
Normal 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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user