154 lines
6.5 KiB
JavaScript
154 lines
6.5 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { api } from "../../scripts/api.js";
|
|
|
|
app.registerExtension({
|
|
name: "Comfy.HALXP.Focus",
|
|
async setup() {
|
|
// --- CONSTANTS ---
|
|
const SMOOTHNESS = 0.15;
|
|
// Clean IDs
|
|
const SETTING_DISPLAY_ID = "HALXP.Focus.DisplayButton";
|
|
const SETTING_AUTO_ID = "HALXP.Focus.AutoEnable";
|
|
|
|
// --- STATE ---
|
|
let isFocusEnabled = false;
|
|
let isWorkflowRunning = false;
|
|
let activeNode = null;
|
|
let animationFrameId = null;
|
|
let focusBtn = null;
|
|
let dot = null;
|
|
|
|
// --- 1. REGISTER SETTINGS ---
|
|
app.ui.settings.addSetting({
|
|
id: SETTING_DISPLAY_ID,
|
|
name: "Display Focus Button",
|
|
type: "boolean",
|
|
defaultValue: true,
|
|
// 3rd element 'Display' ensures uniqueness
|
|
category: ['HALXP-Comfy', 'Focus', 'Display'],
|
|
tooltip: "Show or hide the HALXP Focus button in the top menu.",
|
|
onChange: (value) => {
|
|
if (value) injectNextToManager();
|
|
else {
|
|
if (focusBtn) focusBtn.remove();
|
|
if (isFocusEnabled) setFocusState(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.ui.settings.addSetting({
|
|
id: SETTING_AUTO_ID,
|
|
name: "Auto-Focus on Run",
|
|
type: "boolean",
|
|
defaultValue: true,
|
|
// 3rd element 'Auto' ensures uniqueness
|
|
category: ['HALXP-Comfy', 'Focus', 'Auto'],
|
|
tooltip: "Automatically enable Focus mode when a workflow starts executing.",
|
|
});
|
|
|
|
// --- 2. BUTTON LOGIC ---
|
|
function createButton() {
|
|
const btn = document.createElement("button");
|
|
btn.textContent = "Focus";
|
|
btn.id = "halxp-focus-btn";
|
|
btn.style.cssText = `
|
|
position: relative; display: inline-flex; align-items: center; justify-content: center;
|
|
padding: 0 10px; margin: 0 4px; 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; flex-shrink: 0; white-space: nowrap; z-index: 9999; pointer-events: auto;
|
|
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;
|
|
}
|
|
|
|
function injectNextToManager() {
|
|
const shouldDisplay = app.ui.settings.getSettingValue(SETTING_DISPLAY_ID, true);
|
|
if (!shouldDisplay || document.getElementById("halxp-focus-btn")) return;
|
|
|
|
const allButtons = Array.from(document.querySelectorAll("button"));
|
|
const managerBtn = allButtons.find(b => {
|
|
if (!b.offsetParent) return false;
|
|
const text = b.innerText.trim();
|
|
return text === "Manager" || text === "ComfyUI Manager";
|
|
});
|
|
|
|
if (managerBtn && managerBtn.parentNode) {
|
|
if (!focusBtn) focusBtn = createButton();
|
|
const h = managerBtn.offsetHeight;
|
|
focusBtn.style.height = h > 0 ? `${h}px` : "28px";
|
|
managerBtn.parentNode.insertBefore(focusBtn, managerBtn);
|
|
if (isFocusEnabled) updateButtonStyle(true);
|
|
}
|
|
}
|
|
setInterval(injectNextToManager, 500);
|
|
|
|
// --- 3. FOCUS CORE LOGIC ---
|
|
function toggleFocus() { setFocusState(!isFocusEnabled); }
|
|
|
|
function setFocusState(enabled) {
|
|
isFocusEnabled = enabled;
|
|
if (focusBtn && dot) updateButtonStyle(enabled);
|
|
if (enabled && activeNode) requestAnimationFrame(smoothFollow);
|
|
else if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; }
|
|
}
|
|
|
|
function updateButtonStyle(enabled) {
|
|
if (!dot || !focusBtn) return;
|
|
if (enabled) {
|
|
dot.style.backgroundColor = "#4CAF50"; dot.style.boxShadow = "0 0 6px #4CAF50";
|
|
focusBtn.style.color = "#fff"; focusBtn.style.borderColor = "#4CAF50";
|
|
} else {
|
|
dot.style.backgroundColor = "#666"; dot.style.boxShadow = "none";
|
|
focusBtn.style.color = "var(--fg-color, #ccc)"; focusBtn.style.borderColor = "#d3c1c1ff";
|
|
}
|
|
}
|
|
|
|
// Disable on manual drag
|
|
const waitForCanvas = setInterval(() => {
|
|
if (app.canvas && app.canvas.canvas) {
|
|
clearInterval(waitForCanvas);
|
|
app.canvas.canvas.addEventListener("pointerdown", () => {
|
|
if (isFocusEnabled) setFocusState(false);
|
|
}, { capture: true });
|
|
}
|
|
}, 500);
|
|
|
|
// --- 4. EXECUTION LOOP ---
|
|
api.addEventListener("executing", (e) => {
|
|
if (e.detail) {
|
|
if (!isWorkflowRunning) {
|
|
isWorkflowRunning = true;
|
|
if (app.ui.settings.getSettingValue(SETTING_AUTO_ID, true)) setFocusState(true);
|
|
}
|
|
activeNode = app.graph.getNodeById(e.detail);
|
|
if (isFocusEnabled && activeNode) {
|
|
app.canvas.setDirty(true, true);
|
|
if (!animationFrameId) requestAnimationFrame(smoothFollow);
|
|
}
|
|
} else {
|
|
isWorkflowRunning = false;
|
|
activeNode = null;
|
|
}
|
|
});
|
|
|
|
function smoothFollow() {
|
|
if (isFocusEnabled && activeNode) {
|
|
const canvas = app.canvas;
|
|
const ds = canvas.ds;
|
|
const center = [canvas.canvas.clientWidth / 2, canvas.canvas.clientHeight / 2];
|
|
const nodeCenter = [activeNode.pos[0] + activeNode.size[0] / 2, activeNode.pos[1] + activeNode.size[1] / 2];
|
|
const target = [(center[0] / ds.scale) - nodeCenter[0], (center[1] / ds.scale) - nodeCenter[1]];
|
|
ds.offset[0] += (target[0] - ds.offset[0]) * SMOOTHNESS;
|
|
ds.offset[1] += (target[1] - ds.offset[1]) * SMOOTHNESS;
|
|
canvas.setDirty(true, true);
|
|
animationFrameId = requestAnimationFrame(smoothFollow);
|
|
}
|
|
}
|
|
}
|
|
}); |