mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 22:59:14 +00:00
Overhaul node computeSize - use actual text width (#962)
### Node resize overhaul - Precisely calculates node minimum size - Prevents input & output overlap - Prevents (normal*) widgets from rendering text over the edge of nodes - Performance impact was sub-millisecond for normal usage in a 500-node graph  _Minimum size for a few example node configurations_ ### Widgets - Converts hard-coded draw render values to class static properties - Adds widget button draw function for left/right arrow widgets _*_ Exception: `control_after_generate`, as it is not a true input / widget. A check may be added later to handle this special case.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<unknown>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user