Initial Commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Alexandre Pelletier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,38 @@
|
||||
# HALXP-Comfy (ComfyUI Extension)
|
||||
|
||||
 
|
||||
|
||||
**HALXP-Comfy** (High Availability & Layout eXtension Pack) is a professional UI suite for ComfyUI designed to enhance workflow management.
|
||||
|
||||
This extension is the starting point for a growing collection of UI tools. Currently, it features **HALXP Focus**, a smart navigation utility, with more enhancements currently in development.
|
||||
|
||||
**Verified working on:** Legacy UI, ComfyUI V1 (Nodes 2.0), and Portable Distributions.
|
||||
|
||||
## ⚠️ Prerequisite
|
||||
**This extension requires [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) to be installed.**
|
||||
The HALXP UI elements are designed to dock seamlessly alongside the ComfyUI Manager button in the main menu.
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
### 1. HALXP Focus
|
||||
A smart "Focus" button injected into your main menu bar.
|
||||
* **Auto-Follow:** Smoothly pans the camera to the currently executing node, allowing you to watch your workflow progress hands-free.
|
||||
* **Smart Docking:** Automatically detects the ComfyUI Manager button and injects itself seamlessly beside it.
|
||||
* **Visual Feedback:** Features a status indicator (Green dot = Active) so you always know when auto-follow is engaged.
|
||||
* **V1 Compatible:** Works natively with the new Vue.js frontend and PrimeVue menus.
|
||||
|
||||
### 2. More Coming Soon
|
||||
HALXP is built as a modular suite. Expect future updates to include additional layout and debugging tools to further improve your ComfyUI experience.
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Via ComfyUI Manager (Recommended)
|
||||
1. Open ComfyUI Manager.
|
||||
2. Search for `HALXP-Comfy`.
|
||||
3. Install and Restart.
|
||||
|
||||
### Manual Installation
|
||||
1. Navigate to your `ComfyUI/custom_nodes/` directory.
|
||||
2. Clone this repository:
|
||||
```bash
|
||||
git clone [https://github.com/HAL-XP/halxp-comfy](https://github.com/HAL-XP/halxp-comfy)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# HALXP - High Availability & Layout eXtension Pack
|
||||
# Registers the web directory for JS extensions
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
# Export empty dictionaries so ComfyUI doesn't warn about missing mappings
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
__all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
print("\033[34m[HALXP-ComfyUI] JS Extensions Loaded successfully.\033[0m")
|
||||
@@ -0,0 +1,217 @@
|
||||
// \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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user