270 lines
9.9 KiB
JavaScript
270 lines
9.9 KiB
JavaScript
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;
|
|
} |