diff --git a/src/renderer/core/layout/dom/canvasRectCache.ts b/src/renderer/core/layout/dom/canvasRectCache.ts new file mode 100644 index 000000000..7cbb5576d --- /dev/null +++ b/src/renderer/core/layout/dom/canvasRectCache.ts @@ -0,0 +1,65 @@ +/** + * Canvas Rect Cache (VueUse-based) + * + * Tracks the client-origin and size of the graph canvas container using + * useElementBounding, and exposes a small API to read the rect and + * subscribe to changes. + * + * We assume no document scrolling (body is overflow: hidden). Layout + * changes are driven by window resize and container/splitter changes. + */ +import { useElementBounding } from '@vueuse/core' +import { shallowRef, watch } from 'vue' + +type Rect = DOMRectReadOnly + +// Target container element (covers the canvas fully and shares its origin) +const containerRef = shallowRef(null) + +// Bind bounding measurement once; element may be resolved later +const { x, y, width, height } = useElementBounding(containerRef, { + // Track layout changes from resize; scrolling is disabled globally + windowResize: true, + windowScroll: false, + immediate: true +}) + +// Listener registry for external subscribers +const listeners = new Set<() => void>() + +function ensureContainer() { + if (!containerRef.value) { + containerRef.value = document.getElementById( + 'graph-canvas-container' + ) as HTMLElement | null + } +} + +// Notify subscribers when the bounding rect changes +watch([x, y, width, height], () => { + if (listeners.size) listeners.forEach((cb) => cb()) +}) + +export function invalidate(notify = false) { + if (notify && listeners.size) listeners.forEach((cb) => cb()) +} + +export function onCanvasRectChange(cb: () => void): () => void { + ensureContainer() + listeners.add(cb) + return () => listeners.delete(cb) +} + +export function getCanvasRect(): Rect { + ensureContainer() + const lx = x.value || 0 + const ly = y.value || 0 + const w = width.value || 0 + const h = height.value || 0 + return new DOMRect(lx, ly, w, h) +} + +export function getCanvasClientOrigin() { + ensureContainer() + return { left: x.value || 0, top: y.value || 0 } +} diff --git a/src/renderer/core/layout/slots/useDomSlotRegistration.ts b/src/renderer/core/layout/slots/useDomSlotRegistration.ts index 94a1f09e5..d81b7137f 100644 --- a/src/renderer/core/layout/slots/useDomSlotRegistration.ts +++ b/src/renderer/core/layout/slots/useDomSlotRegistration.ts @@ -21,6 +21,10 @@ import { } from 'vue' import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { + getCanvasClientOrigin, + onCanvasRectChange +} from '@/renderer/core/layout/dom/canvasRectCache' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import type { Point as LayoutPoint } from '@/renderer/core/layout/types' @@ -52,7 +56,7 @@ const cleanupFunctions = new WeakMap< Ref, { stopWatcher?: WatchStopHandle - handleResize?: () => void + unsubscribeRectChange?: () => void } >() @@ -90,6 +94,8 @@ export function useDomSlotRegistration(options: SlotRegistrationOptions) { if (!el || !transform?.screenToCanvas) return const rect = el.getBoundingClientRect() + // Normalize to canvas-relative screen coordinates (CSS pixels) + const { left: canvasLeft, top: canvasTop } = getCanvasClientOrigin() // Skip if bounds haven't changed significantly (within 0.5px) if (lastMeasuredBounds.value) { @@ -106,10 +112,10 @@ export function useDomSlotRegistration(options: SlotRegistrationOptions) { lastMeasuredBounds.value = rect - // Center of the visual connector (dot) in screen coords + // Center of the visual connector (dot) in canvas-relative screen coords const centerScreen = { - x: rect.left + rect.width / 2, - y: rect.top + rect.height / 2 + x: rect.left + rect.width / 2 - canvasLeft, + y: rect.top + rect.height / 2 - canvasTop } const centerCanvas = transform.screenToCanvas(centerScreen) @@ -192,12 +198,11 @@ export function useDomSlotRegistration(options: SlotRegistrationOptions) { const cleanup = cleanupFunctions.get(elRef) || {} cleanup.stopWatcher = stopWatcher - // Window resize - remeasure as viewport changed - const handleResize = () => { + // Subscribe to canvas rect changes (covers window resize and layout changes) + const unsubscribe = onCanvasRectChange(() => scheduleMeasurement(measureAndCacheOffset) - } - window.addEventListener('resize', handleResize, { passive: true }) - cleanup.handleResize = handleResize + ) + cleanup.unsubscribeRectChange = unsubscribe cleanupFunctions.set(elRef, cleanup) }) @@ -209,9 +214,7 @@ export function useDomSlotRegistration(options: SlotRegistrationOptions) { const cleanup = cleanupFunctions.get(elRef) if (cleanup) { if (cleanup.stopWatcher) cleanup.stopWatcher() - if (cleanup.handleResize) { - window.removeEventListener('resize', cleanup.handleResize) - } + if (cleanup.unsubscribeRectChange) cleanup.unsubscribeRectChange() cleanupFunctions.delete(elRef) }