diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 3efd28044..c5dfd1972 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -72,6 +72,7 @@ import { import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange" import { findFirstNode, getAllNestedItems } from "./utils/collections" import { toClass } from "./utils/type" +import { BaseWidget } from "./widgets/BaseWidget" import { WIDGET_TYPE_MAP } from "./widgets/widgetMap" interface IShowSearchOptions { @@ -240,6 +241,11 @@ export class LGraphCanvas implements ConnectionColorContext { black: { color: "#222", bgcolor: "#000", groupcolor: "#444" }, } + /** + * @internal Exclusively a workaround for design limitation in {@link LGraphNode.computeSize}. + */ + static _measureText?: (text: string, fontStyle?: string) => number + /** * The state of this canvas, e.g. whether it is being dragged, or read-only. * @@ -742,6 +748,17 @@ export class LGraphCanvas implements ConnectionColorContext { this.setCanvas(canvas, options.skip_events) this.clear() + LGraphCanvas._measureText = (text: string, fontStyle = this.inner_text_font) => { + const { ctx } = this + const { font } = ctx + try { + ctx.font = fontStyle + return ctx.measureText(text).width + } finally { + ctx.font = font + } + } + if (!options.skip_render) { this.startRendering() } @@ -4041,7 +4058,7 @@ export class LGraphCanvas implements ConnectionColorContext { } else { // Regular widget, probably ctx.roundRect( - nodeX + 15, + nodeX + BaseWidget.margin, nodeY + overWidget.y, overWidget.width ?? area[2], height, diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index f0a5c95b3..e97cee606 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -45,6 +45,7 @@ import { import { findFreeSlotOfType } from "./utils/collections" import { distributeSpace } from "./utils/spaceDistribution" import { toClass } from "./utils/type" +import { BaseWidget } from "./widgets/BaseWidget" import { WIDGET_TYPE_MAP } from "./widgets/widgetMap" // #region Types @@ -1529,7 +1530,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { const ctorSize = this.constructor.size if (ctorSize) return [ctorSize[0], ctorSize[1]] - const { inputs, outputs } = this + const { inputs, outputs, widgets } = this let rows = Math.max( inputs ? inputs.filter(input => !isWidgetInputSlot(input)).length : 1, outputs ? outputs.length : 1, @@ -1539,45 +1540,66 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { // although it should be graphcanvas.inner_text_font size const font_size = LiteGraph.NODE_TEXT_SIZE - const title_width = compute_text_size(this.title) + const padLeft = LiteGraph.NODE_TITLE_HEIGHT + const padRight = padLeft * 0.33 + const title_width = padLeft + compute_text_size(this.title, this.titleFontStyle) + padRight let input_width = 0 + let widgetWidth = 0 let output_width = 0 if (inputs) { for (const input of inputs) { const text = input.label || input.localized_name || input.name || "" - const text_width = compute_text_size(text) - if (input_width < text_width) - input_width = text_width + const text_width = compute_text_size(text, this.innerFontStyle) + if (isWidgetInputSlot(input)) { + const widget = this.getWidgetFromSlot(input) + if (widget && !this.isWidgetVisible(widget)) continue + + if (text_width > widgetWidth) widgetWidth = text_width + } else { + if (text_width > input_width) input_width = text_width + } } } if (outputs) { for (const output of outputs) { const text = output.label || output.localized_name || output.name || "" - const text_width = compute_text_size(text) + const text_width = compute_text_size(text, this.innerFontStyle) if (output_width < text_width) output_width = text_width } } - size[0] = Math.max(input_width + output_width + 10, title_width) - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH) - if (this.widgets?.length) - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5) + const minWidth = LiteGraph.NODE_WIDTH * (widgets?.length ? 1.5 : 1) + // Text + slot width + centre padding + const centrePadding = input_width && output_width ? 5 : 0 + const slotsWidth = input_width + output_width + (2 * LiteGraph.NODE_SLOT_HEIGHT) + centrePadding + // Total distance from edge of node to the inner edge of the widget 'previous' arrow button + const widgetMargin = BaseWidget.margin + BaseWidget.arrowMargin + BaseWidget.arrowWidth + const widgetPadding = BaseWidget.minValueWidth + (2 * widgetMargin) + if (widgetWidth) widgetWidth += widgetPadding + + size[0] = Math.max(slotsWidth, widgetWidth, title_width, minWidth) size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT + // Get widget height & expand size if necessary let widgets_height = 0 - if (this.widgets?.length) { - for (const widget of this.widgets) { + if (widgets?.length) { + for (const widget of widgets) { if (!this.isWidgetVisible(widget)) continue let widget_height = 0 if (widget.computeSize) { widget_height += widget.computeSize(size[0])[1] } else if (widget.computeLayoutSize) { - widget_height += widget.computeLayoutSize(this).minHeight + // Expand widget width if necessary + const { minHeight, minWidth } = widget.computeLayoutSize(this) + const widgetWidth = minWidth + widgetPadding + if (widgetWidth > size[0]) size[0] = widgetWidth + + widget_height += minHeight } else { widget_height += LiteGraph.NODE_WIDGET_HEIGHT } @@ -1594,10 +1616,9 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { else size[1] += widgets_height - function compute_text_size(text: string) { - return text - ? font_size * text.length * 0.6 - : 0 + function compute_text_size(text: string, fontStyle: string) { + return LGraphCanvas._measureText?.(text, fontStyle) ?? + font_size * (text?.length ?? 0) * 0.6 } if (this.constructor.min_height && size[1] < this.constructor.min_height) { @@ -3375,7 +3396,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { const show_text = !lowQuality ctx.save() ctx.globalAlpha = editorAlpha - const margin = 15 + const { margin } = BaseWidget for (const w of widgets) { if (!this.isWidgetVisible(w)) continue diff --git a/src/widgets/BaseWidget.ts b/src/widgets/BaseWidget.ts index 0fb59d981..5de0b4e40 100644 --- a/src/widgets/BaseWidget.ts +++ b/src/widgets/BaseWidget.ts @@ -13,6 +13,15 @@ export interface DrawWidgetOptions { } export abstract class BaseWidget implements IBaseWidget { + /** From node edge to widget edge */ + static margin = 15 + /** From widget edge to tip of arrow button */ + static arrowMargin = 6 + /** Arrow button width */ + static arrowWidth = 10 + /** Absolute minimum display width of widget values */ + static minValueWidth = 42 + linkedWidgets?: IWidget[] name: string options: IWidgetOptions @@ -82,6 +91,28 @@ export abstract class BaseWidget implements IBaseWidget { */ abstract drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void + drawArrowButtons(ctx: CanvasRenderingContext2D, margin: number, y: number, width: number) { + const { height } = this + const { arrowMargin, arrowWidth } = BaseWidget + const arrowTipX = margin + arrowMargin + const arrowInnerX = arrowTipX + arrowWidth + + // Draw left arrow + ctx.fillStyle = this.text_color + ctx.beginPath() + ctx.moveTo(arrowInnerX, y + 5) + ctx.lineTo(arrowTipX, y + height * 0.5) + ctx.lineTo(arrowInnerX, y + height - 5) + ctx.fill() + + // Draw right arrow + ctx.beginPath() + ctx.moveTo(width - arrowInnerX, y + 5) + ctx.lineTo(width - arrowTipX, y + height * 0.5) + ctx.lineTo(width - arrowInnerX, y + height - 5) + ctx.fill() + } + /** * Handles the click event for the widget * @param options The options for handling the click event diff --git a/src/widgets/BooleanWidget.ts b/src/widgets/BooleanWidget.ts index e87cc684e..dd23943c1 100644 --- a/src/widgets/BooleanWidget.ts +++ b/src/widgets/BooleanWidget.ts @@ -20,7 +20,7 @@ export class BooleanWidget extends BaseWidget implements IBooleanWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions) { const { height } = this diff --git a/src/widgets/ButtonWidget.ts b/src/widgets/ButtonWidget.ts index 8685056e7..b081fd676 100644 --- a/src/widgets/ButtonWidget.ts +++ b/src/widgets/ButtonWidget.ts @@ -27,7 +27,7 @@ export class ButtonWidget extends BaseWidget implements IButtonWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions) { // Store original context attributes const originalTextAlign = ctx.textAlign diff --git a/src/widgets/ComboWidget.ts b/src/widgets/ComboWidget.ts index ddc7b7b76..043d945c7 100644 --- a/src/widgets/ComboWidget.ts +++ b/src/widgets/ComboWidget.ts @@ -28,7 +28,7 @@ export class ComboWidget extends BaseWidget implements IComboWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions) { // Store original context attributes const originalTextAlign = ctx.textAlign @@ -51,19 +51,7 @@ export class ComboWidget extends BaseWidget implements IComboWidget { if (show_text) { if (!this.computedDisabled) { ctx.stroke() - // Draw left arrow - ctx.fillStyle = this.text_color - ctx.beginPath() - ctx.moveTo(margin + 16, y + 5) - ctx.lineTo(margin + 6, y + height * 0.5) - ctx.lineTo(margin + 16, y + height - 5) - ctx.fill() - // Draw right arrow - ctx.beginPath() - ctx.moveTo(width - margin - 16, y + 5) - ctx.lineTo(width - margin - 6, y + height * 0.5) - ctx.lineTo(width - margin - 16, y + height - 5) - ctx.fill() + this.drawArrowButtons(ctx, margin, y, width) } // Draw label diff --git a/src/widgets/KnobWidget.ts b/src/widgets/KnobWidget.ts index f01b5e861..95509f01a 100644 --- a/src/widgets/KnobWidget.ts +++ b/src/widgets/KnobWidget.ts @@ -49,7 +49,7 @@ export class KnobWidget extends BaseWidget implements IKnobWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions, ): void { // Store original context attributes diff --git a/src/widgets/NumberWidget.ts b/src/widgets/NumberWidget.ts index 9a95a51e0..fd1389aec 100644 --- a/src/widgets/NumberWidget.ts +++ b/src/widgets/NumberWidget.ts @@ -43,7 +43,7 @@ export class NumberWidget extends BaseWidget implements INumericWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions) { // Store original context attributes const originalTextAlign = ctx.textAlign @@ -66,19 +66,7 @@ export class NumberWidget extends BaseWidget implements INumericWidget { if (show_text) { if (!this.computedDisabled) { ctx.stroke() - // Draw left arrow - ctx.fillStyle = this.text_color - ctx.beginPath() - ctx.moveTo(margin + 16, y + 5) - ctx.lineTo(margin + 6, y + height * 0.5) - ctx.lineTo(margin + 16, y + height - 5) - ctx.fill() - // Draw right arrow - ctx.beginPath() - ctx.moveTo(width - margin - 16, y + 5) - ctx.lineTo(width - margin - 6, y + height * 0.5) - ctx.lineTo(width - margin - 16, y + height - 5) - ctx.fill() + this.drawArrowButtons(ctx, margin, y, width) } // Draw label diff --git a/src/widgets/SliderWidget.ts b/src/widgets/SliderWidget.ts index 3c04f2260..e68f4d75e 100644 --- a/src/widgets/SliderWidget.ts +++ b/src/widgets/SliderWidget.ts @@ -31,7 +31,7 @@ export class SliderWidget extends BaseWidget implements ISliderWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions) { // Store original context attributes const originalTextAlign = ctx.textAlign diff --git a/src/widgets/TextWidget.ts b/src/widgets/TextWidget.ts index 41f097136..48805c046 100644 --- a/src/widgets/TextWidget.ts +++ b/src/widgets/TextWidget.ts @@ -26,7 +26,7 @@ export class TextWidget extends BaseWidget implements IStringWidget { y, width, show_text = true, - margin = 15, + margin = BaseWidget.margin, }: DrawWidgetOptions) { // Store original context attributes const originalTextAlign = ctx.textAlign