Initial release of Node HUD

This commit is contained in:
loficoding
2026-01-21 16:34:39 -07:00
commit 84149c7886
6 changed files with 569 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+249
View File
@@ -0,0 +1,249 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ComfyUI Node HUD &#x1f6e0;&#xfe0f;</title>
<style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only],
.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only],
.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] {
display: none;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
font-size: 14px;
line-height: 1.6;
}
</style>
<style>
.task-list-item {
list-style-type: none;
}
.task-list-item-checkbox {
margin-left: -20px;
vertical-align: middle;
pointer-events: none;
}
</style>
<style>
:root {
--color-note: #0969da;
--color-tip: #1a7f37;
--color-warning: #9a6700;
--color-severe: #bc4c00;
--color-caution: #d1242f;
--color-important: #8250df;
}
</style>
<style>
@media (prefers-color-scheme: dark) {
:root {
--color-note: #2f81f7;
--color-tip: #3fb950;
--color-warning: #d29922;
--color-severe: #db6d28;
--color-caution: #f85149;
--color-important: #a371f7;
}
}
</style>
<style>
.markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 16px;
color: inherit;
border-left: .25em solid #888;
}
.markdown-alert>:first-child {
margin-top: 0
}
.markdown-alert>:last-child {
margin-bottom: 0
}
.markdown-alert .markdown-alert-title {
display: flex;
font-weight: 500;
align-items: center;
line-height: 1
}
.markdown-alert .markdown-alert-title .octicon {
margin-right: 0.5rem;
display: inline-block;
overflow: visible !important;
vertical-align: text-bottom;
fill: currentColor;
}
.markdown-alert.markdown-alert-note {
border-left-color: var(--color-note);
}
.markdown-alert.markdown-alert-note .markdown-alert-title {
color: var(--color-note);
}
.markdown-alert.markdown-alert-important {
border-left-color: var(--color-important);
}
.markdown-alert.markdown-alert-important .markdown-alert-title {
color: var(--color-important);
}
.markdown-alert.markdown-alert-warning {
border-left-color: var(--color-warning);
}
.markdown-alert.markdown-alert-warning .markdown-alert-title {
color: var(--color-warning);
}
.markdown-alert.markdown-alert-tip {
border-left-color: var(--color-tip);
}
.markdown-alert.markdown-alert-tip .markdown-alert-title {
color: var(--color-tip);
}
.markdown-alert.markdown-alert-caution {
border-left-color: var(--color-caution);
}
.markdown-alert.markdown-alert-caution .markdown-alert-title {
color: var(--color-caution);
}
</style>
</head>
<body class="vscode-body vscode-light">
<h1 id="comfyui-node-hud-️">ComfyUI Node HUD 🛠️</h1>
<p><strong>Stop the spaghetti. Keep the data.</strong></p>
<p>A lightweight UI extension for <a href="https://github.com/comfyanonymous/ComfyUI">ComfyUI</a> that adds a smart &quot;Heads-Up Display&quot; (HUD) to your nodes. It allows you to stack nodes behind one another to clean up your workspace, while still seeing exactly what is happening inside the stack via a sleek, tinted-glass dock.</p>
<p><img src="file:///c:\Users\bthom\OneDrive\Desktop\ComfyUI_windows_portable\ComfyUI\custom_nodes\ComfyUI-Node-HUD\placeholder_for_your_screenshot.png" alt="Main Screenshot"></p>
<h2 id="-features">✨ Features</h2>
<h3 id="1-hiding-nodes">1. Hiding Nodes</h3>
<p>Hide utility nodes (like String Primitives, Notes, or multiple LoRAs) behind a single &quot;Parent&quot; node. Clean Layout Dock detects these hidden nodes and generates a live status bar attached to the bottom of the parent.</p>
<h3 id="2-smart-io-indicators-">2. Smart I/O Indicators 🚦</h3>
<p>No more guessing if a hidden node is connected. The dock displays <strong>Smart Dots</strong> that visualize the data flow:</p>
<ul>
<li><strong>Left Dots (Inputs):</strong> Shows active incoming connections.</li>
<li><strong>Right Dots (Outputs):</strong> Shows active outgoing data.</li>
<li><strong>Color Coded:</strong> The dots match ComfyUI's standard wire colors:
<ul>
<li>🟣 <strong>Purple:</strong> Model</li>
<li>🟡 <strong>Yellow:</strong> CLIP</li>
<li>🔴 <strong>Red:</strong> VAE</li>
<li>🌸 <strong>Pink:</strong> Latent</li>
<li>🔵 <strong>Blue:</strong> Image</li>
<li>🟢 <strong>Green:</strong> String / Text</li>
</ul>
</li>
</ul>
<h3 id="3-live-status-feedback">3. Live Status Feedback</h3>
<p>The dock updates in real-time to reflect the state of your nodes:</p>
<ul>
<li><strong>Active:</strong> Green text / Standard border.</li>
<li><strong>Muted:</strong> Gray border &amp; dimmed text.</li>
<li><strong>Bypassed:</strong> Purple border.</li>
<li><strong>Disconnected:</strong> If a node has no active inputs or outputs, a red error dot 🔴 appears.</li>
</ul>
<h3 id="4-tinted-glass-ui">4. &quot;Tinted Glass&quot; UI</h3>
<p>The dock uses a semi-transparent dark background (85% opacity) to ensure readability without feeling &quot;heavy&quot; or blocking the grid entirely.</p>
<hr>
<h2 id="-installation">📥 Installation</h2>
<h3 id="method-1-manager-coming-soon">Method 1: Manager (Coming Soon)</h3>
<p><em>Once indexed, you can install this via the ComfyUI Manager.</em></p>
<h3 id="method-2-manual-install">Method 2: Manual Install</h3>
<ol>
<li>Navigate to your ComfyUI <code>custom_nodes</code> directory:<pre><code class="language-bash"><span class="hljs-built_in">cd</span> ComfyUI/custom_nodes/
</code></pre>
</li>
<li>Clone this repository:<pre><code class="language-bash">git <span class="hljs-built_in">clone</span> [https://github.com/YOUR_USERNAME/ComfyUI-Clean-Dock.git](https://github.com/YOUR_USERNAME/ComfyUI-Clean-Dock.git)
</code></pre>
</li>
<li><strong>Restart ComfyUI</strong>.</li>
</ol>
<hr>
<h2 id="️-usage">🖱️ Usage</h2>
<ol>
<li><strong>Stack Your Nodes:</strong> Drag utility nodes (like <em>Primitive</em> or <em>Note</em>) physically behind a larger &quot;Parent&quot; node (like <em>Prompt Manager</em> or <em>KSampler</em>).</li>
<li><strong>Activate the Dock:</strong>
<ul>
<li>Right-click the Parent Node.</li>
<li>Select <strong>&quot;Toggle Stack Info&quot;</strong> from the menu.</li>
</ul>
</li>
<li><strong>That's it!</strong> The dock will appear. If you move the nodes out from behind the parent, the dock updates instantly.</li>
</ol>
<hr>
<h2 id="-color-legend">🎨 Color Legend</h2>
<table>
<thead>
<tr>
<th style="text-align:left">Color</th>
<th style="text-align:left">Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">🟣</td>
<td style="text-align:left"><strong>Model</strong> (Checkpoint / LoRA)</td>
</tr>
<tr>
<td style="text-align:left">🟡</td>
<td style="text-align:left"><strong>CLIP</strong> (Conditioning)</td>
</tr>
<tr>
<td style="text-align:left">🔴</td>
<td style="text-align:left"><strong>VAE</strong></td>
</tr>
<tr>
<td style="text-align:left">🌸</td>
<td style="text-align:left"><strong>Latent</strong></td>
</tr>
<tr>
<td style="text-align:left">🔵</td>
<td style="text-align:left"><strong>Image</strong></td>
</tr>
<tr>
<td style="text-align:left">🟢</td>
<td style="text-align:left"><strong>String</strong> / Text</td>
</tr>
<tr>
<td style="text-align:left">🔴 (Solid)</td>
<td style="text-align:left"><strong>Error</strong> / Disconnected</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="-contributing">🤝 Contributing</h2>
<p>This extension is pure JavaScript. Feel free to submit PRs for new features or styling tweaks!</p>
<h2 id="-license">📄 License</h2>
<p>MIT License.</p>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
# ComfyUI Node HUD 🛠️
**Stop the spaghetti. Keep the data.**
A lightweight UI extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that adds a smart "Heads-Up Display" (HUD) to your nodes. It allows you to stack nodes behind one another to clean up your workspace, while still seeing exactly what is happening inside the stack via a sleek, tinted-glass dock.
![Main Screenshot](placeholder_for_your_screenshot.png)
## ✨ Features
### 1. Hiding Nodes
Hide utility nodes (like String Primitives, Notes, or multiple LoRAs) behind a single "Parent" node. Clean Layout Dock detects these hidden nodes and generates a live status bar attached to the bottom of the parent.
### 2. Smart I/O Indicators 🚦
No more guessing if a hidden node is connected. The dock displays **Smart Dots** that visualize the data flow:
* **Left Dots (Inputs):** Shows active incoming connections.
* **Right Dots (Outputs):** Shows active outgoing data.
* **Color Coded:** The dots match ComfyUI's standard wire colors:
* 🟣 **Purple:** Model
* 🟡 **Yellow:** CLIP
* 🔴 **Red:** VAE
* 🌸 **Pink:** Latent
* 🔵 **Blue:** Image
* 🟢 **Green:** String / Text
### 3. Live Status Feedback
The dock updates in real-time to reflect the state of your nodes:
* **Active:** Green text / Standard border.
* **Muted:** Gray border & dimmed text.
* **Bypassed:** Purple border.
* **Disconnected:** If a node has no active inputs or outputs, a red error dot 🔴 appears.
### 4. "Tinted Glass" UI
The dock uses a semi-transparent dark background (85% opacity) to ensure readability without feeling "heavy" or blocking the grid entirely.
---
## 📥 Installation
### Method 1: Manager (Coming Soon)
*Once indexed, you can install this via the ComfyUI Manager.*
### Method 2: Manual Install
1. Navigate to your ComfyUI `custom_nodes` directory:
```bash
cd ComfyUI/custom_nodes/
```
2. Clone this repository:
```bash
git clone [https://github.com/YOUR_USERNAME/ComfyUI-Clean-Dock.git](https://github.com/YOUR_USERNAME/ComfyUI-Clean-Dock.git)
```
3. **Restart ComfyUI**.
---
## 🖱️ Usage
1. **Stack Your Nodes:** Drag utility nodes (like *Primitive* or *Note*) physically behind a larger "Parent" node (like *Prompt Manager* or *KSampler*).
2. **Activate the Dock:**
* Right-click the Parent Node.
* Select **"Toggle Stack Info"** from the menu.
3. **That's it!** The dock will appear. If you move the nodes out from behind the parent, the dock updates instantly.
---
## 🎨 Color Legend
| Color | Meaning |
| :--- | :--- |
| 🟣 | **Model** (Checkpoint / LoRA) |
| 🟡 | **CLIP** (Conditioning) |
| 🔴 | **VAE** |
| 🌸 | **Latent** |
| 🔵 | **Image** |
| 🟢 | **String** / Text |
| 🔴 (Solid) | **Error** / Disconnected |
---
## 🤝 Contributing
This extension is pure JavaScript. Feel free to submit PRs for new features or styling tweaks!
## 📄 License
MIT License.
+2
View File
@@ -0,0 +1,2 @@
WEB_DIRECTORY = "./web/js"
__all__ = ["WEB_DIRECTORY"]
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 791 KiB

+211
View File
@@ -0,0 +1,211 @@
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "Comfy.CleanLayoutDock",
// 1. Toggle Menu
async beforeRegisterNodeDef(nodeType, nodeData, app) {
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(_, options) {
if (getExtraMenuOptions) getExtraMenuOptions.apply(this, arguments);
options.push({
content: "Toggle Stack Info",
callback: () => {
this.show_stack_info = !this.show_stack_info;
app.graph.setDirtyCanvas(true, true);
}
});
};
// 2. Draw Foreground
const onDrawForeground = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function(ctx) {
if (onDrawForeground) onDrawForeground.apply(this, arguments);
if (!this.show_stack_info) return;
const stackList = getHiddenNodes(this, app.graph);
if (stackList.length === 0) return;
ctx.save();
drawDock(this, ctx, stackList, app.graph);
ctx.restore();
};
}
});
// --- HELPER: Find Hidden Nodes ---
function getHiddenNodes(parent, graph) {
const stackList = [];
if (!graph || !graph._nodes) return stackList;
const pLeft = parent.pos[0];
const pRight = parent.pos[0] + parent.size[0];
const pTop = parent.pos[1];
const pBottom = parent.pos[1] + parent.size[1];
for (const node of graph._nodes) {
if (node.id === parent.id) continue;
const nx = node.pos[0];
const ny = node.pos[1];
if (nx >= pLeft && nx < pRight && ny >= pTop && ny < pBottom) {
stackList.push(node);
}
}
return stackList;
}
// --- HELPER: Get Link Color ---
function getLinkColor(link) {
const type = link.type;
const colorMap = LGraphCanvas.link_type_colors;
// 1. Check Comfy Registry first
if (colorMap[type]) return colorMap[type];
// 2. Common Fallbacks
if (type === "STRING") return "#66ff66"; // <--- FIXED: Standard Green
if (type === "INT") return "#22cc22"; // Integer Green
if (type === "FLOAT") return "#22cc22"; // Float Green
if (type === "LATENT") return "#ff99aa";
if (type === "IMAGE") return "#66aaff";
if (type === "MODEL") return "#6666aa";
if (type === "VAE") return "#ff4444";
if (type === "CLIP") return "#ffff66";
if (type === "CONDITIONING") return "#aa6666";
return "#aaaaff"; // The "Lint Blue" Default
}
// --- DRAWER ---
function drawDock(node, ctx, stackList, graph) {
if (!node.size) return;
const dockHeight = 26;
const chipHeight = 16;
const padding = 6;
const dotRadius = 3;
const dotSpacing = 8;
const x = 0;
const y = node.size[1];
/*
// Background
ctx.fillStyle = "#000";
ctx.beginPath();
if (ctx.roundRect) ctx.roundRect(x, y, node.size[0], dockHeight, [0, 0, 6, 6]);
else ctx.rect(x, y, node.size[0], dockHeight);
ctx.fill();
*/
// Background (Tinted Glass Style)
ctx.fillStyle = "rgba(20, 20, 20, 0.85)"; // 85% Opaque Dark Grey
ctx.beginPath();
if (ctx.roundRect) ctx.roundRect(x, y, node.size[0], dockHeight, [0, 0, 6, 6]);
else ctx.rect(x, y, node.size[0], dockHeight);
ctx.fill();
// Optional: Add a subtle glass border for definition
ctx.strokeStyle = "rgba(255, 255, 255, 0.1)";
ctx.lineWidth = 1;
ctx.stroke();
let currentX = x + padding;
ctx.font = "10px Arial";
ctx.textBaseline = "middle";
for (const item of stackList) {
// 1. Gather Dots
const leftDots = [];
const rightDots = [];
// Check Inputs
if (item.inputs) {
for (const input of item.inputs) {
if (input.link) {
const link = graph.links[input.link];
if (link) leftDots.push(getLinkColor(link));
}
}
}
// Check Outputs
if (item.outputs) {
for (const output of item.outputs) {
if (output.links && output.links.length > 0) {
// Use color of first link
const link = graph.links[output.links[0]];
if (link) rightDots.push(getLinkColor(link));
else if (LGraphCanvas.link_type_colors[output.type]) {
rightDots.push(LGraphCanvas.link_type_colors[output.type]);
} else {
// Manual check for disconnected outputs that usually have colors
if (output.type === "STRING") rightDots.push("#66ff66");
else rightDots.push("#cccccc");
}
}
}
}
// Fallback: Disconnected
if (leftDots.length === 0 && rightDots.length === 0) {
leftDots.push("#ff4444"); // Red error dot
}
// 2. Calculate Size
let text = item.title || item.type;
if (text.length > 20) text = text.substring(0, 18) + "..";
const textWidth = ctx.measureText(text).width;
const leftSpace = 6 + (leftDots.length * dotSpacing);
const rightSpace = 6 + (rightDots.length * dotSpacing);
const chipWidth = leftSpace + textWidth + rightSpace;
if (currentX + chipWidth > node.size[0]) break;
// 3. Determine Border Color
let borderColor = "#444";
if (item.mode === 2) borderColor = "#888888"; // Mute
else if (item.mode === 4) borderColor = "#cc66ff"; // Bypass
const centerY = y + (dockHeight/2);
// 4. Draw Chip
ctx.fillStyle = "#222";
ctx.strokeStyle = borderColor;
ctx.lineWidth = 1;
ctx.beginPath();
if (ctx.roundRect) ctx.roundRect(currentX, y + (dockHeight - chipHeight)/2, chipWidth, chipHeight, 8);
else ctx.rect(currentX, y + (dockHeight - chipHeight)/2, chipWidth, chipHeight);
ctx.fill();
ctx.stroke();
// 5. Draw Left Dots
let dotX = currentX + 6;
for (const color of leftDots) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(dotX + dotRadius, centerY, dotRadius, 0, Math.PI * 2);
ctx.fill();
dotX += dotSpacing;
}
// 6. Draw Text
ctx.fillStyle = "#ccc";
ctx.fillText(text, currentX + leftSpace, centerY);
// 7. Draw Right Dots
dotX = currentX + leftSpace + textWidth + 6;
for (const color of rightDots) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(dotX + dotRadius, centerY, dotRadius, 0, Math.PI * 2);
ctx.fill();
dotX += dotSpacing;
}
currentX += chipWidth + padding;
}
}