mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
Add right-click context menu on slot dots with 'Connect to...' submenu listing compatible existing nodes. Uses Vue/PrimeVue ContextMenu pattern matching NodeContextMenu.vue. Finds compatible nodes via LiteGraph.isValidConnection, filters wildcards/bypassed/connected inputs, sorts by Y position, caps at 15 results. Add auto-panning when dragging links near canvas edges. Integrated into useSlotLinkInteraction via useAutoPan composable. Velocity-based rAF panning that recomputes canvas coordinates after each offset change. Amp-Thread-ID: https://ampcode.com/threads/T-019c3056-108e-7248-9c30-7d236197edcc
117 lines
2.7 KiB
TypeScript
117 lines
2.7 KiB
TypeScript
import { app } from '@/scripts/app'
|
|
|
|
const EDGE_PX = 48
|
|
const MAX_SPEED = 900
|
|
|
|
interface AutoPanState {
|
|
active: boolean
|
|
rafId: number | null
|
|
lastTime: number
|
|
velocityX: number
|
|
velocityY: number
|
|
lastClientX: number
|
|
lastClientY: number
|
|
}
|
|
|
|
interface AutoPanControls {
|
|
updatePointer: (clientX: number, clientY: number) => void
|
|
stop: () => void
|
|
}
|
|
|
|
export function useAutoPan(
|
|
onPan: (dxCanvas: number, dyCanvas: number) => void
|
|
): AutoPanControls {
|
|
const state: AutoPanState = {
|
|
active: false,
|
|
rafId: null,
|
|
lastTime: 0,
|
|
velocityX: 0,
|
|
velocityY: 0,
|
|
lastClientX: 0,
|
|
lastClientY: 0
|
|
}
|
|
|
|
function computeVelocity(clientX: number, clientY: number): [number, number] {
|
|
const canvas = app.canvas?.canvas
|
|
if (!canvas) return [0, 0]
|
|
|
|
const rect = canvas.getBoundingClientRect()
|
|
let vx = 0
|
|
let vy = 0
|
|
|
|
const distLeft = clientX - rect.left
|
|
const distRight = rect.right - clientX
|
|
const distTop = clientY - rect.top
|
|
const distBottom = rect.bottom - clientY
|
|
|
|
if (distLeft < EDGE_PX) vx = ((EDGE_PX - distLeft) / EDGE_PX) * MAX_SPEED
|
|
else if (distRight < EDGE_PX)
|
|
vx = -(((EDGE_PX - distRight) / EDGE_PX) * MAX_SPEED)
|
|
|
|
if (distTop < EDGE_PX) vy = ((EDGE_PX - distTop) / EDGE_PX) * MAX_SPEED
|
|
else if (distBottom < EDGE_PX)
|
|
vy = -(((EDGE_PX - distBottom) / EDGE_PX) * MAX_SPEED)
|
|
|
|
return [vx, vy]
|
|
}
|
|
|
|
function tick(timestamp: number): void {
|
|
if (!state.active) return
|
|
|
|
const [vx, vy] = computeVelocity(state.lastClientX, state.lastClientY)
|
|
state.velocityX = vx
|
|
state.velocityY = vy
|
|
|
|
if (vx === 0 && vy === 0) {
|
|
state.rafId = requestAnimationFrame(tick)
|
|
return
|
|
}
|
|
|
|
const ds = app.canvas?.ds
|
|
if (!ds) {
|
|
stop()
|
|
return
|
|
}
|
|
|
|
const dt = Math.min((timestamp - state.lastTime) / 1000, 0.1)
|
|
state.lastTime = timestamp
|
|
|
|
const dxCanvas = (vx * dt) / ds.scale
|
|
const dyCanvas = (vy * dt) / ds.scale
|
|
|
|
ds.offset[0] += dxCanvas
|
|
ds.offset[1] += dyCanvas
|
|
app.canvas?.setDirty(true, true)
|
|
|
|
onPan(dxCanvas, dyCanvas)
|
|
|
|
state.rafId = requestAnimationFrame(tick)
|
|
}
|
|
|
|
function updatePointer(clientX: number, clientY: number): void {
|
|
state.lastClientX = clientX
|
|
state.lastClientY = clientY
|
|
|
|
if (!state.active) {
|
|
const [vx, vy] = computeVelocity(clientX, clientY)
|
|
if (vx !== 0 || vy !== 0) {
|
|
state.active = true
|
|
state.lastTime = performance.now()
|
|
state.rafId = requestAnimationFrame(tick)
|
|
}
|
|
}
|
|
}
|
|
|
|
function stop(): void {
|
|
state.active = false
|
|
if (state.rafId !== null) {
|
|
cancelAnimationFrame(state.rafId)
|
|
state.rafId = null
|
|
}
|
|
state.velocityX = 0
|
|
state.velocityY = 0
|
|
}
|
|
|
|
return { updatePointer, stop }
|
|
}
|