1116 lines
56 KiB
JavaScript
1116 lines
56 KiB
JavaScript
// ComfyUI.CRZ.DashboardNode - Single dynamic slider based on connections
|
|
import { app } from "../../scripts/app.js";
|
|
import {
|
|
NODE_WIDTH,
|
|
NODE_HEIGHT_MULTIPLIER,
|
|
NODE_PADDING,
|
|
LABEL_LEFT_PADDING,
|
|
TRACK_LEFT_PADDING,
|
|
TRACK_RIGHT_PADDING,
|
|
HANDLE_LEFT_PADDING,
|
|
HANDLE_RIGHT_PADDING,
|
|
VALUE_RIGHT_SHIFT,
|
|
VALUE_RIGHT_OFFSET,
|
|
VALUE_RIGHT_PADDING,
|
|
TRACK_HEIGHT,
|
|
TRACK_WIDTH,
|
|
HANDLE_RADIUS,
|
|
SLIDER_HEIGHT,
|
|
DASHBOARD_LINE_SPACING,
|
|
NODE_BACKGROUND_COLOR,
|
|
LABEL_COLOR,
|
|
VALUE_COLOR,
|
|
TRACK_COLOR,
|
|
HANDLE_BORDER_COLOR,
|
|
INACTIVE_LABEL_COLOR,
|
|
INACTIVE_VALUE_COLOR,
|
|
INACTIVE_TRACK_COLOR,
|
|
INACTIVE_HANDLE_COLOR,
|
|
TOGGLE_WIDTH,
|
|
TOGGLE_HEIGHT,
|
|
TOGGLE_CORNER_RADIUS,
|
|
HANDLE_SIZE,
|
|
HANDLE_CORNER_RADIUS,
|
|
HANDLE_PADDING,
|
|
TOGGLE_VERTICAL_OFFSET,
|
|
TOGGLE_ACTIVE_COLOR,
|
|
TOGGLE_INACTIVE_COLOR,
|
|
HANDLE_COLOR,
|
|
HANDLE_BORDER_ACTIVE,
|
|
HANDLE_BORDER_INACTIVE,
|
|
DROPDOWN_BG_COLOR,
|
|
DROPDOWN_BG_COLOR_INACTIVE,
|
|
} from "./CRZConfig.js";
|
|
|
|
// Layout constants
|
|
const TRACK_VERTICAL_OFFSET = 10;
|
|
|
|
class CRZDashboardNode {
|
|
constructor(node) {
|
|
this.node = node;
|
|
|
|
// Initialize properties for single slider
|
|
this.node.properties = this.node.properties || {};
|
|
|
|
// Initialize single slider with default properties
|
|
this.node.properties.slider_value = this.node.properties.slider_value ?? 0.0;
|
|
this.node.properties.slider_min = this.node.properties.slider_min ?? 0.0;
|
|
this.node.properties.slider_max = this.node.properties.slider_max ?? 1.0;
|
|
this.node.properties.slider_step = this.node.properties.slider_step ?? 0.01;
|
|
this.node.properties.slider_decimals = this.node.properties.slider_decimals ?? 2;
|
|
this.node.properties.slider_name = this.node.properties.slider_name ?? "Dashboard Node";
|
|
this.node.properties.slider_type = this.node.properties.slider_type ?? "FLOAT";
|
|
this.node.properties.slider_options = this.node.properties.slider_options ?? [""];
|
|
this.node.properties.slider_selected_index = this.node.properties.slider_selected_index ?? 0;
|
|
|
|
// Mouse handling
|
|
this.capture = false;
|
|
this.captureSlider = -1;
|
|
|
|
// Boolean toggle animation properties
|
|
this.booleanAnimationProgress = 0;
|
|
this.booleanTargetProgress = 0;
|
|
this.booleanAnimationSpeed = 8; // Higher = faster animation
|
|
this.isBooleanAnimating = false;
|
|
|
|
this.node.onAdded = function() {
|
|
// Hide widget and set output
|
|
if (this.widgets && this.widgets[0]) {
|
|
this.widgets[0].hidden = true;
|
|
this.widgets[0].type = "hidden";
|
|
}
|
|
if (this.outputs && this.outputs[0]) {
|
|
this.outputs[0].name = this.outputs[0].localized_name = "";
|
|
this.outputs[0].type = "*"; // Use any type to allow FLOAT, INT, and BOOLEAN connections
|
|
}
|
|
this.widgets_start_y = -2.4e8 * LiteGraph.NODE_SLOT_HEIGHT;
|
|
};
|
|
|
|
this.node.onConfigure = function() {
|
|
if (this.outputs && this.outputs[0]) {
|
|
this.outputs[0].type = "*"; // Always use any type
|
|
}
|
|
};
|
|
|
|
this.node.onGraphConfigured = function() {
|
|
this.configured = true;
|
|
|
|
// Sync boolean animation state with actual value on load
|
|
if (this.crzDashboardNode && this.properties.slider_type === "BOOLEAN") {
|
|
this.crzDashboardNode.booleanAnimationProgress = this.properties.slider_value ? 1 : 0;
|
|
this.crzDashboardNode.booleanTargetProgress = this.crzDashboardNode.booleanAnimationProgress;
|
|
this.crzDashboardNode.isBooleanAnimating = false;
|
|
}
|
|
|
|
this.onPropertyChanged();
|
|
this.updateSliderTypeFromConnections();
|
|
};
|
|
|
|
// Property changed handler
|
|
this.node.onPropertyChanged = function(propName) {
|
|
if (!this.configured) return;
|
|
|
|
const sliderType = this.properties.slider_type ?? "FLOAT";
|
|
|
|
if (sliderType === "INT") {
|
|
// INTEGER SLIDER LOGIC
|
|
this.properties.slider_min = Math.floor(this.properties.slider_min);
|
|
this.properties.slider_max = Math.floor(this.properties.slider_max);
|
|
this.properties.slider_step = Math.floor(this.properties.slider_step);
|
|
if (this.properties.slider_step <= 0) this.properties.slider_step = 1;
|
|
this.properties.slider_decimals = 0;
|
|
|
|
if (isNaN(this.properties.slider_value)) this.properties.slider_value = this.properties.slider_min;
|
|
if (this.properties.slider_min >= this.properties.slider_max) this.properties.slider_max = this.properties.slider_min + this.properties.slider_step;
|
|
if (propName === 'slider_min' && this.properties.slider_value < this.properties.slider_min) this.properties.slider_value = this.properties.slider_min;
|
|
if (propName === 'slider_max' && this.properties.slider_value > this.properties.slider_max) this.properties.slider_value = this.properties.slider_max;
|
|
|
|
// Round to integer
|
|
this.properties.slider_value = Math.floor(this.properties.slider_value);
|
|
} else if (sliderType === "BOOLEAN") {
|
|
// BOOLEAN TOGGLE LOGIC
|
|
this.properties.slider_value = Boolean(this.properties.slider_value);
|
|
this.properties.slider_decimals = 0;
|
|
|
|
// Start animation when boolean value changes
|
|
if (this.crzDashboardNode && propName === "slider_value") {
|
|
this.crzDashboardNode.booleanTargetProgress = this.properties.slider_value ? 1 : 0;
|
|
this.crzDashboardNode.startBooleanAnimation();
|
|
}
|
|
} else if (sliderType === "COMBO") {
|
|
// COMBO DROPDOWN LOGIC
|
|
const options = this.properties.slider_options || [""];
|
|
const selectedIndex = this.properties.slider_selected_index || 0;
|
|
|
|
// Ensure selected index is valid
|
|
if (selectedIndex >= options.length) {
|
|
this.properties.slider_selected_index = 0;
|
|
}
|
|
|
|
// Set value to selected option
|
|
this.properties.slider_value = options[this.properties.slider_selected_index] || "";
|
|
} else {
|
|
// FLOAT SLIDER LOGIC
|
|
this.properties.slider_decimals = Math.floor(this.properties.slider_decimals);
|
|
if (this.properties.slider_decimals < 0) this.properties.slider_decimals = 0;
|
|
if (this.properties.slider_decimals > 10) this.properties.slider_decimals = 10;
|
|
|
|
if (isNaN(this.properties.slider_value)) this.properties.slider_value = this.properties.slider_min;
|
|
if (this.properties.slider_min >= this.properties.slider_max) this.properties.slider_max = this.properties.slider_min + this.properties.slider_step;
|
|
if (propName === 'slider_min' && this.properties.slider_value < this.properties.slider_min) this.properties.slider_value = this.properties.slider_min;
|
|
if (propName === 'slider_max' && this.properties.slider_value > this.properties.slider_max) this.properties.slider_value = this.properties.slider_max;
|
|
|
|
// Round to specified decimal places
|
|
const multiplier = Math.pow(10, this.properties.slider_decimals);
|
|
this.properties.slider_value = Math.round(this.properties.slider_value * multiplier) / multiplier;
|
|
}
|
|
|
|
// Update widget with proper type-aware value
|
|
if (this.widgets && this.widgets[0]) {
|
|
let value = this.properties.slider_value;
|
|
|
|
if (sliderType === "INT") {
|
|
this.widgets[0].value = Math.round(value);
|
|
} else if (sliderType === "BOOLEAN") {
|
|
this.widgets[0].value = value ? true : false;
|
|
} else if (sliderType === "COMBO") {
|
|
this.widgets[0].value = String(value);
|
|
} else {
|
|
this.widgets[0].value = value;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Tooltip state
|
|
this.node.showTooltip = false;
|
|
this.node.tooltipText = "";
|
|
this.node.isTruncated = false;
|
|
|
|
// Mouse leave handler
|
|
this.node.onMouseLeave = function(e) {
|
|
this.showTooltip = false;
|
|
if (this.tooltipElement) {
|
|
// Trigger fade out
|
|
this.tooltipElement.style.opacity = '0';
|
|
// Hide after transition completes
|
|
setTimeout(() => {
|
|
if (this.tooltipElement) {
|
|
this.tooltipElement.style.display = 'none';
|
|
}
|
|
}, 200);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Cleanup tooltip when node is removed
|
|
this.node.onRemoved = function() {
|
|
if (this.tooltipElement) {
|
|
this.tooltipElement.remove();
|
|
this.tooltipElement = null;
|
|
}
|
|
};
|
|
|
|
// Mouse event handlers
|
|
this.node.onMouseDown = function(e) {
|
|
if (e.canvasY - this.pos[1] < 0) return false;
|
|
|
|
const sliderType = this.properties.slider_type ?? "FLOAT";
|
|
const sliderY = NODE_PADDING;
|
|
const trackLeft = this.size[0] - TRACK_RIGHT_PADDING;
|
|
|
|
if (sliderType === "COMBO") {
|
|
// COMBO DROPDOWN LOGIC (matching Dropdown.js)
|
|
const dropdownWidth = TRACK_WIDTH + 45; // Make it wider for text
|
|
const dropdownLeft = trackLeft; // Adjust left position to match drawing
|
|
const dropdownY = sliderY + TRACK_VERTICAL_OFFSET - 8;
|
|
const dropdownHeight = 16;
|
|
|
|
// Check if click is in dropdown area
|
|
if (e.canvasX < this.pos[0] + dropdownLeft || e.canvasX > this.pos[0] + dropdownLeft + dropdownWidth) return false;
|
|
if (e.canvasY < this.pos[1] + dropdownY || e.canvasY > this.pos[1] + dropdownY + dropdownHeight) return false;
|
|
|
|
// Show dropdown context menu (matching Dropdown.js)
|
|
const options = this.properties.slider_options || [""];
|
|
if (options.length > 1) {
|
|
const menu = new LiteGraph.ContextMenu(options, {
|
|
event: e,
|
|
callback: (value) => {
|
|
this.properties.slider_selected_index = options.indexOf(value);
|
|
this.properties.slider_value = value;
|
|
this.onPropertyChanged('slider_value');
|
|
|
|
// Update widget with proper string value
|
|
if (this.widgets && this.widgets[0]) {
|
|
this.widgets[0].value = String(value);
|
|
}
|
|
|
|
// Mark the graph as changed
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
} else if (this.graph && this.graph._version !== undefined) {
|
|
this.graph._version++;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return true;
|
|
} else if (sliderType === "BOOLEAN") {
|
|
// BOOLEAN TOGGLE LOGIC - expanded toggle
|
|
const expandedToggleWidth = TOGGLE_WIDTH + 10;
|
|
const expandedToggleLeft = this.size[0] - TRACK_RIGHT_PADDING + 40;
|
|
const trackY = sliderY + TRACK_VERTICAL_OFFSET;
|
|
const clickAreaTop = trackY - TOGGLE_HEIGHT;
|
|
const clickAreaBottom = trackY + TOGGLE_HEIGHT;
|
|
|
|
// Check if click is in expanded toggle area
|
|
if (e.canvasX < this.pos[0] + expandedToggleLeft || e.canvasX > this.pos[0] + expandedToggleLeft + expandedToggleWidth) return false;
|
|
if (e.canvasY < this.pos[1] + clickAreaTop || e.canvasY > this.pos[1] + clickAreaBottom) return false;
|
|
|
|
// Toggle the boolean value
|
|
this.properties.slider_value = !this.properties.slider_value;
|
|
this.onPropertyChanged('slider_value');
|
|
|
|
// Update widget with proper boolean value
|
|
if (this.widgets && this.widgets[0]) {
|
|
this.widgets[0].value = this.properties.slider_value ? true : false;
|
|
}
|
|
|
|
// Mark the graph as changed
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
} else if (this.graph && this.graph._version !== undefined) {
|
|
this.graph._version++;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
// SLIDER LOGIC (INT/FLOAT)
|
|
const trackWidth = TRACK_WIDTH;
|
|
const trackY = sliderY + TRACK_VERTICAL_OFFSET;
|
|
const clickAreaTop = trackY - 8;
|
|
const clickAreaBottom = trackY + 14;
|
|
|
|
// Check if click is in slider area (updated for new positioning)
|
|
if (e.canvasX < this.pos[0] + trackLeft || e.canvasX > this.pos[0] + trackLeft + trackWidth) return false;
|
|
if (e.canvasY < this.pos[1] + clickAreaTop || e.canvasY > this.pos[1] + clickAreaBottom) return false;
|
|
|
|
this.captureSlider = 0;
|
|
this.capture = true;
|
|
this.valueUpdate(e);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
this.node.onMouseMove = function(e, pos, canvas) {
|
|
// Handle tooltip logic when not capturing
|
|
if (!this.capture) {
|
|
if (e.canvasY - this.pos[1] < 0) return false;
|
|
|
|
const sliderType = this.properties.slider_type ?? "FLOAT";
|
|
const sliderY = NODE_PADDING;
|
|
const trackLeft = this.size[0] - TRACK_RIGHT_PADDING;
|
|
|
|
if (sliderType === "COMBO") {
|
|
// COMBO DROPDOWN TOOLTIP LOGIC
|
|
const dropdownWidth = TRACK_WIDTH + 45;
|
|
const dropdownLeft = trackLeft;
|
|
const dropdownY = sliderY + TRACK_VERTICAL_OFFSET - 8;
|
|
const dropdownHeight = 16;
|
|
|
|
const isInDropdown = e.canvasX >= this.pos[0] + dropdownLeft &&
|
|
e.canvasX <= this.pos[0] + dropdownLeft + dropdownWidth &&
|
|
e.canvasY >= this.pos[1] + dropdownY &&
|
|
e.canvasY <= this.pos[1] + dropdownY + dropdownHeight;
|
|
|
|
if (isInDropdown) {
|
|
// Only show tooltip if text is actually truncated
|
|
this.showTooltip = this.isTruncated;
|
|
this.tooltipText = this.properties.slider_value || "";
|
|
// Store the mouse event for later use
|
|
this.lastMouseEvent = e;
|
|
} else {
|
|
this.showTooltip = false;
|
|
}
|
|
} else {
|
|
this.showTooltip = false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Handle slider dragging when capturing
|
|
if (canvas.pointer.isDown === false) { this.onMouseUp(e); return; }
|
|
this.valueUpdate(e);
|
|
};
|
|
|
|
this.node.onMouseUp = function(e) {
|
|
if (!this.capture) return;
|
|
this.capture = false;
|
|
this.captureSlider = -1;
|
|
|
|
// Update widget with proper type-aware value
|
|
if (this.widgets && this.widgets[0]) {
|
|
const sliderType = this.properties.slider_type || "FLOAT";
|
|
let value = this.properties.slider_value;
|
|
|
|
if (sliderType === "INT") {
|
|
this.widgets[0].value = Math.round(value);
|
|
} else if (sliderType === "BOOLEAN") {
|
|
this.widgets[0].value = value ? true : false;
|
|
} else if (sliderType === "COMBO") {
|
|
this.widgets[0].value = String(value);
|
|
} else {
|
|
this.widgets[0].value = value;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.node.valueUpdate = function(e) {
|
|
if (this.captureSlider < 0) return;
|
|
|
|
const min = this.properties.slider_min;
|
|
const max = this.properties.slider_max;
|
|
const step = this.properties.slider_step;
|
|
const decimals = this.properties.slider_decimals;
|
|
|
|
const trackLeft = this.size[0] - TRACK_RIGHT_PADDING;
|
|
const trackWidth = TRACK_WIDTH;
|
|
|
|
let prevValue = this.properties.slider_value;
|
|
let vX = (e.canvasX - this.pos[0] - trackLeft) / trackWidth;
|
|
|
|
if (e.ctrlKey) this.unlock = true;
|
|
if (e.shiftKey) {
|
|
let stepSize = step / (max - min);
|
|
vX = Math.round(vX / stepSize) * stepSize;
|
|
}
|
|
|
|
vX = Math.max(0, Math.min(1, vX));
|
|
this.properties.slider_value = min + (max - min) * vX;
|
|
|
|
// Apply step constraint first
|
|
if (step > 0) {
|
|
this.properties.slider_value = Math.round((this.properties.slider_value - min) / step) * step + min;
|
|
}
|
|
|
|
// Then apply type-specific rounding
|
|
const sliderType = this.properties.slider_type || "FLOAT";
|
|
if (sliderType === "INT") {
|
|
this.properties.slider_value = Math.round(this.properties.slider_value);
|
|
} else {
|
|
const multiplier = Math.pow(10, decimals);
|
|
this.properties.slider_value = Math.round(this.properties.slider_value * multiplier) / multiplier;
|
|
}
|
|
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
} else if (this.graph && this.graph._version !== undefined) {
|
|
this.graph._version++;
|
|
}
|
|
};
|
|
|
|
// Update slider type from connections
|
|
this.node.updateSliderTypeFromConnections = function() {
|
|
if (!this.outputs || !this.outputs[0] || !this.outputs[0].links) {
|
|
return;
|
|
}
|
|
|
|
const output = this.outputs[0];
|
|
if (!output.links || output.links.length === 0) {
|
|
return;
|
|
}
|
|
|
|
for (const linkId of output.links) {
|
|
if (!this.graph || !this.graph.links[linkId]) continue;
|
|
|
|
const link = this.graph.links[linkId];
|
|
const targetNode = this.graph.getNodeById(link.target_id);
|
|
|
|
if (!targetNode || !targetNode.inputs || !targetNode.inputs[link.target_slot]) continue;
|
|
|
|
let targetInput = targetNode.inputs[link.target_slot];
|
|
let detectedType = targetInput.type;
|
|
|
|
// Handle passthrough nodes (using Dropdown's proven method)
|
|
let finalTargetNode = targetNode;
|
|
let finalTargetInput = targetInput;
|
|
|
|
if (targetNode.isPassthrough && targetNode.getPassthroughTarget) {
|
|
const passthroughTarget = targetNode.getPassthroughTarget();
|
|
if (passthroughTarget) {
|
|
finalTargetNode = passthroughTarget.node;
|
|
finalTargetInput = passthroughTarget.input;
|
|
detectedType = finalTargetInput.type;
|
|
}
|
|
} else if (targetNode.isPassthrough && targetNode.outputs && targetNode.outputs[0] && targetNode.outputs[0].links) {
|
|
for (const passthroughLinkId of targetNode.outputs[0].links) {
|
|
if (!this.graph.links[passthroughLinkId]) continue;
|
|
|
|
const passthroughLink = this.graph.links[passthroughLinkId];
|
|
const passthroughTargetNode = this.graph.getNodeById(passthroughLink.target_id);
|
|
|
|
if (passthroughTargetNode && passthroughTargetNode.inputs && passthroughTargetNode.inputs[passthroughLink.target_slot]) {
|
|
finalTargetNode = passthroughTargetNode;
|
|
finalTargetInput = passthroughTargetNode.inputs[passthroughLink.target_slot];
|
|
detectedType = finalTargetInput.type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for ComboBox (using Dropdown's proven methods)
|
|
let comboOptions = null;
|
|
|
|
// Method 1: Try to get from target widget
|
|
const targetWidget = finalTargetNode.widgets?.find(w => w.name === finalTargetInput.name);
|
|
if (targetWidget && targetWidget.options?.values) {
|
|
comboOptions = targetWidget.options.values;
|
|
}
|
|
|
|
// Method 2: Try to get from target node's constructor
|
|
if (!comboOptions) {
|
|
const nodeConstructor = finalTargetNode.constructor;
|
|
if (nodeConstructor && nodeConstructor.nodeData) {
|
|
const inputDef = nodeConstructor.nodeData.input?.required?.[finalTargetInput.name] ||
|
|
nodeConstructor.nodeData.input?.optional?.[finalTargetInput.name];
|
|
if (inputDef && Array.isArray(inputDef[0])) {
|
|
comboOptions = inputDef[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Method 3: Try global app nodeData
|
|
if (!comboOptions && app.nodeData && app.nodeData[finalTargetNode.type]) {
|
|
const nodeDef = app.nodeData[finalTargetNode.type];
|
|
const inputDef = nodeDef.input?.required?.[finalTargetInput.name] ||
|
|
nodeDef.input?.optional?.[finalTargetInput.name];
|
|
if (inputDef && Array.isArray(inputDef[0])) {
|
|
comboOptions = inputDef[0];
|
|
}
|
|
}
|
|
|
|
// Update slider type based on detected type
|
|
if (comboOptions && comboOptions.length > 0) {
|
|
// ComboBox detected - highest priority
|
|
this.properties.slider_type = "COMBO";
|
|
this.properties.slider_options = [...comboOptions];
|
|
// Set value to first option if current value is not in options
|
|
const currentValue = this.properties.slider_value;
|
|
if (!comboOptions.includes(currentValue)) {
|
|
this.properties.slider_value = comboOptions[0] || "";
|
|
this.properties.slider_selected_index = 0;
|
|
// Update the widget with the new string value
|
|
this.onPropertyChanged('slider_value');
|
|
} else {
|
|
this.properties.slider_selected_index = comboOptions.indexOf(currentValue);
|
|
}
|
|
// Force property change notification
|
|
this.onPropertyChanged('slider_type');
|
|
} else if (!comboOptions && detectedType === "INT" && this.properties.slider_type !== "INT") {
|
|
this.properties.slider_type = "INT";
|
|
this.properties.slider_min = 0;
|
|
this.properties.slider_max = 10;
|
|
this.properties.slider_step = 1;
|
|
this.properties.slider_decimals = 0;
|
|
this.properties.slider_value = Math.round(this.properties.slider_value);
|
|
} else if (!comboOptions && detectedType === "BOOLEAN" && this.properties.slider_type !== "BOOLEAN") {
|
|
this.properties.slider_type = "BOOLEAN";
|
|
this.properties.slider_value = this.properties.slider_value > 0.5 ? true : false;
|
|
// Force property change notification
|
|
this.onPropertyChanged('slider_type');
|
|
} else if (!comboOptions && (detectedType === "FLOAT" || detectedType === "*") && this.properties.slider_type !== "FLOAT") {
|
|
this.properties.slider_type = "FLOAT";
|
|
this.properties.slider_min = 0.0;
|
|
this.properties.slider_max = 1.0;
|
|
this.properties.slider_step = 0.01;
|
|
this.properties.slider_decimals = 2;
|
|
this.properties.slider_value = parseFloat(this.properties.slider_value);
|
|
}
|
|
|
|
break; // Only check first connection
|
|
}
|
|
};
|
|
|
|
this.node.size = [300, Math.floor(LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER)];
|
|
}
|
|
|
|
// Boolean animation methods
|
|
startBooleanAnimation() {
|
|
this.isBooleanAnimating = true;
|
|
this.animateBooleanStep();
|
|
}
|
|
|
|
animateBooleanStep() {
|
|
if (!this.isBooleanAnimating) return;
|
|
|
|
// Request animation frame for smooth animation
|
|
requestAnimationFrame(() => this.animateBooleanStep());
|
|
|
|
// Mark canvas as dirty to trigger redraw
|
|
if (this.node.graph && this.node.graph.setDirtyCanvas) {
|
|
this.node.graph.setDirtyCanvas(true);
|
|
}
|
|
}
|
|
|
|
updateBooleanAnimation() {
|
|
if (Math.abs(this.booleanAnimationProgress - this.booleanTargetProgress) < 0.01) {
|
|
this.booleanAnimationProgress = this.booleanTargetProgress;
|
|
this.isBooleanAnimating = false;
|
|
return;
|
|
}
|
|
|
|
// Smooth easing animation
|
|
const diff = this.booleanTargetProgress - this.booleanAnimationProgress;
|
|
this.booleanAnimationProgress += diff * (this.booleanAnimationSpeed * 0.016); // 60fps normalized
|
|
}
|
|
|
|
// Color interpolation method for rgba colors
|
|
interpolateColor(color1, color2, t) {
|
|
// Parse rgba colors
|
|
const parseRgba = (color) => {
|
|
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
if (match) {
|
|
return {
|
|
r: parseInt(match[1]),
|
|
g: parseInt(match[2]),
|
|
b: parseInt(match[3]),
|
|
a: match[4] ? parseFloat(match[4]) : 1
|
|
};
|
|
}
|
|
return { r: 255, g: 255, b: 255, a: 1 }; // fallback to white
|
|
};
|
|
|
|
const c1 = parseRgba(color1);
|
|
const c2 = parseRgba(color2);
|
|
|
|
// Interpolate
|
|
const r = Math.round(c1.r + (c2.r - c1.r) * t);
|
|
const g = Math.round(c1.g + (c2.g - c1.g) * t);
|
|
const b = Math.round(c1.b + (c2.b - c1.b) * t);
|
|
const a = c1.a + (c2.a - c1.a) * t;
|
|
|
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
}
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "CRZ.DashboardNode",
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name === "CRZDashboardNode") {
|
|
// Hide the title bar
|
|
nodeType.title_mode = LiteGraph.NO_TITLE;
|
|
nodeType.prototype.getTitle = function() { return ""; };
|
|
nodeType.prototype.onDrawTitle = function() { return false; };
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
onNodeCreated?.apply(this, arguments);
|
|
this.bgcolor = NODE_BACKGROUND_COLOR;
|
|
|
|
// Initialize and hide the widget
|
|
if (this.widgets && this.widgets[0]) {
|
|
// Set initial value from properties
|
|
this.widgets[0].value = this.properties.slider_value || 0.0;
|
|
this.widgets[0].hidden = true;
|
|
this.widgets[0].type = "hidden";
|
|
} else {
|
|
// Manually create the widget if it doesn't exist
|
|
this.addWidget("number", "slider", 0.0, function(v) {
|
|
this.properties.slider_value = v;
|
|
});
|
|
if (this.widgets && this.widgets[0]) {
|
|
this.widgets[0].hidden = true;
|
|
this.widgets[0].type = "hidden";
|
|
}
|
|
}
|
|
|
|
// Simple hiding
|
|
this.onAdded = function() {
|
|
this.outputs[0].name = this.outputs[0].localized_name = "";
|
|
this.widgets_start_y = -2.4e8*LiteGraph.NODE_SLOT_HEIGHT;
|
|
|
|
if (this.widgets && this.widgets[0]) {
|
|
this.widgets[0].hidden = true;
|
|
this.widgets[0].type = "hidden";
|
|
}
|
|
};
|
|
|
|
this.crzDashboardNode = new CRZDashboardNode(this);
|
|
|
|
// Initialize boolean animation state to match current value
|
|
if (this.crzDashboardNode && this.properties.slider_type === "BOOLEAN") {
|
|
this.crzDashboardNode.booleanAnimationProgress = this.properties.slider_value ? 1 : 0;
|
|
this.crzDashboardNode.booleanTargetProgress = this.crzDashboardNode.booleanAnimationProgress;
|
|
this.crzDashboardNode.isBooleanAnimating = false;
|
|
}
|
|
|
|
// Mark as CRZ node for connection hiding
|
|
this.isCRZNode = true;
|
|
};
|
|
|
|
// Force the node to use explicit width - min
|
|
nodeType.prototype.computeSize = () => [NODE_WIDTH-100, Math.floor(LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER)];
|
|
|
|
nodeType.prototype.onDrawForeground = function(ctx) {
|
|
if (this.flags.collapsed || !this.crzDashboardNode) return;
|
|
|
|
// Height snapping - prevent vertical resize
|
|
if (this.size[1] > LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER) {
|
|
this.size[1] = LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER;
|
|
}
|
|
|
|
// Mark as configured
|
|
this.configured = true;
|
|
|
|
// Update boolean animation if needed
|
|
if (this.crzDashboardNode && this.properties.slider_type === "BOOLEAN") {
|
|
this.crzDashboardNode.updateBooleanAnimation();
|
|
}
|
|
|
|
const value = this.properties.slider_value ?? 0.0;
|
|
const min = this.properties.slider_min ?? 0.0;
|
|
const max = this.properties.slider_max ?? 1.0;
|
|
const decimals = this.properties.slider_decimals ?? 2;
|
|
const name = this.properties.slider_name ?? "Dashboard Node";
|
|
const type = this.properties.slider_type ?? "FLOAT";
|
|
|
|
const sliderY = NODE_PADDING;
|
|
|
|
// Check if this slider should be active (not greyed out)
|
|
const isConnected = this.outputs && this.outputs[0] &&
|
|
this.outputs[0].links && this.outputs[0].links.length > 0;
|
|
const hasCustomName = name !== "Dashboard Node";
|
|
const isActive = isConnected || hasCustomName;
|
|
|
|
// Choose colors based on active status
|
|
const labelColor = isActive ? LABEL_COLOR : INACTIVE_LABEL_COLOR;
|
|
const valueColor = isActive ? VALUE_COLOR : INACTIVE_VALUE_COLOR;
|
|
const trackColor = isActive ? TRACK_COLOR : INACTIVE_TRACK_COLOR;
|
|
const handleColor = isActive ? LABEL_COLOR : INACTIVE_HANDLE_COLOR;
|
|
|
|
// Draw name on the left
|
|
ctx.fillStyle = labelColor;
|
|
ctx.font = LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
|
|
ctx.textAlign = "left";
|
|
ctx.fillText(name, LABEL_LEFT_PADDING, sliderY + 15);
|
|
|
|
if (type === "COMBO") {
|
|
// COMBO DROPDOWN DRAWING (matching Dropdown.js styling)
|
|
const options = this.properties.slider_options || [""];
|
|
const selectedIndex = this.properties.slider_selected_index || 0;
|
|
const selectedValue = options[selectedIndex] || "";
|
|
|
|
// Draw dropdown area - wider for ComboBox text
|
|
const dropdownWidth = TRACK_WIDTH + 45; // Make it wider for text
|
|
const dropdownLeft = this.size[0] - TRACK_RIGHT_PADDING; // Adjust left position
|
|
const dropdownY = sliderY + TRACK_VERTICAL_OFFSET - 8;
|
|
const dropdownHeight = 16;
|
|
const cornerRadius = 3;
|
|
|
|
// Draw rounded dropdown background (matching Dropdown.js)
|
|
ctx.fillStyle = isActive ? DROPDOWN_BG_COLOR : DROPDOWN_BG_COLOR_INACTIVE;
|
|
ctx.beginPath();
|
|
ctx.roundRect(dropdownLeft, dropdownY, dropdownWidth, dropdownHeight, cornerRadius);
|
|
ctx.fill();
|
|
|
|
// Draw rounded dropdown border (matching Dropdown.js)
|
|
// ctx.strokeStyle = isActive ? "#555" : "#333";
|
|
// ctx.lineWidth = 1;
|
|
// ctx.beginPath();
|
|
// ctx.roundRect(dropdownLeft, dropdownY, dropdownWidth, dropdownHeight, cornerRadius);
|
|
// ctx.stroke();
|
|
|
|
// Draw current value (centered in dropdown, matching Dropdown.js)
|
|
ctx.fillStyle = valueColor;
|
|
ctx.font = LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
|
|
ctx.textAlign = "center";
|
|
const maxWidth = dropdownWidth - 20;
|
|
let truncatedValue = selectedValue;
|
|
|
|
// Check if text is truncated and store the state
|
|
this.isTruncated = ctx.measureText(selectedValue).width > maxWidth;
|
|
if (this.isTruncated) {
|
|
truncatedValue = selectedValue.substring(0, 8) + "..";
|
|
}
|
|
ctx.fillText(truncatedValue, dropdownLeft + dropdownWidth/2, sliderY + 14);
|
|
|
|
// Draw tooltip using same method as other CRZ nodes
|
|
if (this.showTooltip && this.tooltipText) {
|
|
if (!this.tooltipElement) {
|
|
this.tooltipElement = document.createElement("div");
|
|
this.tooltipElement.style.cssText = `
|
|
position: fixed;
|
|
background: rgba(24, 24, 24, 0.95);
|
|
color: #868686;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 16px;
|
|
pointer-events: none;
|
|
z-index: 10000;
|
|
border: 1px solid rgba(41, 41, 41, 0.9);
|
|
white-space: nowrap;
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease-in-out;
|
|
`;
|
|
document.body.appendChild(this.tooltipElement);
|
|
}
|
|
|
|
// Only update if tooltip text changed or not visible
|
|
if (this.tooltipElement.textContent !== this.tooltipText || this.tooltipElement.style.opacity === '0') {
|
|
// Get screen coordinates from the stored mouse event
|
|
let screenX = 0;
|
|
let screenY = 0;
|
|
|
|
if (this.lastMouseEvent) {
|
|
// Try to get client coordinates from the original event
|
|
const originalEvent = this.lastMouseEvent.originalEvent || this.lastMouseEvent;
|
|
screenX = originalEvent.clientX || 0;
|
|
screenY = originalEvent.clientY || 0;
|
|
}
|
|
|
|
this.tooltipElement.textContent = this.tooltipText;
|
|
this.tooltipElement.style.display = 'block';
|
|
// Position off-screen first to calculate width
|
|
this.tooltipElement.style.left = '-9999px';
|
|
this.tooltipElement.style.top = '-9999px';
|
|
this.tooltipElement.style.opacity = '0';
|
|
|
|
// Position correctly after width is calculated
|
|
requestAnimationFrame(() => {
|
|
this.tooltipElement.style.left = (screenX - this.tooltipElement.offsetWidth/2) + 'px';
|
|
this.tooltipElement.style.top = (screenY - 60) + 'px';
|
|
// Then fade in
|
|
requestAnimationFrame(() => {
|
|
this.tooltipElement.style.opacity = '1';
|
|
});
|
|
});
|
|
}
|
|
} else if (this.tooltipElement && this.tooltipElement.style.opacity !== '0') {
|
|
// Trigger fade out
|
|
this.tooltipElement.style.opacity = '0';
|
|
// Hide after transition completes
|
|
setTimeout(() => {
|
|
if (this.tooltipElement) {
|
|
this.tooltipElement.style.display = 'none';
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
} else if (type === "BOOLEAN") {
|
|
// BOOLEAN TOGGLE DRAWING WITH ANIMATION (expanded, no text)
|
|
// Value text removed - toggle fills the space
|
|
|
|
// Draw expanded toggle switch - wider and more centered
|
|
const expandedToggleWidth = TOGGLE_WIDTH + 10; // Make it wider
|
|
const toggleLeft = this.size[0] - TRACK_RIGHT_PADDING + 40; // Move it left a bit
|
|
const toggleY = sliderY + TRACK_VERTICAL_OFFSET;
|
|
|
|
// Get animation progress for smooth transitions
|
|
const progress = this.crzDashboardNode ? this.crzDashboardNode.booleanAnimationProgress : (value ? 1 : 0);
|
|
|
|
// Toggle background - interpolate colors during animation
|
|
const bgColor = this.crzDashboardNode ?
|
|
this.crzDashboardNode.interpolateColor(TOGGLE_INACTIVE_COLOR, TOGGLE_ACTIVE_COLOR, progress) :
|
|
(value ? TOGGLE_ACTIVE_COLOR : TOGGLE_INACTIVE_COLOR);
|
|
ctx.fillStyle = bgColor;
|
|
ctx.beginPath();
|
|
ctx.roundRect(toggleLeft, toggleY - TOGGLE_HEIGHT/2, expandedToggleWidth, TOGGLE_HEIGHT, TOGGLE_CORNER_RADIUS);
|
|
ctx.fill();
|
|
|
|
// Animated toggle handle position - use expanded width
|
|
const leftPos = toggleLeft + HANDLE_PADDING;
|
|
const rightPos = toggleLeft + expandedToggleWidth - HANDLE_SIZE - HANDLE_PADDING;
|
|
const handleX = leftPos + (rightPos - leftPos) * progress;
|
|
const handleY = toggleY - HANDLE_SIZE/2;
|
|
|
|
ctx.fillStyle = HANDLE_COLOR;
|
|
ctx.beginPath();
|
|
ctx.roundRect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE, HANDLE_CORNER_RADIUS);
|
|
ctx.fill();
|
|
|
|
// Handle border - interpolate colors during animation
|
|
const borderColor = this.crzDashboardNode ?
|
|
this.crzDashboardNode.interpolateColor(HANDLE_BORDER_INACTIVE, HANDLE_BORDER_ACTIVE, progress) :
|
|
(value ? HANDLE_BORDER_ACTIVE : HANDLE_BORDER_INACTIVE);
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.roundRect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE, HANDLE_CORNER_RADIUS);
|
|
ctx.stroke();
|
|
|
|
} else {
|
|
// SLIDER DRAWING (INT/FLOAT)
|
|
const normalizedValue = Math.max(0, Math.min(1, (value - min) / (max - min)));
|
|
|
|
// Draw value on the right (matching FloatSlider positioning)
|
|
ctx.fillStyle = valueColor;
|
|
ctx.font = LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
|
|
ctx.textAlign = "center";
|
|
const displayValue = type === "INT" ?
|
|
Math.round(value).toString() :
|
|
parseFloat(value).toFixed(decimals);
|
|
ctx.fillText(displayValue, this.size[0] - VALUE_RIGHT_SHIFT + VALUE_RIGHT_OFFSET, sliderY + 15);
|
|
|
|
// Draw slider track (same as Dashboard Multi)
|
|
ctx.fillStyle = trackColor;
|
|
ctx.beginPath();
|
|
const trackY = sliderY + TRACK_VERTICAL_OFFSET-1;
|
|
const trackLeft = this.size[0] - TRACK_RIGHT_PADDING;
|
|
const trackWidth = TRACK_WIDTH;
|
|
ctx.roundRect(trackLeft, trackY, trackWidth, TRACK_HEIGHT, 2);
|
|
ctx.fill();
|
|
|
|
// Draw slider handle as vertical rounded rectangle (same as Dashboard Multi)
|
|
const sliderPos = (value - min) / (max - min);
|
|
const handleX = trackLeft + trackWidth * Math.max(0, Math.min(1, sliderPos));
|
|
const handleY = trackY - 5; // Adjust Y position for vertical rectangle
|
|
const handleWidth = 8;
|
|
const handleHeight = 14;
|
|
|
|
ctx.fillStyle = handleColor;
|
|
ctx.beginPath();
|
|
ctx.roundRect(handleX - handleWidth/2, handleY, handleWidth, handleHeight, 2);
|
|
ctx.fill();
|
|
|
|
// Draw handle border
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeStyle = this.bgcolor || HANDLE_BORDER_COLOR;
|
|
ctx.beginPath();
|
|
ctx.roundRect(handleX - handleWidth/2, handleY, handleWidth, handleHeight, 2);
|
|
ctx.stroke();
|
|
}
|
|
};
|
|
|
|
nodeType.prototype.onDblClick = function(e, pos, canvas) {
|
|
if (this.flags.collapsed || !this.crzDashboardNode) return false;
|
|
|
|
const sliderY = this.pos[1] + NODE_PADDING;
|
|
const currentType = this.properties.slider_type || "FLOAT";
|
|
|
|
// Double-click on expanded toggle area for boolean, or value area for others
|
|
if (currentType === "BOOLEAN") {
|
|
// Check expanded toggle area for boolean
|
|
const expandedToggleWidth = TOGGLE_WIDTH + 10;
|
|
const expandedToggleLeft = this.pos[0] + this.size[0] - TRACK_RIGHT_PADDING + 40;
|
|
const toggleY = sliderY + TRACK_VERTICAL_OFFSET;
|
|
|
|
if (e.canvasX > expandedToggleLeft && e.canvasX < expandedToggleLeft + expandedToggleWidth &&
|
|
e.canvasY > toggleY - TOGGLE_HEIGHT && e.canvasY < toggleY + TOGGLE_HEIGHT) {
|
|
// Toggle boolean value directly
|
|
this.properties.slider_value = !this.properties.slider_value;
|
|
this.onPropertyChanged('slider_value');
|
|
return true;
|
|
}
|
|
} else if (e.canvasX > this.pos[0] + this.size[0] - VALUE_RIGHT_PADDING - 40 &&
|
|
e.canvasY > sliderY && e.canvasY < sliderY + SLIDER_HEIGHT) {
|
|
|
|
if (currentType === "COMBO") {
|
|
// Show combo dropdown menu (matching Dropdown.js)
|
|
const options = this.properties.slider_options || [""];
|
|
if (options.length > 1) {
|
|
const menu = new LiteGraph.ContextMenu(options, {
|
|
event: e,
|
|
callback: (value) => {
|
|
this.properties.slider_selected_index = options.indexOf(value);
|
|
this.properties.slider_value = value;
|
|
this.onPropertyChanged('slider_value');
|
|
|
|
// Update widget with proper string value
|
|
if (this.widgets && this.widgets[0]) {
|
|
this.widgets[0].value = String(value);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return true;
|
|
} else {
|
|
// Edit numeric value
|
|
const currentValue = this.properties.slider_value;
|
|
const displayValue = currentType === "INT" ? Math.round(currentValue).toString() : currentValue.toString();
|
|
|
|
canvas.prompt("value", displayValue, function(v) {
|
|
if (currentType === "INT") {
|
|
v = parseInt(v);
|
|
if (!isNaN(v)) {
|
|
this.properties.slider_value = v;
|
|
this.onPropertyChanged('slider_value');
|
|
}
|
|
} else {
|
|
v = Number(v);
|
|
if (!isNaN(v)) {
|
|
this.properties.slider_value = v;
|
|
this.onPropertyChanged('slider_value');
|
|
}
|
|
}
|
|
}.bind(this), e);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Double-click on slider track area to configure
|
|
if (currentType === "FLOAT" || currentType === "INT") {
|
|
const trackLeft = this.pos[0] + this.size[0] - TRACK_RIGHT_PADDING;
|
|
const trackWidth = TRACK_WIDTH;
|
|
const trackY = sliderY + TRACK_VERTICAL_OFFSET;
|
|
|
|
if (e.canvasX > trackLeft && e.canvasX < trackLeft + trackWidth &&
|
|
e.canvasY > trackY - 10 && e.canvasY < trackY + 10) {
|
|
|
|
if (currentType === "FLOAT") {
|
|
// Configure FLOAT slider
|
|
const currentMin = this.properties.slider_min;
|
|
const currentMax = this.properties.slider_max;
|
|
const currentStep = this.properties.slider_step;
|
|
const currentDecimals = this.properties.slider_decimals;
|
|
|
|
const configText = `${currentMin},${currentMax},${currentStep},${currentDecimals}`;
|
|
|
|
canvas.prompt(
|
|
"Configure Float Slider (min,max,step,decimals)",
|
|
configText,
|
|
function(newConfig) {
|
|
if (newConfig && newConfig.trim()) {
|
|
const parts = newConfig.split(',');
|
|
if (parts.length === 4) {
|
|
const min = parseFloat(parts[0].trim());
|
|
const max = parseFloat(parts[1].trim());
|
|
const step = parseFloat(parts[2].trim());
|
|
const decimals = parseInt(parts[3].trim());
|
|
|
|
if (!isNaN(min) && !isNaN(max) && !isNaN(step) && !isNaN(decimals)) {
|
|
this.properties.slider_min = min;
|
|
this.properties.slider_max = max;
|
|
this.properties.slider_step = step;
|
|
this.properties.slider_decimals = decimals;
|
|
|
|
// Force property validation
|
|
this.configured = true;
|
|
this.onPropertyChanged("slider_min");
|
|
|
|
// Force canvas redraw
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}.bind(this),
|
|
e
|
|
);
|
|
} else if (currentType === "INT") {
|
|
// Configure INT slider
|
|
const currentMin = this.properties.slider_min;
|
|
const currentMax = this.properties.slider_max;
|
|
const currentStep = this.properties.slider_step;
|
|
|
|
const configText = `${currentMin},${currentMax},${currentStep}`;
|
|
|
|
canvas.prompt(
|
|
"Configure Integer Slider (min,max,step)",
|
|
configText,
|
|
function(newConfig) {
|
|
if (newConfig && newConfig.trim()) {
|
|
const parts = newConfig.split(',');
|
|
if (parts.length === 3) {
|
|
const min = parseInt(parts[0].trim());
|
|
const max = parseInt(parts[1].trim());
|
|
const step = parseInt(parts[2].trim());
|
|
|
|
if (!isNaN(min) && !isNaN(max) && !isNaN(step)) {
|
|
this.properties.slider_min = min;
|
|
this.properties.slider_max = max;
|
|
this.properties.slider_step = step;
|
|
|
|
// Force property validation
|
|
this.configured = true;
|
|
this.onPropertyChanged("slider_min");
|
|
|
|
// Force canvas redraw
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}.bind(this),
|
|
e
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Double-click on label area to rename
|
|
const relativeSliderY = NODE_PADDING;
|
|
const clickHeight = DASHBOARD_LINE_SPACING - 2; // Use spacing minus small margin
|
|
if (e.canvasX > this.pos[0] + LABEL_LEFT_PADDING &&
|
|
e.canvasX < this.pos[0] + this.size[0] - TRACK_RIGHT_PADDING - 10 &&
|
|
e.canvasY > this.pos[1] + relativeSliderY && e.canvasY < this.pos[1] + relativeSliderY + clickHeight) {
|
|
|
|
canvas.prompt("Slider Name", this.properties.slider_name, function(newName) {
|
|
if (newName && newName.trim()) {
|
|
this.properties.slider_name = newName.trim();
|
|
// Notify Dashboard Get nodes after a longer delay to ensure properties are fully updated
|
|
setTimeout(() => {
|
|
if (this.notifyDashboardGetNodes) {
|
|
this.notifyDashboardGetNodes();
|
|
}
|
|
}, 300);
|
|
}
|
|
}.bind(this), e);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
nodeType.prototype.onConnectionsChange = function(type, slotIndex, isConnected, linkInfo, ioSlot) {
|
|
if (this.crzDashboardNode && type === LiteGraph.OUTPUT) {
|
|
// Immediate update for responsiveness
|
|
setTimeout(() => {
|
|
this.updateSliderTypeFromConnections();
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
}
|
|
}, 10);
|
|
|
|
// Additional updates with delays to catch late connection changes
|
|
setTimeout(() => {
|
|
this.updateSliderTypeFromConnections();
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
}
|
|
}, 50);
|
|
|
|
// Final update to ensure everything is synced
|
|
setTimeout(() => {
|
|
this.updateSliderTypeFromConnections();
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
}
|
|
}, 200);
|
|
|
|
// Extra update for stubborn cases
|
|
setTimeout(() => {
|
|
this.updateSliderTypeFromConnections();
|
|
if (this.graph && this.graph.setDirtyCanvas) {
|
|
this.graph.setDirtyCanvas(true);
|
|
}
|
|
}, 500);
|
|
}
|
|
};
|
|
|
|
nodeType.prototype.onGraphConfigured = function() {
|
|
if (this.crzDashboardNode) {
|
|
// Update slider type after graph is loaded
|
|
setTimeout(() => {
|
|
this.updateSliderTypeFromConnections();
|
|
}, 100);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
});
|