Files
Dontdrunk-ComfyUI-DD-Nodes/js/Intelligent Layout/LayoutCore.js
T

512 lines
16 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 {
layoutPanelStyles,
injectStyles,
removeStyles,
createPanelElement,
createButtonsContainer,
createButton,
createModeContainer,
createSelectBox,
addOptionsToSelect,
setPanelBackgroundOpacity,
setButtonsOpacity,
showNotification,
adjustButtonsLayout,
registerTheme,
getRegisteredThemes,
getTheme,
applyTheme,
setDefaultTheme,
getDefaultTheme,
createCoinElement,
showAtMousePosition
} from './styles/UIStyles.js';
// 导入通用工具函数 - 修改导入路径为styles文件夹
import {
getComfyUIApp,
getSelectedNodes,
getSelectedGroups,
getRandomColor,
syncNodeSize,
syncNodeWidthHeight,
alignNodes
} from './styles/UIUtils.js';
// 跟踪鼠标位置
let mousePosition = { x: 0, y: 0 };
document.addEventListener('mousemove', (e) => {
mousePosition = { x: e.clientX, y: e.clientY };
});
export class LayoutPanel {
constructor() {
this.shortcut = 'alt+x';
this.visible = false;
this.container = null;
this.buttonsContainer = null;
this.colorMode = '完全随机'; // 默认为完全随机模式
this.currentTheme = null;
this.coinElement = null;
}
setEnabled() {
// 功能始终启用,只需确保面板已创建
if (!this.container) {
this._createPanel();
}
}
setShortcut(shortcut) {
if (typeof shortcut === 'string' && shortcut.includes('+')) {
this.shortcut = shortcut;
}
}
// 设置当前主题
setTheme(themeId) {
if (!this.container) {
this._createPanel();
}
// 保存当前的透明度设置,以便应用主题后恢复
const bgOpacity = this.container.dataset.bgOpacity;
const btnOpacity = this.container.dataset.btnOpacity;
// 应用新主题
if (applyTheme(themeId, this.container, this.coinElement)) {
this.currentTheme = themeId;
// 主题应用后,恢复保存的透明度设置
setTimeout(() => {
if (bgOpacity) this.setOpacity(parseFloat(bgOpacity) * 100);
if (btnOpacity) this.setButtonOpacity(parseFloat(btnOpacity) * 100);
}, 50); // 短延迟确保主题样式完全应用
return true;
}
return false;
}
// 只有通过toggle方法才能控制面板显示/隐藏,确保只能通过快捷键激活
toggle() {
if (this.visible) {
this.hide();
} else {
this.show();
}
}
show() {
if (!this.container) {
this._createPanel();
}
// 使用当前鼠标位置显示面板
showAtMousePosition(this.container, mousePosition);
this.container.style.display = 'flex';
this.container.style.pointerEvents = 'auto'; // 显示时允许事件
// 使用动画效果显示 - 修改:使用 opacity 动画,不再修改 transform
requestAnimationFrame(() => {
this.container.style.opacity = '1';
});
this.visible = true;
}
hide() {
if (!this.container) return;
// 只淡出,不修改 transform
this.container.style.opacity = '0';
this.container.style.pointerEvents = 'none'; // 隐藏时禁止事件
setTimeout(() => {
if (this.container) {
this.container.style.display = 'none';
}
}, 300);
this.visible = false;
}
setOpacity(opacity) {
// opacity: 0~100
if (!this.container) return;
setPanelBackgroundOpacity(this.container, opacity);
}
// 添加新方法:设置按钮透明度
setButtonOpacity(opacity) {
// opacity: 0~100
if (!this.container) return;
setButtonsOpacity(this.container, opacity);
}
_createPanel() {
if (this.container) return;
// 使用外部样式模块创建面板
this.container = createPanelElement(mousePosition);
// 存储初始透明度值到容器数据属性中,便于后续恢复
this.container.dataset.initialSetup = 'pending';
// 初始化透明度为全局设置值
let opacity = 85;
let buttonOpacity = 90;
try {
if (window.app?.extensionManager?.setting) {
opacity = window.app.extensionManager.setting.get("LayoutPanel.opacity", 85);
buttonOpacity = window.app.extensionManager.setting.get("LayoutPanel.buttonOpacity", 90);
}
} catch(e) {
console.warn("获取透明度设置失败:", e);
}
// 将初始透明度值存储到数据属性
this.container.dataset.initialBgOpacity = opacity / 100;
this.container.dataset.initialBtnOpacity = buttonOpacity / 100;
// 预先保存透明度值到数据属性,但延迟应用
// 这样可以确保主题应用后再设置透明度
this.container.dataset.bgOpacity = opacity / 100;
this.container.dataset.btnOpacity = buttonOpacity / 100;
// 确保面板存在后才设置透明度
setTimeout(() => {
// 确保面板已经完全创建并添加到DOM中
this.setOpacity(opacity);
this.setButtonOpacity(buttonOpacity);
}, 50);
// 监听设置变化
if (window.app?.extensionManager?.setting?.onChange) {
window.app.extensionManager.setting.onChange("LayoutPanel.opacity", (val) => {
this.setOpacity(val);
});
window.app.extensionManager.setting.onChange("LayoutPanel.buttonOpacity", (val) => {
this.setButtonOpacity(val);
});
}
// 创建硬币容器
this.coinElement = createCoinElement();
// 确保硬币元素添加到面板顶部
if (this.container.firstChild) {
this.container.insertBefore(this.coinElement, this.container.firstChild);
} else {
this.container.appendChild(this.coinElement);
}
// 创建按钮容器
this.buttonsContainer = createButtonsContainer();
// 所有按钮使用统一的创建和样式方法
const randomColorBtn = createButton('随机颜色', 'normal');
randomColorBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._setRandomNodeColor();
});
// 创建自定义颜色按钮
const customColorBtn = createButton('自定义颜色', 'normal');
customColorBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._openCustomColorPicker();
});
// 创建节点同步按钮
const syncBtn = createButton('节点同步', 'normal');
// 节点同步模式选择器
const { modeContainer: syncModeContainer, modeLabel: syncModeLabel } = createModeContainer('节点同步模式:');
const syncModeSelect = createSelectBox();
addOptionsToSelect(syncModeSelect, [
{ value: 'size', text: '同步大小' },
{ value: 'wh', text: '同步宽高' },
{ value: 'align', text: '同步对齐' }
]);
syncModeContainer.appendChild(syncModeSelect);
syncBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._syncSelectedNodes(syncModeSelect.value);
});
// 创建颜色模式选择器
const { modeContainer, modeLabel } = createModeContainer('随机颜色模式:');
const modeSelect = createSelectBox();
addOptionsToSelect(modeSelect, [
{ value: '完全随机', text: '完全随机(每个节点不同颜色)' },
{ value: '统一随机', text: '统一随机(所有节点相同颜色)' }
]);
// 设置默认选中项
Array.from(modeSelect.options).forEach(option => {
if (option.value === this.colorMode) {
option.selected = true;
}
});
// 监听选择变化
modeSelect.addEventListener('change', (e) => {
this.colorMode = e.target.value;
});
modeContainer.appendChild(modeSelect);
// 添加按钮到容器
this.buttonsContainer.appendChild(randomColorBtn);
this.buttonsContainer.appendChild(customColorBtn);
this.buttonsContainer.appendChild(syncBtn);
// 添加按钮容器和模式选择器到主容器
this.container.appendChild(this.buttonsContainer);
this.container.appendChild(modeContainer);
this.container.appendChild(syncModeContainer);
// 添加面板调整大小时重新调整按钮文字大小的功能
const resizeObserver = new ResizeObserver(() => {
if (this.buttonsContainer) {
const buttons = this.buttonsContainer.querySelectorAll('.layout-btn');
buttons.forEach(button => {
// 检查是否有adjustTextFontSize函数
if (typeof adjustTextFontSize === 'function') {
adjustTextFontSize(button);
} else if (window.adjustTextFontSize) {
window.adjustTextFontSize(button);
}
});
}
});
// 观察容器大小变化
resizeObserver.observe(this.container);
// 调整按钮布局
adjustButtonsLayout(this.buttonsContainer);
// 强制触发一次字体调整,确保初始状态正确
setTimeout(() => {
const buttons = this.buttonsContainer.querySelectorAll('.layout-btn');
buttons.forEach(button => {
if (typeof adjustTextFontSize === 'function') {
adjustTextFontSize(button);
} else if (window.adjustTextFontSize) {
window.adjustTextFontSize(button);
} else {
// 如果没有找到调整函数,则确保基本字体大小
button.style.fontSize = '15px';
}
});
}, 200);
// 使用外部样式模块注入样式
injectStyles('layout-panel-styles', layoutPanelStyles);
// 添加面板点击事件,阻止冒泡
this.container.addEventListener('click', (e) => {
e.stopPropagation();
});
// 添加到文档
document.body.appendChild(this.container);
// 应用当前主题或默认主题
setTimeout(() => {
let themeId = null;
try {
if (window.app?.extensionManager?.setting) {
themeId = window.app.extensionManager.setting.get("LayoutPanel.theme");
}
} catch(e) {
console.warn("获取主题设置失败:", e);
}
if (!themeId) {
themeId = getDefaultTheme();
}
if (themeId) {
this.setTheme(themeId);
// 最后应用透明度,确保不被主题覆盖
setTimeout(() => {
this.container.dataset.initialSetup = 'completed';
this.setOpacity(opacity);
this.setButtonOpacity(buttonOpacity);
}, 150);
}
}, 100);
}
// 设置随机节点颜色
_setRandomNodeColor() {
try {
// 获取ComfyUI应用实例
const app = getComfyUIApp();
if (!app) {
showNotification("无法获取ComfyUI应用实例");
return;
}
// 获取选中的节点和组
const selectedNodes = getSelectedNodes(app);
const selectedGroups = getSelectedGroups(app);
if (selectedNodes.length === 0 && selectedGroups.length === 0) {
showNotification("请先选择要应用颜色的节点或组");
return;
}
// 根据颜色模式应用颜色
if (this.colorMode === '统一随机') {
// 统一随机模式 - 所有节点和组使用同一个随机颜色
const uniformColor = getRandomColor();
selectedNodes.forEach(node => {
node.color = uniformColor;
});
selectedGroups.forEach(group => {
group.color = uniformColor;
});
showNotification("已应用统一随机颜色", "info");
} else {
// 完全随机模式 - 每个节点和组使用不同的随机颜色
selectedNodes.forEach(node => {
const randomColor = getRandomColor();
node.color = randomColor;
});
selectedGroups.forEach(group => {
group.color = getRandomColor();
});
showNotification("已应用完全随机颜色", "info");
}
app.graph.setDirtyCanvas(true, true);
} catch (error) {
console.error("设置随机颜色失败:", error);
showNotification(`设置随机颜色失败: ${error.message}`);
}
}
// 打开自定义颜色选择器
_openCustomColorPicker() {
try {
// 获取ComfyUI应用实例
const app = getComfyUIApp();
if (!app) {
showNotification("无法获取ComfyUI应用实例");
return;
}
// 获取选中的节点和组
const selectedNodes = getSelectedNodes(app);
const selectedGroups = getSelectedGroups(app);
if (selectedNodes.length === 0 && selectedGroups.length === 0) {
showNotification("请先选择要应用颜色的节点或组");
return;
}
// 创建颜色选择器
setTimeout(() => {
const defaultColor = selectedNodes.length > 0 ?
(selectedNodes[0].color || '#3355aa') :
(selectedGroups.length > 0 ? (selectedGroups[0].color || '#3355aa') : '#3355aa');
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.value = defaultColor.replace(/rgba?\(.*\)/, '#000000');
colorInput.style.position = 'absolute';
colorInput.style.visibility = 'hidden';
document.body.appendChild(colorInput);
// 处理颜色变化
colorInput.addEventListener('input', (e) => {
const color = e.target.value;
// 应用颜色到节点和组
selectedNodes.forEach(node => {
node.color = color;
});
selectedGroups.forEach(group => {
group.color = color;
});
app.graph.setDirtyCanvas(true, true);
});
colorInput.addEventListener('change', (e) => {
showNotification("已应用自定义颜色", "info");
document.body.removeChild(colorInput);
});
// 模拟点击打开颜色选择器
colorInput.click();
}, 100);
} catch (error) {
console.error("打开颜色选择器失败:", error);
showNotification(`打开颜色选择器失败: ${error.message}`);
}
}
// 节点同步逻辑
_syncSelectedNodes(mode) {
try {
const app = getComfyUIApp();
if (!app) {
showNotification('无法获取ComfyUI应用实例');
return;
}
const selectedNodes = getSelectedNodes(app);
if (selectedNodes.length < 2) {
showNotification('请至少选择两个节点进行同步');
return;
}
let result;
if (mode === 'size') {
// 大小同步:统一为最大宽高
result = syncNodeSize(selectedNodes);
showNotification('已同步为统一大小', 'info');
} else if (mode === 'wh') {
// 宽高同步:判断排列方向
result = syncNodeWidthHeight(selectedNodes);
const direction = result.direction === 'horizontal' ? '横向排列' : '纵向排列';
const dimension = result.direction === 'horizontal' ? '高度' : '宽度';
showNotification(`已同步${dimension}(${direction})`, 'info');
} else if (mode === 'align') {
// 同步对齐:自动排列节点
result = alignNodes(selectedNodes);
const alignment = result.direction === 'horizontal' ? 'Y轴对齐' : 'X轴对齐';
const direction = result.direction === 'horizontal' ? '横向' : '纵向';
showNotification(`已${direction}对齐(${alignment})`, 'info');
}
app.graph.setDirtyCanvas(true, true);
} catch (e) {
showNotification('节点同步失败: ' + e.message);
}
}
}
export const DEFAULT_CONFIG = {
enabled: true, // 默认启用
shortcut: 'alt+x',
colorMode: '完全随机', // 默认颜色模式
continueIteration: false, // 默认应用颜色后关闭面板
theme: '古神之眼' // 默认主题
};