Separated CSS to make it cleaner and more maintainable. Fixed bug only 2 nodes would be displayed.
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
WEB_DIRECTORY = "./web/js"
|
||||
WEB_DIRECTORY = "./web"
|
||||
__all__ = ["WEB_DIRECTORY"]
|
||||
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
/* main dock cotainer */
|
||||
.hud-dock{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
margin-top: 5px;
|
||||
padding: 6px;
|
||||
z-index: 999;
|
||||
|
||||
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; /* Default border */
|
||||
border-radius: 8px;
|
||||
padding: 2px 6px;
|
||||
height: 20px; /* Equivalent to your chipHeight + padding */
|
||||
white-space: nowrap;
|
||||
color: #ccc;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
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.CleanLayoutDock",
|
||||
|
||||
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;
|
||||
// Force a redraw to update visibility immediately
|
||||
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 or empty, remove the dock
|
||||
const stackList = getHiddenNodes(this, app.graph);
|
||||
const shouldShow = this.show_stack_info && stackList.length > 0;
|
||||
|
||||
if (!shouldShow) {
|
||||
if (this.hudElement) {
|
||||
this.hudElement.remove();
|
||||
this.hudElement = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Update the Dock (Position & Content)
|
||||
updateDock(this, stackList, app.graph);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// --- 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];
|
||||
// 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";
|
||||
document.body.appendChild(node.hudElement);
|
||||
node.lastStackSignature = ""; // Init signature
|
||||
}
|
||||
const dock = node.hudElement;
|
||||
|
||||
// 2. Content Update (Optimization: Only if stack changed)
|
||||
// We create a "signature" string of IDs (e.g., "12,15,22")
|
||||
const currentSignature = stackList.map(n => n.id).join(",");
|
||||
|
||||
if (currentSignature !== node.lastStackSignature) {
|
||||
dock.innerHTML = ""; // Clear
|
||||
node.lastStackSignature = currentSignature; // Update signature
|
||||
|
||||
// Build Chips
|
||||
for (const item of stackList) {
|
||||
const chip = document.createElement("div");
|
||||
chip.className = "hud-chip";
|
||||
|
||||
// Determine Border Color based on Mode
|
||||
if (item.mode === 2) chip.style.borderColor = "#888888"; // Muted
|
||||
else if (item.mode === 4) chip.style.borderColor = "#cc66ff"; // Bypassed
|
||||
|
||||
// --- GATHER DOT COLORS ---
|
||||
const leftDots = [];
|
||||
const rightDots = [];
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Outputs
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback Red Dot
|
||||
if (leftDots.length === 0 && rightDots.length === 0) {
|
||||
leftDots.push("#ff4444");
|
||||
}
|
||||
|
||||
// --- BUILD CHIP DOM ---
|
||||
// Left Dots
|
||||
leftDots.forEach(c => {
|
||||
const d = document.createElement("div");
|
||||
d.className = "hud-dot";
|
||||
d.style.backgroundColor = c;
|
||||
chip.appendChild(d);
|
||||
});
|
||||
|
||||
// Text
|
||||
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);
|
||||
|
||||
// Right Dots
|
||||
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)
|
||||
// Map Graph Coordinates -> Screen Coordinates
|
||||
const canvas = app.canvas;
|
||||
const scale = canvas.ds.scale;
|
||||
const offset = canvas.ds.offset;
|
||||
|
||||
// Calculate screen position
|
||||
// (node.pos is [x, y])
|
||||
const screenX = (node.pos[0] + offset[0]) * scale;
|
||||
// Position it at the bottom of the node
|
||||
const screenY = (node.pos[1] + node.size[1] + offset[1]) * scale;
|
||||
|
||||
dock.style.position = "absolute";
|
||||
dock.style.left = screenX + "px";
|
||||
dock.style.top = screenY + "px";
|
||||
dock.style.width = (node.size[0] * scale) + "px";
|
||||
|
||||
// Scale the UI to match the zoom level
|
||||
// (Using transform-origin top-left ensures it scales from the corner)
|
||||
dock.style.transform = `scale(${scale})`;
|
||||
dock.style.transformOrigin = "top left";
|
||||
dock.style.zIndex = "1000"; // Ensure it's on top
|
||||
}
|
||||
Reference in New Issue
Block a user