Files
2025-04-15 22:43:13 +03:00

235 lines
11 KiB
JavaScript

import { app } from "../../scripts/app.js";
console.log("Loading context.js for Slider Stacker");
// --- Helper Functions (Copied from Dynamic.js for self-containment) ---
const findWidgetByName = (node, name) => {
return node.widgets ? node.widgets.find((w) => w.name === name) : null;
};
const getWidgetValue = (widget) => {
const val = widget?.value;
const num = Number(val);
return isNaN(num) ? 0 : num;
};
// Gets the actual max strength based on current slider values and activity
function recalculateCurrentMaxStrength(node) {
const sliderCountWidget = findWidgetByName(node, "slider_count");
const count = getWidgetValue(sliderCountWidget);
let currentMaxValue = 0;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(node, `(${i})`);
if (wtWidget) {
currentMaxValue = Math.max(currentMaxValue, getWidgetValue(wtWidget));
}
}
const maxStrengthWidget = findWidgetByName(node, "slider_max_strength");
const overallMax = maxStrengthWidget?.options?.max ?? 2.0;
const overallMin = maxStrengthWidget?.options?.min ?? 0.0;
return Math.max(overallMin, Math.min(currentMaxValue, overallMax));
}
// Updates the stored normalized weights based on current displayed values and max strength
function updateNormalization(node, currentMaxStrength) {
if (!node.normalizedSliderWeights) node.normalizedSliderWeights = {}; // Ensure it exists
const sliderCountWidget = findWidgetByName(node, "slider_count");
const maxSliders = sliderCountWidget?.options?.max || 50;
for (let i = 1; i <= maxSliders; i++) {
const wtWidget = findWidgetByName(node, `(${i})`);
if (wtWidget) {
const currentWt = getWidgetValue(wtWidget);
node.normalizedSliderWeights[i] = (currentMaxStrength > 0)
? (currentWt / currentMaxStrength)
: (node.normalizedSliderWeights[i] !== undefined ? node.normalizedSliderWeights[i] : 0);
}
}
}
// Updates the displayed values of individual sliders based on normalized weights and new max strength
// Note: Needs access to node._isUpdatingInternally if called directly, but here it's used post-operation
function updateAllSliderDisplaysFromContext(node, newMaxStrength) {
if (!node.normalizedSliderWeights) return;
node._isUpdatingInternally = true; // Set flag for this operation
const count = getWidgetValue(findWidgetByName(node, "slider_count"));
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(node, `(${i})`);
if (wtWidget) {
const normalizedValue = node.normalizedSliderWeights[i] || 0;
let newDisplayValue = normalizedValue * newMaxStrength;
const widgetMax = wtWidget.options?.max ?? 2.0;
const widgetMin = wtWidget.options?.min ?? 0.0;
newDisplayValue = Math.max(widgetMin, Math.min(newDisplayValue, widgetMax, newMaxStrength));
if (Math.abs(getWidgetValue(wtWidget) - newDisplayValue) > 0.001) {
wtWidget.value = newDisplayValue; // This should use the intercepted setter
}
}
}
node.graph.setDirtyCanvas(true, true);
node._isUpdatingInternally = false;
}
// --- Function to add menu options ---
function addContextMenuOptions(node) {
const originalGetExtraMenuOptions = node.getExtraMenuOptions;
node.getExtraMenuOptions = function(_, options) {
if (originalGetExtraMenuOptions) {
originalGetExtraMenuOptions.apply(this, arguments);
}
// Option 1: Average Values
options.push({
content: "Average Slider Values",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
let sum = 0;
let activeCount = 0;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if(wtWidget) {
sum += getWidgetValue(wtWidget);
activeCount++;
}
}
const average = activeCount > 0 ? sum / activeCount : 0;
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = average; }
}
this._isUpdatingInternally = false;
// Recalculate max and update normalization *after* loop
const newMax = recalculateCurrentMaxStrength(this);
updateNormalization(this, newMax);
// Trigger update on master slider to sync everything
const masterWidget = findWidgetByName(this, "slider_max_strength");
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax; // This uses the setter from Dynamic.js
} else {
// If max didn't change, still redraw displays based on new average
updateAllSliderDisplaysFromContext(this, newMax);
}
this.graph.setDirtyCanvas(true, true);
}
});
// Option 2: Reset Values
options.push({
content: "Reset Slider Values",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
const defaultValue = 1.0;
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = defaultValue; }
}
this._isUpdatingInternally = false;
const newMax = recalculateCurrentMaxStrength(this);
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "slider_max_strength");
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax;
} else {
updateAllSliderDisplaysFromContext(this, newMax);
}
this.graph.setDirtyCanvas(true, true);
}
});
// Option 3: Set All to Max Value
options.push({
content: "Set All to Max Value",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
let maxValue = -Infinity; // Start low
// First pass: Find the maximum value among visible sliders
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if(wtWidget) {
maxValue = Math.max(maxValue, getWidgetValue(wtWidget));
}
}
// Ensure maxValue is within widget bounds if needed (though setters handle clamping)
// const widgetMaxBound = findWidgetByName(this, `(1)`)?.options?.max ?? 2.0;
// maxValue = Math.min(maxValue, widgetMaxBound);
// Second pass: Set all visible sliders to the found maximum value
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = maxValue; } // Use setter
}
this._isUpdatingInternally = false;
// Recalculate max (should be == maxValue), normalize, and update master slider
const newMax = recalculateCurrentMaxStrength(this); // Will be maxValue
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "slider_max_strength");
// Update master slider directly - its setter handles updating displays
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax;
}
this.graph.setDirtyCanvas(true, true);
}
});
// Option 4: Set All to Min Value
options.push({
content: "Set All to Min Value",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
let minValue = Infinity; // Start high
// First pass: Find the minimum value among visible sliders
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if(wtWidget) {
minValue = Math.min(minValue, getWidgetValue(wtWidget));
}
}
// Ensure minValue isn't below widget bounds
// const widgetMinBound = findWidgetByName(this, `(1)`)?.options?.min ?? 0.0;
// minValue = Math.max(minValue, widgetMinBound);
// Second pass: Set all visible sliders to the found minimum value
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = minValue; } // Use setter
}
this._isUpdatingInternally = false;
// Recalculate max (will be == minValue), normalize, and update master slider
const newMax = recalculateCurrentMaxStrength(this); // Will be minValue
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "slider_max_strength");
// Update master slider directly
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax;
}
this.graph.setDirtyCanvas(true, true);
}
});
};
}
// --- ComfyUI Extension ---
app.registerExtension({
name: "Comfy.DynamicSlidersStack.ContextMenu",
nodeCreated(node) {
const nodeTitle = node.getTitle ? node.getTitle() : node.title;
if (nodeTitle === "Slider Stacker") {
addContextMenuOptions(node);
}
}
});