Files
ComfyUI_frontend/src/composables/canvas/useSelectionToolboxPosition.ts

129 lines
3.4 KiB
TypeScript

import { ref, watch } from 'vue'
import type { Ref } from 'vue'
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useCanvasStore } from '@/stores/graphStore'
import { computeUnionBounds } from '@/utils/mathUtil'
/**
* Manages the position of the selection toolbox independently.
* Uses CSS custom properties for performant transform updates.
*/
export function useSelectionToolboxPosition(
toolboxRef: Ref<HTMLElement | undefined>
) {
const canvasStore = useCanvasStore()
const lgCanvas = canvasStore.getCanvas()
const { getSelectableItems } = useSelectedLiteGraphItems()
// World position of selection center
const worldPosition = ref({ x: 0, y: 0 })
const visible = ref(false)
/**
* Update position based on selection
*/
const updateSelectionBounds = () => {
const selectableItems = getSelectableItems()
if (!selectableItems.size) {
visible.value = false
return
}
visible.value = true
// Get bounds from layout store for all selected items
const allBounds: ReadOnlyRect[] = []
for (const item of selectableItems) {
if (typeof item.id === 'string') {
const layout = layoutStore.getNodeLayoutRef(item.id).value
if (layout) {
allBounds.push([
layout.bounds.x,
layout.bounds.y,
layout.bounds.width,
layout.bounds.height
])
}
}
}
// Compute union bounds
const unionBounds = computeUnionBounds(allBounds)
if (!unionBounds) return
worldPosition.value = {
x: unionBounds.x + unionBounds.width / 2,
y: unionBounds.y
}
updateTransform()
}
const updateTransform = () => {
if (!visible.value) return
const { scale, offset } = lgCanvas.ds
const canvasRect = lgCanvas.canvas.getBoundingClientRect()
const screenX =
(worldPosition.value.x + offset[0]) * scale + canvasRect.left
const screenY = (worldPosition.value.y + offset[1]) * scale + canvasRect.top
// Update CSS custom properties directly for best performance
if (toolboxRef.value) {
toolboxRef.value.style.setProperty('--tb-x', `${screenX}px`)
toolboxRef.value.style.setProperty('--tb-y', `${screenY}px`)
}
}
// Sync with canvas transform
const { startSync, stopSync } = useCanvasTransformSync(updateTransform, {
autoStart: false
})
// Watch for selection changes
watch(
() => canvasStore.getCanvas().state.selectionChanged,
(changed) => {
if (changed) {
updateSelectionBounds()
canvasStore.getCanvas().state.selectionChanged = false
// Start transform sync if we have selection
if (visible.value) {
startSync()
} else {
stopSync()
}
}
},
{ immediate: true }
)
// Watch for dragging state
watch(
() => canvasStore.canvas?.state?.draggingItems,
(dragging) => {
if (dragging) {
// Hide during node dragging
visible.value = false
} else {
// Update after dragging ends
requestAnimationFrame(() => {
updateSelectionBounds()
})
}
}
)
return {
visible
}
}