mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-01 19:20:10 +00:00
Add Subgraphs (#1000)
This commit is contained in:
75
src/infrastructure/ConstrainedSize.ts
Normal file
75
src/infrastructure/ConstrainedSize.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { ReadOnlyRect, ReadOnlySize, Size } from "@/interfaces"
|
||||
|
||||
import { clamp } from "@/litegraph"
|
||||
|
||||
/**
|
||||
* Basic width and height, with min/max constraints.
|
||||
*
|
||||
* - The {@link width} and {@link height} properties are readonly
|
||||
* - Size is set via {@link desiredWidth} and {@link desiredHeight} properties
|
||||
* - Width and height are then updated, clamped to min/max values
|
||||
*/
|
||||
export class ConstrainedSize {
|
||||
#width: number = 0
|
||||
#height: number = 0
|
||||
#desiredWidth: number = 0
|
||||
#desiredHeight: number = 0
|
||||
|
||||
minWidth: number = 0
|
||||
minHeight: number = 0
|
||||
maxWidth: number = Infinity
|
||||
maxHeight: number = Infinity
|
||||
|
||||
get width() {
|
||||
return this.#width
|
||||
}
|
||||
|
||||
get height() {
|
||||
return this.#height
|
||||
}
|
||||
|
||||
get desiredWidth() {
|
||||
return this.#desiredWidth
|
||||
}
|
||||
|
||||
set desiredWidth(value: number) {
|
||||
this.#desiredWidth = value
|
||||
this.#width = clamp(value, this.minWidth, this.maxWidth)
|
||||
}
|
||||
|
||||
get desiredHeight() {
|
||||
return this.#desiredHeight
|
||||
}
|
||||
|
||||
set desiredHeight(value: number) {
|
||||
this.#desiredHeight = value
|
||||
this.#height = clamp(value, this.minHeight, this.maxHeight)
|
||||
}
|
||||
|
||||
constructor(width: number, height: number) {
|
||||
this.desiredWidth = width
|
||||
this.desiredHeight = height
|
||||
}
|
||||
|
||||
static fromSize(size: ReadOnlySize): ConstrainedSize {
|
||||
return new ConstrainedSize(size[0], size[1])
|
||||
}
|
||||
|
||||
static fromRect(rect: ReadOnlyRect): ConstrainedSize {
|
||||
return new ConstrainedSize(rect[2], rect[3])
|
||||
}
|
||||
|
||||
setSize(size: ReadOnlySize): void {
|
||||
this.desiredWidth = size[0]
|
||||
this.desiredHeight = size[1]
|
||||
}
|
||||
|
||||
setValues(width: number, height: number): void {
|
||||
this.desiredWidth = width
|
||||
this.desiredHeight = height
|
||||
}
|
||||
|
||||
toSize(): Size {
|
||||
return [this.#width, this.#height]
|
||||
}
|
||||
}
|
||||
6
src/infrastructure/InvalidLinkError.ts
Normal file
6
src/infrastructure/InvalidLinkError.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class InvalidLinkError extends Error {
|
||||
constructor(message: string = "Attempted to access a link that was invalid.", cause?: Error) {
|
||||
super(message, { cause })
|
||||
this.name = "InvalidLinkError"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
import type { ConnectingLink } from "@/interfaces"
|
||||
import type { LGraph } from "@/LGraph"
|
||||
import type { LGraphGroup } from "@/LGraphGroup"
|
||||
import type { LGraphNode } from "@/LGraphNode"
|
||||
import type { Subgraph } from "@/subgraph/Subgraph"
|
||||
import type { CanvasPointerEvent } from "@/types/events"
|
||||
|
||||
export interface LGraphCanvasEventMap {
|
||||
/** The active graph has changed. */
|
||||
"litegraph:set-graph": {
|
||||
/** The new active graph. */
|
||||
newGraph: LGraph | Subgraph
|
||||
/** The old active graph, or `null` if there was no active graph. */
|
||||
oldGraph: LGraph | Subgraph | null | undefined
|
||||
}
|
||||
|
||||
"litegraph:canvas":
|
||||
| { subType: "before-change" | "after-change" }
|
||||
| {
|
||||
|
||||
47
src/infrastructure/LGraphEventMap.ts
Normal file
47
src/infrastructure/LGraphEventMap.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { ReadOnlyRect } from "@/interfaces"
|
||||
import type { LGraph } from "@/LGraph"
|
||||
import type { LLink, ResolvedConnection } from "@/LLink"
|
||||
import type { Subgraph } from "@/subgraph/Subgraph"
|
||||
import type { ExportedSubgraph, ISerialisedGraph, SerialisableGraph } from "@/types/serialisation"
|
||||
|
||||
export interface LGraphEventMap {
|
||||
"configuring": {
|
||||
/** The data that was used to configure the graph. */
|
||||
data: ISerialisedGraph | SerialisableGraph
|
||||
/** If `true`, the graph will be cleared prior to adding the configuration. */
|
||||
clearGraph: boolean
|
||||
}
|
||||
"configured": never
|
||||
|
||||
"subgraph-created": {
|
||||
/** The subgraph that was created. */
|
||||
subgraph: Subgraph
|
||||
/** The raw data that was used to create the subgraph. */
|
||||
data: ExportedSubgraph
|
||||
}
|
||||
|
||||
/** Dispatched when a group of items are converted to a subgraph. */
|
||||
"convert-to-subgraph": {
|
||||
/** The type of subgraph to create. */
|
||||
subgraph: Subgraph
|
||||
/** The boundary around every item that was moved into the subgraph. */
|
||||
bounds: ReadOnlyRect
|
||||
/** The raw data that was used to create the subgraph. */
|
||||
exportedSubgraph: ExportedSubgraph
|
||||
/** The links that were used to create the subgraph. */
|
||||
boundaryLinks: LLink[]
|
||||
/** Links that go from outside the subgraph in, via an input on the subgraph node. */
|
||||
resolvedInputLinks: ResolvedConnection[]
|
||||
/** Links that go from inside the subgraph out, via an output on the subgraph node. */
|
||||
resolvedOutputLinks: ResolvedConnection[]
|
||||
/** The floating links that were used to create the subgraph. */
|
||||
boundaryFloatingLinks: LLink[]
|
||||
/** The internal links that were used to create the subgraph. */
|
||||
internalLinks: LLink[]
|
||||
}
|
||||
|
||||
"open-subgraph": {
|
||||
subgraph: Subgraph
|
||||
closingGraph: LGraph | Subgraph
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import type { ToInputRenderLink } from "@/canvas/ToInputRenderLink"
|
||||
import type { LGraphNode } from "@/LGraphNode"
|
||||
import type { LLink } from "@/LLink"
|
||||
import type { Reroute } from "@/Reroute"
|
||||
import type { SubgraphInputNode } from "@/subgraph/SubgraphInputNode"
|
||||
import type { SubgraphOutputNode } from "@/subgraph/SubgraphOutputNode"
|
||||
import type { CanvasPointerEvent } from "@/types/events"
|
||||
import type { IWidget } from "@/types/widgets"
|
||||
|
||||
@@ -37,6 +39,10 @@ export interface LinkConnectorEventMap {
|
||||
node: LGraphNode
|
||||
event: CanvasPointerEvent
|
||||
}
|
||||
"dropped-on-io-node": {
|
||||
node: SubgraphInputNode | SubgraphOutputNode
|
||||
event: CanvasPointerEvent
|
||||
}
|
||||
"dropped-on-canvas": CanvasPointerEvent
|
||||
|
||||
"dropped-on-widget": {
|
||||
|
||||
@@ -6,7 +6,8 @@ import { isInRectangle } from "@/measure"
|
||||
* A rectangle, represented as a float64 array of 4 numbers: [x, y, width, height].
|
||||
*
|
||||
* This class is a subclass of Float64Array, and so has all the methods of that class. Notably,
|
||||
* {@link Rectangle.from} can be used to convert a {@link ReadOnlyRect}.
|
||||
* {@link Rectangle.from} can be used to convert a {@link ReadOnlyRect}. Typing of this however,
|
||||
* is broken due to the base TS lib returning Float64Array rather than `this`.
|
||||
*
|
||||
* Sub-array properties ({@link Float64Array.subarray}):
|
||||
* - {@link pos}: The position of the top-left corner of the rectangle.
|
||||
@@ -25,6 +26,29 @@ export class Rectangle extends Float64Array {
|
||||
this[3] = height
|
||||
}
|
||||
|
||||
static override from([x, y, width, height]: ReadOnlyRect): Rectangle {
|
||||
return new Rectangle(x, y, width, height)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new rectangle positioned at the given centre, with the given width/height.
|
||||
* @param centre The centre of the rectangle, as an `[x, y]` point
|
||||
* @param width The width of the rectangle
|
||||
* @param height The height of the rectangle. Default: {@link width}
|
||||
* @returns A new rectangle whose centre is at {@link x}
|
||||
*/
|
||||
static fromCentre([x, y]: ReadOnlyPoint, width: number, height = width): Rectangle {
|
||||
const left = x - width * 0.5
|
||||
const top = y - height * 0.5
|
||||
return new Rectangle(left, top, width, height)
|
||||
}
|
||||
|
||||
static ensureRect(rect: ReadOnlyRect): Rectangle {
|
||||
return rect instanceof Rectangle
|
||||
? rect
|
||||
: new Rectangle(rect[0], rect[1], rect[2], rect[3])
|
||||
}
|
||||
|
||||
override subarray(begin: number = 0, end?: number): Float64Array<ArrayBuffer> {
|
||||
const byteOffset = begin << 3
|
||||
const length = end === undefined ? end : end - begin
|
||||
@@ -163,7 +187,7 @@ export class Rectangle extends Float64Array {
|
||||
* @returns `true` if the point is inside this rectangle, otherwise `false`.
|
||||
*/
|
||||
containsXy(x: number, y: number): boolean {
|
||||
const { x: left, y: top, width, height } = this
|
||||
const [left, top, width, height] = this
|
||||
return x >= left &&
|
||||
x < left + width &&
|
||||
y >= top &&
|
||||
@@ -175,23 +199,35 @@ export class Rectangle extends Float64Array {
|
||||
* @param point The point to check
|
||||
* @returns `true` if {@link point} is inside this rectangle, otherwise `false`.
|
||||
*/
|
||||
containsPoint(point: ReadOnlyPoint): boolean {
|
||||
return this.x <= point[0] &&
|
||||
this.y <= point[1] &&
|
||||
this.x + this.width >= point[0] &&
|
||||
this.y + this.height >= point[1]
|
||||
containsPoint([x, y]: ReadOnlyPoint): boolean {
|
||||
const [left, top, width, height] = this
|
||||
return x >= left &&
|
||||
x < left + width &&
|
||||
y >= top &&
|
||||
y < top + height
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@link rect} is inside this rectangle.
|
||||
* @param rect The rectangle to check
|
||||
* @returns `true` if {@link rect} is inside this rectangle, otherwise `false`.
|
||||
* Checks if {@link other} is a smaller rectangle inside this rectangle.
|
||||
* One **must** be larger than the other; identical rectangles are not considered to contain each other.
|
||||
* @param other The rectangle to check
|
||||
* @returns `true` if {@link other} is inside this rectangle, otherwise `false`.
|
||||
*/
|
||||
containsRect(rect: ReadOnlyRect): boolean {
|
||||
return this.x <= rect[0] &&
|
||||
this.y <= rect[1] &&
|
||||
this.x + this.width >= rect[0] + rect[2] &&
|
||||
this.y + this.height >= rect[1] + rect[3]
|
||||
containsRect(other: ReadOnlyRect): boolean {
|
||||
const { right, bottom } = this
|
||||
const otherRight = other[0] + other[2]
|
||||
const otherBottom = other[1] + other[3]
|
||||
|
||||
const identical = this.x === other[0] &&
|
||||
this.y === other[1] &&
|
||||
right === otherRight &&
|
||||
bottom === otherBottom
|
||||
|
||||
return !identical &&
|
||||
this.x <= other[0] &&
|
||||
this.y <= other[1] &&
|
||||
right >= otherRight &&
|
||||
bottom >= otherBottom
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,6 +381,10 @@ export class Rectangle extends Float64Array {
|
||||
this[1] += currentHeight - height
|
||||
}
|
||||
|
||||
clone(): Rectangle {
|
||||
return new Rectangle(this[0], this[1], this[2], this[3])
|
||||
}
|
||||
|
||||
/** Alias of {@link export}. */
|
||||
toArray() { return this.export() }
|
||||
|
||||
@@ -353,7 +393,10 @@ export class Rectangle extends Float64Array {
|
||||
return [this[0], this[1], this[2], this[3]]
|
||||
}
|
||||
|
||||
/** Draws a debug outline of this rectangle. */
|
||||
/**
|
||||
* Draws a debug outline of this rectangle.
|
||||
* @internal Convenience debug/development interface; not for production use.
|
||||
*/
|
||||
_drawDebug(ctx: CanvasRenderingContext2D, colour = "red") {
|
||||
const { strokeStyle, lineWidth } = ctx
|
||||
try {
|
||||
|
||||
9
src/infrastructure/RecursionError.ts
Normal file
9
src/infrastructure/RecursionError.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Error thrown when infinite recursion is detected.
|
||||
*/
|
||||
export class RecursionError extends Error {
|
||||
constructor(subject: string) {
|
||||
super(subject)
|
||||
this.name = "RecursionError"
|
||||
}
|
||||
}
|
||||
6
src/infrastructure/SlotIndexError.ts
Normal file
6
src/infrastructure/SlotIndexError.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class SlotIndexError extends Error {
|
||||
constructor(message: string = "Attempted to access a slot that was out of bounds.", cause?: Error) {
|
||||
super(message, { cause })
|
||||
this.name = "SlotIndexError"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user