Added an icon

This commit is contained in:
loficoding
2026-01-24 12:37:52 -07:00
commit 7365e71c5b
10 changed files with 493 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
.tracking
LICENSE.txt
README.md
__init__.py
__pycache__/__init__.cpython-313.pyc
mainuse.gif
pyproject.toml
web/css/hud_styles.css
web/node_hud.js
+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.
+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.
![Demo GIF](mainuse.gif)
## ✨ 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/loficoding/ComfyUI-Node-HUD.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.
+7
View File
@@ -0,0 +1,7 @@
# Mappings required for ComfyUI to acknowledge the node folder
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 791 KiB

+36
View File
@@ -0,0 +1,36 @@
[project]
name = "ComfyUI-Node-HUD"
description = "Adds a smart I/O status dock to stacked nodes for cleaner layouts."
version = "1.2.3"
license = {file = "LICENSE"}
# classifiers = [
# # For OS-independent nodes (works on all operating systems)
# "Operating System :: OS Independent",
#
# # OR for OS-specific nodes, specify the supported systems:
# "Operating System :: Microsoft :: Windows", # Windows specific
# "Operating System :: POSIX :: Linux", # Linux specific
# "Operating System :: MacOS", # macOS specific
#
# # GPU Accelerator support. Pick the ones that are supported by your extension.
# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support
# "Environment :: GPU :: AMD ROCm", # AMD ROCm support
# "Environment :: GPU :: Intel Arc", # Intel Arc support
# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support
# "Environment :: GPU :: Apple Metal", # Apple Metal support
# ]
[project.urls]
Repository = "https://github.com/loficoding/ComfyUI-Node-HUD"
# Used by Comfy Registry https://registry.comfy.org
Documentation = "https://github.com/loficoding/ComfyUI-Node-HUD/wiki"
"Bug Tracker" = "https://github.com/loficoding/ComfyUI-Node-HUD/issues"
[tool.comfy]
PublisherId = "loficoding"
DisplayName = "ComfyUI-Node-HUD"
Icon = "icon.png"
includes = []
# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility
+64
View File
@@ -0,0 +1,64 @@
/* main dock container */
.hud-dock {
/* VISUALS ONLY - NO POSITIONS OR MARGINS */
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
box-sizing: border-box;
pointer-events: none;
/* Decoration */
background-color: rgba(20, 20, 20, 0.85);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 8px;
backdrop-filter: blur(4px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
.hud-dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.5);
transition: transform 0.1s ease;
cursor: help;
}
.hud-dot:hover {
transform: scale(1.3);
border-color: white;
}
.hud-chip {
display: flex;
align-items: center;
gap: 4px;
background-color: #222;
border: 1px solid #444;
border-radius: 8px;
padding: 2px 6px;
height: 20px;
white-space: nowrap;
color: #ccc;
font-family: Arial, sans-serif;
font-size: 10px;
pointer-events: auto;
}
/* --- New Zoomed Out State --- */
.hud-dock-zoomed-out {
/* When chips are hidden, the container would collapse.
We force it to "1 chip height" (approx 20-24px)
so the user still sees the dock exists.
*/
min-height: 24px;
/* Optional: Make it slightly subtler when zoomed out */
background-color: rgba(20, 20, 20, 0.6);
border-color: rgba(255, 255, 255, 0.1);
}
.hud-dock-zoomed-out .hud-chip {
display: none;
}
+270
View File
@@ -0,0 +1,270 @@
import { app } from "../../scripts/app.js";
// 1. Inject CSS
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "extensions/ComfyUI-Node-HUD/css/hud_styles.css";
document.head.appendChild(link);
app.registerExtension({
name: "Comfy.NodeHUD",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// --- A. Toggle Menu Option ---
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;
// Bring node to front of the array so it renders on top of everything
if (this.show_stack_info){
const nodes = app.graph._nodes;
const index = nodes.indexOf(this);
if (index > -1){
nodes.splice(index, 1);
nodes.push(this);
}
}
app.graph.setDirtyCanvas(true, true);
}
});
};
// --- B. The Main Loop (Replaces Draw) ---
// We hook into onDrawForeground to handle Position updates
const onDrawForeground = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function(ctx) {
if (onDrawForeground) onDrawForeground.apply(this, arguments);
// 1. Cleanup: If toggled off, empty, OR COLLAPSED, remove the dock
const stackList = getHiddenNodes(this, app.graph);
// ADDED: Check !this.flags.collapsed so HUD hides when node is collapsed
const shouldShow = this.show_stack_info && stackList.length > 0 && !this.flags.collapsed;
if (!shouldShow) {
if (this.hudElement) {
this.hudElement.remove();
this.hudElement = null;
}
return;
}
// 2. Update the Dock (Position & Content)
updateDock(this, stackList, app.graph);
// If movement is laggy this ensures the HUD follows mouse during drag
if(this.is_selected){
app.graph.setDirtyCanvas(true, false);
}
};
}
});
// --- HELPER: Find Hidden Nodes ---
function getHiddenNodes(parent, graph) {
const stackList = [];
if (!graph || !graph._nodes) return stackList;
const parentIndex = graph._nodes.indexOf(parent);
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 (let i = 0; i < graph._nodes.length; i++) {
const node = graph._nodes[i];
if (node.id === parent.id) continue;
// Only include nodes that are "below" the parent in the stack
if (i > parentIndex) continue;
const nx = node.pos[0];
const ny = node.pos[1];
// Check if center of node is inside parent (simple hit test)
if (nx >= pLeft && nx < pRight && ny >= pTop && ny < pBottom) {
stackList.push(node);
}
}
return stackList;
}
// --- HELPER: Get Link Color ---
function getLinkColor(link) {
if (!link) return "#aaaaff";
const type = link.type;
const colorMap = LGraphCanvas.link_type_colors;
if (colorMap[type]) return colorMap[type];
if (type === "STRING") return "#66ff66";
if (type === "INT") return "#22cc22";
if (type === "FLOAT") return "#22cc22";
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 NEW DOM BUILDER ---
function updateDock(node, stackList, graph) {
// 1. Create Element if missing
if (!node.hudElement) {
node.hudElement = document.createElement("div");
node.hudElement.className = "hud-dock";
// Fix Zooming/Scrolling Issue:
// Intercept wheel events to prevent browser page zoom, but manually trigger canvas zoom.
node.hudElement.addEventListener("wheel", (e) => {
// Stop the event from reaching the window (prevents whole page zoom/scroll)
e.preventDefault();
e.stopPropagation();
// Manually apply zoom to the canvas so the user can still zoom the graph
if (app.canvas && app.canvas.ds) {
// Determine zoom direction and factor (standard LiteGraph logic)
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
app.canvas.ds.changeScale(app.canvas.ds.scale * zoomFactor, [e.clientX, e.clientY]);
app.canvas.setDirty(true, true);
}
}, { passive: false });
// We append to document.body to ensure getBoundingClientRect + scroll math works reliably
document.body.appendChild(node.hudElement);
node.lastStackSignature = "";
}
const dock = node.hudElement;
// ... [Content Update Section remains exactly the same] ...
const currentSignature = stackList.map(n => n.id).join(",");
if (currentSignature !== node.lastStackSignature) {
dock.innerHTML = "";
node.lastStackSignature = currentSignature;
// Build Chips (Keep existing logic)
for (const item of stackList) {
const chip = document.createElement("div");
chip.className = "hud-chip";
if (item.mode === 2) chip.style.borderColor = "#888888";
else if (item.mode === 4) chip.style.borderColor = "#cc66ff";
const leftDots = [];
const rightDots = [];
if (item.inputs) {
for (const input of item.inputs) {
if (input.link) {
const link = graph.links[input.link];
if (link) leftDots.push(getLinkColor(link));
}
}
}
if (item.outputs) {
for (const output of item.outputs) {
if (output.links && output.links.length > 0) {
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 {
if (output.type === "STRING") rightDots.push("#66ff66");
else rightDots.push("#cccccc");
}
}
}
}
if (leftDots.length === 0 && rightDots.length === 0) {
leftDots.push("#ff4444");
}
leftDots.forEach(c => {
const d = document.createElement("div");
d.className = "hud-dot";
d.style.backgroundColor = c;
chip.appendChild(d);
});
const span = document.createElement("span");
let text = item.title || item.type;
if (text.length > 20) text = text.substring(0, 18) + "..";
span.innerText = text;
chip.appendChild(span);
rightDots.forEach(c => {
const d = document.createElement("div");
d.className = "hud-dot";
d.style.backgroundColor = c;
chip.appendChild(d);
});
dock.appendChild(chip);
}
}
// 3. Position Update (Run every frame)
const canvas = app.canvas;
const scale = canvas.ds.scale;
const offset = canvas.ds.offset;
// Get the DOM position of the canvas element itself (fixes window offset issues)
const canvasRect = canvas.canvas.getBoundingClientRect();
const scrollX = window.scrollX || window.pageXOffset;
const scrollY = window.scrollY || window.pageYOffset;
// Graph Coordinates
const graphX = node.pos[0];
const graphY = node.pos[1] + node.size[1] + 2;
// --- CORRECTION: MATCH LITEGRAPH TRANSFORM ---
const screenX = (graphX + offset[0]) * scale + canvasRect.left + scrollX;
const screenY = (graphY + offset[1]) * scale + canvasRect.top + scrollY;
// GEOMETRY ONLY
dock.style.position = "absolute";
dock.style.left = screenX + "px";
dock.style.top = screenY + "px";
dock.style.width = (node.size[0] * scale) + "px";
dock.style.transform = "none";
dock.style.margin = "0";
// --- UPDATES: Zoom Class & Z-Index ---
// 1. Zoom Logic (Around 57% zoom, text usually disappears)
if (scale < 0.58) {
dock.classList.add("hud-dock-zoomed-out");
} else {
dock.classList.remove("hud-dock-zoomed-out");
}
// 2. Z-Index Logic (Refined)
// We base the z-index on the node's position in the graph stack (0, 1, 2...).
// Standard UI widgets (like active text boxes) usually have high z-indices (1000+).
// By keeping this low, we allow other DOM elements to sit on top if needed.
let zIndex = graph._nodes.indexOf(node);
// Safety check: Ensure we don't accidentally get -1 or weird values
if (zIndex < 0) zIndex = 0;
if (node.zIndex !== undefined) {
zIndex = node.zIndex;
} else if (node.style && node.style.zIndex) {
zIndex = node.style.zIndex;
}
// Important: We assign this dynamically every frame to ensure it updates if node order changes
dock.style.zIndex = zIndex;
}