Addressed compatibility issues from a recent ComfyUI update and introduced several new features and stability improvements across the entire node suite.
- **NodeDesign**: Implemented 'Place Adjacent' tools ('Place Right' & 'Place Below') for rapid, connection-aware alignment and removed outdated keyboard shortcuts.
- **PaintPro**: Added a new fullscreen editing mode and a refresh button for an improved workflow.
- **TitlePlus**: Refactored the animation engine to use transform offsets, fixing position corruption bugs and increasing stability.
- **Core**: Introduced a `dragHelper.js` utility to centralize and standardize mouse drag prevention logic.
- **Fixes & Polish**: Fixed a separator rendering bug in MultiText, patched a NullGraphError in LensFlare, and applied general compatibility fixes to other nodes.
757 lines
29 KiB
JavaScript
757 lines
29 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { preventNodeDrag } from "./dragHelper.js";
|
|
|
|
class MultiFloatNode {
|
|
constructor(node) {
|
|
this.node = node;
|
|
this.currentSlider = undefined;
|
|
this.isPressed = false;
|
|
this.currentHoverSlider = undefined;
|
|
|
|
this.colors = {
|
|
sliderBackground: "rgba(240, 240, 245, 0.1)",
|
|
sliderProgress: ["#000000", "#2980b9"],
|
|
handle: "#ecf0f1",
|
|
tooltip: "rgba(52, 152, 219, 0.9)",
|
|
text: "#2c3e50"
|
|
};
|
|
|
|
this.fonts = {
|
|
primary: "'Inter', sans-serif",
|
|
size: {
|
|
medium: "12px"
|
|
}
|
|
};
|
|
|
|
this.handleRadius = {
|
|
normal: 3,
|
|
hover: 4,
|
|
active: 5
|
|
};
|
|
|
|
|
|
this.topMargin = 12;
|
|
this.bottomMargin = 50;
|
|
this.margin = 30;
|
|
this.sliderHeight = 4;
|
|
this.sliderSpacing = 16;
|
|
|
|
this.setupNode();
|
|
this.updateColors(node.properties.startColor || "#423438", node.properties.slidercolor || "#69414e");
|
|
this.updateSliderCount(node.properties.sliderCount || 3);
|
|
this.calculateConstants();
|
|
|
|
// Instance-level onMouseDown to handle slider interactions cleanly
|
|
this.node.onMouseDown = function(e, pos) {
|
|
if (this.flags.collapsed) return false;
|
|
|
|
if (this.multiFloat.checkSliderInteraction(pos)) return true;
|
|
|
|
return this.multiFloat.checkSliderCountButtons(pos);
|
|
}.bind(this.node);
|
|
}
|
|
|
|
calculateConstants() {
|
|
|
|
this.sliderWidth = this.node.size[0] - (this.margin * 2);
|
|
}
|
|
|
|
setupNode() {
|
|
this.node.outputs = [];
|
|
|
|
|
|
const sliderCount = 3;
|
|
const minHeight = this.topMargin + (sliderCount * (this.sliderHeight + this.sliderSpacing)) + this.bottomMargin;
|
|
this.node.size = [240, minHeight];
|
|
|
|
|
|
this.node.properties = {
|
|
values: [0, 0, 0],
|
|
min: -100,
|
|
max: 100,
|
|
step: 0.1,
|
|
decimals: 1,
|
|
snap: false,
|
|
startColor: "#4A90E2",
|
|
slidercolor: "#D0021B",
|
|
sliderCount: 3,
|
|
|
|
};
|
|
|
|
this.node.widgets = [];
|
|
|
|
this.node.onDrawForeground = this.onDrawForeground.bind(this);
|
|
|
|
this.node.serialize_widgets = true;
|
|
}
|
|
|
|
updateValue(index, value) {
|
|
const decimals = this.node.properties.decimals;
|
|
const roundedValue = Number(value.toFixed(decimals));
|
|
|
|
this.node.properties.values[index] = roundedValue;
|
|
|
|
if (this.node.widgets[index]) {
|
|
const widget = this.node.widgets[index];
|
|
widget.value = roundedValue;
|
|
}
|
|
|
|
this.node.setDirtyCanvas(true);
|
|
this.updateOutputs();
|
|
}
|
|
|
|
updateOutputs() {
|
|
const activeCount = this.node.properties.sliderCount || 0;
|
|
this.node.properties.values.forEach((value, i) => {
|
|
if (i < activeCount && this.node.outputs[i]) {
|
|
const decimals = this.node.properties.decimals;
|
|
const roundedValue = parseFloat(value.toFixed(decimals));
|
|
|
|
this.node.outputs[i].value = roundedValue;
|
|
this.node.triggerSlot(i, roundedValue);
|
|
|
|
if (this.node.widgets[i]) {
|
|
this.node.widgets[i].value = roundedValue;
|
|
}
|
|
}
|
|
});
|
|
|
|
for (let i = activeCount; i < (this.node.outputs?.length || 0); i++) {
|
|
if (this.node.outputs[i]) {
|
|
this.node.outputs[i].value = undefined;
|
|
}
|
|
}
|
|
}
|
|
onDrawForeground(ctx) {
|
|
if (this.node.flags.collapsed) return;
|
|
|
|
this.calculateConstants();
|
|
const { properties } = this.node;
|
|
const activeCount = properties.sliderCount || 0;
|
|
|
|
properties.values.forEach((value, i) => {
|
|
if (i < activeCount) {
|
|
this.drawSlider(ctx, value, i);
|
|
}
|
|
});
|
|
|
|
this.drawSliderCountControls(ctx);
|
|
}
|
|
|
|
drawSlider(ctx, value, index) {
|
|
const y = this.topMargin + index * (this.sliderHeight + this.sliderSpacing);
|
|
|
|
ctx.fillStyle = this.colors.sliderBackground;
|
|
this.drawRoundRect(ctx, this.margin, y, this.sliderWidth, this.sliderHeight, 3);
|
|
|
|
let progress = (value - this.node.properties.min) / (this.node.properties.max - this.node.properties.min);
|
|
progress = Math.min(Math.max(progress, 0), 1);
|
|
|
|
const progressWidth = Math.max(0, Math.min(this.sliderWidth * progress, this.sliderWidth));
|
|
const progressGradient = ctx.createLinearGradient(this.margin, y, this.margin + this.sliderWidth, y);
|
|
progressGradient.addColorStop(0, this.colors.sliderProgress[0]);
|
|
progressGradient.addColorStop(1, this.colors.sliderProgress[1]);
|
|
ctx.fillStyle = progressGradient;
|
|
this.drawRoundRect(ctx, this.margin, y, progressWidth, this.sliderHeight, 3);
|
|
|
|
const handleX = this.margin + progressWidth;
|
|
const handleY = y + this.sliderHeight / 2;
|
|
|
|
const isHovering = this.currentHoverSlider === index;
|
|
const isActive = this.isPressed && this.currentSlider === index;
|
|
|
|
if (isActive || isHovering) {
|
|
this.drawTooltip(ctx, handleX, handleY, value);
|
|
}
|
|
|
|
this.drawSliderHandle(ctx, progress, y, index, value);
|
|
}
|
|
|
|
drawSliderHandle(ctx, progress, y, index, value) {
|
|
const handleX = this.margin + this.sliderWidth * progress;
|
|
const handleY = y + this.sliderHeight / 2;
|
|
|
|
const isHovering = this.isHoveringHandle(handleX, handleY, index);
|
|
const isActive = this.isPressed && this.currentSlider === index;
|
|
let handleRadius = this.handleRadius.normal;
|
|
|
|
if (isActive) {
|
|
handleRadius = this.handleRadius.active;
|
|
} else if (isHovering) {
|
|
handleRadius = this.handleRadius.hover;
|
|
}
|
|
|
|
ctx.fillStyle = this.colors.handle;
|
|
ctx.beginPath();
|
|
ctx.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
}
|
|
|
|
drawTooltip(ctx, x, y, value) {
|
|
const text = value.toFixed(this.node.properties.decimals);
|
|
const padding = 6;
|
|
|
|
ctx.font = `10px ${this.fonts.primary}`;
|
|
const textWidth = ctx.measureText(text).width;
|
|
|
|
const tooltipWidth = textWidth + (padding * 2);
|
|
const tooltipHeight = 20;
|
|
|
|
let tooltipX = x - tooltipWidth / 2;
|
|
const tooltipY = y - 30;
|
|
|
|
tooltipX = Math.max(0, Math.min(tooltipX, this.node.size[0] - tooltipWidth));
|
|
|
|
ctx.fillStyle = this.colors.tooltip;
|
|
this.drawRoundRect(ctx, tooltipX, tooltipY, tooltipWidth, tooltipHeight, 4);
|
|
|
|
ctx.fillStyle = "#FFFFFF";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
ctx.fillText(text, tooltipX + tooltipWidth / 2, tooltipY + tooltipHeight / 2);
|
|
}
|
|
|
|
drawSliderCountControls(ctx) {
|
|
const buttonY = this.node.size[1] - this.bottomMargin/2;
|
|
const buttonWidth = 10;
|
|
const buttonHeight = 10;
|
|
const buttonSpacing = 2;
|
|
const canDecrease = this.node.properties.sliderCount > 1;
|
|
ctx.fillStyle = canDecrease ? "rgba(60, 60, 60, 0.6)" : "rgba(60, 60, 60, 0.2)";
|
|
this.drawRoundRect(ctx, this.margin, buttonY, buttonWidth, buttonHeight, 3);
|
|
ctx.fillStyle = canDecrease ? "white" : "rgba(255, 255, 255, 0.4)";
|
|
ctx.font = "bold 8px Arial";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
ctx.fillText("-", this.margin + buttonWidth / 2, buttonY + buttonHeight / 2);
|
|
const canIncrease = this.node.properties.sliderCount < 10;
|
|
ctx.fillStyle = canIncrease ? "rgba(60, 60, 60, 0.6)" : "rgba(60, 60, 60, 0.2)";
|
|
this.drawRoundRect(ctx, this.margin + buttonWidth + buttonSpacing, buttonY, buttonWidth, buttonHeight, 3);
|
|
ctx.fillStyle = canIncrease ? "white" : "rgba(255, 255, 255, 0.4)";
|
|
ctx.fillText("+", this.margin + buttonWidth + buttonSpacing + buttonWidth / 2, buttonY + buttonHeight / 2);
|
|
const precisionX = this.margin + this.sliderWidth - 30;
|
|
ctx.fillStyle = "rgba(60, 60, 60, 0.6)";
|
|
this.drawRoundRect(ctx, precisionX, buttonY, 30, buttonHeight, 3);
|
|
ctx.fillStyle = "white";
|
|
ctx.textAlign = "center";
|
|
ctx.font = "7px Arial";
|
|
const precisionText = "1." + "0".repeat(this.node.properties.decimals);
|
|
ctx.fillText(precisionText, precisionX + 15, buttonY + buttonHeight / 2);
|
|
}
|
|
|
|
onMouseDown(e, pos) {
|
|
if (this.node.flags.collapsed) return false;
|
|
|
|
if (this.checkSliderInteraction(pos)) return true;
|
|
|
|
return this.checkSliderCountButtons(pos);
|
|
}
|
|
|
|
checkSliderInteraction(pos) {
|
|
const { properties } = this.node;
|
|
const hitAreaHeight = 20;
|
|
|
|
for (let i = 0; i < properties.values.length; i++) {
|
|
const y = this.topMargin + i * (this.sliderHeight + this.sliderSpacing);
|
|
|
|
if (pos[1] >= y - hitAreaHeight / 2 && pos[1] <= y + hitAreaHeight / 2) {
|
|
if (pos[0] >= this.margin && pos[0] <= this.margin + this.sliderWidth) {
|
|
this.currentSlider = i;
|
|
this.isPressed = true;
|
|
this.updateSliderValue(pos);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
checkSliderCountButtons(pos) {
|
|
const buttonY = this.node.size[1] - this.bottomMargin/2;
|
|
const buttonWidth = 10;
|
|
const buttonHeight = 10;
|
|
const buttonSpacing = 2;
|
|
|
|
if (pos[1] >= buttonY && pos[1] <= buttonY + buttonHeight) {
|
|
if (pos[0] >= this.margin && pos[0] <= this.margin + buttonWidth) {
|
|
if (this.node.properties.sliderCount > 1) {
|
|
this.updateSliderCount(this.node.properties.sliderCount - 1);
|
|
return true;
|
|
}
|
|
}
|
|
else if (pos[0] >= this.margin + buttonWidth + buttonSpacing &&
|
|
pos[0] <= this.margin + buttonWidth * 2 + buttonSpacing) {
|
|
if (this.node.properties.sliderCount < 10) {
|
|
this.updateSliderCount(this.node.properties.sliderCount + 1);
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
const precisionX = this.margin + this.sliderWidth - 30;
|
|
if (pos[0] >= precisionX && pos[0] <= precisionX + 30) {
|
|
let newDecimals = (this.node.properties.decimals % 3) + 1;
|
|
this.node.properties.decimals = newDecimals;
|
|
|
|
this._syncPrecisionStateAndVisuals(newDecimals);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_syncPrecisionStateAndVisuals(newDecimals) {
|
|
this.node.widgets.forEach(w => {
|
|
if (w?.options) {
|
|
w.options.precision = newDecimals;
|
|
}
|
|
});
|
|
|
|
this.updateAllValues();
|
|
this.node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
onMouseMove(e, pos) {
|
|
if (e.buttons === 0 && this.isPressed) {
|
|
this.currentSlider = undefined;
|
|
this.isPressed = false;
|
|
}
|
|
|
|
if (!this.isPressed) {
|
|
const { properties } = this.node;
|
|
const hitAreaHeight = 20;
|
|
let found = false;
|
|
|
|
for (let i = 0; i < properties.values.length; i++) {
|
|
const y = this.topMargin + i * (this.sliderHeight + this.sliderSpacing);
|
|
if (pos[1] >= y - hitAreaHeight / 2 && pos[1] <= y + hitAreaHeight / 2) {
|
|
this.currentHoverSlider = i;
|
|
found = true;
|
|
this.node.setDirtyCanvas(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
this.currentHoverSlider = undefined;
|
|
this.node.setDirtyCanvas(true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (this.currentSlider !== undefined) {
|
|
this.updateSliderValue(pos);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
onMouseLeave() {
|
|
this.currentSlider = undefined;
|
|
this.isPressed = false;
|
|
this.currentHoverSlider = undefined;
|
|
this.node.setDirtyCanvas(true);
|
|
}
|
|
|
|
onMouseUp() {
|
|
this.currentSlider = undefined;
|
|
this.isPressed = false;
|
|
this.currentHoverSlider = undefined;
|
|
this.node.setDirtyCanvas(true);
|
|
return true;
|
|
}
|
|
|
|
updateSliderValue(pos) {
|
|
if (this.currentSlider === undefined) return;
|
|
|
|
const { properties } = this.node;
|
|
|
|
const relativeX = Math.min(Math.max(pos[0] - this.margin, 0), this.sliderWidth);
|
|
const t = relativeX / this.sliderWidth;
|
|
|
|
let value = properties.min + (properties.max - properties.min) * t;
|
|
value = Math.min(Math.max(value, properties.min), properties.max);
|
|
|
|
if (properties.snap) {
|
|
value = Math.round(value / properties.step) * properties.step;
|
|
}
|
|
|
|
this.updateValue(this.currentSlider, value);
|
|
this.node.setDirtyCanvas(true);
|
|
}
|
|
|
|
drawRoundRect(ctx, x, y, width, height, radius, stroke = false) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + radius, y);
|
|
ctx.arcTo(x + width, y, x + width, y + height, radius);
|
|
ctx.arcTo(x + width, y + height, x, y + height, radius);
|
|
ctx.arcTo(x, y + height, x, y, radius);
|
|
ctx.arcTo(x, y, x + width, y, radius);
|
|
ctx.closePath();
|
|
if (stroke) {
|
|
ctx.strokeStyle = "#555";
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
} else {
|
|
ctx.fillStyle = ctx.fillStyle || this.colors.sliderBackground;
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
isHoveringHandle(handleX, handleY, sliderIndex) {
|
|
if (!this.node.mouse) return false;
|
|
const distance = Math.sqrt(
|
|
Math.pow(this.node.mouse[0] - handleX, 2) +
|
|
Math.pow(this.node.mouse[1] - handleY, 2)
|
|
);
|
|
return distance < this.handleRadius.hover && this.currentHoverSlider === sliderIndex;
|
|
}
|
|
|
|
updateColors(startColor, endColor) {
|
|
this.colors.sliderProgress = [startColor, endColor];
|
|
|
|
|
|
if (endColor) {
|
|
const rgbResult = this.hexToRgb(endColor);
|
|
if (rgbResult) {
|
|
this.colors.tooltip = `rgba(${rgbResult}, 0.9)`;
|
|
}
|
|
}
|
|
|
|
this.node.setDirtyCanvas(true);
|
|
}
|
|
|
|
hexToRgb(hex) {
|
|
if (!hex) return null;
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` : null;
|
|
}
|
|
|
|
updateSliderCount(count) {
|
|
count = Math.round(count);
|
|
count = Math.max(1, Math.min(10, count));
|
|
|
|
if (this.node.properties.sliderCount === count) {
|
|
return;
|
|
}
|
|
|
|
this.rebuildStructure(count, this.node.properties.values);
|
|
}
|
|
|
|
onResize() {
|
|
this.calculateConstants();
|
|
this.node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
updateAllValues() {
|
|
const activeCount = this.node.properties.sliderCount || 0;
|
|
this.node.properties.values.forEach((value, index) => {
|
|
if (index < activeCount) {
|
|
const roundedValue = Number(value.toFixed(this.node.properties.decimals));
|
|
this.updateValue(index, roundedValue);
|
|
}
|
|
});
|
|
|
|
this.node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
rebuildStructure(count, existingValues = []) {
|
|
count = Math.round(count);
|
|
count = Math.max(1, Math.min(10, count));
|
|
|
|
if (this.node.widgets) {
|
|
this.node.widgets.length = 0;
|
|
}
|
|
this.node.widgets = this.node.widgets || [];
|
|
|
|
const newValues = existingValues.slice(0, count);
|
|
while (newValues.length < count) {
|
|
let defaultValue = 0;
|
|
const min = this.node.properties.min ?? -100;
|
|
const max = this.node.properties.max ?? 100;
|
|
if (min > 0 || max < 0) {
|
|
defaultValue = min + (max - min) / 2;
|
|
}
|
|
newValues.push(defaultValue);
|
|
}
|
|
this.node.properties.values = newValues;
|
|
|
|
while (this.node.outputs && this.node.outputs.length > count) {
|
|
this.node.removeOutput(this.node.outputs.length - 1);
|
|
}
|
|
while (!this.node.outputs || this.node.outputs.length < count) {
|
|
const outputIndex = this.node.outputs ? this.node.outputs.length : 0;
|
|
this.node.addOutput("", "FLOAT");
|
|
}
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const widgetName = `value${i + 1}`;
|
|
const widget = this.node.addWidget(
|
|
"number",
|
|
widgetName,
|
|
this.node.properties.values[i],
|
|
(v) => {
|
|
const currentIndex = this.node.widgets.findIndex(w => w.name === widgetName);
|
|
if (currentIndex !== -1 && currentIndex < this.node.properties.sliderCount) {
|
|
this.updateValue(currentIndex, v);
|
|
}
|
|
},
|
|
{
|
|
precision: this.node.properties.decimals,
|
|
min: this.node.properties.min,
|
|
max: this.node.properties.max,
|
|
step: this.node.properties.step,
|
|
hidden: true
|
|
}
|
|
);
|
|
widget.type = "number";
|
|
widget.computeSize = () => [0, -4];
|
|
widget.hidden = true;
|
|
Object.defineProperty(widget, "height", { get: () => 0, set: () => {} });
|
|
}
|
|
|
|
this.node.properties.sliderCount = count;
|
|
this.updateAllValues();
|
|
this.calculateConstants();
|
|
this.node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
updateWidgetOptions() {
|
|
if (!this.node.widgets) return;
|
|
const options = {
|
|
precision: this.node.properties.decimals,
|
|
min: this.node.properties.min,
|
|
max: this.node.properties.max,
|
|
step: this.node.properties.step
|
|
};
|
|
this.node.widgets.forEach(w => {
|
|
if (w.type === 'number' && w.options) {
|
|
Object.assign(w.options, options);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "MultiFloat",
|
|
nodeType: "MultiFloat",
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name === "MultiFloat") {
|
|
nodeType.properties = {
|
|
startColor: { type: "color", default: "#4A90E2" },
|
|
slidercolor: { type: "color", default: "#D0021B" },
|
|
sliderCount: { type: "number", default: 3, min: 1, max: 10, step: 1 },
|
|
decimals: { type: "number", default: 1, min: 1, max: 3, step: 1 },
|
|
values: { type: "array", default: [0, 0, 0] },
|
|
min: { type: "number", default: -100 },
|
|
max: { type: "number", default: 100 },
|
|
step: { type: "number", default: 0.1 },
|
|
snap: { type: "boolean", default: false },
|
|
};
|
|
|
|
Object.assign(nodeType.prototype, {
|
|
computeSize() {
|
|
const count = this.properties?.sliderCount || 3;
|
|
const topMargin = 12;
|
|
const bottomMargin = 50;
|
|
const sliderHeight = 4;
|
|
const sliderSpacing = 16;
|
|
const height = topMargin + (count * (sliderHeight + sliderSpacing)) + bottomMargin;
|
|
const minHeight = 50;
|
|
|
|
|
|
|
|
let width = 240;
|
|
|
|
|
|
if (this.size && Array.isArray(this.size) && this.size.length >= 2) {
|
|
width = this.size[0];
|
|
}
|
|
|
|
return [width, Math.max(minHeight, height)];
|
|
},
|
|
|
|
onSerialize(o) {
|
|
if (this.multiFloat && this.properties) {
|
|
o.multiFloatState = {
|
|
values: this.properties.values || [],
|
|
sliderCount: this.properties.sliderCount || 3,
|
|
startColor: this.properties.startColor,
|
|
slidercolor: this.properties.slidercolor,
|
|
decimals: this.properties.decimals,
|
|
min: this.properties.min,
|
|
max: this.properties.max,
|
|
step: this.properties.step,
|
|
snap: this.properties.snap
|
|
};
|
|
}
|
|
},
|
|
|
|
onConfigure(config) {
|
|
if (!this.multiFloat) {
|
|
this.multiFloat = new MultiFloatNode(this);
|
|
} else {
|
|
}
|
|
|
|
const state = config.multiFloatState;
|
|
let loadedCount = 3;
|
|
let loadedValues = [0,0,0];
|
|
|
|
if (state) {
|
|
this.properties = this.properties || {};
|
|
loadedValues = state.values || [0, 0, 0];
|
|
loadedCount = state.sliderCount || 3;
|
|
Object.assign(this.properties, {
|
|
values: loadedValues,
|
|
sliderCount: loadedCount,
|
|
startColor: state.startColor || nodeType.properties.startColor.default,
|
|
slidercolor: state.slidercolor || nodeType.properties.slidercolor.default,
|
|
decimals: state.decimals ?? nodeType.properties.decimals.default,
|
|
min: state.min ?? nodeType.properties.min.default,
|
|
max: state.max ?? nodeType.properties.max.default,
|
|
step: state.step ?? nodeType.properties.step.default,
|
|
snap: state.snap ?? nodeType.properties.snap.default,
|
|
});
|
|
} else {
|
|
loadedCount = nodeType.properties.sliderCount.default;
|
|
loadedValues = Array(loadedCount).fill(0);
|
|
this.properties = this.properties || {};
|
|
Object.assign(this.properties, {
|
|
values: loadedValues,
|
|
sliderCount: loadedCount,
|
|
startColor: nodeType.properties.startColor.default,
|
|
slidercolor: nodeType.properties.slidercolor.default,
|
|
decimals: nodeType.properties.decimals.default,
|
|
min: nodeType.properties.min.default,
|
|
max: nodeType.properties.max.default,
|
|
step: nodeType.properties.step.default,
|
|
snap: nodeType.properties.snap.default,
|
|
});
|
|
}
|
|
|
|
this.multiFloat.rebuildStructure(loadedCount, loadedValues);
|
|
this.multiFloat.updateColors(this.properties.startColor, this.properties.slidercolor);
|
|
|
|
|
|
if (config.size && Array.isArray(config.size) && config.size.length >= 2) {
|
|
|
|
this.size = [config.size[0], config.size[1]];
|
|
} else {
|
|
|
|
const [width, height] = this.computeSize();
|
|
this.size = [width, height];
|
|
}
|
|
|
|
this.setDirtyCanvas(true, true);
|
|
},
|
|
|
|
onNodeCreated() {
|
|
if (!this.multiFloat) {
|
|
this.multiFloat = new MultiFloatNode(this);
|
|
|
|
this.outputs = this.outputs || [];
|
|
|
|
if (!this.graph || !this.graph._configuring) {
|
|
const initialCount = this.properties?.sliderCount || nodeType.properties.sliderCount.default;
|
|
const initialValues = this.properties?.values || Array(initialCount).fill(0);
|
|
this.properties = this.properties || {};
|
|
Object.assign(this.properties, {
|
|
values: initialValues,
|
|
sliderCount: initialCount,
|
|
startColor: nodeType.properties.startColor.default,
|
|
slidercolor: nodeType.properties.slidercolor.default,
|
|
decimals: nodeType.properties.decimals.default,
|
|
min: nodeType.properties.min.default,
|
|
max: nodeType.properties.max.default,
|
|
step: nodeType.properties.step.default,
|
|
snap: nodeType.properties.snap.default,
|
|
});
|
|
this.multiFloat.rebuildStructure(initialCount, initialValues);
|
|
const [width, height] = this.computeSize();
|
|
this.size = [width, height];
|
|
}
|
|
}
|
|
if (this.multiFloat) {
|
|
this.onDrawForeground = this.multiFloat.onDrawForeground.bind(this.multiFloat);
|
|
}
|
|
},
|
|
|
|
onPropertyChanged(property, value, prevValue) {
|
|
try {
|
|
if (!this.multiFloat || !this.properties) return;
|
|
if (property === "startColor" || property === "slidercolor") {
|
|
this.multiFloat.updateColors(this.properties.startColor, this.properties.slidercolor);
|
|
} else if (property === "sliderCount") {
|
|
const newCount = Math.round(value);
|
|
if (newCount !== this.properties.sliderCount) {
|
|
this.properties.sliderCount = newCount;
|
|
this.multiFloat.rebuildStructure(newCount, this.properties.values);
|
|
const [width, height] = this.computeSize();
|
|
this.size = [width, height];
|
|
this.setDirtyCanvas(true, true);
|
|
}
|
|
} else if (property === "decimals") {
|
|
const newDecimals = Math.round(value);
|
|
if (newDecimals !== this.properties.decimals) {
|
|
this.properties.decimals = newDecimals;
|
|
this.multiFloat._syncPrecisionStateAndVisuals(newDecimals);
|
|
this.multiFloat.updateAllValues();
|
|
}
|
|
} else if (["min", "max", "step"].includes(property)) {
|
|
this.properties[property] = value;
|
|
this.multiFloat.updateWidgetOptions();
|
|
this.multiFloat.updateAllValues();
|
|
} else if (property === "snap") {
|
|
this.properties.snap = value;
|
|
this.multiFloat.updateAllValues();
|
|
}
|
|
this.setDirtyCanvas(true, true);
|
|
} catch (error) {
|
|
console.warn(`MultiFloat property change error (${property}):`, error);
|
|
}
|
|
},
|
|
|
|
onMouseDown(e, pos) {
|
|
try {
|
|
return this.multiFloat?.onMouseDown(e, pos);
|
|
} catch (error) {
|
|
console.warn("MultiFloat mouse down error:", error);
|
|
return false;
|
|
}
|
|
},
|
|
onMouseMove(e, pos) {
|
|
try {
|
|
return this.multiFloat?.onMouseMove(e, pos);
|
|
} catch (error) {
|
|
console.warn("MultiFloat mouse move error:", error);
|
|
return false;
|
|
}
|
|
},
|
|
onMouseUp(e, pos) {
|
|
try {
|
|
return this.multiFloat?.onMouseUp(e, pos);
|
|
} catch (error) {
|
|
console.warn("MultiFloat mouse up error:", error);
|
|
return true;
|
|
}
|
|
},
|
|
onMouseLeave(e) {
|
|
try {
|
|
return this.multiFloat?.onMouseLeave(e);
|
|
} catch (error) {
|
|
console.warn("MultiFloat mouse leave error:", error);
|
|
}
|
|
},
|
|
onResize(size) {
|
|
if (this.multiFloat) {
|
|
this.multiFloat.calculateConstants();
|
|
this.multiFloat.node.setDirtyCanvas(true, true);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|