diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png index f0ca5e712d..603c598add 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png index f5c4af9bcd..7cc712f74b 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png index 11356a8da9..895e429a19 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png index dfccbf6414..a37ea3f8ce 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png index dfccbf6414..a37ea3f8ce 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png index dfccbf6414..a37ea3f8ce 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png index dfccbf6414..a37ea3f8ce 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png index d67b73fc3c..4512bc9da9 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png index d6976c27e0..821129dcbc 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png differ diff --git a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png index c1fb924d40..32edf383c5 100644 Binary files a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png differ diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 309bf93421..4ad2ed7708 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -38,9 +38,7 @@ canvasStore.canvas to be initialized. --> @@ -55,7 +53,6 @@ import DomWidgets from '@/components/graph/DomWidgets.vue' import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue' import MiniMap from '@/components/graph/MiniMap.vue' import NodeTooltip from '@/components/graph/NodeTooltip.vue' -import SelectionOverlay from '@/components/graph/SelectionOverlay.vue' import SelectionToolbox from '@/components/graph/SelectionToolbox.vue' import TitleEditor from '@/components/graph/TitleEditor.vue' import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue' diff --git a/src/components/graph/SelectionOverlay.vue b/src/components/graph/SelectionOverlay.vue deleted file mode 100644 index 3bc41574a4..0000000000 --- a/src/components/graph/SelectionOverlay.vue +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue index bbbeadef94..2caca6bcf1 100644 --- a/src/components/graph/SelectionToolbox.vue +++ b/src/components/graph/SelectionToolbox.vue @@ -1,34 +1,45 @@ diff --git a/src/composables/canvas/useSelectionToolboxPosition.ts b/src/composables/canvas/useSelectionToolboxPosition.ts new file mode 100644 index 0000000000..8e327b05f3 --- /dev/null +++ b/src/composables/canvas/useSelectionToolboxPosition.ts @@ -0,0 +1,113 @@ +import { ref, watch } from 'vue' +import type { Ref } from 'vue' + +import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync' +import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' +import { createBounds } from '@/lib/litegraph/src/litegraph' +import { useCanvasStore } from '@/stores/graphStore' + +/** + * Manages the position of the selection toolbox independently. + * Uses CSS custom properties for performant transform updates. + */ +export function useSelectionToolboxPosition( + toolboxRef: Ref +) { + 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 + const bounds = createBounds(selectableItems) + + if (!bounds) { + return + } + + const [xBase, y, width] = bounds + + worldPosition.value = { + x: xBase + width / 2, + y: 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 + } +} diff --git a/src/extensions/core/index.ts b/src/extensions/core/index.ts index 0d39c207ad..5354ef4e91 100644 --- a/src/extensions/core/index.ts +++ b/src/extensions/core/index.ts @@ -14,6 +14,7 @@ import './previewAny' import './rerouteNode' import './saveImageExtraOutput' import './saveMesh' +import './selectionBorder' import './simpleTouchSupport' import './slotDefaults' import './uploadAudio' diff --git a/src/extensions/core/selectionBorder.ts b/src/extensions/core/selectionBorder.ts new file mode 100644 index 0000000000..c4afafad11 --- /dev/null +++ b/src/extensions/core/selectionBorder.ts @@ -0,0 +1,70 @@ +import { type LGraphCanvas, createBounds } from '@/lib/litegraph/src/litegraph' +import { app } from '@/scripts/app' + +/** + * Draws a dashed border around selected items that maintains constant pixel size + * regardless of zoom level, similar to the DOM selection overlay. + */ +function drawSelectionBorder( + ctx: CanvasRenderingContext2D, + canvas: LGraphCanvas +) { + const selectedItems = canvas.selectedItems + + // Only draw if multiple items selected + if (selectedItems.size <= 1) return + + // Use the same bounds calculation as the toolbox + const bounds = createBounds(selectedItems, 10) + if (!bounds) return + + const [x, y, width, height] = bounds + + // Save context state + ctx.save() + + // Set up dashed line style that doesn't scale with zoom + const borderWidth = 2 / canvas.ds.scale // Constant 2px regardless of zoom + ctx.lineWidth = borderWidth + ctx.strokeStyle = + getComputedStyle(document.documentElement) + .getPropertyValue('--border-color') + .trim() || '#ffffff66' + + // Create dash pattern that maintains visual size + const dashSize = 5 / canvas.ds.scale + ctx.setLineDash([dashSize, dashSize]) + + // Draw the border using the bounds directly + ctx.beginPath() + ctx.roundRect(x, y, width, height, 8 / canvas.ds.scale) + ctx.stroke() + + // Restore context + ctx.restore() +} + +/** + * Extension that adds a dashed selection border for multiple selected nodes + */ +const ext = { + name: 'Comfy.SelectionBorder', + + async init() { + // Hook into the canvas drawing + const originalDrawForeground = app.canvas.onDrawForeground + + app.canvas.onDrawForeground = function ( + ctx: CanvasRenderingContext2D, + visibleArea: any + ) { + // Call original if it exists + originalDrawForeground?.call(this, ctx, visibleArea) + + // Draw our selection border + drawSelectionBorder(ctx, app.canvas) + } + } +} + +app.registerExtension(ext) diff --git a/src/types/selectionOverlayTypes.ts b/src/types/selectionOverlayTypes.ts deleted file mode 100644 index ee370e2748..0000000000 --- a/src/types/selectionOverlayTypes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { InjectionKey, Ref } from 'vue' - -export interface SelectionOverlayState { - visible: Readonly> - updateCount: Readonly> -} - -export const SelectionOverlayInjectionKey: InjectionKey = - Symbol('selectionOverlayState')