Files
CoreyCorza-ComfyUI-CRZnodes/js/BooleanToggle.js
T
2025-09-05 22:43:44 +10:00

291 lines
12 KiB
JavaScript

// ComfyUI.CRZ.BooleanToggle
import { app } from "../../scripts/app.js";
import {
NODE_WIDTH,
NODE_HEIGHT_MULTIPLIER,
LABEL_LEFT_PADDING,
TRACK_RIGHT_PADDING,
VALUE_RIGHT_PADDING,
TOGGLE_WIDTH,
TOGGLE_HEIGHT,
TOGGLE_CORNER_RADIUS,
HANDLE_SIZE,
HANDLE_CORNER_RADIUS,
HANDLE_PADDING,
TOGGLE_VERTICAL_OFFSET,
NODE_BACKGROUND_COLOR,
TOGGLE_ACTIVE_COLOR,
TOGGLE_INACTIVE_COLOR,
HANDLE_COLOR,
HANDLE_BORDER_ACTIVE,
HANDLE_BORDER_INACTIVE
} from "./CRZConfig.js";
class CRZBooleanToggle
{
constructor(node)
{
this.node = node;
this.node.properties = this.node.properties || {};
this.node.properties.value = false;
this.node.properties.label = "Boolean";
// Animation properties - initialize after properties are set
this.animationProgress = 0;
this.targetProgress = 0;
this.animationSpeed = 8; // Higher = faster animation
this.isAnimating = false;
this.node.size = [NODE_WIDTH, Math.floor(LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER)];
const fontsize = LiteGraph.NODE_SUBTEXT_SIZE;
const shX = (this.node.slot_start_y || 0)+fontsize*1.5;
const shY = LiteGraph.NODE_SLOT_HEIGHT/1.5;
const shiftLeft = 10;
const shiftRight = 60;
this.node.widgets[0].hidden = true;
this.node.widgets[0].type = "hidden";
this.node.onAdded = function ()
{
this.outputs[0].name = this.outputs[0].localized_name = "";
this.widgets_start_y = -2.4e8*LiteGraph.NODE_SLOT_HEIGHT;
if (this.size) if (this.size.length) if (this.size[1] > LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER) this.size[1] = LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER;
this.outputs[0].type = "BOOLEAN";
};
this.node.onConfigure = function ()
{
this.outputs[0].type = "BOOLEAN";
}
this.node.onGraphConfigured = function ()
{
this.configured = true;
// Sync animation state with actual boolean value on load
if (this.crzBooleanToggle) {
this.crzBooleanToggle.animationProgress = this.properties.value ? 1 : 0;
this.crzBooleanToggle.targetProgress = this.crzBooleanToggle.animationProgress;
this.crzBooleanToggle.isAnimating = false;
}
this.onPropertyChanged();
}
this.node.onPropertyChanged = function (propName)
{
if (!this.configured) return;
this.outputs[0].type = "BOOLEAN";
this.widgets[0].value = this.properties.value;
// Start animation when value changes
if (this.crzBooleanToggle && propName === "value") {
this.crzBooleanToggle.targetProgress = this.properties.value ? 1 : 0;
this.crzBooleanToggle.startAnimation();
}
}
this.node.onDrawForeground = function(ctx)
{
this.configured = true;
if ( this.flags.collapsed ) return false;
if (this.size[1] > LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER) this.size[1] = LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER;
// Update animation if needed
if (this.crzBooleanToggle) {
this.crzBooleanToggle.updateAnimation();
}
// Draw label on the left
ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
ctx.font = LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
ctx.textAlign = "left";
ctx.fillText(this.properties.label || "Boolean Toggle", LABEL_LEFT_PADDING, shX);
// 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
// Get animation progress for smooth color transitions
const progress = this.crzBooleanToggle ? this.crzBooleanToggle.animationProgress : (this.properties.value ? 1 : 0);
// Toggle background - interpolate colors during animation
const bgColor = this.interpolateColor(TOGGLE_INACTIVE_COLOR, TOGGLE_ACTIVE_COLOR, progress);
ctx.fillStyle = bgColor;
ctx.beginPath();
ctx.roundRect(toggleLeft, shY - TOGGLE_HEIGHT/2 + TOGGLE_VERTICAL_OFFSET, 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 = shY - HANDLE_SIZE/2 + TOGGLE_VERTICAL_OFFSET;
ctx.fillStyle = HANDLE_COLOR;
ctx.beginPath();
ctx.roundRect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE, HANDLE_CORNER_RADIUS);
ctx.fill();
// Draw handle border - interpolate colors during animation
const borderColor = this.interpolateColor(HANDLE_BORDER_INACTIVE, HANDLE_BORDER_ACTIVE, progress);
ctx.lineWidth = 1;
ctx.strokeStyle = borderColor;
ctx.beginPath();
ctx.roundRect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE, HANDLE_CORNER_RADIUS);
ctx.stroke();
// Value text removed - toggle fills the space
}
this.node.onDblClick = function(e, pos, canvas)
{
// Double-click on expanded toggle area to toggle value
const expandedToggleWidth = TOGGLE_WIDTH + 10;
const toggleLeft = this.pos[0] + this.size[0] - TRACK_RIGHT_PADDING + 40;
if (e.canvasX > toggleLeft && e.canvasX < toggleLeft + expandedToggleWidth)
{
this.properties.value = !this.properties.value;
this.onPropertyChanged("value");
return true;
}
// This is now handled by the expanded toggle area above
// Double-click on label area to rename
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] && e.canvasY < this.pos[1] + this.size[1]) {
canvas.prompt("Label", this.properties.label || "Boolean Toggle", function(newLabel) {
if (newLabel && newLabel.trim()) {
this.properties.label = newLabel.trim();
}
}.bind(this), e);
return true;
}
}
this.node.onMouseDown = function(e)
{
if ( e.canvasY - this.pos[1] < 0 ) return false;
// Update hit detection to match the expanded toggle position
const expandedToggleWidth = TOGGLE_WIDTH + 10;
const toggleLeft = this.size[0] - TRACK_RIGHT_PADDING + 40;
if ( e.canvasX < this.pos[0] + toggleLeft - 5 || e.canvasX > this.pos[0] + toggleLeft + expandedToggleWidth + 5 ) return false;
if ( e.canvasY < this.pos[1] + shiftLeft - 5 || e.canvasY > this.pos[1] + this.size[1] - shiftLeft + 5 ) return false;
// Toggle the value on click
this.properties.value = !this.properties.value;
this.onPropertyChanged("value");
this.widgets[0].value = this.properties.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;
}
this.node.onSelected = function(e) { }
this.node.computeSize = () => [NODE_WIDTH-100, Math.floor(LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER)];
// Add color interpolation method to node
this.node.interpolateColor = function(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})`;
};
}
// Animation methods
startAnimation() {
this.isAnimating = true;
this.animateStep();
}
animateStep() {
if (!this.isAnimating) return;
// Request animation frame for smooth animation
requestAnimationFrame(() => this.animateStep());
// Mark canvas as dirty to trigger redraw
if (this.node.graph && this.node.graph.setDirtyCanvas) {
this.node.graph.setDirtyCanvas(true);
}
}
updateAnimation() {
if (Math.abs(this.animationProgress - this.targetProgress) < 0.01) {
this.animationProgress = this.targetProgress;
this.isAnimating = false;
return;
}
// Smooth easing animation
const diff = this.targetProgress - this.animationProgress;
this.animationProgress += diff * (this.animationSpeed * 0.016); // 60fps normalized
}
}
app.registerExtension(
{
name: "CRZBooleanToggle",
async beforeRegisterNodeDef(nodeType, nodeData, _app)
{
if (nodeData.name === "CRZBooleanToggle")
{
// Hide the title
nodeType.title_mode = LiteGraph.NO_TITLE;
nodeType.prototype.getTitle = function() { return ""; };
nodeType.prototype.onDrawTitle = function() { return false; };
// Set minimum size
nodeType.min_size = [NODE_WIDTH, Math.floor(LiteGraph.NODE_SLOT_HEIGHT * NODE_HEIGHT_MULTIPLIER)];
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
if (onNodeCreated) onNodeCreated.apply(this, []);
this.bgcolor = NODE_BACKGROUND_COLOR;
this.crzBooleanToggle = new CRZBooleanToggle(this);
// Initialize animation state to match current value
if (this.crzBooleanToggle) {
this.crzBooleanToggle.animationProgress = this.properties.value ? 1 : 0;
this.crzBooleanToggle.targetProgress = this.crzBooleanToggle.animationProgress;
this.crzBooleanToggle.isAnimating = false;
}
// Mark this as a CRZ node for connection hiding
this.isCRZNode = true;
}
}
}
});