diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 782c7aa5..3ea0151a 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -43,7 +43,7 @@ import { LinkConnector } from "@/canvas/LinkConnector" import { isOverNodeInput, isOverNodeOutput } from "./canvas/measureSlots" import { CanvasPointer } from "./CanvasPointer" import { type AnimationOptions, DragAndScale } from "./DragAndScale" -import { strokeShape } from "./draw" +import { SlotShape, strokeShape } from "./draw" import { NullGraphError } from "./infrastructure/NullGraphError" import { LGraphGroup } from "./LGraphGroup" import { LGraphNode, type NodeId, type NodeProperty } from "./LGraphNode" @@ -4233,7 +4233,7 @@ export class LGraphCanvas implements CustomEventDispatcher ) ctx.beginPath() - if (connType === LiteGraph.EVENT || connShape === RenderShape.BOX) { + if (connType === LiteGraph.EVENT || connShape === SlotShape.Box) { ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10) ctx.rect( highlightPos[0] - 6 + 0.5, @@ -4241,7 +4241,7 @@ export class LGraphCanvas implements CustomEventDispatcher 14, 10, ) - } else if (connShape === RenderShape.ARROW) { + } else if (connShape === SlotShape.Arrow) { ctx.moveTo(pos[0] + 8, pos[1] + 0.5) ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5) ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5) @@ -4333,7 +4333,7 @@ export class LGraphCanvas implements CustomEventDispatcher ctx.beginPath() const shape = this._highlight_input?.shape - if (shape === RenderShape.ARROW) { + if (shape === SlotShape.Arrow) { ctx.moveTo(highlightPos[0] + 8, highlightPos[1] + 0.5) ctx.lineTo(highlightPos[0] - 4, highlightPos[1] + 6 + 0.5) ctx.lineTo(highlightPos[0] - 4, highlightPos[1] - 6 + 0.5) diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index dd81cb7c..f7ef4f6e 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -33,6 +33,7 @@ import type { ISerialisedNode, SubgraphIO } from "./types/serialisation" import type { IBaseWidget, IWidgetOptions, TWidgetType, TWidgetValue } from "./types/widgets" import { getNodeInputOnPos, getNodeOutputOnPos } from "./canvas/measureSlots" +import { validateSlotShape } from "./draw" import { NullGraphError } from "./infrastructure/NullGraphError" import { Rectangle } from "./infrastructure/Rectangle" import { BadgePosition, LGraphBadge } from "./LGraphBadge" @@ -735,6 +736,12 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable this.inputs ??= [] this.inputs = this.inputs.map(input => toClass(NodeInputSlot, input, this)) + // Validate and migrate slot shapes for backward compatibility + for (const input of this.inputs) { + if (input.shape != null) { + input.shape = validateSlotShape(input.shape) + } + } for (const [i, input] of this.inputs.entries()) { const link = this.graph && input.link != null ? this.graph._links.get(input.link) @@ -745,6 +752,12 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable this.outputs ??= [] this.outputs = this.outputs.map(output => toClass(NodeOutputSlot, output, this)) + // Validate and migrate slot shapes for backward compatibility + for (const output of this.outputs) { + if (output.shape != null) { + output.shape = validateSlotShape(output.shape) + } + } for (const [i, output] of this.outputs.entries()) { if (!output.links) continue @@ -1464,6 +1477,11 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable type: ISlotType, extra_info?: TProperties, ): INodeOutputSlot & TProperties { + // Validate and convert shape if provided + if (extra_info?.shape != null) { + extra_info = { ...extra_info, shape: validateSlotShape(extra_info.shape) } + } + const output = Object.assign( new NodeOutputSlot({ name, type, links: null }, this), extra_info, @@ -1513,6 +1531,11 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable addInput>(name: string, type: ISlotType, extra_info?: TProperties): INodeInputSlot & TProperties { type ||= 0 + // Validate and convert shape if provided + if (extra_info?.shape != null) { + extra_info = { ...extra_info, shape: validateSlotShape(extra_info.shape) } + } + const input = Object.assign( new NodeInputSlot({ name, type, link: null }, this), extra_info, diff --git a/src/draw.ts b/src/draw.ts index f7981009..22aba76b 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -22,6 +22,43 @@ export enum SlotShape { HollowCircle = RenderShape.HollowCircle, } +/** + * Validates and converts a shape value to a valid SlotShape. + * Provides backward compatibility for RenderShape values that aren't valid for slots. + * @param shape The shape value to validate (can be RenderShape, SlotShape, or any number) + * @returns A valid SlotShape value, or undefined if input was undefined/null + */ +export function validateSlotShape(shape: RenderShape | SlotShape | number | undefined | null): SlotShape | undefined { + if (shape == null) return undefined + + // Check if it's already a valid SlotShape value + if (Object.values(SlotShape).includes(shape as SlotShape)) { + return shape as SlotShape + } + + // Handle RenderShape values that aren't valid for slots + switch (shape) { + case RenderShape.BOX: + return SlotShape.Box + case RenderShape.ROUND: + return SlotShape.Circle // Convert rounded rectangle to circle for slots + case RenderShape.CIRCLE: + return SlotShape.Circle + case RenderShape.CARD: + return SlotShape.Box // Convert card shape to box for slots + case RenderShape.ARROW: + return SlotShape.Arrow + case RenderShape.GRID: + return SlotShape.Grid + case RenderShape.HollowCircle: + return SlotShape.HollowCircle + default: + // For any invalid/unknown values, default to circle + console.warn(`Invalid slot shape value: ${shape}. Defaulting to Circle.`) + return SlotShape.Circle + } +} + /** @see LinkDirection */ export enum SlotDirection { Up = LinkDirection.UP, diff --git a/src/interfaces.ts b/src/interfaces.ts index 33290a24..11638831 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,10 +1,11 @@ import type { ContextMenu } from "./ContextMenu" +import type { SlotShape } from "./draw" import type { LGraphNode, NodeId } from "./LGraphNode" import type { LinkId, LLink } from "./LLink" import type { Reroute, RerouteId } from "./Reroute" import type { SubgraphInputNode } from "./subgraph/SubgraphInputNode" import type { SubgraphOutputNode } from "./subgraph/SubgraphOutputNode" -import type { LinkDirection, RenderShape } from "./types/globalEnums" +import type { LinkDirection } from "./types/globalEnums" import type { IBaseWidget } from "./types/widgets" import type { Rectangle } from "@/infrastructure/Rectangle" import type { CanvasPointerEvent } from "@/types/events" @@ -306,7 +307,7 @@ export interface INodeSlot extends HasBoundingRect { type: ISlotType dir?: LinkDirection removable?: boolean - shape?: RenderShape + shape?: SlotShape color_off?: CanvasColour color_on?: CanvasColour locked?: boolean diff --git a/src/litegraph.ts b/src/litegraph.ts index 08015202..7e7ea26a 100644 --- a/src/litegraph.ts +++ b/src/litegraph.ts @@ -96,7 +96,7 @@ export * as Constants from "./constants" export { ContextMenu } from "./ContextMenu" export { CurveEditor } from "./CurveEditor" export { DragAndScale } from "./DragAndScale" -export { LabelPosition, SlotDirection, SlotShape, SlotType } from "./draw" +export { LabelPosition, SlotDirection, SlotShape, SlotType, validateSlotShape } from "./draw" export { strokeShape } from "./draw" export { Rectangle } from "./infrastructure/Rectangle" export type { diff --git a/src/node/NodeSlot.ts b/src/node/NodeSlot.ts index 849bd567..df69e0ba 100644 --- a/src/node/NodeSlot.ts +++ b/src/node/NodeSlot.ts @@ -4,7 +4,7 @@ import type { LGraphNode } from "@/LGraphNode" import { LabelPosition, SlotShape, SlotType } from "@/draw" import { LiteGraph, Rectangle } from "@/litegraph" import { getCentre } from "@/measure" -import { LinkDirection, RenderShape } from "@/types/globalEnums" +import { LinkDirection } from "@/types/globalEnums" import { NodeInputSlot } from "./NodeInputSlot" import { SlotBase } from "./SlotBase" @@ -98,9 +98,8 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { const pos = this.#centreOffset const slot_type = this.type - const slot_shape = ( - slot_type === SlotType.Array ? SlotShape.Grid : this.shape - ) as SlotShape + const slot_shape = + slot_type === SlotType.Array ? SlotShape.Grid : (this.shape ?? SlotShape.Circle) ctx.beginPath() let doFill = true @@ -201,9 +200,10 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { ctx.fillStyle = "#686" ctx.beginPath() - if (this.type === SlotType.Event || this.shape === RenderShape.BOX) { + const collapsedShape = this.shape ?? SlotShape.Circle + if (this.type === SlotType.Event || collapsedShape === SlotShape.Box) { ctx.rect(x - 7 + 0.5, y - 4, 14, 8) - } else if (this.shape === RenderShape.ARROW) { + } else if (collapsedShape === SlotShape.Arrow) { // Adjust arrow direction based on whether this is an input or output slot const isInput = this instanceof NodeInputSlot if (isInput) { diff --git a/src/node/SlotBase.ts b/src/node/SlotBase.ts index 9d87b4ae..6e933ed6 100644 --- a/src/node/SlotBase.ts +++ b/src/node/SlotBase.ts @@ -1,6 +1,6 @@ +import type { SlotShape } from "@/draw" import type { CanvasColour, DefaultConnectionColors, INodeSlot, ISlotType, IWidgetLocator, Point } from "@/interfaces" import type { LLink } from "@/LLink" -import type { RenderShape } from "@/types/globalEnums" import type { LinkDirection } from "@/types/globalEnums" import { Rectangle } from "@/infrastructure/Rectangle" @@ -14,7 +14,7 @@ export abstract class SlotBase implements INodeSlot { type: ISlotType dir?: LinkDirection removable?: boolean - shape?: RenderShape + shape?: SlotShape color_off?: CanvasColour color_on?: CanvasColour locked?: boolean diff --git a/src/subgraph/SubgraphSlotBase.ts b/src/subgraph/SubgraphSlotBase.ts index bf5b148e..0b8e1a45 100644 --- a/src/subgraph/SubgraphSlotBase.ts +++ b/src/subgraph/SubgraphSlotBase.ts @@ -144,8 +144,7 @@ export abstract class SubgraphSlot extends SlotBase implements SubgraphIO, Hover /** @remarks Leaves the context dirty. */ draw({ ctx, colorContext, lowQuality }: SubgraphSlotDrawOptions): void { - // Assertion: SlotShape is a subset of RenderShape - const shape = this.shape as unknown as SlotShape + const shape = this.shape ?? SlotShape.Circle const { isPointerOver, pos: [x, y] } = this ctx.beginPath()