selection rectangle for vueNodes (#7088)

## Summary

fix: render selection rectangle in DOM layer for Vue nodes mode.

When Vue nodes are enabled, the canvas selection rectangle was being
rendered behind Vue node elements due to DOM stacking order (canvas
layer is below the TransformPane layer).

## Changes
- Adds a new SelectionRectangle.vue component that renders the selection
box as a DOM element
- Places it above the Vue nodes layer so it's always visible during drag
selection
- Skips canvas-based selection rectangle rendering when Vue nodes mode
is active
- Bonus: adds a semi-transparent blue fill style for better visibility


## Screenshots
before

https://github.com/user-attachments/assets/a8ee2ca3-00fd-4fdc-925a-dc9f846f4280

after

https://github.com/user-attachments/assets/66b7f2f5-f0a0-486f-9556-3872d07d65be

One more thing, the following improvement will be live selection,
something like:


https://github.com/user-attachments/assets/05a2b7ea-89b1-4568-bd2a-792f4fc11d8e

but I don't want to increase this PR, so I will send live selection
after this selection rectangle

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7088-selection-rectangle-for-vueNodes-2bd6d73d3650817aa2e9cf4526f179d8)
by [Unito](https://www.unito.io)
This commit is contained in:
Terry Jia
2025-12-03 08:26:57 -05:00
committed by GitHub
parent 19d98a09ea
commit e9d5ce7f3f
3 changed files with 69 additions and 1 deletions

View File

@@ -77,6 +77,9 @@
/>
</TransformPane>
<!-- Selection rectangle overlay for Vue nodes mode -->
<SelectionRectangle v-if="shouldRenderVueNodes && comfyAppReady" />
<NodeTooltip v-if="tooltipEnabled" />
<NodeSearchboxPopover ref="nodeSearchboxPopoverRef" />
@@ -159,6 +162,7 @@ import { useWorkspaceStore } from '@/stores/workspaceStore'
import { isNativeWindow } from '@/utils/envUtil'
import TryVueNodeBanner from '../topbar/TryVueNodeBanner.vue'
import SelectionRectangle from './SelectionRectangle.vue'
const emit = defineEmits<{
ready: []

View File

@@ -0,0 +1,63 @@
<template>
<div
v-if="isVisible"
class="pointer-events-none absolute border border-blue-400 bg-blue-500/20"
:style="rectangleStyle"
/>
</template>
<script setup lang="ts">
import { useRafFn } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
const canvasStore = useCanvasStore()
const selectionRect = ref<{
x: number
y: number
w: number
h: number
} | null>(null)
useRafFn(() => {
const canvas = canvasStore.canvas
if (!canvas) {
selectionRect.value = null
return
}
const { pointer, dragging_rectangle } = canvas
if (dragging_rectangle && pointer.eDown && pointer.eMove) {
const x = pointer.eDown.safeOffsetX
const y = pointer.eDown.safeOffsetY
const w = pointer.eMove.safeOffsetX - x
const h = pointer.eMove.safeOffsetY - y
selectionRect.value = { x, y, w, h }
} else {
selectionRect.value = null
}
})
const isVisible = computed(() => selectionRect.value !== null)
const rectangleStyle = computed(() => {
const rect = selectionRect.value
if (!rect) return {}
const left = rect.w >= 0 ? rect.x : rect.x + rect.w
const top = rect.h >= 0 ? rect.y : rect.y + rect.h
const width = Math.abs(rect.w)
const height = Math.abs(rect.h)
return {
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`
}
})
</script>

View File

@@ -4799,7 +4799,8 @@ export class LGraphCanvas
}
// Area-selection rectangle
if (this.dragging_rectangle) {
// In Vue nodes mode, selection rectangle is rendered in DOM layer
if (this.dragging_rectangle && !LiteGraph.vueNodesMode) {
const { eDown, eMove } = this.pointer
ctx.strokeStyle = '#FFF'