feat: 适配 nodes2.0 模式连线动画

- getConnectionPos 改用 getOutputPos/getInputPos (兼容 layoutStore)
- StyleManager/CircuitBoardStyle/StaticCircuitBoardStyle 同步替换
- Vue 模式下 document mousemove 替代 canvas 实现悬停检测
- canvas.select()/deselect() 补丁确保选中模式动画循环启动
This commit is contained in:
Suzu008
2026-06-05 20:19:18 +08:00
parent 4a13d9d8e3
commit 4fc2db0bc7
5 changed files with 125 additions and 17 deletions
@@ -1,4 +1,4 @@
// 动画核心模块,只包含动画逻辑和默认配置
// 动画核心模块,只包含动画逻辑和默认配置
import { EffectManager } from './effects/EffectManager.js';
import { StyleManager } from './styles/StyleManager.js';
import { StaticStyleManager } from './styles/static/StaticStyleManager.js';
@@ -671,7 +671,13 @@ export class ConnectionAnimation {
_getConnectionPos(node, slot, isOutput) {
if (!node || slot === undefined) return null;
// 使用ComfyUI标准的连接点位置获取方法
// 优先使用现代 API(Vue nodes2.0 模式下依赖 layoutStore,旧模式也能用)
if (isOutput) {
if (typeof node.getOutputPos === 'function') return node.getOutputPos(slot);
} else {
if (typeof node.getInputPos === 'function') return node.getInputPos(slot);
}
// 回退到 getConnectionPos
if (typeof node.getConnectionPos === 'function') {
return node.getConnectionPos(!isOutput, slot); // 注意:getConnectionPos的第一个参数是isInput
}
@@ -123,10 +123,69 @@ function setupNodeHoverListeners(connectionAnimation) {
}
}
// Vue nodes2.0 模式下的悬停检测(LiteGraph 的 onNodeMouseEnter/Leave 在 Vue 模式下不触发)
function setupVueModeHoverDetection(connectionAnimation) {
if (!app.canvas) return;
const canvas = app.canvas;
const canvasEl = canvas.canvas;
if (!canvasEl) return;
document.addEventListener('mousemove', (e) => {
if (!window.LiteGraph?.vueNodesMode) return;
const rect = canvasEl.getBoundingClientRect();
if (e.clientX < rect.left || e.clientX > rect.right ||
e.clientY < rect.top || e.clientY > rect.bottom) {
connectionAnimation.setHoveredNode(null);
return;
}
const graphPos = canvas.convertEventToCanvasOffset({ clientX: e.clientX, clientY: e.clientY });
let hoveredNode = null;
const graph = canvas.graph;
if (graph && graph._nodes) {
for (const node of Object.values(graph._nodes)) {
if (node.isPointInside && node.isPointInside(graphPos[0], graphPos[1])) {
hoveredNode = node;
break;
}
}
}
connectionAnimation.setHoveredNode(hoveredNode);
});
}
// 设置节点选择监听
function setupNodeSelectionListeners(connectionAnimation) {
if (!app.canvas) return;
// Vue nodes2.0 模式使用 select()/deselect(),不走 selectNode()/deselectNode()
const originalSelect = app.canvas.select;
app.canvas.select = function(item) {
const result = originalSelect?.call(this, item);
try {
if (connectionAnimation.notifySelectionChanged) {
connectionAnimation.notifySelectionChanged();
}
} catch (e) {
console.warn("Connection Animation: Error handling select:", e);
}
return result;
};
const originalDeselect = app.canvas.deselect;
app.canvas.deselect = function(item) {
const result = originalDeselect?.call(this, item);
try {
if (connectionAnimation.notifySelectionChanged) {
connectionAnimation.notifySelectionChanged();
}
} catch (e) {
console.warn("Connection Animation: Error handling deselect:", e);
}
return result;
};
// 监听选择变化
const originalOnSelectionChange = app.canvas.onSelectionChange;
app.canvas.onSelectionChange = function(nodes) {
@@ -198,6 +257,9 @@ app.registerExtension({
// 设置节点悬停监听
setupNodeHoverListeners(connectionAnimation);
// 设置 Vue nodes2.0 悬停检测(LiteGraph hover 在 Vue 模式不触发)
setupVueModeHoverDetection(connectionAnimation);
// 设置节点选择监听
setupNodeSelectionListeners(connectionAnimation);
},
@@ -186,6 +186,22 @@ class MapLinks {
this.gridHeight = 0;
}
/**
* 获取输出端口位置(Vue nodes2.0 兼容)
*/
_getOutputPos(node, slot, outArray) {
if (typeof node.getOutputPos === 'function') return node.getOutputPos(slot);
return node.getConnectionPos ? node.getConnectionPos(false, slot, outArray) : null;
}
/**
* 获取输入端口位置(Vue nodes2.0 兼容)
*/
_getInputPos(node, slot, outArray) {
if (typeof node.getInputPos === 'function') return node.getInputPos(slot);
return node.getConnectionPos ? node.getConnectionPos(true, slot, outArray) : null;
}
/**
* 检查点是否在某个节点内部
*/
@@ -787,7 +803,7 @@ class MapLinks {
}
const linkPos = new Float32Array(2);
const outputXYConnection = node.getConnectionPos(false, slot, linkPos);
const outputXYConnection = this._getOutputPos(node, slot, linkPos);
const outputNodeInfo = this.nodesById[node.id];
let outputXY = Array.from(outputXYConnection);
@@ -805,11 +821,7 @@ class MapLinks {
}
const inputLinkPos = new Float32Array(2);
const inputXYConnection = targetNode.getConnectionPos(
true,
link.target_slot,
inputLinkPos
);
const inputXYConnection = this._getInputPos(targetNode, link.target_slot, inputLinkPos);
const inputXY = Array.from(inputXYConnection);
const nodeInfo = this.nodesById[targetNode.id];
inputXY[0] = nodeInfo.linesArea[0] - 1;
@@ -89,7 +89,23 @@ export class StyleManager {
this.currentStyleName = "曲线";
}
}
/**
/**
* 获取输出端口位置(Vue nodes2.0 兼容)
*/
_getOutputPos(node, slot, outArray) {
if (typeof node.getOutputPos === 'function') return node.getOutputPos(slot);
return node.getConnectionPos ? node.getConnectionPos(false, slot, outArray) : null;
}
/**
* 获取输入端口位置(Vue nodes2.0 兼容)
*/
_getInputPos(node, slot, outArray) {
if (typeof node.getInputPos === 'function') return node.getInputPos(slot);
return node.getConnectionPos ? node.getConnectionPos(true, slot, outArray) : null;
}
/**
* 计算指定连线的路径
* @param {Array} specificLinks - 要计算的特定连线数组,如果为null则计算所有连线
* @returns {Array} 路径数据数组
@@ -123,8 +139,8 @@ export class StyleManager {
const inNode = link.target_id === -20 ? this.animationManager.canvas.graph.outputNode : this.animationManager.canvas.graph.getNodeById(link.target_id);
if (!outNode || !inNode) return;
const outPos = link.origin_id === -10 ? outNode.slots[link.origin_slot].pos : outNode.getConnectionPos(false, link.origin_slot);
const inPos = link.target_id === -20 ? inNode.slots[link.target_slot].pos : inNode.getConnectionPos(true, link.target_slot);
const outPos = link.origin_id === -10 ? outNode.slots[link.origin_slot].pos : this._getOutputPos(outNode, link.origin_slot);
const inPos = link.target_id === -20 ? inNode.slots[link.target_slot].pos : this._getInputPos(inNode, link.target_slot);
// 使用统一的getBaseColor方法获取颜色
const baseColor = this.currentStyle.getBaseColor(link.origin_id !== -10 ? outNode : inNode, link);
@@ -174,6 +174,22 @@ class StaticMapLinks {
this.gridHeight = 0;
}
/**
* 获取输出端口位置(Vue nodes2.0 兼容)
*/
_getOutputPos(node, slot, outArray) {
if (typeof node.getOutputPos === 'function') return node.getOutputPos(slot);
return node.getConnectionPos ? node.getConnectionPos(false, slot, outArray) : null;
}
/**
* 获取输入端口位置(Vue nodes2.0 兼容)
*/
_getInputPos(node, slot, outArray) {
if (typeof node.getInputPos === 'function') return node.getInputPos(slot);
return node.getConnectionPos ? node.getConnectionPos(true, slot, outArray) : null;
}
/**
* 检查点是否在某个节点内部
*/
@@ -725,7 +741,7 @@ class StaticMapLinks {
}
const linkPos = new Float32Array(2);
const outputXYConnection = node.getConnectionPos(false, slot, linkPos);
const outputXYConnection = this._getOutputPos(node, slot, linkPos);
const outputNodeInfo = this.nodesById[node.id];
let outputXY = Array.from(outputXYConnection);
@@ -744,11 +760,7 @@ class StaticMapLinks {
}
const inputLinkPos = new Float32Array(2);
const inputXYConnection = targetNode.getConnectionPos(
true,
link.target_slot,
inputLinkPos
);
const inputXYConnection = this._getInputPos(targetNode, link.target_slot, inputLinkPos);
const inputXY = Array.from(inputXYConnection);
const nodeInfo = this.nodesById[targetNode.id];
// 关键!调整输入点到节点左边缘