diff --git a/web/js/multi_bool_panel.js b/web/js/multi_bool_panel.js index 5704165..91d9927 100644 --- a/web/js/multi_bool_panel.js +++ b/web/js/multi_bool_panel.js @@ -16,27 +16,42 @@ function getPanelMode(node) { return getModeWidget(node)?.value ?? "widgets"; } -/** Resolve target nodes whose title starts with ":" and item part is non-empty. */ +/** Resolve target nodes/groups whose title starts with ":" and item part is non-empty. */ function resolveTargets(panelNode) { const prefix = panelNode.title; if (!prefix) return []; const needle = prefix + ":"; - return (app.graph._nodes || []).filter((n) => { - if (n.id === panelNode.id) return false; - if (!n.title || !n.title.startsWith(needle)) return false; - const item = n.title.slice(needle.length).trim(); - return item.length > 0; + const mode = getPanelMode(panelNode); + const targets = []; + // Nodes + (app.graph._nodes || []).forEach((n) => { + if (n.id === panelNode.id) return; + if (!n.title || !n.title.startsWith(needle)) return; + const name = n.title.slice(needle.length).trim(); + if (name.length > 0) targets.push({ kind: "node", ref: n, name }); }); + // Groups — only relevant in bypass/mute modes (widgets mode targets boolean widgets) + if (mode !== "widgets") { + (app.graph._groups || []).forEach((g) => { + if (!g.title || !g.title.startsWith(needle)) return; + const name = g.title.slice(needle.length).trim(); + if (name.length > 0) targets.push({ kind: "group", ref: g, name }); + }); + } + return targets; } -/** Extract item name (part after ":") from a target node title. */ -function itemName(panelNode, targetNode) { - const prefix = panelNode.title + ":"; - return targetNode.title.slice(prefix.length).trim(); -} - -/** Get current real state of a target node for a given panel mode. */ -function currentRealValue(targetNode, mode) { +/** Get current real state of a target (node or group) for a given panel mode. */ +function currentRealValue(target, mode) { + if (target.kind === "group") { + const g = target.ref; + if (typeof g.recomputeInsideNodes === "function") g.recomputeInsideNodes(); + const members = g._nodes || []; + if (members.length === 0) return false; + const m = mode === "bypass" ? 2 : 4; + return members.every((n) => n.mode === m); + } + const targetNode = target.ref; if (mode === "bypass") return targetNode.mode === 2; if (mode === "mute") return targetNode.mode === 4; // widgets: first boolean widget value @@ -53,8 +68,18 @@ function hasBooleanWidget(targetNode) { ); } -/** Apply a toggle value to a target node. */ -function applyValue(targetNode, mode, value) { +/** Apply a toggle value to a target (node or group). */ +function applyValue(target, mode, value) { + if (target.kind === "group") { + if (mode === "widgets") return; // groups are never included in widgets mode + const g = target.ref; + if (typeof g.recomputeInsideNodes === "function") g.recomputeInsideNodes(); + const m = value ? (mode === "bypass" ? 2 : 4) : 0; + (g._nodes || []).forEach((n) => { n.mode = m; }); + app.graph.setDirtyCanvas(true, true); + return; + } + const targetNode = target.ref; if (mode === "bypass") { targetNode.mode = value ? 2 : 0; } else if (mode === "mute") { @@ -87,7 +112,7 @@ const PANELS_CHECK_MS = 150; function relevantKey(panel) { const mode = getPanelMode(panel); const names = resolveTargets(panel) - .map((t) => itemName(panel, t).toLowerCase()) + .map((t) => t.name.toLowerCase()) .sort(); return (panel.title || "") + "\u0000" + mode + "\u0000" + names.join("\u0000"); } @@ -179,16 +204,13 @@ function buildPanel(panelNode) { // Sort case-insensitively by item name targets.sort((a, b) => { - const na = itemName(panelNode, a).toLowerCase(); - const nb = itemName(panelNode, b).toLowerCase(); + const na = a.name.toLowerCase(); + const nb = b.name.toLowerCase(); return na.localeCompare(nb); }); // Build rows data - let rows = targets.map((t) => { - const name = itemName(panelNode, t); - return { node: t, name }; - }); + let rows = targets.map((t) => ({ target: t, name: t.name })); // Limit const overflow = rows.length > MAX_ITEMS ? rows.length - MAX_ITEMS : 0; @@ -210,7 +232,7 @@ function buildPanel(panelNode) { } // Render rows - rows.forEach(({ node: t, name }) => { + rows.forEach(({ target: t, name }) => { const row = document.createElement("div"); row.style.cssText = "display:flex;align-items:center;justify-content:space-between;" + @@ -219,8 +241,16 @@ function buildPanel(panelNode) { // Determine enabled state let disabled = false; - if (mode === "widgets" && !hasBooleanWidget(t)) { + let disabledTip = ""; + if (t.kind === "group") { + if (typeof t.ref.recomputeInsideNodes === "function") t.ref.recomputeInsideNodes(); + if ((t.ref._nodes || []).length === 0) { + disabled = true; + disabledTip = "グループ内にノードがありません"; + } + } else if (mode === "widgets" && !hasBooleanWidget(t.ref)) { disabled = true; + disabledTip = "booleanウィジェットがありません"; } // Label @@ -230,7 +260,7 @@ function buildPanel(panelNode) { "font-size:12px;" + (disabled ? "color:#555;" : "color:#ccc;"); label.textContent = name; if (disabled) { - label.title = "booleanウィジェットがありません"; + label.title = disabledTip; } row.appendChild(label); @@ -337,9 +367,9 @@ function buildPanel(panelNode) { let prevChange = null; let prevNodeAdded = null; let prevNodeRemoved = null; - const onChangeWrapper = (g) => { prevChange?.(g); scheduleRefresh(); }; - const onNodeAddedWrapper = (n) => { prevNodeAdded?.(n); scheduleRefresh(); }; - const onNodeRemovedWrapper = (n) => { prevNodeRemoved?.(n); scheduleRefresh(); }; + const onChangeWrapper = (g) => { prevChange?.(g); schedulePanelsCheck(); }; + const onNodeAddedWrapper = (n) => { prevNodeAdded?.(n); schedulePanelsCheck(); }; + const onNodeRemovedWrapper = (n) => { prevNodeRemoved?.(n); schedulePanelsCheck(); }; function installGraphHooks(g) { if (!g || panelNode.__hooksInstalled) return;