From 4fc2db0bc748aa5a262d41e8dcee97f692db953f Mon Sep 17 00:00:00 2001 From: Suzu008 Date: Tue, 2 Jun 2026 04:55:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D=20nodes2.0=20?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E8=BF=9E=E7=BA=BF=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getConnectionPos 改用 getOutputPos/getInputPos (兼容 layoutStore) - StyleManager/CircuitBoardStyle/StaticCircuitBoardStyle 同步替换 - Vue 模式下 document mousemove 替代 canvas 实现悬停检测 - canvas.select()/deselect() 补丁确保选中模式动画循环启动 --- .../ConnectionAnimationCore.js | 10 ++- .../ConnectionAnimationExt.js | 62 +++++++++++++++++++ .../styles/CircuitBoardStyle.js | 24 +++++-- .../Online_Animation/styles/StyleManager.js | 22 ++++++- .../styles/static/StaticCircuitBoardStyle.js | 24 +++++-- 5 files changed, 125 insertions(+), 17 deletions(-) diff --git a/extensions/Online_Animation/ConnectionAnimationCore.js b/extensions/Online_Animation/ConnectionAnimationCore.js index f9d9da3..4ef3f61 100644 --- a/extensions/Online_Animation/ConnectionAnimationCore.js +++ b/extensions/Online_Animation/ConnectionAnimationCore.js @@ -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 } diff --git a/extensions/Online_Animation/ConnectionAnimationExt.js b/extensions/Online_Animation/ConnectionAnimationExt.js index 5f076ec..ccf06bf 100644 --- a/extensions/Online_Animation/ConnectionAnimationExt.js +++ b/extensions/Online_Animation/ConnectionAnimationExt.js @@ -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); }, diff --git a/extensions/Online_Animation/styles/CircuitBoardStyle.js b/extensions/Online_Animation/styles/CircuitBoardStyle.js index 25c1398..6f5987d 100644 --- a/extensions/Online_Animation/styles/CircuitBoardStyle.js +++ b/extensions/Online_Animation/styles/CircuitBoardStyle.js @@ -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; diff --git a/extensions/Online_Animation/styles/StyleManager.js b/extensions/Online_Animation/styles/StyleManager.js index 99410e2..3316db2 100644 --- a/extensions/Online_Animation/styles/StyleManager.js +++ b/extensions/Online_Animation/styles/StyleManager.js @@ -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); diff --git a/extensions/Online_Animation/styles/static/StaticCircuitBoardStyle.js b/extensions/Online_Animation/styles/static/StaticCircuitBoardStyle.js index 1a0e82d..7f0a793 100644 --- a/extensions/Online_Animation/styles/static/StaticCircuitBoardStyle.js +++ b/extensions/Online_Animation/styles/static/StaticCircuitBoardStyle.js @@ -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]; // 关键!调整输入点到节点左边缘