Files
rubz 6f8ad8320b fix: complete Nodes 2.0 compatibility for custom widgets, bump to 1.1.1
Simplifies the preset-chip picker (Smart Resize / Smart Batch Resize)
to a single canvas implementation - drops the dual DOM-widget path,
so chips stay positioned correctly under "Longest side (px)" in both
classic and Nodes 2.0 rendering instead of jumping to the bottom of
the node.

Adds a shared setWidgetVisible() helper so dynamic show/hide (Counter
start, the hidden customtext output on every node) actually works
under Nodes 2.0's Vue reactivity - it previously only worked in
classic canvas mode.

Fixes two silent Nodes 2.0 rendering bugs: widgets only repaint via
their own triggerDraw(), not setDirtyCanvas(), so the active preset
chip and status-box text were going visually stale after an update
even though the underlying value was correct. And Nodes 2.0's
WidgetLegacy bridge permanently stamps a widget.width that classic
mode's own click hit-test then reads forever after - once a node had
ever rendered under Nodes 2.0, classic mode silently lost clicks on
the right side of a wide node. Both fixed by keeping widget state in
sync on every draw() instead of trusting it to survive across
renderer switches.

Also centers section-divider labels with flanking lines (was
label-left/line-right) and brightens the line color, which had
become nearly invisible under Nodes 2.0's extra canvas scaling.

README: reorders node sections into actual pipeline order (Prompt
Controller -> Profile Switch -> Resize -> Resolution Multiplier ->
Batch Resize, standalone tool last instead of mid-list), adds a
Nodes 2.0 compatibility screenshot, and refreshes the Smart Batch
Resize screenshot to match its current UI.
2026-08-13 20:45:33 +02:00

100 lines
4.6 KiB
JavaScript

import { app } from "../../../scripts/app.js";
// A single canvas-drawn implementation, used in both classic LiteGraph and
// Nodes 2.0 (via the WidgetLegacy.vue bridge — type: "custom" isn't a
// recognized Vue registry name, so it falls through to the bridge, same as
// the divider/status widgets in canvas_widgets.mjs). No separate DOM-widget
// path: an earlier version used addDOMWidget for Nodes 2.0, but repositioning
// that DOM widget into the middle of node.widgets (to sit under longest_side
// instead of at the end) desynced Vue's value-to-row binding for every widget
// after it on save/reload. Canvas "custom" widgets don't have that problem —
// they've been confirmed to survive reposition + reload fine — so there's no
// need for the dual-path/DOM version this node's click-only (no hover)
// interaction never actually required.
const PRESET_ROW_HEIGHT = 26;
// Hit-test area is padded a few px beyond the visible chip on every side —
// forgiving actual mouse precision, which drifts a little between press and
// release, unlike pixel-perfect synthetic clicks.
const HIT_PAD = 3;
export function createPresetPickerWidget(node, targetWidget, presets) {
const widget = {
name: "size_presets",
type: "custom",
value: null,
serialize: false,
options: {},
_rects: [],
computeSize(width) {
return [width, PRESET_ROW_HEIGHT];
},
draw(ctx, node, widgetWidth, y, height) {
// Read the live node width rather than the (possibly stale-cached)
// widgetWidth parameter — see the comment in canvas_widgets.mjs.
const w = node?.size?.[0] || widgetWidth;
// Nodes 2.0's WidgetLegacy bridge permanently stamps widget.width
// from its own container onto this widget object (and never
// clears it on unmount). Classic LiteGraph's own click hit-test
// (getWidgetOnPos) reads widget.width — not node.size[0] — to
// decide the clickable X range, so once this node has ever been
// painted under Nodes 2.0, classic mode silently loses clicks on
// the right side of a wide node forever. Keep width in sync on
// every draw so classic's hit-test always agrees with reality.
this.width = w;
const margin = 14;
const gap = 7;
const innerW = Math.max(40, w - margin * 2);
const n = presets.length;
const cellW = Math.max(10, (innerW - gap * (n - 1)) / n);
const h = Math.min(height - 2, 22);
const top = y + (height - h) / 2;
const current = targetWidget.value;
this._rects = [];
ctx.save();
ctx.font = "11px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (let i = 0; i < n; i++) {
const x = margin + i * (cellW + gap);
const active = Number(current) === presets[i];
ctx.beginPath();
if (ctx.roundRect) ctx.roundRect(x, top, cellW, h, 4);
else ctx.rect(x, top, cellW, h);
ctx.fillStyle = active ? "#f28f41" : "#2a2a2a";
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = active ? "#f28f41" : "#555";
ctx.stroke();
ctx.fillStyle = active ? "#111" : "#ccc";
ctx.fillText(String(presets[i]), x + cellW / 2, top + h / 2 + 0.5);
this._rects.push({ x1: x, x2: x + cellW, y1: top, y2: top + h, size: presets[i] });
}
ctx.restore();
},
mouse(event, pos, node) {
if (event.type !== "pointerdown" && event.type !== "mousedown") return false;
const [lx, ly] = pos;
for (const r of this._rects) {
if (lx >= r.x1 - HIT_PAD && lx <= r.x2 + HIT_PAD && ly >= r.y1 - HIT_PAD && ly <= r.y2 + HIT_PAD) {
targetWidget.value = r.size;
targetWidget.callback?.(r.size, app.canvas, node);
node.setDirtyCanvas(true, true);
// Nodes 2.0's WidgetLegacy bridge only repaints its canvas
// via the widget's own triggerDraw() (set on it once mounted),
// not via setDirtyCanvas — without this the newly-active chip
// never highlights even though the value did change.
this.triggerDraw?.();
return true;
}
}
return false;
},
};
return node.addCustomWidget(widget);
}