Files
HAL-XP-halxp-comfy/js/halxp-focus.js
T
2025-11-28 17:28:17 +01:00

217 lines
8.2 KiB
JavaScript

// \ComfyUI\custom_nodes\better-focus\js\universal_focus.js
// console.log("%c[HALXP-Comfy-Focus] Loaded", "background: purple; color: white; font-weight: bold;");
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "Comfy.HALXP-ComfyUI.Focus",
async setup() {
// --- CONFIG ---
const SMOOTHNESS = 0.15;
const SETTING_ID = "HALXP-Comfy.Focus.DisplayButton";
let isFocusEnabled = false;
let activeNode = null;
let animationFrameId = null;
let focusBtn = null;
let dot = null;
// --- 0. REGISTER SETTING ---
app.ui.settings.addSetting({
id: SETTING_ID,
name: "Display Focus Button",
type: "boolean",
defaultValue: true,
// MUST be an array.
// Element 0 = Tab Name (HALXP-Comfy)
// Element 1 = Section Header (Focus)
category: ['HALXP-Comfy', 'Focus'],
tooltip: "Show or hide the HALXP Focus button in the top menu.",
onChange: (value) => {
if (value) {
// Try to inject immediately if turned on
injectNextToManager();
} else {
// Remove button and disable focus if turned off
if (focusBtn) {
focusBtn.remove();
// Also force stop focus mode if it was active
if (isFocusEnabled) setFocusState(false);
}
}
}
});
// --- 1. CREATE BUTTON ---
function createButton() {
const btn = document.createElement("button");
btn.textContent = "Focus";
btn.id = "halxp-focus-btn";
// CSS: Flex-safe, visible, matches Manager style
btn.style.cssText = `
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 10px;
margin: 0 4px; /* Space between Focus and Manager */
background-color: var(--comfy-input-bg, #333);
color: var(--fg-color, #fff);
border: 1px solid var(--border-color, #555);
border-radius: 4px;
cursor: pointer;
font-family: sans-serif;
font-size: 13px;
font-weight: 600;
/* CRITICAL VISIBILITY SETTINGS */
flex-shrink: 0;
white-space: nowrap;
z-index: 9999;
pointer-events: auto;
/* DEBUG: REMOVE THIS RED BORDER LATER IF YOU WANT */
border: 1px solid #d3c1c1ff;
`;
const d = document.createElement("span");
d.style.cssText = `
width: 8px; height: 8px; border-radius: 50%;
background-color: #666; margin-right: 8px;
display: inline-block;
flex-shrink: 0;
`;
btn.prepend(d);
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
toggleFocus();
};
dot = d;
return btn;
}
// --- 2. FIND MANAGER & INJECT ---
function injectNextToManager() {
// Check Settings first. If setting is false, do not inject.
const shouldDisplay = app.ui.settings.getSettingValue(SETTING_ID, true);
if (!shouldDisplay) return;
// Stop if we already exist
if (document.getElementById("halxp-focus-btn")) return;
// 1. Find the Manager Button
// We look for any button that strictly contains the text "Manager"
const allButtons = Array.from(document.querySelectorAll("button"));
const managerBtn = allButtons.find(b => {
// Must be visible
if (!b.offsetParent) return false;
const text = b.innerText.trim();
return text === "Manager" || text === "ComfyUI Manager";
});
// 2. Inject
if (managerBtn && managerBtn.parentNode) {
// console.log("[Visual Assist] Found Manager Button. Attaching...");
if (!focusBtn) focusBtn = createButton();
// Match the height of the Manager button exactly
const h = managerBtn.offsetHeight;
if (h > 0) focusBtn.style.height = `${h}px`;
else focusBtn.style.height = "28px";
// Insert BEFORE the Manager button (to its left)
managerBtn.parentNode.insertBefore(focusBtn, managerBtn);
// Restore state appearance if we were somehow already enabled
if (isFocusEnabled) {
dot.style.backgroundColor = "#4CAF50";
dot.style.boxShadow = "0 0 6px #4CAF50";
focusBtn.style.color = "#fff";
focusBtn.style.borderColor = "#4CAF50";
}
return;
}
}
// --- 3. LOOP ---
// We poll every 500ms because the Manager itself loads slowly.
setInterval(injectNextToManager, 500);
// --- 4. FOCUS LOGIC (Standard) ---
function toggleFocus() {
setFocusState(!isFocusEnabled);
}
function setFocusState(enabled) {
isFocusEnabled = enabled;
// Guard: If button doesn't exist (setting off), we can't style it.
if (!focusBtn || !dot) return;
if (isFocusEnabled) {
dot.style.backgroundColor = "#4CAF50";
dot.style.boxShadow = "0 0 6px #4CAF50";
focusBtn.style.color = "#fff";
focusBtn.style.borderColor = "#4CAF50";
if (activeNode) requestAnimationFrame(smoothFollow);
} else {
dot.style.backgroundColor = "#666";
dot.style.boxShadow = "none";
focusBtn.style.color = "var(--fg-color, #ccc)";
focusBtn.style.borderColor = "#d3c1c1ff"; // Back to disabled
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
}
// Listen for canvas drag
const waitForCanvas = setInterval(() => {
if (app.canvas && app.canvas.canvas) {
clearInterval(waitForCanvas);
app.canvas.canvas.addEventListener("pointerdown", () => {
if (isFocusEnabled) setFocusState(false);
}, { capture: true });
}
}, 500);
api.addEventListener("executing", (e) => {
if (e.detail) {
activeNode = app.graph.getNodeById(e.detail);
if (isFocusEnabled && activeNode) {
app.canvas.setDirty(true, true);
if (!animationFrameId) requestAnimationFrame(smoothFollow);
}
}
});
function smoothFollow() {
if (isFocusEnabled && activeNode) {
const canvas = app.canvas;
const ds = canvas.ds;
const screenCenterX = canvas.canvas.clientWidth / 2;
const screenCenterY = canvas.canvas.clientHeight / 2;
const nodeCenterX = activeNode.pos[0] + activeNode.size[0] / 2;
const nodeCenterY = activeNode.pos[1] + activeNode.size[1] / 2;
const targetOffsetX = (screenCenterX / ds.scale) - nodeCenterX;
const targetOffsetY = (screenCenterY / ds.scale) - nodeCenterY;
ds.offset[0] += (targetOffsetX - ds.offset[0]) * SMOOTHNESS;
ds.offset[1] += (targetOffsetY - ds.offset[1]) * SMOOTHNESS;
canvas.setDirty(true, true);
animationFrameId = requestAnimationFrame(smoothFollow);
}
}
}
});