Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign invalid node indicator #358

Merged
merged 12 commits into from
Nov 29, 2024
8 changes: 4 additions & 4 deletions src/LGraphBadge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export class LGraphBadge {
getWidth(ctx: CanvasRenderingContext2D) {
if (!this.visible) return 0

ctx.save()
const { font } = ctx
ctx.font = `${this.fontSize}px sans-serif`
const textWidth = ctx.measureText(this.text).width
ctx.restore()
ctx.font = font
return textWidth + this.padding * 2
}

Expand All @@ -61,7 +61,7 @@ export class LGraphBadge {
): void {
if (!this.visible) return

ctx.save()
const { fillStyle } = ctx
ctx.font = `${this.fontSize}px sans-serif`
const badgeWidth = this.getWidth(ctx)
const badgeX = 0
Expand All @@ -85,6 +85,6 @@ export class LGraphBadge {
y + this.height - this.padding,
)

ctx.restore()
ctx.fillStyle = fillStyle
}
}
161 changes: 85 additions & 76 deletions src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,10 @@ interface IDrawSelectionBoundingOptions {
shape?: RenderShape
title_height?: number
title_mode?: TitleMode
fgcolor?: CanvasColour
colour?: CanvasColour
padding?: number
collapsed?: boolean
thickness?: number
}

/** @inheritdoc {@link LGraphCanvas.state} */
Expand Down Expand Up @@ -4737,7 +4738,7 @@ export class LGraphCanvas {
this.current_node = node

const color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR
let bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR
const bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR

const low_quality = this.ds.scale < 0.6 // zoomed out
const editor_alpha = this.editor_alpha
Expand Down Expand Up @@ -4790,9 +4791,6 @@ export class LGraphCanvas {
}

// draw shape
if (node.has_errors) {
bgcolor = "red"
}
this.drawNodeShape(
node,
ctx,
Expand All @@ -4808,7 +4806,10 @@ export class LGraphCanvas {

ctx.shadowColor = "transparent"

// draw foreground
// TODO: Legacy behaviour: onDrawForeground received ctx in this state
ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR

// Draw Foreground
node.onDrawForeground?.(ctx, this, this.canvas)

// connection slots
Expand Down Expand Up @@ -5117,13 +5118,14 @@ export class LGraphCanvas {
): void {
// Rendering options
ctx.strokeStyle = fgcolor
ctx.fillStyle = bgcolor
ctx.fillStyle = LiteGraph.use_legacy_node_error_indicator ? "#F00" : bgcolor

const title_height = LiteGraph.NODE_TITLE_HEIGHT
const low_quality = this.ds.scale < 0.5

const { collapsed } = node.flags
const shape = node._shape || node.constructor.shape || RenderShape.ROUND
const title_mode = node.constructor.title_mode
const { title_mode } = node.constructor

const render_title = title_mode == TitleMode.TRANSPARENT_TITLE || title_mode == TitleMode.NO_TITLE
? false
Expand All @@ -5138,39 +5140,48 @@ export class LGraphCanvas {
const old_alpha = ctx.globalAlpha

// Draw node background (shape)
{
ctx.beginPath()
if (shape == RenderShape.BOX || low_quality) {
ctx.fillRect(area[0], area[1], area[2], area[3])
} else if (shape == RenderShape.ROUND || shape == RenderShape.CARD) {
ctx.roundRect(
area[0],
area[1],
area[2],
area[3],
shape == RenderShape.CARD
? [this.round_radius, this.round_radius, 0, 0]
: [this.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)
}
ctx.fill()
ctx.beginPath()
if (shape == RenderShape.BOX || low_quality) {
ctx.fillRect(area[0], area[1], area[2], area[3])
} else if (shape == RenderShape.ROUND || shape == RenderShape.CARD) {
ctx.roundRect(
area[0],
area[1],
area[2],
area[3],
shape == RenderShape.CARD
? [this.round_radius, this.round_radius, 0, 0]
: [this.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)
}
ctx.fill()

// separator
if (!node.flags.collapsed && render_title) {
ctx.shadowColor = "transparent"
ctx.fillStyle = "rgba(0,0,0,0.2)"
ctx.fillRect(0, -1, area[2], 2)
}
if (node.has_errors && !LiteGraph.use_legacy_node_error_indicator) {
this.strokeShape(ctx, area, {
shape,
title_mode,
title_height,
padding: 12,
colour: LiteGraph.NODE_ERROR_COLOUR,
collapsed,
thickness: 10,
})
}

// Separator - title bar <-> body
if (!collapsed && render_title) {
ctx.shadowColor = "transparent"
ctx.fillStyle = "rgba(0,0,0,0.2)"
ctx.fillRect(0, -1, area[2], 2)
}
ctx.shadowColor = "transparent"

node.onDrawBackground?.(ctx, this, this.canvas, this.graph_mouse)

// title bg (remember, it is rendered ABOVE the node)
// Title bar background (remember, it is rendered ABOVE the node)
if (render_title || title_mode == TitleMode.TRANSPARENT_TITLE) {
// title bar
if (node.onDrawTitleBar) {
node.onDrawTitleBar(ctx, title_height, size, this.ds.scale, fgcolor)
} else if (
Expand All @@ -5179,30 +5190,13 @@ export class LGraphCanvas {
) {
const title_color = node.constructor.title_color || fgcolor

if (node.flags.collapsed) {
if (collapsed) {
ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR
}

//* gradient test
if (this.use_gradients) {
// TODO: This feature may not have been completed. Could finish or remove.
// Original impl. may cause CanvasColour to be used as index key. Also, colour requires validation before blindly passing on.
// @ts-expect-error Fix or remove gradient feature
let grad = LGraphCanvas.gradients[title_color]
if (!grad) {
// @ts-expect-error Fix or remove gradient feature
grad = LGraphCanvas.gradients[title_color] =
ctx.createLinearGradient(0, 0, 400, 0)
grad.addColorStop(0, title_color)
grad.addColorStop(1, "#000")
}
ctx.fillStyle = grad
} else {
ctx.fillStyle = title_color
}

// ctx.globalAlpha = 0.5 * old_alpha;
ctx.fillStyle = title_color
ctx.beginPath()

if (shape == RenderShape.BOX || low_quality) {
ctx.rect(0, -title_height, size[0], title_height)
} else if (shape == RenderShape.ROUND || shape == RenderShape.CARD) {
Expand All @@ -5211,7 +5205,7 @@ export class LGraphCanvas {
-title_height,
size[0],
title_height,
node.flags.collapsed
collapsed
? [this.round_radius]
: [this.round_radius, this.round_radius, 0, 0],
)
Expand All @@ -5220,12 +5214,10 @@ export class LGraphCanvas {
ctx.shadowColor = "transparent"
}

let colState: string | boolean = false
if (LiteGraph.node_box_coloured_by_mode) {
if (LiteGraph.NODE_MODES_COLORS[node.mode]) {
colState = LiteGraph.NODE_MODES_COLORS[node.mode]
}
}
let colState = LiteGraph.node_box_coloured_by_mode && LiteGraph.NODE_MODES_COLORS[node.mode]
? LiteGraph.NODE_MODES_COLORS[node.mode]
: false

if (LiteGraph.node_box_coloured_when_on) {
colState = node.action_triggered
? "#FFF"
Expand Down Expand Up @@ -5256,8 +5248,7 @@ export class LGraphCanvas {
ctx.fill()
}

ctx.fillStyle =
node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR
ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR
if (low_quality)
ctx.fillRect(
title_height * 0.5 - box_size * 0.5,
Expand Down Expand Up @@ -5309,14 +5300,15 @@ export class LGraphCanvas {
}
if (!low_quality) {
ctx.font = this.title_text_font
const title = String(node.getTitle()) + (node.pinned ? "📌" : "")
const rawTitle = node.getTitle() ?? `❌ ${node.type}`
const title = String(rawTitle) + (node.pinned ? "📌" : "")
if (title) {
if (selected) {
ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR
} else {
ctx.fillStyle = node.constructor.title_text_color || this.node_title_color
}
if (node.flags.collapsed) {
if (collapsed) {
ctx.textAlign = "left"
// const measure = ctx.measureText(title)
ctx.fillText(
Expand All @@ -5338,7 +5330,7 @@ export class LGraphCanvas {

// subgraph box
if (
!node.flags.collapsed &&
!collapsed &&
node.subgraph &&
!node.skip_subgraph_button
) {
Expand Down Expand Up @@ -5376,11 +5368,13 @@ export class LGraphCanvas {
if (selected) {
node.onBounding?.(area)

this.drawSelectionBounding(ctx, area, {
const padding = node.has_errors && !LiteGraph.use_legacy_node_error_indicator ? 20 : undefined

this.strokeShape(ctx, area, {
shape,
title_height,
title_mode,
fgcolor,
padding,
collapsed: node.flags?.collapsed,
})
}
Expand All @@ -5391,29 +5385,42 @@ export class LGraphCanvas {
}

/**
* Draws the selection bounding of an area.
* 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
*/
drawSelectionBounding(
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,
fgcolor = LiteGraph.NODE_BOX_OUTLINE_COLOR,
/** 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
ctx.lineWidth = 1
const { lineWidth, strokeStyle } = ctx
ctx.lineWidth = thickness
ctx.globalAlpha = 0.8
ctx.strokeStyle = colour
ctx.beginPath()

// Draw shape based on type
Expand All @@ -5430,7 +5437,7 @@ export class LGraphCanvas {
}
case RenderShape.ROUND:
case RenderShape.CARD: {
const radius = this.round_radius * 2
const radius = this.round_radius + padding
const isCollapsed = shape === RenderShape.CARD && collapsed
const cornerRadii =
isCollapsed || shape === RenderShape.ROUND
Expand All @@ -5455,11 +5462,13 @@ export class LGraphCanvas {
}

// Stroke the shape
ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR
ctx.stroke()

// Reset context
ctx.strokeStyle = fgcolor
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeStyle

// TODO: Store and reset value properly. Callers currently expect this behaviour (e.g. muted nodes).
ctx.globalAlpha = 1
}

Expand Down
5 changes: 1 addition & 4 deletions src/LGraphGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,8 @@ 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.drawSelectionBounding(ctx, this._bounding, {
shape: RenderShape.BOX,
graphCanvas.strokeShape(ctx, this._bounding, {
title_height: this.titleHeight,
title_mode: TitleMode.NORMAL_TITLE,
fgcolor: this.color,
padding,
})
}
Expand Down
4 changes: 4 additions & 0 deletions src/LiteGraphGlobal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class LiteGraphGlobal {
NODE_DEFAULT_BOXCOLOR = "#666"
NODE_DEFAULT_SHAPE = "box"
NODE_BOX_OUTLINE_COLOR = "#FFF"
NODE_ERROR_COLOUR = "#E00"
DEFAULT_SHADOW_COLOR = "rgba(0,0,0,0.5)"
DEFAULT_GROUP_FONT = 24
DEFAULT_GROUP_FONT_SIZE?: any
Expand Down Expand Up @@ -252,6 +253,9 @@ export class LiteGraphGlobal {
// Whether to highlight the bounding box of selected groups
highlight_selected_group = true

/** If `true`, the old "eye-melting-red" error indicator will be used for nodes */
use_legacy_node_error_indicator = false

// TODO: Remove legacy accessors
LGraph = LGraph
LLink = LLink
Expand Down
2 changes: 2 additions & 0 deletions test/__snapshots__/litegraph.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ LiteGraphGlobal {
"NODE_DEFAULT_BOXCOLOR": "#666",
"NODE_DEFAULT_COLOR": "#333",
"NODE_DEFAULT_SHAPE": "box",
"NODE_ERROR_COLOUR": "#E00",
"NODE_MIN_WIDTH": 50,
"NODE_MODES": [
"Always",
Expand Down Expand Up @@ -175,6 +176,7 @@ LiteGraphGlobal {
"snap_highlights_node": true,
"snaps_for_comfy": true,
"throw_errors": true,
"use_legacy_node_error_indicator": false,
"use_uuids": false,
}
`;
Loading