Files
IxMxAMAR-ComfyUI-Utility-Me…/web/wire-engine.js
T
IxMxAMAR 5afe8e7eb1 release(v0.2.0): wires + 4 themes + category accents + group/grid sync + cache
Wire theming:
- per-theme wirePalette mutates app.canvas.default_connection_color_byType
- wire render-mode dropdown (straight/linear/bezier/manhattan)
- wire thickness + opacity settings via one-time renderLink wrap
- preserves ComfyUI's native execution-flow dots

4 new themes (15 total):
- Blueprint (cyanotype schematic, cached grid)
- Nord (frosted arctic)
- Synthwave (80s retro-futurism)
- E-Ink (monochrome with cached noise overlay)

Per-node-category accent stripes:
- themes declare categoryAccents map keyed by node.constructor.category
- 4-px colored bar on left edge of each node, matched by first path segment

Group colors + canvas bg sync:
- themes declare groupColors + bgGridColor; engine applies and restores

Performance:
- OffscreenCanvas pattern cache for static expensive draws (retro scanlines,
  blueprint grid, e-ink noise). O(area) -> O(1) per frame after first render.
- wire-engine fast path: zero overhead when thickness=1.0 and opacity=1.0

Theme schema bumped to v2; v1 themes still load (new fields just unused).

Tests: 340 Python + 33 JS (was 26).
2026-05-13 22:21:20 +05:30

112 lines
3.9 KiB
JavaScript

/**
* Wire (link/connection) theming.
*
* Three concerns:
* 1. Per-data-type color palette: mutate ComfyUI's `app.canvas.default_connection_color_byType`
* when a theme has a `wirePalette`. Save originals so we can restore.
* 2. Render mode (bezier/straight/linear/manhattan): set `app.canvas.links_render_mode` directly.
* 3. Thickness/opacity: wrap `LGraphCanvas.prototype.renderLink` once, read live settings.
*
* We deliberately do NOT add an idle animation loop (would force per-frame
* `setDirty` calls and drain battery). ComfyUI's native execution-flow dots
* are preserved via the wrap calling the original `renderLink`.
*
* Verified against existing packs:
* - ComfyUI-Custom-Scripts mutates `app.canvas.links_render_mode`.
* - ComfyUI-KJNodes mutates `default_connection_color_byType` and wraps `renderLink`.
*/
let _originalColorByType = null;
let _renderLinkPatched = false;
let _wireState = {
// Live values read by the wrapped renderLink on every call.
// Mutate these when settings change; no need to re-wrap.
thickness: 1.0, // multiplier on ctx.lineWidth (1.0 = LiteGraph default)
opacity: 1.0, // ctx.globalAlpha multiplier
forcedColor: null, // if set, override the per-type color
};
export function applyWirePalette(app, palette) {
if (!app?.canvas) return;
if (!_originalColorByType) {
// Snapshot the original color map ONCE so we can restore later.
_originalColorByType = { ...(app.canvas.default_connection_color_byType || {}) };
}
if (!palette) {
// Restore originals
if (_originalColorByType) {
app.canvas.default_connection_color_byType = { ..._originalColorByType };
}
_wireState.forcedColor = null;
return;
}
// Apply theme palette. Unknown types keep the original color.
app.canvas.default_connection_color_byType = {
..._originalColorByType,
...palette,
};
_wireState.forcedColor = palette.default ?? null;
}
/**
* Set the link render mode globally.
* 1 = Straight, 2 = Linear, 3 = Spline/Bezier, 4 = L-Shape (ComfyUI Manhattan).
* null = leave ComfyUI's user setting alone.
*/
export function applyWireRenderMode(app, mode) {
if (!app?.canvas) return;
if (mode !== null && mode !== undefined) {
app.canvas.links_render_mode = Number(mode);
app.canvas.setDirty?.(true, true);
}
}
export function setWireThickness(multiplier) {
_wireState.thickness = Number(multiplier) || 1.0;
}
export function setWireOpacity(alpha) {
_wireState.opacity = Math.max(0, Math.min(1, Number(alpha) || 1.0));
}
/**
* Install a one-time wrapper around LGraphCanvas.prototype.renderLink that
* applies the live thickness/opacity settings. Safe against repeated calls.
*/
export function ensureRenderLinkWrapped(LGraphCanvas) {
if (_renderLinkPatched || !LGraphCanvas?.prototype?.renderLink) return;
const original = LGraphCanvas.prototype.renderLink;
LGraphCanvas.prototype.renderLink = function (ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublines) {
// Fast path: no theme changes
if (_wireState.thickness === 1.0 && _wireState.opacity === 1.0) {
return original.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublines);
}
ctx.save();
if (_wireState.thickness !== 1.0) {
// Multiply lineWidth before original draws. LiteGraph reads ctx.lineWidth
// when it calls stroke(), so this scales the wire.
ctx.lineWidth = (ctx.lineWidth || 2) * _wireState.thickness;
}
if (_wireState.opacity !== 1.0) {
ctx.globalAlpha = (ctx.globalAlpha ?? 1) * _wireState.opacity;
}
const result = original.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublines);
ctx.restore();
return result;
};
_renderLinkPatched = true;
}
/** For tests only. */
export function _resetForTests() {
_originalColorByType = null;
_renderLinkPatched = false;
_wireState = { thickness: 1.0, opacity: 1.0, forcedColor: null };
}