diff --git a/__init__.py b/__init__.py index 49fe65b..d038001 100644 --- a/__init__.py +++ b/__init__.py @@ -1,2 +1,2 @@ -WEB_DIRECTORY = "./web/js" +WEB_DIRECTORY = "./web" __all__ = ["WEB_DIRECTORY"] \ No newline at end of file diff --git a/__pycache__/__init__.cpython-313.pyc b/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..98fd94a Binary files /dev/null and b/__pycache__/__init__.cpython-313.pyc differ diff --git a/web/css/hud_styles.css b/web/css/hud_styles.css new file mode 100644 index 0000000..c14a602 --- /dev/null +++ b/web/css/hud_styles.css @@ -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; +} \ No newline at end of file diff --git a/web/js/node_hud.js b/web/js/node_hud.js deleted file mode 100644 index 9077554..0000000 --- a/web/js/node_hud.js +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/web/node_hud.js b/web/node_hud.js new file mode 100644 index 0000000..1a8601a --- /dev/null +++ b/web/node_hud.js @@ -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 +} \ No newline at end of file