291 lines
12 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
}
|
|
});
|