diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index cbb644cbff..86c3c14c26 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -8,6 +8,7 @@ import { LGraphGroup } from './LGraphGroup' import { LGraphNode, type NodeId, type NodeProperty } from './LGraphNode' import { LLink, type LinkId } from './LLink' import { Reroute, type RerouteId } from './Reroute' +import { RenderedLinkSegment } from './canvas/RenderedLinkSegment' import { isOverNodeInput, isOverNodeOutput } from './canvas/measureSlots' import { strokeShape } from './draw' import type { @@ -5470,7 +5471,6 @@ export class LGraphCanvas const endPos = node.getInputPos(link.target_slot) const endDirection = node.inputs[link.target_slot]?.dir - firstReroute._dragging = true this.#renderAllLinkSegments( ctx, link, @@ -5490,7 +5490,6 @@ export class LGraphCanvas const endPos = reroute.pos const startDirection = node.outputs[link.origin_slot]?.dir - link._dragging = true this.#renderAllLinkSegments( ctx, link, @@ -5550,6 +5549,10 @@ export class LGraphCanvas // Has reroutes if (reroutes.length) { + const lastReroute = reroutes[reroutes.length - 1] + const floatingType = lastReroute?.floating?.slotType + const skipFirstSegment = floatingType === 'input' + const skipLastSegment = floatingType === 'output' let startControl: Point | undefined const l = reroutes.length @@ -5558,7 +5561,6 @@ export class LGraphCanvas // Only render once if (!renderedPaths.has(reroute)) { - renderedPaths.add(reroute) visibleReroutes.push(reroute) reroute._colour = link.color || @@ -5567,10 +5569,16 @@ export class LGraphCanvas const prevReroute = graph.getReroute(reroute.parentId) const rerouteStartPos = prevReroute?.pos ?? startPos - reroute.calculateAngle(this.last_draw_time, graph, rerouteStartPos) + const params = reroute.computeRenderParams(graph, rerouteStartPos) // Skip the first segment if it is being dragged - if (!reroute._dragging) { + if (!(skipFirstSegment && j === 0)) { + const rendered = new RenderedLinkSegment({ + id: reroute.id, + origin_id: link.origin_id, + origin_slot: link.origin_slot, + parentId: reroute.parentId + }) this.renderLink( ctx, rerouteStartPos, @@ -5583,35 +5591,45 @@ export class LGraphCanvas LinkDirection.CENTER, { startControl, - endControl: reroute.controlPoint, - reroute, - disabled + endControl: params.controlPoint, + disabled, + renderTarget: rendered } ) + renderedPaths.add(rendered) } } - if (!startControl && reroutes.at(-1)?.floating?.slotType === 'input') { + if (!startControl && skipFirstSegment) { // Floating link connected to an input startControl = [0, 0] } else { // Calculate start control for the next iter control point const nextPos = reroutes[j + 1]?.pos ?? endPos + const prevR = graph.getReroute(reroute.parentId) + const startPosForParams = prevR?.pos ?? startPos + const params = reroute.computeRenderParams(graph, startPosForParams) const dist = Math.min( Reroute.maxSplineOffset, distance(reroute.pos, nextPos) * 0.25 ) - startControl = [dist * reroute.cos, dist * reroute.sin] + startControl = [dist * params.cos, dist * params.sin] } } - // Skip the last segment if it is being dragged - if (link._dragging) return + // For floating links from output, skip the last segment + if (skipLastSegment) return // Use runtime fallback; TypeScript cannot evaluate this correctly. const segmentStartPos = points.at(-2) ?? startPos // Render final link segment + const rendered = new RenderedLinkSegment({ + id: link.id, + origin_id: link.origin_id, + origin_slot: link.origin_slot, + parentId: link.parentId + }) this.renderLink( ctx, segmentStartPos, @@ -5622,10 +5640,17 @@ export class LGraphCanvas null, LinkDirection.CENTER, end_dir, - { startControl, disabled } + { startControl, disabled, renderTarget: rendered } ) + renderedPaths.add(rendered) // Skip normal render when link is being dragged } else if (!link._dragging) { + const rendered = new RenderedLinkSegment({ + id: link.id, + origin_id: link.origin_id, + origin_slot: link.origin_slot, + parentId: link.parentId + }) this.renderLink( ctx, startPos, @@ -5635,10 +5660,11 @@ export class LGraphCanvas 0, null, start_dir, - end_dir + end_dir, + { renderTarget: rendered } ) + renderedPaths.add(rendered) } - renderedPaths.add(link) // event triggered rendered on top if (link?._last_time && now - link._last_time < 1000) { @@ -5687,7 +5713,8 @@ export class LGraphCanvas endControl, reroute, num_sublines = 1, - disabled = false + disabled = false, + renderTarget }: { /** When defined, render data will be saved to this reroute instead of the {@link link}. */ reroute?: Reroute @@ -5699,6 +5726,8 @@ export class LGraphCanvas num_sublines?: number /** Whether this is a floating link segment */ disabled?: boolean + /** Where to store the drawn path for hit testing if not using a reroute */ + renderTarget?: RenderedLinkSegment } = {} ): void { const linkColour = @@ -5729,13 +5758,13 @@ export class LGraphCanvas const path = new Path2D() /** The link or reroute we're currently rendering */ - const linkSegment = reroute ?? link + const linkSegment = reroute ?? renderTarget if (linkSegment) linkSegment.path = path const innerA = LGraphCanvas.#lTempA const innerB = LGraphCanvas.#lTempB - /** Reference to {@link reroute._pos} if present, or {@link link._pos} if present. Caches the centre point of the link. */ + /** Reference to render-time centre point of this segment. */ const pos: Point = linkSegment?._pos ?? [0, 0] for (let i = 0; i < num_sublines; i++) { diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index eaebc3ff80..aa5de9839e 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -36,9 +36,12 @@ import type { } from './interfaces' import { type LGraphNodeConstructor, + LabelPosition, LiteGraph, type Subgraph, - type SubgraphNode + type SubgraphNode, + drawCollapsedSlot, + drawSlot } from './litegraph' import { createBounds, @@ -3894,13 +3897,13 @@ export class LGraphNode // Render the first connected slot only. for (const slot of this.#concreteInputs) { if (slot.link != null) { - slot.drawCollapsed(ctx) + drawCollapsedSlot(ctx, slot, slot.collapsedPos) break } } for (const slot of this.#concreteOutputs) { if (slot.links?.length) { - slot.drawCollapsed(ctx) + drawCollapsedSlot(ctx, slot, slot.collapsedPos) break } } @@ -4034,11 +4037,32 @@ export class LGraphNode slot.isConnected ) { ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha - slot.draw(ctx, { - colorContext, - lowQuality, - highlight - }) + + // - Inputs: label on the right, no stroke, left textAlign + // - Outputs: label on the left, black stroke, right textAlign + const isInput = slot instanceof NodeInputSlot + const labelPosition = isInput ? LabelPosition.Right : LabelPosition.Left + + const { strokeStyle, textAlign } = ctx + if (isInput) { + ctx.textAlign = 'left' + } else { + ctx.textAlign = 'right' + ctx.strokeStyle = 'black' + } + + try { + drawSlot(ctx, slot as any, { + colorContext, + lowQuality, + highlight, + labelPosition, + doStroke: !isInput + }) + } finally { + ctx.strokeStyle = strokeStyle + ctx.textAlign = textAlign + } } } } diff --git a/src/lib/litegraph/src/LLink.ts b/src/lib/litegraph/src/LLink.ts index c493a27a1b..f7e7ff8162 100644 --- a/src/lib/litegraph/src/LLink.ts +++ b/src/lib/litegraph/src/LLink.ts @@ -11,7 +11,6 @@ import type { INodeOutputSlot, ISlotType, LinkNetwork, - LinkSegment, ReadonlyLinkNetwork } from './interfaces' import type { @@ -86,7 +85,7 @@ type BasicReadonlyNetwork = Pick< > // this is the class in charge of storing link information -export class LLink implements LinkSegment, Serialisable { +export class LLink implements Serialisable { static _drawDebug = false /** Link ID */ @@ -104,14 +103,8 @@ export class LLink implements LinkSegment, Serialisable { data?: number | string | boolean | { toToolTip?(): string } _data?: unknown - /** Centre point of the link, calculated during render only - can be inaccurate */ - _pos: Float32Array /** @todo Clean up - never implemented in comfy. */ _last_time?: number - /** The last canvas 2D path that was used to render this link */ - path?: Path2D - /** @inheritdoc */ - _centreAngle?: number /** @inheritdoc */ _dragging?: boolean @@ -166,8 +159,6 @@ export class LLink implements LinkSegment, Serialisable { this.parentId = parentId this._data = null - // center - this._pos = new Float32Array(2) } /** @deprecated Use {@link LLink.create} */ @@ -199,7 +190,7 @@ export class LLink implements LinkSegment, Serialisable { */ static getReroutes( network: Pick, - linkSegment: LinkSegment + linkSegment: { parentId?: RerouteId } ): Reroute[] { if (!linkSegment.parentId) return [] return network.reroutes.get(linkSegment.parentId)?.getReroutes() ?? [] @@ -207,7 +198,7 @@ export class LLink implements LinkSegment, Serialisable { static getFirstReroute( network: Pick, - linkSegment: LinkSegment + linkSegment: { parentId?: RerouteId } ): Reroute | undefined { return LLink.getReroutes(network, linkSegment).at(0) } @@ -222,7 +213,7 @@ export class LLink implements LinkSegment, Serialisable { */ static findNextReroute( network: Pick, - linkSegment: LinkSegment, + linkSegment: { parentId?: RerouteId }, rerouteId: RerouteId ): Reroute | null | undefined { if (!linkSegment.parentId) return diff --git a/src/lib/litegraph/src/Reroute.ts b/src/lib/litegraph/src/Reroute.ts index 886930227c..d462e620f8 100644 --- a/src/lib/litegraph/src/Reroute.ts +++ b/src/lib/litegraph/src/Reroute.ts @@ -537,6 +537,66 @@ export class Reroute } } + /** + * Computes render-time parameters for this reroute without mutating the model. + * Returns the bezier end control-point offset and the direction cos/sin. + */ + computeRenderParams( + network: ReadonlyLinkNetwork, + linkStart: Point + ): { cos: number; sin: number; controlPoint: Point } { + const thisPos = this.#pos + const angles: number[] = [] + let sum = 0 + + // Collect angles of all links passing through this reroute + addAngles(this.linkIds, network.links) + addAngles(this.floatingLinkIds, network.floatingLinks) + + // Default values when invalid + if (!angles.length) { + return { cos: 0, sin: 0, controlPoint: [0, 0] } + } + + sum /= angles.length + + const originToReroute = Math.atan2( + thisPos[1] - linkStart[1], + thisPos[0] - linkStart[0] + ) + let diff = (originToReroute - sum) * 0.5 + if (Math.abs(diff) > Math.PI * 0.5) diff += Math.PI + const dist = Math.min( + Reroute.maxSplineOffset, + distance(linkStart, thisPos) * 0.25 + ) + + const originDiff = originToReroute - diff + const cos = Math.cos(originDiff) + const sin = Math.sin(originDiff) + const controlPoint: Point = [dist * -cos, dist * -sin] + + return { cos, sin, controlPoint } + + function addAngles( + linkIds: Iterable, + links: ReadonlyMap + ) { + for (const linkId of linkIds) { + const link = links.get(linkId) + const pos = getNextPos(network, link, thisObj.id) + if (!pos) continue + const angle = getDirection(thisPos, pos) + angles.push(angle) + sum += angle + } + } + + // Preserve lexical `this` values inside helper + // eslint-disable-next-line @typescript-eslint/no-this-alias + const thisObj = this + } + /** * Renders the reroute on the canvas. * @param ctx Canvas context to draw on diff --git a/src/lib/litegraph/src/canvas/RenderedLinkSegment.ts b/src/lib/litegraph/src/canvas/RenderedLinkSegment.ts new file mode 100644 index 0000000000..7532314885 --- /dev/null +++ b/src/lib/litegraph/src/canvas/RenderedLinkSegment.ts @@ -0,0 +1,32 @@ +import type { NodeId } from '@/lib/litegraph/src/LGraphNode' +import type { LinkId } from '@/lib/litegraph/src/LLink' +import type { RerouteId } from '@/lib/litegraph/src/Reroute' +import type { LinkSegment } from '@/lib/litegraph/src/interfaces' + +/** + * Lightweight, render-only representation of a link segment used for hit testing and tooltips. + * Decouples canvas state from the LLink data model. + */ +export class RenderedLinkSegment implements LinkSegment { + readonly id: LinkId | RerouteId + readonly origin_id: NodeId + readonly origin_slot: number + readonly parentId?: RerouteId + + path?: Path2D + readonly _pos: Float32Array = new Float32Array(2) + _centreAngle?: number + _dragging?: boolean + + constructor(args: { + id: LinkId | RerouteId + origin_id: NodeId + origin_slot: number + parentId?: RerouteId + }) { + this.id = args.id + this.origin_id = args.origin_id + this.origin_slot = args.origin_slot + this.parentId = args.parentId + } +} diff --git a/src/lib/litegraph/src/canvas/SlotRenderer.ts b/src/lib/litegraph/src/canvas/SlotRenderer.ts new file mode 100644 index 0000000000..1c338fbb34 --- /dev/null +++ b/src/lib/litegraph/src/canvas/SlotRenderer.ts @@ -0,0 +1,175 @@ +import { LabelPosition } from '@/lib/litegraph/src/draw' +import type { + DefaultConnectionColors, + INodeInputSlot, + INodeOutputSlot +} from '@/lib/litegraph/src/interfaces' +import type { ReadOnlyPoint } from '@/lib/litegraph/src/interfaces' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { getCentre } from '@/lib/litegraph/src/measure' +import { NodeInputSlot } from '@/lib/litegraph/src/node/NodeInputSlot' +import type { NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' +import { RenderShape } from '@/lib/litegraph/src/types/globalEnums' + +export interface SlotDrawOptions { + colorContext: DefaultConnectionColors + labelPosition?: LabelPosition + lowQuality?: boolean + doStroke?: boolean + highlight?: boolean +} + +/** Draw a node input or output slot without coupling to the model class. */ +export function drawSlot( + ctx: CanvasRenderingContext2D, + slot: NodeSlot, + { + colorContext, + labelPosition = LabelPosition.Right, + lowQuality = false, + highlight = false, + doStroke = false + }: SlotDrawOptions +) { + // Save the current fillStyle and strokeStyle + const originalFillStyle = ctx.fillStyle + const originalStrokeStyle = ctx.strokeStyle + const originalLineWidth = ctx.lineWidth + + const labelColor = highlight ? slot.highlightColor : LiteGraph.NODE_TEXT_COLOR + + const nodePos = slot.node.pos + const { boundingRect } = slot + const diameter = boundingRect[3] + const [cx, cy] = getCentre([ + boundingRect[0] - nodePos[0], + boundingRect[1] - nodePos[1], + diameter, + diameter + ]) + + const slot_type = slot.type + const slot_shape = (slot_type === 'array' ? RenderShape.GRID : slot.shape) as + | RenderShape + | undefined + + ctx.beginPath() + let doFill = true + + ctx.fillStyle = slot.renderingColor(colorContext) + ctx.lineWidth = 1 + if (slot_type === LiteGraph.EVENT || slot_shape === RenderShape.BOX) { + ctx.rect(cx - 6 + 0.5, cy - 5 + 0.5, 14, 10) + } else if (slot_shape === RenderShape.ARROW) { + ctx.moveTo(cx + 8, cy + 0.5) + ctx.lineTo(cx - 4, cy + 6 + 0.5) + ctx.lineTo(cx - 4, cy - 6 + 0.5) + ctx.closePath() + } else if (slot_shape === RenderShape.GRID) { + const gridSize = 3 + const cellSize = 2 + const spacing = 3 + + for (let x = 0; x < gridSize; x++) { + for (let y = 0; y < gridSize; y++) { + ctx.rect(cx - 4 + x * spacing, cy - 4 + y * spacing, cellSize, cellSize) + } + } + doStroke = false + } else { + // Default rendering for circle, hollow circle. + if (lowQuality) { + ctx.rect(cx - 4, cy - 4, 8, 8) + } else { + let radius: number + if (slot_shape === RenderShape.HollowCircle) { + doFill = false + doStroke = true + ctx.lineWidth = 3 + ctx.strokeStyle = ctx.fillStyle + radius = highlight ? 4 : 3 + } else { + // Normal circle + radius = highlight ? 5 : 4 + } + ctx.arc(cx, cy, radius, 0, Math.PI * 2) + } + } + + if (doFill) ctx.fill() + if (!lowQuality && doStroke) ctx.stroke() + + // render slot label + const hideLabel = lowQuality || slot.isWidgetInputSlot + if (!hideLabel) { + const text = slot.renderingLabel + if (text) { + ctx.fillStyle = labelColor + if (labelPosition === LabelPosition.Right) { + if (slot.dir == LiteGraph.UP) { + ctx.fillText(text, cx, cy - 10) + } else { + ctx.fillText(text, cx + 10, cy + 5) + } + } else { + if (slot.dir == LiteGraph.DOWN) { + ctx.fillText(text, cx, cy - 8) + } else { + ctx.fillText(text, cx - 10, cy + 5) + } + } + } + } + + // Draw a red circle if the slot has errors. + if (slot.hasErrors) { + ctx.lineWidth = 2 + ctx.strokeStyle = 'red' + ctx.beginPath() + ctx.arc(cx, cy, 12, 0, Math.PI * 2) + ctx.stroke() + } + + // Restore the original fillStyle and strokeStyle + ctx.fillStyle = originalFillStyle + ctx.strokeStyle = originalStrokeStyle + ctx.lineWidth = originalLineWidth +} + +/** Draw a minimal collapsed representation for the first connected slot. */ +export function drawCollapsedSlot( + ctx: CanvasRenderingContext2D, + slot: INodeInputSlot | INodeOutputSlot, + collapsedPos: ReadOnlyPoint +) { + const x = collapsedPos[0] + const y = collapsedPos[1] + + // Save original styles + const { fillStyle } = ctx + + ctx.fillStyle = '#686' + ctx.beginPath() + + if (slot.type === LiteGraph.EVENT || slot.shape === RenderShape.BOX) { + ctx.rect(x - 7 + 0.5, y - 4, 14, 8) + } else if (slot.shape === RenderShape.ARROW) { + const isInput = slot instanceof NodeInputSlot + if (isInput) { + ctx.moveTo(x + 8, y) + ctx.lineTo(x - 4, y - 4) + ctx.lineTo(x - 4, y + 4) + } else { + ctx.moveTo(x + 6, y) + ctx.lineTo(x - 6, y - 4) + ctx.lineTo(x - 6, y + 4) + } + ctx.closePath() + } else { + ctx.arc(x, y, 4, 0, Math.PI * 2) + } + ctx.fill() + + // Restore original styles + ctx.fillStyle = fillStyle +} diff --git a/src/lib/litegraph/src/litegraph.ts b/src/lib/litegraph/src/litegraph.ts index 0104bbad52..fd2bd53192 100644 --- a/src/lib/litegraph/src/litegraph.ts +++ b/src/lib/litegraph/src/litegraph.ts @@ -87,6 +87,7 @@ export interface LGraphNodeConstructor { export { InputIndicators } from './canvas/InputIndicators' export { LinkConnector } from './canvas/LinkConnector' export { isOverNodeInput, isOverNodeOutput } from './canvas/measureSlots' +export { drawSlot, drawCollapsedSlot } from './canvas/SlotRenderer' export { CanvasPointer } from './CanvasPointer' export * as Constants from './constants' export { ContextMenu } from './ContextMenu' diff --git a/src/lib/litegraph/src/node/NodeInputSlot.ts b/src/lib/litegraph/src/node/NodeInputSlot.ts index 8a6af55e9d..7cb732cea9 100644 --- a/src/lib/litegraph/src/node/NodeInputSlot.ts +++ b/src/lib/litegraph/src/node/NodeInputSlot.ts @@ -1,6 +1,5 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import type { LinkId } from '@/lib/litegraph/src/LLink' -import { LabelPosition } from '@/lib/litegraph/src/draw' import type { INodeInputSlot, INodeOutputSlot, @@ -8,7 +7,7 @@ import type { ReadOnlyPoint } from '@/lib/litegraph/src/interfaces' import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' +import { NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput' import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput' import { isSubgraphInput } from '@/lib/litegraph/src/subgraph/subgraphUtils' @@ -61,20 +60,4 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot { return false } - - override draw( - ctx: CanvasRenderingContext2D, - options: Omit - ) { - const { textAlign } = ctx - ctx.textAlign = 'left' - - super.draw(ctx, { - ...options, - labelPosition: LabelPosition.Right, - doStroke: false - }) - - ctx.textAlign = textAlign - } } diff --git a/src/lib/litegraph/src/node/NodeOutputSlot.ts b/src/lib/litegraph/src/node/NodeOutputSlot.ts index a1120dd8ec..6d4cdd869f 100644 --- a/src/lib/litegraph/src/node/NodeOutputSlot.ts +++ b/src/lib/litegraph/src/node/NodeOutputSlot.ts @@ -1,6 +1,5 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import type { LinkId } from '@/lib/litegraph/src/LLink' -import { LabelPosition } from '@/lib/litegraph/src/draw' import type { INodeInputSlot, INodeOutputSlot, @@ -8,7 +7,7 @@ import type { ReadOnlyPoint } from '@/lib/litegraph/src/interfaces' import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' +import { NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput' import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput' import { isSubgraphOutput } from '@/lib/litegraph/src/subgraph/subgraphUtils' @@ -59,22 +58,4 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { override get isConnected(): boolean { return this.links != null && this.links.length > 0 } - - override draw( - ctx: CanvasRenderingContext2D, - options: Omit - ) { - const { textAlign, strokeStyle } = ctx - ctx.textAlign = 'right' - ctx.strokeStyle = 'black' - - super.draw(ctx, { - ...options, - labelPosition: LabelPosition.Left, - doStroke: true - }) - - ctx.textAlign = textAlign - ctx.strokeStyle = strokeStyle - } } diff --git a/src/lib/litegraph/src/node/NodeSlot.ts b/src/lib/litegraph/src/node/NodeSlot.ts index 48f0a443cb..589560f22a 100644 --- a/src/lib/litegraph/src/node/NodeSlot.ts +++ b/src/lib/litegraph/src/node/NodeSlot.ts @@ -1,8 +1,6 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' -import { LabelPosition, SlotShape, SlotType } from '@/lib/litegraph/src/draw' import type { CanvasColour, - DefaultConnectionColors, INodeInputSlot, INodeOutputSlot, INodeSlot, @@ -12,45 +10,15 @@ import type { ReadOnlyPoint } from '@/lib/litegraph/src/interfaces' import { LiteGraph, Rectangle } from '@/lib/litegraph/src/litegraph' -import { getCentre } from '@/lib/litegraph/src/measure' import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput' import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput' -import { - LinkDirection, - RenderShape -} from '@/lib/litegraph/src/types/globalEnums' -import { NodeInputSlot } from './NodeInputSlot' import { SlotBase } from './SlotBase' -export interface IDrawOptions { - colorContext: DefaultConnectionColors - labelPosition?: LabelPosition - lowQuality?: boolean - doStroke?: boolean - highlight?: boolean -} - /** Shared base class for {@link LGraphNode} input and output slots. */ export abstract class NodeSlot extends SlotBase implements INodeSlot { pos?: Point - /** The offset from the parent node to the centre point of this slot. */ - get #centreOffset(): ReadOnlyPoint { - const nodePos = this.node.pos - const { boundingRect } = this - - // Use height; widget input slots may be thinner. - const diameter = boundingRect[3] - - return getCentre([ - boundingRect[0] - nodePos[0], - boundingRect[1] - nodePos[1], - diameter, - diameter - ]) - } - /** The center point of this slot when the node is collapsed. */ abstract get collapsedPos(): ReadOnlyPoint @@ -105,152 +73,4 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { get renderingLabel(): string { return this.label || this.localized_name || this.name || '' } - - draw( - ctx: CanvasRenderingContext2D, - { - colorContext, - labelPosition = LabelPosition.Right, - lowQuality = false, - highlight = false, - doStroke = false - }: IDrawOptions - ) { - // Save the current fillStyle and strokeStyle - const originalFillStyle = ctx.fillStyle - const originalStrokeStyle = ctx.strokeStyle - const originalLineWidth = ctx.lineWidth - - const labelColor = highlight - ? this.highlightColor - : LiteGraph.NODE_TEXT_COLOR - - const pos = this.#centreOffset - const slot_type = this.type - const slot_shape = ( - slot_type === SlotType.Array ? SlotShape.Grid : this.shape - ) as SlotShape - - ctx.beginPath() - let doFill = true - - ctx.fillStyle = this.renderingColor(colorContext) - ctx.lineWidth = 1 - if (slot_type === SlotType.Event || slot_shape === SlotShape.Box) { - ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10) - } else if (slot_shape === 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) - ctx.closePath() - } else if (slot_shape === SlotShape.Grid) { - const gridSize = 3 - const cellSize = 2 - const spacing = 3 - - for (let x = 0; x < gridSize; x++) { - for (let y = 0; y < gridSize; y++) { - ctx.rect( - pos[0] - 4 + x * spacing, - pos[1] - 4 + y * spacing, - cellSize, - cellSize - ) - } - } - doStroke = false - } else { - // Default rendering for circle, hollow circle. - if (lowQuality) { - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8) - } else { - let radius: number - if (slot_shape === SlotShape.HollowCircle) { - doFill = false - doStroke = true - ctx.lineWidth = 3 - ctx.strokeStyle = ctx.fillStyle - radius = highlight ? 4 : 3 - } else { - // Normal circle - radius = highlight ? 5 : 4 - } - ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2) - } - } - - if (doFill) ctx.fill() - if (!lowQuality && doStroke) ctx.stroke() - - // render slot label - const hideLabel = lowQuality || this.isWidgetInputSlot - if (!hideLabel) { - const text = this.renderingLabel - if (text) { - // TODO: Finish impl. Highlight text on mouseover unless we're connecting links. - ctx.fillStyle = labelColor - - if (labelPosition === LabelPosition.Right) { - if (this.dir == LinkDirection.UP) { - ctx.fillText(text, pos[0], pos[1] - 10) - } else { - ctx.fillText(text, pos[0] + 10, pos[1] + 5) - } - } else { - if (this.dir == LinkDirection.DOWN) { - ctx.fillText(text, pos[0], pos[1] - 8) - } else { - ctx.fillText(text, pos[0] - 10, pos[1] + 5) - } - } - } - } - - // Draw a red circle if the slot has errors. - if (this.hasErrors) { - ctx.lineWidth = 2 - ctx.strokeStyle = 'red' - ctx.beginPath() - ctx.arc(pos[0], pos[1], 12, 0, Math.PI * 2) - ctx.stroke() - } - - // Restore the original fillStyle and strokeStyle - ctx.fillStyle = originalFillStyle - ctx.strokeStyle = originalStrokeStyle - ctx.lineWidth = originalLineWidth - } - - drawCollapsed(ctx: CanvasRenderingContext2D) { - const [x, y] = this.collapsedPos - - // Save original styles - const { fillStyle } = ctx - - ctx.fillStyle = '#686' - ctx.beginPath() - - if (this.type === SlotType.Event || this.shape === RenderShape.BOX) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8) - } else if (this.shape === RenderShape.ARROW) { - // Adjust arrow direction based on whether this is an input or output slot - const isInput = this instanceof NodeInputSlot - if (isInput) { - ctx.moveTo(x + 8, y) - ctx.lineTo(x - 4, y - 4) - ctx.lineTo(x - 4, y + 4) - } else { - ctx.moveTo(x + 6, y) - ctx.lineTo(x - 6, y - 4) - ctx.lineTo(x - 6, y + 4) - } - ctx.closePath() - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2) - } - ctx.fill() - - // Restore original styles - ctx.fillStyle = fillStyle - } }