Files
siraxe-ComfyUI-WanVideoWrap…/web/power_spline_editor/widget_utils.js
T
siraxe edaa843d7d refactorv1
spline refactor
position change
+ preview
scaling works
2025-10-13 03:45:31 +01:00

702 lines
28 KiB
JavaScript

import { app } from '../../../scripts/app.js';
import { isLowQuality, drawTogglePart, drawNumberWidgetPart, drawRoundedRectangle, fitString, RgthreeBaseWidget } from './drawing_utils.js';
import { initializeDrivenConfig, initializeEasingConfig, initializeScaleConfig, toggleDrivenState, prepareDrivenMenu } from './persistence.js';
import { showCustomEasingMenu, showCustomDrivenToggleMenu } from './context_menu.js';
// === SPLINE MULTI WIDGET ===
export class PowerSplineHeaderWidget extends RgthreeBaseWidget {
constructor(name = "PowerSplineHeaderWidget") {
super(name);
this.value = { type: "PowerSplineHeaderWidget" };
this.type = "custom";
this.options = { serialize: false };
this.hitAreas = {
toggle: { bounds: [0, 0], onDown: this.onToggleDown },
};
}
draw(ctx, node, w, posY, height) {
const margin = 10;
const innerMargin = margin * 0.33;
const lowQuality = isLowQuality();
const allSplineState = node.allSplinesState ? node.allSplinesState() : false;
posY += 2;
const midY = posY + height * 0.5;
let posX = 10;
ctx.save();
this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: allSplineState });
if (!lowQuality) {
posX += this.hitAreas.toggle.bounds[1] + innerMargin;
ctx.globalAlpha = app.canvas.editor_alpha * 0.55;
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText("Toggle All", posX, midY);
let rposX = node.size[0] - margin - innerMargin - innerMargin;
ctx.textAlign = "center";
// Match the widget layout exactly:
// 1. Repeat control (far right) - drawNumberWidgetPart.WIDTH_TOTAL = 56
const repeatWidth = drawNumberWidgetPart.WIDTH_TOTAL;
ctx.fillText("Repeat", rposX - repeatWidth / 2, midY);
rposX -= repeatWidth + 10;
// 2. Interpolation selector - just text, no arrows (width = 70)
const interpTextWidth = 70;
ctx.fillText("Interpolation", rposX - interpTextWidth / 2, midY);
rposX -= interpTextWidth + 10;
const numberWidth = drawNumberWidgetPart.WIDTH_TOTAL;
ctx.fillText("Z-Pause", rposX - numberWidth / 2, midY);
rposX -= numberWidth + 10;
ctx.fillText("A-Pause", rposX - numberWidth / 2, midY);
rposX -= numberWidth + 10;
ctx.fillText("Offset", rposX - numberWidth / 2, midY);
rposX -= numberWidth + 10;
// Easing column - same width as interpolation
const easingTextWidth = 70;
ctx.fillText("Easing", rposX - easingTextWidth / 2, midY);
rposX -= easingTextWidth + 10;
// Scale column
ctx.fillText("Scale", rposX - numberWidth / 2, midY);
rposX -= numberWidth + 10;
// Driven column (moved after Scale)
ctx.fillText("Driven", rposX - numberWidth / 2, midY);
rposX -= numberWidth + 10;
}
ctx.restore();
}
onToggleDown(event, pos, node) {
if (node.toggleAllSplines) {
node.toggleAllSplines();
}
this.cancelMouseDown();
return true;
}
}
export class PowerSplineWidget extends RgthreeBaseWidget {
constructor(name) {
super(name);
this.type = "custom";
this.options = { serialize: true };
this.haveMouseMovedValue = false;
this.hitAreas = {
toggle: { bounds: [0, 0], onDown: this.onToggleDown },
name: { bounds: [0, 0], onClick: this.onNameClick },
interpVal: { bounds: [0, 0], onClick: this.onInterpClick },
repeatDec: { bounds: [0, 0], onClick: this.onRepeatDec },
repeatVal: { bounds: [0, 0], onClick: this.onRepeatClick },
repeatInc: { bounds: [0, 0], onClick: this.onRepeatInc },
repeatAny: { bounds: [0, 0], onMove: this.onRepeatMove },
offsetDec: { bounds: [0, 0], onClick: this.onOffsetDec },
offsetVal: { bounds: [0, 0], onClick: this.onOffsetClick },
offsetInc: { bounds: [0, 0], onClick: this.onOffsetInc },
offsetAny: { bounds: [0, 0], onMove: this.onOffsetMove },
aPauseDec: { bounds: [0, 0], onClick: this.onAPauseDec },
aPauseVal: { bounds: [0, 0], onClick: this.onAPauseClick },
aPauseInc: { bounds: [0, 0], onClick: this.onAPauseInc },
aPauseAny: { bounds: [0, 0], onMove: this.onAPauseMove },
zPauseDec: { bounds: [0, 0], onClick: this.onZPauseDec },
zPauseVal: { bounds: [0, 0], onClick: this.onZPauseClick },
zPauseInc: { bounds: [0, 0], onClick: this.onZPauseInc },
zPauseAny: { bounds: [0, 0], onMove: this.onZPauseMove },
easingVal: { bounds: [0, 0], onClick: this.onEasingClick, onRightDown: this.onEasingRightDown },
drivenToggle: { bounds: [0, 0], onDown: this.onDrivenToggleDown, onRightDown: this.onDrivenToggleRightDown },
scaleDec: { bounds: [0, 0], onClick: this.onScaleDec },
scaleVal: { bounds: [0, 0], onClick: this.onScaleClick },
scaleInc: { bounds: [0, 0], onClick: this.onScaleInc },
scaleAny: { bounds: [0, 0], onMove: this.onScaleMove },
};
this._value = {
on: true,
name: "Spline",
interpolation: 'linear',
repeat: 1,
offset: 0,
a_pause: 0,
z_pause: 0,
easing: 'in_out', // Use simple name for consistency
driven: false, // false = off, object = on with config
_drivenConfig: { driver: "", rotate: 0, d_scale: 1.0 }, // Preserved config
easingConfig: { path: "each", strength: 1.0 }, // Easing configuration
scale: 1.00, // Scale factor with min 0.01, max 8.00, default 1.00
points_store: "[]",
coordinates: "[]",
};
}
set value(v) {
// Merge constructor defaults with loaded data
this._value = { ...this._value, ...(typeof v === 'object' && v !== null ? v : {}) };
// Initialize persistent configurations using centralized functions
initializeDrivenConfig(this._value, v);
initializeEasingConfig(this._value, v);
initializeScaleConfig(this._value, v);
}
get value() {
return this._value;
}
draw(ctx, node, w, posY, height) {
ctx.save();
const margin = 10;
const innerMargin = margin * 0.33;
const lowQuality = isLowQuality();
const midY = posY + height * 0.5;
let posX = margin;
// Highlight if active
if (node.layerManager && node.layerManager.getActiveWidget() === this) {
drawRoundedRectangle(ctx, { pos: [posX, posY], size: [node.size[0] - margin * 2, height], colorStroke: "#1f77b4", colorBackground: "#222222CC" });
} else {
drawRoundedRectangle(ctx, { pos: [posX, posY], size: [node.size[0] - margin * 2, height], colorBackground: "#222222CC" });
}
this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: this.value.on });
posX += this.hitAreas.toggle.bounds[1] + innerMargin;
if (lowQuality) {
ctx.restore();
return;
}
if (!this.value.on) {
ctx.globalAlpha = app.canvas.editor_alpha * 0.4;
}
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.textBaseline = "middle";
let rposX = node.size[0] - margin - innerMargin;
// Draw repeat control (far right)
const [repeatL, repeatT, repeatR] = drawNumberWidgetPart(ctx, {
posX: rposX, posY, height, value: this.value.repeat || 1, direction: -1,
});
this.hitAreas.repeatDec.bounds = repeatL;
this.hitAreas.repeatVal.bounds = repeatT;
this.hitAreas.repeatInc.bounds = repeatR;
this.hitAreas.repeatAny.bounds = [repeatL[0], repeatR[0] + repeatR[1] - repeatL[0]];
rposX -= drawNumberWidgetPart.WIDTH_TOTAL + 10;
// Draw interpolation selector
const interpModes = ['linear', 'cardinal', 'basis', 'points'];
const interpShortNames = ['linear', 'cardinal', 'basis', 'points'];
const interpIndex = interpModes.indexOf(this.value.interpolation || 'linear');
const interpDisplayText = interpIndex >= 0 ? interpShortNames[interpIndex] : 'linear';
const interpTextWidth = 70;
ctx.textAlign = "center";
ctx.fillText(interpDisplayText, rposX - interpTextWidth / 2, midY);
this.hitAreas.interpVal.bounds = [rposX - interpTextWidth, interpTextWidth];
rposX -= interpTextWidth + 10;
// Draw z_pause control
const [zPauseL, zPauseT, zPauseR] = drawNumberWidgetPart(ctx, {
posX: rposX, posY, height, value: this.value.z_pause || 0, direction: -1,
});
this.hitAreas.zPauseDec.bounds = zPauseL;
this.hitAreas.zPauseVal.bounds = zPauseT;
this.hitAreas.zPauseInc.bounds = zPauseR;
this.hitAreas.zPauseAny.bounds = [zPauseL[0], zPauseR[0] + zPauseR[1] - zPauseL[0]];
rposX -= drawNumberWidgetPart.WIDTH_TOTAL + 10;
// Draw a_pause control
const [aPauseL, aPauseT, aPauseR] = drawNumberWidgetPart(ctx, {
posX: rposX, posY, height, value: this.value.a_pause || 0, direction: -1,
});
this.hitAreas.aPauseDec.bounds = aPauseL;
this.hitAreas.aPauseVal.bounds = aPauseT;
this.hitAreas.aPauseInc.bounds = aPauseR;
this.hitAreas.aPauseAny.bounds = [aPauseL[0], aPauseR[0] + aPauseR[1] - aPauseL[0]];
rposX -= drawNumberWidgetPart.WIDTH_TOTAL + 10;
// Draw offset control
// Offset creates pause frames by removing coordinates and adjusting pause metadata
// Positive: waits at START for N frames, then animates to N frames before end
// Negative: animates normally, then holds at END for N frames
const [offsetL, offsetT, offsetR] = drawNumberWidgetPart(ctx, {
posX: rposX, posY, height, value: this.value.offset || 0, direction: -1,
});
this.hitAreas.offsetDec.bounds = offsetL;
this.hitAreas.offsetVal.bounds = offsetT;
this.hitAreas.offsetInc.bounds = offsetR;
this.hitAreas.offsetAny.bounds = [offsetL[0], offsetR[0] + offsetR[1] - offsetL[0]];
rposX -= drawNumberWidgetPart.WIDTH_TOTAL + 10;
// Draw easing selector (same approach as interpolation)
const easingModes = ['linear', 'in', 'out', 'in_out', 'out_in'];
const easingShortNames = ['linear', 'in', 'out', 'in_out', 'out_in'];
const easingIndex = easingModes.indexOf(this.value.easing || 'in_out');
const easingDisplayText = easingIndex >= 0 ? easingShortNames[easingIndex] : 'in_out';
const easingTextWidth = 70;
ctx.textAlign = "center";
ctx.fillText(easingDisplayText, rposX - easingTextWidth / 2, midY);
this.hitAreas.easingVal.bounds = [rposX - easingTextWidth, easingTextWidth];
rposX -= easingTextWidth + 10;
// Draw scale control
const [scaleL, scaleT, scaleR] = drawNumberWidgetPart(ctx, {
posX: rposX, posY, height, value: this.value.scale || 1.00, direction: -1, precision: 2,
});
this.hitAreas.scaleDec.bounds = scaleL;
this.hitAreas.scaleVal.bounds = scaleT;
this.hitAreas.scaleInc.bounds = scaleR;
this.hitAreas.scaleAny.bounds = [scaleL[0], scaleR[0] + scaleR[1] - scaleL[0]];
rposX -= drawNumberWidgetPart.WIDTH_TOTAL + 10;
// Draw driven toggle
const numberWidth = drawNumberWidgetPart.WIDTH_TOTAL;
// Align driven toggle with the header label by using precise positioning
const drivenPosX = rposX - numberWidth / 2 - 15; // Shift driven toggle a bit to the left (by 5 pixels)
this.hitAreas.drivenToggle.bounds = drawTogglePart(ctx, { posX: drivenPosX, posY, height, value: this.value.driven });
rposX -= numberWidth + 10;
// Draw name (left side, after toggle)
ctx.textAlign = "left";
const nameText = fitString(ctx, this.value.name || "Spline", rposX - posX - innerMargin);
ctx.fillText(nameText, posX, midY);
this.hitAreas.name.bounds = [posX, 0, rposX - posX, height];
ctx.restore();
}
onMouseDown(event, pos, node) {
// Check if the click was on the toggle button
if (this.hitAreas.toggle && this.clickWasWithinBounds(pos, this.hitAreas.toggle.bounds)) {
// If it was on the toggle, do not set active widget.
// The onToggleDown handler will take care of the toggle action.
} else if (node.layerManager) {
// Otherwise, set this widget as active
node.layerManager.setActiveWidget(this);
}
return super.onMouseDown?.(event, pos, node);
}
onToggleDown(event, pos, node) {
this.value.on = !this.value.on;
this.cancelMouseDown();
node.setDirtyCanvas(true, true);
return true;
}
onNameClick(event, pos, node) {
// Single click just activates the layer
// Double-click rename is handled at the node level via onDblClick
return true;
}
onInterpDec() {
const modes = ['linear', 'cardinal', 'basis', 'points'];
const currentIndex = modes.indexOf(this.value.interpolation || 'linear');
const newIndex = (currentIndex - 1 + modes.length) % modes.length;
this.value.interpolation = modes[newIndex];
this.parent.setDirtyCanvas(true, true);
// Update editor immediately if this is the active layer
if (this.parent.layerManager?.getActiveWidget() === this && this.parent.editor) {
if (this.parent.editor.layerRenderer) {
this.parent.editor.layerRenderer.render();
}
}
return true;
}
onInterpInc() {
const modes = ['linear', 'cardinal', 'basis', 'points'];
const currentIndex = modes.indexOf(this.value.interpolation || 'linear');
const newIndex = (currentIndex + 1) % modes.length;
this.value.interpolation = modes[newIndex];
this.parent.setDirtyCanvas(true, true);
// Update editor immediately if this is the active layer
if (this.parent.layerManager?.getActiveWidget() === this && this.parent.editor) {
if (this.parent.editor.layerRenderer) {
this.parent.editor.layerRenderer.render();
}
}
return true;
}
onInterpClick() {
// Cycle through modes
this.onInterpInc();
return true;
}
onRepeatDec() {
this.value.repeat = Math.max(1, (this.value.repeat || 1) - 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onRepeatInc() {
this.value.repeat = Math.min(20, (this.value.repeat || 1) + 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onRepeatClick() {
const canvas = app.canvas;
canvas.prompt("Repeat", this.value.repeat || 1, (v) => {
this.value.repeat = Math.max(1, Math.min(20, Number(v)));
this.parent.setDirtyCanvas(true, true);
});
return true;
}
onRepeatMove(event, pos, node) {
if (event.deltaX) {
this.haveMouseMovedValue = true;
this.value.repeat = Math.max(1, Math.min(20, (this.value.repeat || 1) + Math.round(event.deltaX / 10)));
node.setDirtyCanvas(true, true);
}
}
onOffsetDec() {
this.value.offset = Math.max(-100, (this.value.offset || 0) - 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onOffsetInc() {
this.value.offset = Math.min(100, (this.value.offset || 0) + 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onOffsetClick() {
if (this.haveMouseMovedValue) return;
const canvas = app.canvas;
// Offset creates pause frames: +5 waits 5 frames then plays, -5 plays then holds 5 frames
canvas.prompt("Offset", this.value.offset || 0, (v) => {
this.value.offset = Math.max(-100, Math.min(100, Number(v)));
this.parent.setDirtyCanvas(true, true);
});
return true;
}
onOffsetMove(event, pos, node) {
if (event.deltaX) {
this.haveMouseMovedValue = true;
this.value.offset = Math.max(-100, Math.min(100, (this.value.offset || 0) + Math.round(event.deltaX / 10)));
node.setDirtyCanvas(true, true);
}
}
onEasingClick() {
// Cycle through easing modes
const modes = ['linear', 'in', 'out', 'in_out', 'out_in'];
const currentIndex = modes.indexOf(this.value.easing || 'in_out');
const newIndex = (currentIndex + 1) % modes.length;
this.value.easing = modes[newIndex];
this.parent.setDirtyCanvas(true, true);
return true;
}
onEasingRightDown(event, pos, node) {
event.preventDefault(); // Prevent default browser context menu
event.stopPropagation(); // Stop event from propagating further
// Get screen position for the menu (similar to Driven toggle)
const canvas = app.canvas;
let x = 100;
let y = 100;
// Try to get from last_mouse_position (canvas screen coords)
if (canvas.last_mouse_position) {
x = canvas.last_mouse_position[0];
y = canvas.last_mouse_position[1];
}
// Try canvas_mouse (might be available)
else if (canvas.canvas_mouse) {
x = canvas.canvas_mouse[0];
y = canvas.canvas_mouse[1];
}
// Convert graph coordinates to screen coordinates
else if (canvas.graph_mouse && canvas.ds) {
const graphX = canvas.graph_mouse[0];
const graphY = canvas.graph_mouse[1];
// Convert from graph space to canvas space
x = graphX * canvas.ds.scale + canvas.ds.offset[0];
y = graphY * canvas.ds.scale + canvas.ds.offset[1];
// Convert from canvas space to screen space
const canvasRect = canvas.canvas.getBoundingClientRect();
x += canvasRect.left;
y += canvasRect.top;
}
console.log("Easing menu position calculated:", {x, y});
// Show context menu for easing configuration (UI only)
showCustomEasingMenu(event, this, { x, y });
this.cancelMouseDown();
return true;
}
onAPauseDec() {
this.value.a_pause = Math.max(0, (this.value.a_pause || 0) - 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onAPauseInc() {
this.value.a_pause = Math.min(100, (this.value.a_pause || 0) + 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onAPauseClick() {
if (this.haveMouseMovedValue) return;
const canvas = app.canvas;
canvas.prompt("A Pause", this.value.a_pause || 0, (v) => {
this.value.a_pause = Math.max(0, Math.min(100, Number(v)));
this.parent.setDirtyCanvas(true, true);
});
return true;
}
onAPauseMove(event, pos, node) {
if (event.deltaX) {
this.haveMouseMovedValue = true;
this.value.a_pause = Math.max(0, Math.min(100, (this.value.a_pause || 0) + Math.round(event.deltaX / 10)));
node.setDirtyCanvas(true, true);
}
}
onZPauseDec() {
this.value.z_pause = Math.max(0, (this.value.z_pause || 0) - 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onZPauseInc() {
this.value.z_pause = Math.min(100, (this.value.z_pause || 0) + 1);
this.parent.setDirtyCanvas(true, true);
return true;
}
onZPauseClick() {
if (this.haveMouseMovedValue) return;
const canvas = app.canvas;
canvas.prompt("Z Pause", this.value.z_pause || 0, (v) => {
this.value.z_pause = Math.max(0, Math.min(100, Number(v)));
this.parent.setDirtyCanvas(true, true);
});
return true;
}
onZPauseMove(event, pos, node) {
if (event.deltaX) {
this.haveMouseMovedValue = true;
this.value.z_pause = Math.max(0, Math.min(100, (this.value.z_pause || 0) + Math.round(event.deltaX / 10)));
node.setDirtyCanvas(true, true);
}
}
onMouseUp(event, pos, node) {
super.onMouseUp(event, pos, node);
this.haveMouseMovedValue = false;
// Set this widget as active when clicked
if (node.layerManager) {
node.layerManager.setActiveWidget(this);
}
}
onDrivenToggleDown(event, pos, node) {
// Call the centralized toggle logic
toggleDrivenState(this);
this.cancelMouseDown();
return true;
}
onDrivenToggleRightDown(event, pos, node) {
event.preventDefault(); // Prevent default browser context menu
event.stopPropagation(); // Stop event from propagating further
// Call the centralized function to prepare the config object
prepareDrivenMenu(this);
// Show context menu for the driven toggle specifically
showCustomDrivenToggleMenu(event, this, { x: pos[0], y: pos[1] });
this.cancelMouseDown();
return true;
}
onScaleDec() {
this.value.scale = Math.max(0.01, parseFloat((this.value.scale || 1.00) - 0.01).toFixed(2));
this.parent.setDirtyCanvas(true, true);
return true;
}
onScaleInc() {
this.value.scale = Math.min(8.00, parseFloat((this.value.scale || 1.00) + 0.01).toFixed(2));
this.parent.setDirtyCanvas(true, true);
return true;
}
onScaleClick() {
if (this.haveMouseMovedValue) return;
const canvas = app.canvas;
canvas.prompt("Scale", this.value.scale || 1.00, (v) => {
this.value.scale = Math.max(0.01, Math.min(8.00, parseFloat(Number(v).toFixed(2))));
this.parent.setDirtyCanvas(true, true);
});
return true;
}
onScaleMove(event, pos, node) {
if (event.deltaX) {
this.haveMouseMovedValue = true;
this.value.scale = Math.max(0.01, Math.min(8.00, parseFloat(((this.value.scale || 1.00) + (event.deltaX / 1000)).toFixed(2))));
node.setDirtyCanvas(true, true);
}
}
// Also need to make sure we properly handle the right-click event in the base mouse handler
mouse(event, pos, node) {
var _a, _b, _c;
const canvas = app.canvas;
if (event.type == "pointerdown") {
this.mouseDowned = [...pos];
this.isMouseDownedAndOver = true;
this.downedHitAreasForMove.length = 0;
this.downedHitAreasForClick.length = 0;
let anyHandled = false;
// Check if it's a right click (button 2)
const isRightClick = event.button === 2;
for (const part of Object.values(this.hitAreas)) {
if (this.clickWasWithinBounds(pos, part.bounds)) {
if (part.onMove) {
this.downedHitAreasForMove.push(part);
}
if (part.onClick) {
this.downedHitAreasForClick.push(part);
}
// Check for right-click BEFORE calling onDown to prevent toggle from firing on right-click
if (isRightClick && part.onRightDown) {
const thisHandled = part.onRightDown.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
} else if (part.onDown) {
// Only call onDown if it's NOT a right-click with onRightDown
const thisHandled = part.onDown.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
}
part.wasMouseClickedAndIsOver = true;
}
}
return (_a = this.onMouseDown(event, pos, node)) !== null && _a !== void 0 ? _a : anyHandled;
}
if (event.type == "pointerup") {
if (!this.mouseDowned)
return true;
this.downedHitAreasForMove.length = 0;
const wasMouseDownedAndOver = this.isMouseDownedAndOver;
this.cancelMouseDown();
let anyHandled = false;
for (const part of Object.values(this.hitAreas)) {
if (part.onUp && this.clickWasWithinBounds(pos, part.bounds)) {
const thisHandled = part.onUp.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
}
part.wasMouseClickedAndIsOver = false;
}
for (const part of this.downedHitAreasForClick) {
if (this.clickWasWithinBounds(pos, part.bounds)) {
const thisHandled = part.onClick.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
}
}
this.downedHitAreasForClick.length = 0;
if (wasMouseDownedAndOver) {
const thisHandled = this.onMouseClick(event, pos, node);
anyHandled = anyHandled || thisHandled == true;
}
return (_b = this.onMouseUp(event, pos, node)) !== null && _b !== void 0 ? _b : anyHandled;
}
if (event.type == "pointermove") {
this.isMouseDownedAndOver = !!this.mouseDowned;
if (this.mouseDowned &&
(pos[0] < 15 ||
pos[0] > node.size[0] - 15 ||
pos[1] < this.last_y ||
pos[1] > this.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) {
this.isMouseDownedAndOver = false;
}
for (const part of Object.values(this.hitAreas)) {
if (this.downedHitAreasForMove.includes(part)) {
part.onMove.apply(this, [event, pos, node, part]);
}
if (this.downedHitAreasForClick.includes(part)) {
part.wasMouseClickedAndIsOver = this.clickWasWithinBounds(pos, part.bounds);
}
}
return (_c = this.onMouseMove(event, pos, node)) !== null && _c !== void 0 ? _c : true;
}
return false;
}
serializeValue(node, index) {
// Return a deep copy to prevent reference sharing between widgets during serialization
// This ensures each widget has independent interpolation and other values
const serialized = JSON.parse(JSON.stringify(this.value));
return serialized;
}
computeSize(width) {
return [width, LiteGraph.NODE_WIDGET_HEIGHT];
}
}
export function chainCallback(object, property, callback) {
if (object == undefined) {
//This should not happen.
console.error("Tried to add callback to non-existant object")
return;
}
if (property in object) {
const callback_orig = object[property]
object[property] = function () {
const r = callback_orig.apply(this, arguments);
callback.apply(this, arguments);
return r
};
} else {
object[property] = callback;
}
}
export function hideWidgetForGood(node, widget, suffix = '') {
widget.origType = widget.type;
widget.type = 'hidden' + suffix;
widget.hidden = true;
// Monkeypatch draw to do nothing
widget.draw = () => {};
// Monkeypatch computeSize to return [0, -4]
// This is a hack to make the widget not take up any space
// We need to return -4 instead of 0 because of how LiteGraph calculates node height
// In recent versions of LiteGraph, it adds 4 to the widget height
widget.computeSize = () => [0, -4];
// Prevent the widget from being serialized
if (!widget.options) {
widget.options = {};
}
// widget.options.serialize = false;
// Hide the widget from the node's list of widgets
// This is another hack to prevent the widget from being drawn
// We can't just remove it from the list, because other parts of the code
// might still need to access it
const index = node.widgets.indexOf(widget);
if (index > -1) {
node.widgets.splice(index, 1);
node.widgets.push(widget);
}
}