From cd8c93b853a6e941a585846bcdd4256de2c439d5 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Tue, 16 May 2023 19:04:34 -0500 Subject: [PATCH] Two-way selection --- litegraph | 2 +- src/lib/ComfyGraph.ts | 8 +- src/lib/ComfyGraphCanvas.ts | 148 +++++++++++++------ src/lib/components/AccordionContainer.svelte | 8 +- src/lib/components/BlockContainer.svelte | 8 +- src/lib/components/ComfyApp.svelte | 5 +- src/lib/components/ComfyApp.ts | 1 - src/lib/components/ComfyProperties.svelte | 9 +- src/lib/components/ComfyUIPane.svelte | 32 ++-- src/lib/components/Container.svelte | 3 +- src/lib/components/TabsContainer.svelte | 8 +- src/lib/components/WidgetContainer.svelte | 27 ++-- src/lib/init.ts | 3 + src/lib/nodes/ComfyWidgetNodes.ts | 6 +- src/lib/stores/layoutState.ts | 41 ++--- src/lib/stores/selectionState.ts | 57 +++++++ src/lib/utils.ts | 30 ++-- src/scss/global.scss | 8 + 18 files changed, 278 insertions(+), 126 deletions(-) create mode 100644 src/lib/stores/selectionState.ts diff --git a/litegraph b/litegraph index b9762fa..5fe79f7 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit b9762fa6d56bf6b1f4f61361b6f9cd2e3972de1d +Subproject commit 5fe79f73971ed8869c680380307fae14e5ca5cae diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts index 3022664..6eb2561 100644 --- a/src/lib/ComfyGraph.ts +++ b/src/lib/ComfyGraph.ts @@ -88,7 +88,7 @@ export default class ComfyGraph extends LGraph { if (!("svelteComponentType" in node) && options.addedBy == null) { console.debug("[ComfyGraph] AutoAdd UI") const comfyNode = node as ComfyGraphNode; - const widgetNodesAdded = [] + const widgetNodesAdded: ComfyWidgetNode[] = [] for (let index = 0; index < comfyNode.inputs.length; index++) { const input = comfyNode.inputs[index]; if ("config" in input) { @@ -109,10 +109,10 @@ export default class ComfyGraph extends LGraph { } } } - const dragItems = widgetNodesAdded.map(wn => get(layoutState).allItemsByNode[wn.id]?.dragItem).filter(di => di) - console.debug("[ComfyGraph] Group new widgets", dragItems) + const dragItemIDs = widgetNodesAdded.map(wn => get(layoutState).allItemsByNode[wn.id]?.dragItem?.id).filter(Boolean) + console.debug("[ComfyGraph] Group new widgets", dragItemIDs) - layoutState.groupItems(dragItems, { title: node.title }) + layoutState.groupItems(dragItemIDs, { title: node.title }) } } diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index 3da6311..45fc6e3 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -1,11 +1,13 @@ -import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem, Subgraph, LLink } from "@litegraph-ts/core"; +import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem, Subgraph, LLink, type NodeID } from "@litegraph-ts/core"; import type ComfyApp from "./components/ComfyApp"; import queueState from "./stores/queueState"; -import { get } from "svelte/store"; +import { get, type Unsubscriber } from "svelte/store"; import uiState from "./stores/uiState"; import layoutState from "./stores/layoutState"; import { Watch } from "@litegraph-ts/nodes-basic"; import { ComfyReroute } from "./nodes"; +import type { Progress } from "./components/ComfyApp"; +import selectionState from "./stores/selectionState"; export type SerializedGraphCanvasState = { offset: Vector2, @@ -14,6 +16,7 @@ export type SerializedGraphCanvasState = { export default class ComfyGraphCanvas extends LGraphCanvas { app: ComfyApp | null; + private _unsubscribe: Unsubscriber; constructor( app: ComfyApp, @@ -27,8 +30,22 @@ export default class ComfyGraphCanvas extends LGraphCanvas { ) { super(canvas, app.lGraph, options); this.app = app; + this._unsubscribe = selectionState.subscribe(ss => { + for (const node of Object.values(this.selected_nodes)) { + node.is_selected = false; + } + this.selected_nodes = {} + for (const node of ss.currentSelectionNodes) { + this.selected_nodes[node.id] = node; + node.is_selected = true + } + this._selectedNodes = new Set() + this.setDirty(true, true); + }) } + _selectedNodes: Set = new Set(); + serialize(): SerializedGraphCanvasState { return { offset: this.ds.offset, @@ -58,51 +75,61 @@ export default class ComfyGraphCanvas extends LGraphCanvas { super.drawNodeShape(node, ctx, size, fgColor, bgColor, selected, mouseOver); let state = get(queueState); + let ss = get(selectionState); let color = null; - if (node.id === +state.runningNodeID) { + let thickness = 1; + // if (this._selectedNodes.has(node.id)) { + // color = "yellow"; + // thickness = 5; + // } + if (ss.currentHoveredNodes.has(node.id)) { + color = "lightblue"; + } + else if (node.id === +state.runningNodeID) { color = "#0f0"; - // this.app can be null inside the constructor if rendering is taking place already - } else if (this.app && this.app.dragOverNode && node.id === this.app.dragOverNode.id) { - color = "dodgerblue"; } if (color) { - const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE; - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == BuiltInSlotShape.BOX_SHAPE) - ctx.rect(-6, -6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT); - else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed)) - ctx.roundRect( - -6, - -6 - LiteGraph.NODE_TITLE_HEIGHT, - 12 + size[0] + 1, - 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, - this.round_radius * 2 - ); - else if (shape == BuiltInSlotShape.CARD_SHAPE) - ctx.roundRect( - -6, - -6 + LiteGraph.NODE_TITLE_HEIGHT, - 12 + size[0] + 1, - 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, - this.round_radius * 2, - 2 - ); - else if (shape == BuiltInSlotShape.CIRCLE_SHAPE) - ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); - ctx.strokeStyle = color; - ctx.stroke(); - ctx.strokeStyle = fgColor; - ctx.globalAlpha = 1; + this.drawNodeOutline(node, ctx, state.progress, size, fgColor, bgColor, color, thickness) + } + } - if (state.progress) { - ctx.fillStyle = "green"; - ctx.fillRect(0, 0, size[0] * (state.progress.value / state.progress.max), 6); - ctx.fillStyle = bgColor; - } + private drawNodeOutline(node: LGraphNode, ctx: CanvasRenderingContext2D, progress?: Progress, size: Vector2, fgColor: string, bgColor: string, outlineColor: string, outlineThickness: number) { + const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE; + ctx.lineWidth = outlineThickness; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + if (shape == BuiltInSlotShape.BOX_SHAPE) + ctx.rect(-6, -6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT); + else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed)) + ctx.roundRect( + -6, + -6 - LiteGraph.NODE_TITLE_HEIGHT, + 12 + size[0] + 1, + 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, + this.round_radius * 2 + ); + else if (shape == BuiltInSlotShape.CARD_SHAPE) + ctx.roundRect( + -6, + -6 + LiteGraph.NODE_TITLE_HEIGHT, + 12 + size[0] + 1, + 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, + this.round_radius * 2, + 2 + ); + else if (shape == BuiltInSlotShape.CIRCLE_SHAPE) + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); + ctx.strokeStyle = outlineColor; + ctx.stroke(); + ctx.strokeStyle = fgColor; + ctx.globalAlpha = 1; + + if (progress) { + ctx.fillStyle = "green"; + ctx.fillRect(0, 0, size[0] * (progress.value / progress.max), 6); + ctx.fillStyle = bgColor; } } @@ -235,10 +262,43 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } override onSelectionChange(nodes: Record) { - const ls = get(layoutState) - ls.currentSelectionNodes = Object.values(nodes) - ls.currentSelection = [] - layoutState.set(ls) + selectionState.update(ss => { + ss.currentSelectionNodes = Object.values(nodes) + ss.currentSelection = [] + const ls = get(layoutState) + for (const node of ss.currentSelectionNodes) { + const widget = ls.allItemsByNode[node.id] + if (widget) + ss.currentSelection.push(widget.dragItem.id) + } + return ss + }) + } + + override onHoverChange(node: LGraphNode | null) { + selectionState.update(ss => { + ss.currentHoveredNodes.clear() + if (node) { + ss.currentHoveredNodes.add(node.id) + } + ss.currentHovered.clear() + const ls = get(layoutState) + for (const nodeID of ss.currentHoveredNodes) { + const widget = ls.allItemsByNode[nodeID] + if (widget) + ss.currentHovered.add(widget.dragItem.id) + } + return ss + }) + } + + override clear() { + super.clear(); + selectionState.update(ss => { + ss.currentSelectionNodes = []; + ss.currentHoveredNodes.clear() + return ss; + }) } override onNodeMoved(node: LGraphNode) { diff --git a/src/lib/components/AccordionContainer.svelte b/src/lib/components/AccordionContainer.svelte index 978da9e..e0cf0c1 100644 --- a/src/lib/components/AccordionContainer.svelte +++ b/src/lib/components/AccordionContainer.svelte @@ -2,6 +2,7 @@ import { Block, BlockTitle } from "@gradio/atoms"; import Accordion from "$lib/components/gradio/app/Accordion.svelte"; import uiState from "$lib/stores/uiState"; + import selectionState from "$lib/stores/selectionState"; import WidgetContainer from "./WidgetContainer.svelte" import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action'; @@ -61,9 +62,10 @@ {#if container && children} + {@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
@@ -120,6 +122,10 @@