Spaces:
Running
Running
| import { computed, onMounted, onUnmounted, reactive, type Ref } from "vue"; | |
| import { clamp, round } from "../lib/onnxHelpers"; | |
| export function useCompareStage( | |
| compareStageRef: Ref<HTMLElement | null>, | |
| hasBaseImage: Ref<boolean>, | |
| hasOverlayImage: Ref<boolean>, | |
| ) { | |
| const state = reactive({ | |
| splitPercent: 50, | |
| zoom: 1, | |
| originX: 50, | |
| originY: 50, | |
| dragging: false, | |
| panning: false, | |
| panStartX: 0, | |
| panStartY: 0, | |
| panOriginStartX: 50, | |
| panOriginStartY: 50, | |
| }); | |
| const adjustedSplitPercent = computed(() => { | |
| const splitRatio = state.splitPercent / 100; | |
| const originRatio = state.originX / 100; | |
| const zoom = Math.max(1, state.zoom); | |
| const adjustedRatio = (splitRatio - (1 - zoom) * originRatio) / zoom; | |
| return round(clamp(adjustedRatio, 0, 1) * 100) ?? 50; | |
| }); | |
| const styleVars = computed(() => ({ | |
| "--split": `${state.splitPercent}%`, | |
| "--split-adjusted": `${adjustedSplitPercent.value}%`, | |
| "--zoom": String(state.zoom), | |
| "--origin-x": `${state.originX}%`, | |
| "--origin-y": `${state.originY}%`, | |
| })); | |
| function startCompareDrag(event: PointerEvent) { | |
| if (!hasBaseImage.value || !compareStageRef.value) { | |
| return; | |
| } | |
| if (event.button === 2 && state.zoom > 1) { | |
| event.preventDefault(); | |
| state.panning = true; | |
| state.panStartX = event.clientX; | |
| state.panStartY = event.clientY; | |
| state.panOriginStartX = state.originX; | |
| state.panOriginStartY = state.originY; | |
| compareStageRef.value.setPointerCapture?.(event.pointerId); | |
| compareStageRef.value.style.cursor = "grabbing"; | |
| return; | |
| } | |
| if (event.button === 0 && hasOverlayImage.value) { | |
| state.dragging = true; | |
| compareStageRef.value.setPointerCapture?.(event.pointerId); | |
| updateSplitFromPointer(event); | |
| } | |
| } | |
| function updateSplitFromPointer(event: PointerEvent) { | |
| const rect = compareStageRef.value?.getBoundingClientRect(); | |
| if (!rect?.width) { | |
| return; | |
| } | |
| const ratio = (event.clientX - rect.left) / rect.width; | |
| state.splitPercent = clamp(ratio * 100, 0, 100); | |
| } | |
| function updatePanFromPointer(event: PointerEvent) { | |
| const rect = compareStageRef.value?.getBoundingClientRect(); | |
| if (!rect?.width || !rect.height) { | |
| return; | |
| } | |
| const deltaX = event.clientX - state.panStartX; | |
| const deltaY = event.clientY - state.panStartY; | |
| const zoom = state.zoom; | |
| const deltaXPercent = ((deltaX / rect.width) / zoom) * 100; | |
| const deltaYPercent = ((deltaY / rect.height) / zoom) * 100; | |
| state.originX = clamp(state.panOriginStartX - deltaXPercent, 0, 100); | |
| state.originY = clamp(state.panOriginStartY - deltaYPercent, 0, 100); | |
| } | |
| function handlePointerMove(event: PointerEvent) { | |
| if (state.panning) { | |
| updatePanFromPointer(event); | |
| return; | |
| } | |
| if (state.dragging) { | |
| updateSplitFromPointer(event); | |
| } | |
| } | |
| function stopCompareDrag() { | |
| if (state.panning) { | |
| state.panning = false; | |
| if (compareStageRef.value) { | |
| compareStageRef.value.style.cursor = ""; | |
| } | |
| return; | |
| } | |
| state.dragging = false; | |
| } | |
| function preventContextMenu(event: MouseEvent) { | |
| if (state.zoom > 1) { | |
| event.preventDefault(); | |
| } | |
| } | |
| function handleWheel(event: WheelEvent) { | |
| if (!hasBaseImage.value) { | |
| return; | |
| } | |
| const rect = compareStageRef.value?.getBoundingClientRect(); | |
| if (!rect?.width || !rect.height) { | |
| return; | |
| } | |
| event.preventDefault(); | |
| const mouseX = (event.clientX - rect.left) / rect.width; | |
| const mouseY = (event.clientY - rect.top) / rect.height; | |
| const currentZoom = state.zoom; | |
| const originXN = state.originX / 100; | |
| const originYN = state.originY / 100; | |
| const contentX = (mouseX - originXN * (1 - currentZoom)) / currentZoom; | |
| const contentY = (mouseY - originYN * (1 - currentZoom)) / currentZoom; | |
| const multiplier = event.deltaY < 0 ? 1.12 : 0.9; | |
| const newZoom = clamp((round(currentZoom * multiplier) ?? currentZoom), 1, 6); | |
| let newOriginXN = originXN; | |
| let newOriginYN = originYN; | |
| if (newZoom !== 1) { | |
| newOriginXN = (mouseX - contentX * newZoom) / (1 - newZoom); | |
| newOriginYN = (mouseY - contentY * newZoom) / (1 - newZoom); | |
| } | |
| state.originX = clamp(newOriginXN * 100, 0, 100); | |
| state.originY = clamp(newOriginYN * 100, 0, 100); | |
| state.zoom = newZoom; | |
| } | |
| onMounted(() => { | |
| window.addEventListener("pointermove", handlePointerMove); | |
| window.addEventListener("pointerup", stopCompareDrag); | |
| }); | |
| onUnmounted(() => { | |
| window.removeEventListener("pointermove", handlePointerMove); | |
| window.removeEventListener("pointerup", stopCompareDrag); | |
| }); | |
| return { | |
| styleVars, | |
| zoomScale: computed(() => state.zoom), | |
| zoomLabel: computed(() => `${Math.round(state.zoom * 100)}%`), | |
| startCompareDrag, | |
| preventContextMenu, | |
| handleWheel, | |
| }; | |
| } | |