修改操作逻辑
This commit is contained in:
@@ -1,15 +1,28 @@
|
||||
# ComfyUI-NodeAligner
|
||||
|
||||
ComfyUI-NodeAligner 是一个轻量级的 ComfyUI 布局插件,包含节点对齐、分布和调整大小等功能。该插件旨在简化可视化节点编辑器或自定义 UI 组件中的布局调整,使节点排列更加方便和高效。
|
||||
|
||||
ComfyUI-NodeAligner is a lightweight ComfyUI layout plugin that includes features such as node alignment, distribution, and resizing. This plugin is designed to simplify layout adjustments in visual node editors or custom UI components, making node arrangement more convenient and efficient.
|
||||
|
||||
## Features
|
||||
- Align nodes to left, right, top, bottom
|
||||
- Center nodes horizontally and vertically
|
||||
- Equalize width and height of selected nodes
|
||||
- Distribute nodes evenly across horizontal or vertical space
|
||||
## 功能 Features
|
||||
- 节点左对齐、右对齐、顶部对齐、底部对齐
|
||||
Align nodes to left, right, top, bottom
|
||||
- 水平和垂直居中对齐
|
||||
Center nodes horizontally and vertically
|
||||
- 使选中的节点等宽等高
|
||||
Equalize width and height of selected nodes
|
||||
- 水平或垂直均匀分布节点
|
||||
Distribute nodes evenly across horizontal or vertical space
|
||||
|
||||
## Installation
|
||||
1. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/Tenney95/ComfyUI-NodeAligner.git
|
||||
```
|
||||
## 使用方法 Method of application
|
||||
1. 选择单个节点,左对齐和顶部对齐会使节点的 left 和 top 归零
|
||||
Select a single node; left and top alignment will reset the node's left and top to zero.
|
||||
2. 按住 Shift 键或 Ctrl 键,点选多个节点,点击按钮,即可完成对齐和分布
|
||||
Hold the Shift or Ctrl key, select multiple nodes, and click the button to complete alignment and distribution.
|
||||
|
||||
## 安装 Installation
|
||||
1. 克隆仓库:
|
||||
Clone the repository:
|
||||
```
|
||||
git clone https://github.com/Tenney95/ComfyUI-NodeAligner.git
|
||||
```
|
||||
+175
-53
@@ -10,47 +10,6 @@ const equalWidthSvg = `<svg t="1725606034670" class="icon" viewBox="0 0 1088 102
|
||||
const equalHeightSvg = `<svg t="1725606224564" class="icon" viewBox="0 0 1088 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7790" width="100%"><path d="M572.16 936a42.688 42.688 0 0 1-42.688-42.688V130.688c0-23.616 19.136-42.688 42.688-42.688h266.688c23.552 0 42.624 19.072 42.624 42.688v762.624a42.688 42.688 0 0 1-42.624 42.688H572.16z" fill="#666666" p-id="7791"></path><path d="M318.016 214.72c14.08 0 25.6 11.456 25.6 25.6v543.36a25.6 25.6 0 1 1-51.2 0v-543.36c0-14.144 11.456-25.6 25.6-25.6z" fill="#666666" p-id="7792"></path><path d="M306.944 94.4a12.8 12.8 0 0 1 22.144 0l106.368 184.192a12.8 12.8 0 0 1-11.072 19.2H211.648a12.8 12.8 0 0 1-11.072-19.2l106.368-184.192zM306.944 929.6a12.8 12.8 0 0 0 22.144 0l106.368-184.192a12.8 12.8 0 0 0-11.072-19.2H211.648a12.8 12.8 0 0 0-11.072 19.2l106.368 184.192z" fill="#666666" p-id="7793"></path></svg>`
|
||||
|
||||
const current_node_list = []; // 存储已选中的节点
|
||||
|
||||
function pollForCanvas() {
|
||||
const canvas = document.querySelector('canvas#graph-canvas');
|
||||
if (canvas) {
|
||||
// 监听左键单击事件
|
||||
canvas.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const { offsetX, offsetY } = event;
|
||||
|
||||
// 获取当前的 `LGraphCanvas` 数据
|
||||
const current_node = event.target.data?.current_node;
|
||||
|
||||
// 判断 `current_node` 是否存在
|
||||
if (current_node !== null && current_node !== undefined) {
|
||||
// 判断 `current_node` 是否已经在 `current_node_list` 中
|
||||
const nodeExists = current_node_list.some(node => node.id === current_node.id);
|
||||
if (!nodeExists) {
|
||||
// 如果当前节点不存在列表中,则添加
|
||||
current_node_list.push(current_node);
|
||||
}
|
||||
} else {
|
||||
// 如果没有选中的节点,清空列表
|
||||
current_node_list.length = 0;
|
||||
ButtonManager.hide();
|
||||
}
|
||||
|
||||
const selectedNodes = current_node_list.filter(node => node.is_selected === true);
|
||||
|
||||
if (selectedNodes.length >= 2) {
|
||||
ButtonManager.show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setTimeout(pollForCanvas, 1000); // 每隔 1 秒尝试查找一次
|
||||
}
|
||||
}
|
||||
|
||||
// 启动轮询查找
|
||||
pollForCanvas();
|
||||
|
||||
function getTopNode(selectedNodes) {
|
||||
// 通过比较节点的 y 坐标,找到最上方的节点
|
||||
return selectedNodes.reduce((topNode, node) => (node.pos[1] < topNode.pos[1] ? node : topNode), selectedNodes[0]);
|
||||
@@ -59,6 +18,11 @@ function getTopNode(selectedNodes) {
|
||||
const ButtonManager = {
|
||||
isInitialized: false, // 检查是否已经初始化
|
||||
buttonContainer: null, // 保存按钮容器
|
||||
initialX: 0, // 初始位置X
|
||||
initialY: 0, // 初始位置Y
|
||||
dragStartX: 0, // 拖拽开始时的X
|
||||
dragStartY: 0, // 拖拽开始时的Y
|
||||
isDragging: false, // 检查是否正在拖拽
|
||||
|
||||
// 初始化按钮
|
||||
init() {
|
||||
@@ -71,12 +35,15 @@ const ButtonManager = {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 10px;
|
||||
left: unset;
|
||||
bottom: unset;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: #333333;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
z-index: 9999;
|
||||
z-index: 999;
|
||||
width: 290px;
|
||||
white-space: nowrap; /* 禁止换行,确保按钮在同一行显示 */
|
||||
}
|
||||
.custom-button {
|
||||
width: 25px;
|
||||
@@ -92,7 +59,7 @@ const ButtonManager = {
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
.custom-button:hover {
|
||||
background-color: #555555; /* 在 hover 时改变背景颜色 */
|
||||
background-color: #555555;
|
||||
}
|
||||
.divider {
|
||||
width: 2.5px;
|
||||
@@ -100,6 +67,10 @@ const ButtonManager = {
|
||||
background-color: #262626;
|
||||
margin: 5px 4px;
|
||||
border-radius: 2.5px;
|
||||
cursor: grab;
|
||||
}
|
||||
.divider:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
@@ -107,6 +78,10 @@ const ButtonManager = {
|
||||
// 创建按钮容器
|
||||
this.buttonContainer = document.createElement('div');
|
||||
this.buttonContainer.id = 'alignment-buttons';
|
||||
|
||||
// 从 localStorage 恢复位置
|
||||
this.restorePosition();
|
||||
|
||||
document.body.appendChild(this.buttonContainer);
|
||||
|
||||
// 创建按钮并分配功能
|
||||
@@ -116,6 +91,11 @@ const ButtonManager = {
|
||||
// 创建分割线
|
||||
const divider = document.createElement('div');
|
||||
divider.classList.add('divider');
|
||||
// 添加拖拽功能
|
||||
divider.addEventListener('mousedown', this.onDragStart.bind(this));
|
||||
document.addEventListener('mousemove', this.onDragging.bind(this));
|
||||
document.addEventListener('mouseup', this.onDragEnd.bind(this));
|
||||
|
||||
this.buttonContainer.appendChild(divider);
|
||||
} else {
|
||||
// 创建按钮
|
||||
@@ -131,6 +111,7 @@ const ButtonManager = {
|
||||
this.isInitialized = true; // 标记已经初始化
|
||||
},
|
||||
|
||||
// 获取按钮配置
|
||||
getButtons() {
|
||||
return [
|
||||
/* 左对齐 */
|
||||
@@ -161,7 +142,6 @@ const ButtonManager = {
|
||||
{ id: 'equalHeight', svg: equalHeightSvg, action: this.equalHeight.bind(this) },
|
||||
];
|
||||
},
|
||||
|
||||
// 显示按钮
|
||||
show() {
|
||||
if (!this.isInitialized) this.init(); // 如果没有初始化,先初始化
|
||||
@@ -174,7 +154,88 @@ const ButtonManager = {
|
||||
this.buttonContainer.style.display = 'none'; // 隐藏按钮容器
|
||||
}
|
||||
},
|
||||
// 开始拖拽
|
||||
onDragStart(e) {
|
||||
if (e.target.classList.contains('divider')) {
|
||||
this.isDragging = true;
|
||||
this.initialX = e.clientX;
|
||||
this.initialY = e.clientY;
|
||||
const rect = this.buttonContainer.getBoundingClientRect();
|
||||
this.dragStartX = rect.left;
|
||||
this.dragStartY = rect.top;
|
||||
// 锁定宽度和高度,防止拖拽时改变
|
||||
this.buttonContainer.style.width = `${rect.width}px`;
|
||||
this.buttonContainer.style.height = `${rect.height}px`;
|
||||
}
|
||||
},
|
||||
|
||||
// 拖拽中
|
||||
onDragging(e) {
|
||||
if (this.isDragging) {
|
||||
const deltaX = e.clientX - this.initialX;
|
||||
const deltaY = e.clientY - this.initialY;
|
||||
this.buttonContainer.style.left = `${this.dragStartX + deltaX}px`;
|
||||
this.buttonContainer.style.top = `${this.dragStartY + deltaY}px`;
|
||||
}
|
||||
},
|
||||
// 结束拖拽
|
||||
onDragEnd() {
|
||||
if (this.isDragging) {
|
||||
this.isDragging = false;
|
||||
|
||||
// 获取按钮容器的位置信息
|
||||
const rect = this.buttonContainer.getBoundingClientRect();
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// 计算距离四个边的距离
|
||||
const distanceToTop = rect.top;
|
||||
const distanceToBottom = windowHeight - rect.bottom;
|
||||
const distanceToLeft = rect.left;
|
||||
const distanceToRight = windowWidth - rect.right;
|
||||
|
||||
// 选择两个最近的边
|
||||
let top = 'unset';
|
||||
let bottom = 'unset';
|
||||
let left = 'unset';
|
||||
let right = 'unset';
|
||||
|
||||
if (distanceToTop < distanceToBottom) {
|
||||
top = `${distanceToTop}px`;
|
||||
} else {
|
||||
bottom = `${distanceToBottom}px`;
|
||||
}
|
||||
|
||||
if (distanceToLeft < distanceToRight) {
|
||||
left = `${distanceToLeft}px`;
|
||||
} else {
|
||||
right = `${distanceToRight}px`;
|
||||
}
|
||||
|
||||
// 更新样式使其根据距离四边的情况固定
|
||||
this.buttonContainer.style.top = top;
|
||||
this.buttonContainer.style.bottom = bottom;
|
||||
this.buttonContainer.style.left = left;
|
||||
this.buttonContainer.style.right = right;
|
||||
|
||||
// 保存位置到 localStorage
|
||||
localStorage.setItem('buttonContainerPosition', JSON.stringify({ top, left, right, bottom }));
|
||||
|
||||
// 恢复自动宽度和高度
|
||||
this.buttonContainer.style.width = 'auto';
|
||||
this.buttonContainer.style.height = 'auto';
|
||||
}
|
||||
},
|
||||
// 恢复位置
|
||||
restorePosition() {
|
||||
const savedPosition = JSON.parse(localStorage.getItem('buttonContainerPosition'));
|
||||
if (savedPosition) {
|
||||
this.buttonContainer.style.top = savedPosition.top;
|
||||
this.buttonContainer.style.bottom = savedPosition.bottom;
|
||||
this.buttonContainer.style.left = savedPosition.left;
|
||||
this.buttonContainer.style.right = savedPosition.right;
|
||||
}
|
||||
},
|
||||
// 获取当前选中的节点
|
||||
getSelectedNodes() {
|
||||
return current_node_list.filter(node => node.is_selected === true);
|
||||
@@ -193,10 +254,15 @@ const ButtonManager = {
|
||||
|
||||
// 左对齐
|
||||
alignLeft() {
|
||||
const leftMost = Math.min(...this.getSelectedNodes().map(node => node.pos[0]));
|
||||
this.alignNodesByProperty(0, leftMost);
|
||||
const selectedNodes = this.getSelectedNodes();
|
||||
if (selectedNodes.length === 1) {
|
||||
selectedNodes[0].pos[0] = 0;
|
||||
} else if (selectedNodes.length > 1) {
|
||||
const leftMost = Math.min(...selectedNodes.map(node => node.pos[0]));
|
||||
this.alignNodesByProperty(0, leftMost);
|
||||
}
|
||||
LGraphCanvas.active_canvas.setDirty(true, true);
|
||||
},
|
||||
|
||||
// 右对齐
|
||||
alignRight() {
|
||||
const rightMost = Math.max(...this.getSelectedNodes().map(node => node.pos[0] + node.size[0]));
|
||||
@@ -205,11 +271,16 @@ const ButtonManager = {
|
||||
});
|
||||
LGraphCanvas.active_canvas.setDirty(true, true);
|
||||
},
|
||||
|
||||
// 顶部对齐
|
||||
alignTop() {
|
||||
const topMost = Math.min(...this.getSelectedNodes().map(node => node.pos[1]));
|
||||
this.alignNodesByProperty(1, topMost);
|
||||
const selectedNodes = this.getSelectedNodes();
|
||||
if (selectedNodes.length === 1) {
|
||||
selectedNodes[0].pos[1] = 0;
|
||||
} else if (selectedNodes.length > 1) {
|
||||
const topMost = Math.min(...selectedNodes.map(node => node.pos[1]));
|
||||
this.alignNodesByProperty(1, topMost);
|
||||
}
|
||||
LGraphCanvas.active_canvas.setDirty(true, true);
|
||||
},
|
||||
|
||||
// 底部对齐
|
||||
@@ -282,17 +353,68 @@ const ButtonManager = {
|
||||
// 通用的节点分布方法
|
||||
distributeNodes(axis) {
|
||||
const nodes = this.getSelectedNodes();
|
||||
|
||||
if (nodes.length > 1) {
|
||||
// 按照节点在 axis 轴上的位置(高低)进行排序
|
||||
nodes.sort((a, b) => a.pos[axis] - b.pos[axis]);
|
||||
|
||||
// 获取最小和最大的位置范围
|
||||
const min = Math.min(...nodes.map(node => node.pos[axis]));
|
||||
const max = Math.max(...nodes.map(node => node.pos[axis] + node.size[axis]));
|
||||
|
||||
// 计算节点的总尺寸
|
||||
const totalSize = nodes.reduce((sum, node) => sum + node.size[axis], 0);
|
||||
|
||||
// 计算间距
|
||||
const spacing = (max - min - totalSize) / (nodes.length - 1);
|
||||
|
||||
// 初始化当前分布位置
|
||||
let current = min;
|
||||
|
||||
// 遍历排序后的节点,按顺序分布
|
||||
nodes.forEach(node => {
|
||||
node.pos[axis] = current;
|
||||
current += node.size[axis] + spacing;
|
||||
node.pos[axis] = current; // 设置当前节点位置
|
||||
current += node.size[axis] + spacing; // 更新下一个节点位置
|
||||
});
|
||||
|
||||
// 重新渲染画布
|
||||
LGraphCanvas.active_canvas.setDirty(true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function pollForCanvas() {
|
||||
const canvas = document.querySelector('canvas#graph-canvas');
|
||||
if (canvas) {
|
||||
ButtonManager.show();
|
||||
// 监听左键单击事件
|
||||
canvas.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const { offsetX, offsetY } = event;
|
||||
|
||||
// 获取当前的 `LGraphCanvas` 数据
|
||||
const current_node = event.target.data?.current_node;
|
||||
|
||||
// 判断 `current_node` 是否存在
|
||||
if (current_node !== null && current_node !== undefined) {
|
||||
// 判断 `current_node` 是否已经在 `current_node_list` 中
|
||||
const nodeExists = current_node_list.some(node => node.id === current_node.id);
|
||||
if (!nodeExists) {
|
||||
// 如果当前节点不存在列表中,则添加
|
||||
current_node_list.push(current_node);
|
||||
}
|
||||
} else {
|
||||
// 如果没有选中的节点,清空列表
|
||||
current_node_list.length = 0;
|
||||
}
|
||||
// const selectedNodes = current_node_list.filter(node => node.is_selected === true);
|
||||
});
|
||||
} else {
|
||||
setTimeout(pollForCanvas, 1000); // 每隔 1 秒尝试查找一次
|
||||
}
|
||||
}
|
||||
|
||||
// 启动轮询查找
|
||||
pollForCanvas();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user