Added an icon
This commit is contained in:
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
## ✨ 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.
|
||||
@@ -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
Binary file not shown.
|
After Width: | Height: | Size: 791 KiB |
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user