Key-checked refresh (anti-flicker) + group bypass/mute targets

This commit is contained in:
solidlime
2026-08-02 19:27:49 +09:00
parent 113679fdde
commit f5332038e8
+59 -29
View File
@@ -16,27 +16,42 @@ function getPanelMode(node) {
return getModeWidget(node)?.value ?? "widgets";
}
/** Resolve target nodes whose title starts with "<panelTitle>:" and item part is non-empty. */
/** Resolve target nodes/groups whose title starts with "<panelTitle>:" 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;