From 817214e6da4e02e907bfc7842f8611e76e04d4de Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Sun, 2 Feb 2025 19:29:26 -0800 Subject: [PATCH] [Refactor] Move strokeShape from LGraphCanvas to draw (#435) * [Refactor] Move strokeShape from LGraphCanvas to draw * Fix round radius * nit * nit * nit --- src/LGraphCanvas.ts | 130 ++++------------------ src/LGraphGroup.ts | 4 +- src/LiteGraphGlobal.ts | 1 + src/draw.ts | 107 +++++++++++++++++- src/litegraph.ts | 1 + test/__snapshots__/litegraph.test.ts.snap | 1 + 6 files changed, 131 insertions(+), 113 deletions(-) diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index b267b6a56..bc06306dd 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -56,7 +56,7 @@ import { isInRect, snapPoint, } from "./measure" -import { drawSlot, LabelPosition } from "./draw" +import { drawSlot, LabelPosition, strokeShape } from "./draw" import { DragAndScale } from "./DragAndScale" import { LinkReleaseContextExtended, LiteGraph, clamp } from "./litegraph" import { stringOrEmpty, stringOrNull } from "./strings" @@ -132,16 +132,6 @@ interface IDialogOptions { onclose?(): void } -interface IDrawSelectionBoundingOptions { - shape?: RenderShape - title_height?: number - title_mode?: TitleMode - colour?: CanvasColour - padding?: number - collapsed?: boolean - thickness?: number -} - /** @inheritdoc {@link LGraphCanvas.state} */ export interface LGraphCanvasState { /** {@link Positionable} items are being dragged on the canvas. */ @@ -313,6 +303,20 @@ export class LGraphCanvas { this.#maximumFrameGap = value > Number.EPSILON ? 1000 / value : 0 } + /** + * @deprecated Use {@link LiteGraphGlobal.ROUND_RADIUS} instead. + */ + get round_radius() { + return LiteGraph.ROUND_RADIUS + } + + /** + * @deprecated Use {@link LiteGraphGlobal.ROUND_RADIUS} instead. + */ + set round_radius(value: number) { + LiteGraph.ROUND_RADIUS = value + } + options: { skip_events?: any viewport?: any @@ -388,7 +392,6 @@ export class LGraphCanvas { /** to render foreground objects (above nodes and connections) in the canvas affected by transform */ onDrawForeground?: (arg0: CanvasRenderingContext2D, arg1: any) => void connections_width: number - round_radius: number /** The current node being drawn by {@link drawNode}. This should NOT be used to determine the currently selected node. See {@link selectedItems} */ current_node: LGraphNode | null /** used for widgets */ @@ -628,7 +631,6 @@ export class LGraphCanvas { this.onAfterChange = null this.connections_width = 3 - this.round_radius = 8 this.current_node = null this.node_widget = null @@ -4534,7 +4536,7 @@ export class LGraphCanvas { const area = node.boundingRect const gap = 3 - const radius = this.round_radius + gap + const radius = LiteGraph.ROUND_RADIUS + gap const x = area[0] - gap const y = area[1] - gap @@ -5191,8 +5193,8 @@ export class LGraphCanvas { area[2], area[3], shape == RenderShape.CARD - ? [this.round_radius, this.round_radius, 0, 0] - : [this.round_radius], + ? [LiteGraph.ROUND_RADIUS, LiteGraph.ROUND_RADIUS, 0, 0] + : [LiteGraph.ROUND_RADIUS], ) } else if (shape == RenderShape.CIRCLE) { ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI * 2) @@ -5200,7 +5202,7 @@ export class LGraphCanvas { ctx.fill() if (node.has_errors && !LiteGraph.use_legacy_node_error_indicator) { - this.strokeShape(ctx, area, { + strokeShape(ctx, area, { shape, title_mode, title_height, @@ -5247,8 +5249,8 @@ export class LGraphCanvas { size[0], title_height, collapsed - ? [this.round_radius] - : [this.round_radius, this.round_radius, 0, 0], + ? [LiteGraph.ROUND_RADIUS] + : [LiteGraph.ROUND_RADIUS, LiteGraph.ROUND_RADIUS, 0, 0], ) } ctx.fill() @@ -5411,7 +5413,7 @@ export class LGraphCanvas { const padding = node.has_errors && !LiteGraph.use_legacy_node_error_indicator ? 20 : undefined - this.strokeShape(ctx, area, { + strokeShape(ctx, area, { shape, title_height, title_mode, @@ -5425,94 +5427,6 @@ export class LGraphCanvas { if (node.action_triggered > 0) node.action_triggered-- } - /** - * Draws only the path of a shape on the canvas, without filling. - * Used to draw indicators for node status, e.g. "selected". - * @param ctx The 2D context to draw on - * @param area The position and size of the shape to render - */ - strokeShape( - ctx: CanvasRenderingContext2D, - area: Rect, - { - /** The shape to render */ - shape = RenderShape.BOX, - /** Shape will extend above the Y-axis 0 by this amount */ - title_height = LiteGraph.NODE_TITLE_HEIGHT, - /** @deprecated This is node-specific: it should be removed entirely, and behaviour defined by the caller more explicitly */ - title_mode = TitleMode.NORMAL_TITLE, - /** The colour that should be drawn */ - colour = LiteGraph.NODE_BOX_OUTLINE_COLOR, - /** The distance between the edge of the {@link area} and the middle of the line */ - padding = 6, - /** @deprecated This is node-specific: it should be removed entirely, and behaviour defined by the caller more explicitly */ - collapsed = false, - /** Thickness of the line drawn (`lineWidth`) */ - thickness = 1, - }: IDrawSelectionBoundingOptions = {}, - ): void { - // Adjust area if title is transparent - if (title_mode === TitleMode.TRANSPARENT_TITLE) { - area[1] -= title_height - area[3] += title_height - } - - // Set up context - const { lineWidth, strokeStyle } = ctx - ctx.lineWidth = thickness - ctx.globalAlpha = 0.8 - ctx.strokeStyle = colour - ctx.beginPath() - - // Draw shape based on type - const [x, y, width, height] = area - switch (shape) { - case RenderShape.BOX: { - ctx.rect( - x - padding, - y - padding, - width + 2 * padding, - height + 2 * padding, - ) - break - } - case RenderShape.ROUND: - case RenderShape.CARD: { - const radius = this.round_radius + padding - const isCollapsed = shape === RenderShape.CARD && collapsed - const cornerRadii = - isCollapsed || shape === RenderShape.ROUND - ? [radius] - : [radius, 2, radius, 2] - ctx.roundRect( - x - padding, - y - padding, - width + 2 * padding, - height + 2 * padding, - cornerRadii, - ) - break - } - case RenderShape.CIRCLE: { - const centerX = x + width / 2 - const centerY = y + height / 2 - const radius = Math.max(width, height) / 2 + padding - ctx.arc(centerX, centerY, radius, 0, Math.PI * 2) - break - } - } - - // Stroke the shape - ctx.stroke() - - // Reset context - ctx.lineWidth = lineWidth - ctx.strokeStyle = strokeStyle - - // TODO: Store and reset value properly. Callers currently expect this behaviour (e.g. muted nodes). - ctx.globalAlpha = 1 - } - /** * Draws a snap guide for a {@link Positionable} item. * diff --git a/src/LGraphGroup.ts b/src/LGraphGroup.ts index cde512c5e..d96c954b1 100644 --- a/src/LGraphGroup.ts +++ b/src/LGraphGroup.ts @@ -18,7 +18,7 @@ import { snapPoint, } from "./measure" import { LGraphNode } from "./LGraphNode" -import { RenderShape, TitleMode } from "./types/globalEnums" +import { strokeShape } from "./draw" export interface IGraphGroupFlags extends Record { pinned?: true @@ -183,7 +183,7 @@ export class LGraphGroup implements Positionable, IPinnable { ctx.fillText(this.title + (this.pinned ? "📌" : ""), x + padding, y + font_size) if (LiteGraph.highlight_selected_group && this.selected) { - graphCanvas.strokeShape(ctx, this._bounding, { + strokeShape(ctx, this._bounding, { title_height: this.titleHeight, padding, }) diff --git a/src/LiteGraphGlobal.ts b/src/LiteGraphGlobal.ts index d5b3b10b9..03cd21164 100644 --- a/src/LiteGraphGlobal.ts +++ b/src/LiteGraphGlobal.ts @@ -76,6 +76,7 @@ export class LiteGraphGlobal { DEFAULT_POSITION = [100, 100] /** ,"circle" */ VALID_SHAPES = ["default", "box", "round", "card"] + ROUND_RADIUS = 8 // shapes are used for nodes but also for slots BOX_SHAPE = RenderShape.BOX diff --git a/src/draw.ts b/src/draw.ts index e198cd148..66bff6f7c 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -1,6 +1,6 @@ -import type { Vector2 } from "./litegraph" -import type { INodeSlot } from "./interfaces" -import { LinkDirection, RenderShape } from "./types/globalEnums" +import { LiteGraph, type Vector2 } from "./litegraph" +import type { CanvasColour, INodeSlot, Rect } from "./interfaces" +import { LinkDirection, RenderShape, TitleMode } from "./types/globalEnums" export enum SlotType { Array = "array", @@ -143,3 +143,104 @@ export function drawSlot( ctx.strokeStyle = originalStrokeStyle ctx.lineWidth = originalLineWidth } + +interface IDrawSelectionBoundingOptions { + shape?: RenderShape + round_radius?: number + title_height?: number + title_mode?: TitleMode + colour?: CanvasColour + padding?: number + collapsed?: boolean + thickness?: number +} + +/** + * Draws only the path of a shape on the canvas, without filling. + * Used to draw indicators for node status, e.g. "selected". + * @param ctx The 2D context to draw on + * @param area The position and size of the shape to render + */ +export function strokeShape( + ctx: CanvasRenderingContext2D, + area: Rect, + { + /** The shape to render */ + shape = RenderShape.BOX, + /** The radius of the rounded corners for {@link RenderShape.ROUND} and {@link RenderShape.CARD} */ + round_radius = LiteGraph.ROUND_RADIUS, + /** Shape will extend above the Y-axis 0 by this amount */ + title_height = LiteGraph.NODE_TITLE_HEIGHT, + /** @deprecated This is node-specific: it should be removed entirely, and behaviour defined by the caller more explicitly */ + title_mode = TitleMode.NORMAL_TITLE, + /** The colour that should be drawn */ + colour = LiteGraph.NODE_BOX_OUTLINE_COLOR, + /** The distance between the edge of the {@link area} and the middle of the line */ + padding = 6, + /** @deprecated This is node-specific: it should be removed entirely, and behaviour defined by the caller more explicitly */ + collapsed = false, + /** Thickness of the line drawn (`lineWidth`) */ + thickness = 1, + }: IDrawSelectionBoundingOptions = {}, +): void { + // Adjust area if title is transparent + if (title_mode === TitleMode.TRANSPARENT_TITLE) { + area[1] -= title_height + area[3] += title_height + } + + // Set up context + const { lineWidth, strokeStyle } = ctx + ctx.lineWidth = thickness + ctx.globalAlpha = 0.8 + ctx.strokeStyle = colour + ctx.beginPath() + + // Draw shape based on type + const [x, y, width, height] = area + switch (shape) { + case RenderShape.BOX: { + ctx.rect( + x - padding, + y - padding, + width + 2 * padding, + height + 2 * padding, + ) + break + } + case RenderShape.ROUND: + case RenderShape.CARD: { + const radius = round_radius + padding + const isCollapsed = shape === RenderShape.CARD && collapsed + const cornerRadii = + isCollapsed || shape === RenderShape.ROUND + ? [radius] + : [radius, 2, radius, 2] + ctx.roundRect( + x - padding, + y - padding, + width + 2 * padding, + height + 2 * padding, + cornerRadii, + ) + break + } + case RenderShape.CIRCLE: { + const centerX = x + width / 2 + const centerY = y + height / 2 + const radius = Math.max(width, height) / 2 + padding + ctx.arc(centerX, centerY, radius, 0, Math.PI * 2) + break + } + } + + // Stroke the shape + ctx.stroke() + + // Reset context + ctx.lineWidth = lineWidth + ctx.strokeStyle = strokeStyle + + // TODO: Store and reset value properly. Callers currently expect this behaviour (e.g. muted nodes). + ctx.globalAlpha = 1 +} diff --git a/src/litegraph.ts b/src/litegraph.ts index 1305073a6..b3aa92e55 100644 --- a/src/litegraph.ts +++ b/src/litegraph.ts @@ -83,6 +83,7 @@ export type { export { CanvasPointer } from "./CanvasPointer" export { Reroute } from "./Reroute" export { createBounds } from "./measure" +export { strokeShape } from "./draw" export function clamp(v: number, a: number, b: number): number { return a > v ? a : b < v ? b : v diff --git a/test/__snapshots__/litegraph.test.ts.snap b/test/__snapshots__/litegraph.test.ts.snap index 496319b06..e292997b2 100644 --- a/test/__snapshots__/litegraph.test.ts.snap +++ b/test/__snapshots__/litegraph.test.ts.snap @@ -88,6 +88,7 @@ LiteGraphGlobal { "ON_TRIGGER": 3, "OUTPUT": 2, "RIGHT": 4, + "ROUND_RADIUS": 8, "ROUND_SHAPE": 2, "Reroute": [Function], "SPLINE_LINK": 2,