mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 14:27:40 +00:00
Remove LayoutElement, resolve root TS issues (#953)
- Converts type assertions to use inference via discriminated unions - Removes the LayoutElement class (only used by node slots, and recently reduced to a single function) - Splits `boundingRect` property out from `Positionable` interface - Slots now use the standard `boundingRect` property - Perf improvements / Removes redundant code
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import type { DragAndScale } from "./DragAndScale"
|
||||
import type { IDrawBoundingOptions } from "./draw"
|
||||
import type {
|
||||
CanvasColour,
|
||||
ColorOption,
|
||||
Dictionary,
|
||||
IColorable,
|
||||
@@ -41,7 +40,6 @@ import {
|
||||
TitleMode,
|
||||
} from "./types/globalEnums"
|
||||
import { findFreeSlotOfType } from "./utils/collections"
|
||||
import { LayoutElement } from "./utils/layout"
|
||||
import { distributeSpace } from "./utils/spaceDistribution"
|
||||
import { toClass } from "./utils/type"
|
||||
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
|
||||
@@ -3448,31 +3446,22 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
|
||||
}
|
||||
}
|
||||
|
||||
get highlightColor(): CanvasColour {
|
||||
return LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR ?? LiteGraph.NODE_SELECTED_TITLE_COLOR ?? LiteGraph.NODE_TEXT_COLOR
|
||||
}
|
||||
|
||||
get slots(): INodeSlot[] {
|
||||
get slots(): (INodeInputSlot | INodeOutputSlot)[] {
|
||||
return [...this.inputs, ...this.outputs]
|
||||
}
|
||||
|
||||
#measureSlot(slot: INodeSlot, slotIndex: number): LayoutElement {
|
||||
#measureSlot(slot: INodeSlot, slotIndex: number): void {
|
||||
const isInput = isINodeInputSlot(slot)
|
||||
const pos = isInput ? this.getInputPos(slotIndex) : this.getOutputPos(slotIndex)
|
||||
|
||||
slot._layoutElement = new LayoutElement({
|
||||
boundingRect: [
|
||||
pos[0] - this.pos[0] - LiteGraph.NODE_SLOT_HEIGHT * 0.5,
|
||||
pos[1] - this.pos[1] - LiteGraph.NODE_SLOT_HEIGHT * 0.5,
|
||||
LiteGraph.NODE_SLOT_HEIGHT,
|
||||
LiteGraph.NODE_SLOT_HEIGHT,
|
||||
],
|
||||
})
|
||||
return slot._layoutElement
|
||||
slot.boundingRect[0] = pos[0] - this.pos[0] - LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
slot.boundingRect[1] = pos[1] - this.pos[1] - LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
slot.boundingRect[2] = LiteGraph.NODE_SLOT_HEIGHT
|
||||
slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
#measureSlots(): ReadOnlyRect | null {
|
||||
const slots: LayoutElement[] = []
|
||||
const slots: INodeSlot[] = []
|
||||
|
||||
for (const [slotIndex, slot] of this.inputs.entries()) {
|
||||
// Unrecognized nodes (Nodes with error) has inputs but no widgets. Treat
|
||||
@@ -3480,12 +3469,12 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
|
||||
/** Widget input slots are handled in {@link layoutWidgetInputSlots} */
|
||||
if (this.widgets?.length && isWidgetInputSlot(slot)) continue
|
||||
|
||||
const layoutElement = this.#measureSlot(slot, slotIndex)
|
||||
slots.push(layoutElement)
|
||||
this.#measureSlot(slot, slotIndex)
|
||||
slots.push(slot)
|
||||
}
|
||||
for (const [slotIndex, slot] of this.outputs.entries()) {
|
||||
const layoutElement = this.#measureSlot(slot, slotIndex)
|
||||
slots.push(layoutElement)
|
||||
this.#measureSlot(slot, slotIndex)
|
||||
slots.push(slot)
|
||||
}
|
||||
|
||||
return slots.length ? createBounds(slots, 0) : null
|
||||
@@ -3533,32 +3522,29 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
|
||||
lowQuality,
|
||||
}: DrawSlotsOptions) {
|
||||
for (const slot of this.slots) {
|
||||
// change opacity of incompatible slots when dragging a connection
|
||||
const layoutElement = slot._layoutElement
|
||||
const slotInstance = toNodeSlotClass(slot)
|
||||
const isValid = !fromSlot || slotInstance.isValidTarget(fromSlot)
|
||||
const highlight = isValid && this.#isMouseOverSlot(slot)
|
||||
const labelColor = highlight
|
||||
? this.highlightColor
|
||||
: LiteGraph.NODE_TEXT_COLOR
|
||||
const isValidTarget = fromSlot && slotInstance.isValidTarget(fromSlot)
|
||||
const isMouseOverSlot = this.#isMouseOverSlot(slot)
|
||||
|
||||
// change opacity of incompatible slots when dragging a connection
|
||||
const isValid = !fromSlot || isValidTarget
|
||||
const highlight = isValid && isMouseOverSlot
|
||||
|
||||
// Show slot if it's not a widget input slot
|
||||
// or if it's a widget input slot and satisfies one of the following:
|
||||
// - the mouse is over the widget
|
||||
// - the slot is valid during link drop
|
||||
// - the slot is connected
|
||||
const showSlot = !isWidgetInputSlot(slot) ||
|
||||
this.#isMouseOverSlot(slot) ||
|
||||
this.#isMouseOverWidget(this.getWidgetFromSlot(slot)!) ||
|
||||
(fromSlot && slotInstance.isValidTarget(fromSlot)) ||
|
||||
const showSlot = isMouseOverSlot ||
|
||||
isValidTarget ||
|
||||
!slotInstance.isWidgetInputSlot ||
|
||||
this.#isMouseOverWidget(this.getWidgetFromSlot(slotInstance)!) ||
|
||||
slotInstance.isConnected()
|
||||
|
||||
ctx.globalAlpha = showSlot ? (isValid ? editorAlpha : 0.4 * editorAlpha) : 0
|
||||
|
||||
slotInstance.draw(ctx, {
|
||||
pos: layoutElement?.center ?? [0, 0],
|
||||
colorContext,
|
||||
labelColor,
|
||||
lowQuality,
|
||||
highlight,
|
||||
})
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { CanvasColour, Dictionary, INodeInputSlot, INodeOutputSlot, INodeSlot, ISlotType, IWidgetInputSlot, IWidgetLocator, Point, SharedIntersection } from "./interfaces"
|
||||
import type { CanvasColour, Dictionary, INodeInputSlot, INodeOutputSlot, INodeSlot, ISlotType, IWidgetInputSlot, IWidgetLocator, OptionalProps, Point, Rect, SharedIntersection } from "./interfaces"
|
||||
import type { LinkId } from "./LLink"
|
||||
import type { IWidget } from "./types/widgets"
|
||||
|
||||
import { LabelPosition, SlotShape, SlotType } from "./draw"
|
||||
import { LiteGraph } from "./litegraph"
|
||||
import { getCentre } from "./measure"
|
||||
import { LinkDirection, RenderShape } from "./types/globalEnums"
|
||||
import { ISerialisableNodeInput, ISerialisableNodeOutput } from "./types/serialisation"
|
||||
|
||||
@@ -19,9 +20,7 @@ export interface ConnectionColorContext {
|
||||
}
|
||||
|
||||
interface IDrawOptions {
|
||||
pos: Point
|
||||
colorContext: ConnectionColorContext
|
||||
labelColor?: CanvasColour
|
||||
labelPosition?: LabelPosition
|
||||
lowQuality?: boolean
|
||||
doStroke?: boolean
|
||||
@@ -64,20 +63,19 @@ export function outputAsSerialisable(slot: INodeOutputSlot & { widget?: IWidget
|
||||
}
|
||||
}
|
||||
|
||||
export function toNodeSlotClass(slot: INodeSlot): NodeSlot {
|
||||
if (isINodeInputSlot(slot)) {
|
||||
return new NodeInputSlot(slot)
|
||||
} else if (isINodeOutputSlot(slot)) {
|
||||
return new NodeOutputSlot(slot)
|
||||
}
|
||||
throw new Error("Invalid slot type")
|
||||
export function toNodeSlotClass(slot: INodeInputSlot | INodeOutputSlot): NodeInputSlot | NodeOutputSlot {
|
||||
if (slot instanceof NodeInputSlot || slot instanceof NodeOutputSlot) return slot
|
||||
|
||||
return "link" in slot
|
||||
? new NodeInputSlot(slot)
|
||||
: new NodeOutputSlot(slot)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this slot is an input slot and attached to a widget.
|
||||
* @param slot The slot to check.
|
||||
*/
|
||||
export function isWidgetInputSlot(slot: INodeSlot): slot is IWidgetInputSlot {
|
||||
export function isWidgetInputSlot(slot: INodeInputSlot): slot is IWidgetInputSlot {
|
||||
return isINodeInputSlot(slot) && !!slot.widget
|
||||
}
|
||||
|
||||
@@ -96,11 +94,19 @@ export abstract class NodeSlot implements INodeSlot {
|
||||
pos?: Point
|
||||
widget?: IWidgetLocator
|
||||
hasErrors?: boolean
|
||||
boundingRect: Rect
|
||||
|
||||
constructor(slot: INodeSlot) {
|
||||
get highlightColor(): CanvasColour {
|
||||
return LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR ?? LiteGraph.NODE_SELECTED_TITLE_COLOR ?? LiteGraph.NODE_TEXT_COLOR
|
||||
}
|
||||
|
||||
abstract get isWidgetInputSlot(): boolean
|
||||
|
||||
constructor(slot: OptionalProps<INodeSlot, "boundingRect">) {
|
||||
Object.assign(this, slot)
|
||||
this.name = slot.name
|
||||
this.type = slot.type
|
||||
this.boundingRect = slot.boundingRect ?? [0, 0, 0, 0]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,9 +146,7 @@ export abstract class NodeSlot implements INodeSlot {
|
||||
draw(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
{
|
||||
pos,
|
||||
colorContext,
|
||||
labelColor = "#AAA",
|
||||
labelPosition = LabelPosition.Right,
|
||||
lowQuality = false,
|
||||
highlight = false,
|
||||
@@ -154,6 +158,11 @@ export abstract class NodeSlot implements INodeSlot {
|
||||
const originalStrokeStyle = ctx.strokeStyle
|
||||
const originalLineWidth = ctx.lineWidth
|
||||
|
||||
const labelColor = highlight
|
||||
? this.highlightColor
|
||||
: LiteGraph.NODE_TEXT_COLOR
|
||||
|
||||
const pos = getCentre(this.boundingRect)
|
||||
const slot_type = this.type
|
||||
const slot_shape = (
|
||||
slot_type === SlotType.Array ? SlotShape.Grid : this.shape
|
||||
@@ -211,7 +220,7 @@ export abstract class NodeSlot implements INodeSlot {
|
||||
if (!lowQuality && doStroke) ctx.stroke()
|
||||
|
||||
// render slot label
|
||||
const hideLabel = lowQuality || isWidgetInputSlot(this)
|
||||
const hideLabel = lowQuality || this.isWidgetInputSlot
|
||||
if (!hideLabel) {
|
||||
const text = this.renderingLabel
|
||||
if (text) {
|
||||
@@ -290,7 +299,11 @@ export function isINodeInputSlot(slot: INodeSlot): slot is INodeInputSlot {
|
||||
export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
|
||||
link: LinkId | null
|
||||
|
||||
constructor(slot: INodeInputSlot) {
|
||||
get isWidgetInputSlot(): boolean {
|
||||
return !!this.widget
|
||||
}
|
||||
|
||||
constructor(slot: OptionalProps<INodeInputSlot, "boundingRect">) {
|
||||
super(slot)
|
||||
this.link = slot.link
|
||||
}
|
||||
@@ -326,7 +339,11 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot {
|
||||
_data?: unknown
|
||||
slot_index?: number
|
||||
|
||||
constructor(slot: INodeOutputSlot) {
|
||||
get isWidgetInputSlot(): false {
|
||||
return false
|
||||
}
|
||||
|
||||
constructor(slot: OptionalProps<INodeOutputSlot, "boundingRect">) {
|
||||
super(slot)
|
||||
this.links = slot.links
|
||||
this._data = slot._data
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { LGraphNode, NodeId } from "./LGraphNode"
|
||||
import type { LinkId, LLink } from "./LLink"
|
||||
import type { Reroute, RerouteId } from "./Reroute"
|
||||
import type { LinkDirection, RenderShape } from "./types/globalEnums"
|
||||
import type { LayoutElement } from "./utils/layout"
|
||||
|
||||
export type Dictionary<T> = { [key: string]: T }
|
||||
|
||||
@@ -30,6 +29,22 @@ export type SharedIntersection<T1, T2> = {
|
||||
|
||||
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
||||
|
||||
/**
|
||||
* Any object that has a {@link boundingRect}.
|
||||
*/
|
||||
export interface HasBoundingRect {
|
||||
/**
|
||||
* A rectangle that represents the outer edges of the item.
|
||||
*
|
||||
* Used for various calculations, such as overlap, selective rendering, and click checks.
|
||||
* For most items, this is cached position & size as `x, y, width, height`.
|
||||
* Some items (such as nodes) may extend above and/or to the left of their {@link pos}.
|
||||
* @readonly
|
||||
* @see {@link move}
|
||||
*/
|
||||
readonly boundingRect: ReadOnlyRect
|
||||
}
|
||||
|
||||
/** An object containing a set of child objects */
|
||||
export interface Parent<TChild> {
|
||||
/** All objects owned by the parent object. */
|
||||
@@ -41,7 +56,7 @@ export interface Parent<TChild> {
|
||||
*
|
||||
* May contain other {@link Positionable} objects.
|
||||
*/
|
||||
export interface Positionable extends Parent<Positionable> {
|
||||
export interface Positionable extends Parent<Positionable>, HasBoundingRect {
|
||||
readonly id: NodeId | RerouteId | number
|
||||
/** Position in graph coordinates. Default: 0,0 */
|
||||
readonly pos: Point
|
||||
@@ -68,17 +83,6 @@ export interface Positionable extends Parent<Positionable> {
|
||||
*/
|
||||
snapToGrid(snapTo: number): boolean
|
||||
|
||||
/**
|
||||
* A rectangle that represents the outer edges of the item.
|
||||
*
|
||||
* Used for various calculations, such as overlap, selective rendering, and click checks.
|
||||
* For most items, this is cached position & size as `x, y, width, height`.
|
||||
* Some items (such as nodes) may extend above and/or to the left of their {@link pos}.
|
||||
* @readonly
|
||||
* @see {@link move}
|
||||
*/
|
||||
readonly boundingRect: ReadOnlyRect
|
||||
|
||||
/** Called whenever the item is selected */
|
||||
onSelected?(): void
|
||||
/** Called whenever the item is deselected */
|
||||
@@ -256,7 +260,7 @@ export interface IOptionalSlotData<TSlot extends INodeInputSlot | INodeOutputSlo
|
||||
*/
|
||||
export type ISlotType = number | string
|
||||
|
||||
export interface INodeSlot {
|
||||
export interface INodeSlot extends HasBoundingRect {
|
||||
/**
|
||||
* The name of the slot in English.
|
||||
* Will be included in the serialized data.
|
||||
@@ -284,11 +288,8 @@ export interface INodeSlot {
|
||||
locked?: boolean
|
||||
nameLocked?: boolean
|
||||
pos?: Point
|
||||
/**
|
||||
* A layout element that is used internally to position the slot.
|
||||
* Set by {@link LGraphNode.#layoutSlots}.
|
||||
*/
|
||||
_layoutElement?: LayoutElement
|
||||
/** @remarks Automatically calculated; not included in serialisation. */
|
||||
boundingRect: Rect
|
||||
/**
|
||||
* A list of floating link IDs that are connected to this slot.
|
||||
* This is calculated at runtime; it is **not** serialized.
|
||||
@@ -322,7 +323,6 @@ export interface IWidgetLocator {
|
||||
|
||||
export interface INodeInputSlot extends INodeSlot {
|
||||
link: LinkId | null
|
||||
_layoutElement?: LayoutElement
|
||||
widget?: IWidgetLocator
|
||||
}
|
||||
|
||||
@@ -334,7 +334,6 @@ export interface INodeOutputSlot extends INodeSlot {
|
||||
links: LinkId[] | null
|
||||
_data?: unknown
|
||||
slot_index?: number
|
||||
_layoutElement?: LayoutElement
|
||||
}
|
||||
|
||||
/** Links */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
HasBoundingRect,
|
||||
Point,
|
||||
ReadOnlyPoint,
|
||||
ReadOnlyRect,
|
||||
@@ -146,6 +147,18 @@ export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the centre of a rectangle.
|
||||
* @param rect The rectangle, as `x, y, width, height`
|
||||
* @returns The centre of the rectangle, as `x, y`
|
||||
*/
|
||||
export function getCentre(rect: ReadOnlyRect): Point {
|
||||
return [
|
||||
rect[0] + (rect[2] * 0.5),
|
||||
rect[1] + (rect[3] * 0.5),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if rectangle {@link a} contains the centre point of rectangle {@link b}.
|
||||
* @param a Container rectangle A as `x, y, width, height`
|
||||
@@ -331,7 +344,7 @@ export function findPointOnCurve(
|
||||
}
|
||||
|
||||
export function createBounds(
|
||||
objects: Iterable<{ boundingRect: ReadOnlyRect }>,
|
||||
objects: Iterable<HasBoundingRect>,
|
||||
padding: number = 10,
|
||||
): ReadOnlyRect | null {
|
||||
const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity])
|
||||
|
||||
@@ -56,10 +56,10 @@ export interface SerialisableGraph extends BaseExportedGraph {
|
||||
extra?: Dictionary<unknown>
|
||||
}
|
||||
|
||||
export type ISerialisableNodeInput = Omit<INodeInputSlot, "_layoutElement" | "widget"> & {
|
||||
export type ISerialisableNodeInput = Omit<INodeInputSlot, "boundingRect" | "widget"> & {
|
||||
widget?: { name: string }
|
||||
}
|
||||
export type ISerialisableNodeOutput = Omit<INodeOutputSlot, "_layoutElement" | "_data"> & {
|
||||
export type ISerialisableNodeOutput = Omit<INodeOutputSlot, "boundingRect" | "_data"> & {
|
||||
widget?: { name: string }
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export interface ExportedSubgraph extends ISerialisedGraph {
|
||||
}
|
||||
|
||||
/** Properties shared by subgraph and node I/O slots. */
|
||||
type SubgraphIOShared = Omit<INodeSlot, "nameLocked" | "locked" | "removable" | "_layoutElement" | "_floatingLinks">
|
||||
type SubgraphIOShared = Omit<INodeSlot, "nameLocked" | "locked" | "removable" | "boundingRect" | "_floatingLinks">
|
||||
|
||||
/** Subgraph I/O slots */
|
||||
export interface SubgraphIO extends SubgraphIOShared {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Point, ReadOnlyRect } from "@/interfaces"
|
||||
|
||||
export class LayoutElement {
|
||||
public readonly boundingRect: ReadOnlyRect
|
||||
|
||||
constructor(o: {
|
||||
boundingRect: ReadOnlyRect
|
||||
}) {
|
||||
this.boundingRect = o.boundingRect
|
||||
}
|
||||
|
||||
get center(): Point {
|
||||
return [
|
||||
this.boundingRect[0] + this.boundingRect[2] / 2,
|
||||
this.boundingRect[1] + this.boundingRect[3] / 2,
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user