Files
Dontdrunk-ComfyUI-DD-Nodes/extensions/Intelligent_Layout/LayoutExt.js
T
171696257 f374869b32 界面布局工具适配 Nodes 2.0 节点系统
选中项改为读取 canvas.selectedItems(Set),并保留 selected_nodes / is_selected 回退

节点尺寸与位置改用 setSize()/setPos() 写入 layout store,旧版自动回退数组写入

修复主题重绘时控制区重复创建,导致面板底部空白、点击落到已移除元素的问题

修复 showAtMousePosition 中使用浏览器不存在的 require() 导致定位异常

面板实例改为模块级单例,Nodes 2.0 切换 / 画布重建后不再丢失或重复

配色与同步结果按实际生效数量提示,失败时明确报错;更新中英文版本文档
2026-11-11 16:21:52 +08:00

255 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 智能布局扩展
import { app } from "/scripts/app.js";
import { LayoutPanel, DEFAULT_CONFIG } from "./LayoutCore.js";
import { getRegisteredThemes, initDefaultThemes } from "./styles/UIStyles.js";
import { isVueNodesMode } from "./styles/UIUtils.js";
// 初始化默认主题
try {
// 调用主题初始化函数
if (initDefaultThemes()) {
console.log("主题系统已成功初始化");
} else {
console.warn("主题系统初始化返回失败");
}
} catch (error) {
console.error("主题系统初始化失败:", error);
}
function normalizeLayoutThemeId(themeId) {
if (themeId === "古神之眼") return "ancient_gods_eye";
return themeId;
}
// 确保全局唯一实例
// Nodes 2.0 下画布实例可能被重建,因此实例不能只挂在 app.canvas 上
let layoutPanelInstance = null;
function getOrCreateLayoutInstance() {
if (!layoutPanelInstance) {
layoutPanelInstance = new LayoutPanel();
}
// 兼容旧调用:仍然同步一份到 app.canvas._layoutPanel
const canvas = app.canvas || app.canvasOrUndefined;
if (canvas && canvas._layoutPanel !== layoutPanelInstance) {
canvas._layoutPanel = layoutPanelInstance;
}
return layoutPanelInstance;
}
// 处理快捷键
function handleShortcut(e) {
// 面板是纯 DOM 实现,不依赖画布是否就绪(Nodes 2.0 下画布初始化时机有变化)
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
const shortcut = layoutPanel.shortcut;
const [mod, key] = shortcut.split('+');
if ((mod === 'alt' && e.altKey ||
mod === 'ctrl' && (e.ctrlKey || e.metaKey) ||
mod === 'shift' && e.shiftKey) &&
e.key.toLowerCase() === key) {
e.preventDefault();
layoutPanel.toggle();
}
}
// 注册全局快捷键监听
document.addEventListener('keydown', handleShortcut, true);
// 主扩展导出
export const layoutExt = {
name: "layout-panel",
init() {
// 面板是纯 DOM 实现,画布未就绪时也先初始化,避免 Nodes 2.0 下设置项迟迟不生效
this.initLayoutPanel();
},
initLayoutPanel() {
// 初始化布局面板
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
// 应用所有初始设置 - 确保所有参数都在初始化时应用
const applySettings = () => {
try {
// 主要设置
const shortcut = app.extensionManager?.setting?.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut;
const rawTheme = app.extensionManager?.setting?.get("LayoutPanel.theme") ?? DEFAULT_CONFIG.theme;
const theme = normalizeLayoutThemeId(rawTheme);
if (rawTheme !== theme) {
app.extensionManager?.setting?.set?.("LayoutPanel.theme", theme)?.catch?.(() => {});
}
// 透明度设置
const opacity = app.extensionManager?.setting?.get("LayoutPanel.opacity") ?? 85;
const buttonOpacity = app.extensionManager?.setting?.get("LayoutPanel.buttonOpacity") ?? 90;
// 启用面板(始终启用)
layoutPanel.setEnabled(true);
// 应用所有设置 - 顺序很重要
layoutPanel.setShortcut(shortcut);
layoutPanel.setTheme(theme);
// 确保透明度设置最后应用,这样不会被主题初始化覆盖
setTimeout(() => {
layoutPanel.setOpacity(opacity);
layoutPanel.setButtonOpacity(buttonOpacity);
}, 100);
console.log("布局面板初始设置已加载:", { shortcut, theme, opacity, buttonOpacity });
} catch (e) {
console.warn("应用设置时出错:", e);
// 应用默认设置作为回退
layoutPanel.setShortcut(DEFAULT_CONFIG.shortcut);
layoutPanel.setTheme(DEFAULT_CONFIG.theme);
layoutPanel.setOpacity(85);
layoutPanel.setButtonOpacity(90);
}
};
applySettings();
}
};
// 获取所有注册主题用于设置选择
function getThemeOptions() {
try {
const themes = getRegisteredThemes();
return themes.map(theme => theme.id);
} catch (e) {
console.error("获取主题选项失败:", e);
return ["ancient_gods_eye"];
}
}
// 注册设置面板
app.registerExtension({
name: "ComfyUI.LayoutPanel.Settings",
setup() {
// 初始化布局面板(面板不依赖画布实例,Nodes 2.0 下同样可用)
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
// 确保显式加载和应用所有设置,与init方法保持一致
try {
// 先启用面板
layoutPanel.setEnabled(true);
// 然后应用所有保存的设置
const shortcut = app.extensionManager?.setting?.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut;
const rawTheme = app.extensionManager?.setting?.get("LayoutPanel.theme") ?? DEFAULT_CONFIG.theme;
const theme = normalizeLayoutThemeId(rawTheme);
if (rawTheme !== theme) {
app.extensionManager?.setting?.set?.("LayoutPanel.theme", theme)?.catch?.(() => {});
}
const opacity = app.extensionManager?.setting?.get("LayoutPanel.opacity") ?? 85;
const buttonOpacity = app.extensionManager?.setting?.get("LayoutPanel.buttonOpacity") ?? 90;
// 应用设置 - 注意顺序
layoutPanel.setShortcut(shortcut);
layoutPanel.setTheme(theme);
// 确保透明度在主题后设置
setTimeout(() => {
layoutPanel.setOpacity(opacity);
layoutPanel.setButtonOpacity(buttonOpacity);
}, 100);
} catch (e) {
console.warn("应用设置时出错:", e);
// 应用默认设置
layoutPanel.setShortcut(DEFAULT_CONFIG.shortcut);
layoutPanel.setTheme(DEFAULT_CONFIG.theme);
layoutPanel.setOpacity(85);
layoutPanel.setButtonOpacity(90);
}
},
settings: [
// 移除启用/禁用设置选项
{
id: "LayoutPanel.shortcut",
name: "Shortcut",
type: "text",
defaultValue: DEFAULT_CONFIG.shortcut,
tooltip: "Keyboard shortcut to toggle the UI layout panel (e.g. Alt+X).",
category: ["DD_UI_LAYOUT", "1_FEATURES", "SHORTCUT"],
onChange(value) {
if (typeof value === 'string' && value.includes('+')) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel) {
layoutPanel.setShortcut(value);
}
}
}
},
{
id: "LayoutPanel.opacity",
name: "Background Opacity",
type: "slider",
defaultValue: 85,
attrs: { min: 0, max: 100, step: 1 },
tooltip: "Adjust the background opacity of the UI layout panel (0-100%).",
category: ["DD_UI_LAYOUT", "2_APPEARANCE", "BACKGROUND_OPACITY"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setOpacity === 'function') {
layoutPanel.setOpacity(value);
}
}
},
{
id: "LayoutPanel.buttonOpacity",
name: "Button Opacity",
type: "slider",
defaultValue: 90,
attrs: { min: 0, max: 100, step: 1 },
tooltip: "Adjust the button opacity inside the UI layout panel (0-100%).",
category: ["DD_UI_LAYOUT", "2_APPEARANCE", "BUTTON_OPACITY"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setButtonOpacity === 'function') {
layoutPanel.setButtonOpacity(value);
}
}
},
{
id: "LayoutPanel.theme",
name: "Panel Theme",
type: "combo",
defaultValue: DEFAULT_CONFIG.theme,
tooltip: "Select a visual theme for the UI layout panel.",
options: getThemeOptions,
category: ["DD_UI_LAYOUT", "2_APPEARANCE", "PANEL_THEME"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setTheme === 'function') {
layoutPanel.setTheme(normalizeLayoutThemeId(value));
}
}
}
]
});
// 主扩展注册
app.registerExtension({
name: "ComfyUI.LayoutPanel",
init() {
try {
console.log("正在初始化智能布局面板...");
// 延迟初始化,确保其他扩展已经加载
setTimeout(() => {
layoutExt.init();
console.log(
"智能布局面板初始化完成(渲染模式:" +
(isVueNodesMode() ? "Nodes 2.0" : "经典画布") + ")"
);
}, 1000);
} catch (e) {
console.error("初始化智能布局面板时出错:", e);
}
}
});