fix: convert Empty Latent Batch swap button from canvas to DOM widget

- Switch from unreliable canvas-based drawing to DOM widget approach
- Eliminate coordinate calculation complexity and mouse position issues
- Use same proven pattern as Seed History node buttons
- Add proper hover/click animations and visual feedback
- Fix callback bug in heightWidget.callback assignment
- Remove all canvas drawing, mouse handling, and coordinate code
- Button now works consistently without coordinate system problems

Resolves swap button not working issue by using reliable DOM elements
instead of manual canvas coordinate calculations.
This commit is contained in:
Vito Sansevero
2025-08-19 11:48:53 -07:00
parent f9db6f8635
commit e6c8dd583f
2 changed files with 77 additions and 186 deletions
@@ -202,8 +202,25 @@ def format_parameter_text(param: Dict, mode: str = "full") -> str:
# Optional LoRA line
if "lora" in param and param["lora"]:
lora_name = param["lora"][:32] if len(param["lora"]) > 32 else param["lora"]
lora_line = f"LoRA: {lora_name}, str: {param.get('lora_strength', 'N/A')}"
lora_path = param["lora"]
# Extract just the filename and immediate parent directory for better readability
import os
path_parts = lora_path.replace("\\", "/").split("/")
if len(path_parts) > 2:
# Show parent directory and filename
lora_display = f"{path_parts[-2]}/{path_parts[-1]}"
else:
# Use full path if it's short
lora_display = lora_path
# Remove file extension for cleaner display
if lora_display.endswith(".safetensors"):
lora_display = lora_display[:-12]
lora_line = (
f"LoRA: {lora_display}, str: {param.get('lora_strength', 'N/A')}"
)
# Add batch info if available
if "lora_batch" in param:
lora_line += f" [{param['lora_batch']}]"
+58 -184
View File
@@ -9,9 +9,6 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
if (onNodeCreated) onNodeCreated.apply(this, []);
// Track button click state for visual feedback
this.swapButtonPressed = false;
// Helper function to extract resolution from formatted preset string
this.extractResolutionFromPreset = function (presetValue) {
if (presetValue === "custom") return null;
@@ -26,6 +23,9 @@ app.registerExtension({
return presetValue;
};
// Create swap button as DOM widget
this.createSwapButton();
// Override preset callback to update width/height widgets when preset changes
const presetWidget = this.widgets.find((w) => w.name === "preset");
if (presetWidget) {
@@ -194,200 +194,74 @@ app.registerExtension({
this.graph?.setDirtyCanvas(true, true);
}
};
// Override onResize to refresh button position
const originalOnResize = this.onResize;
this.onResize = function (size) {
if (originalOnResize) {
originalOnResize.call(this, size);
}
// Force redraw to update button position
this.setDirtyCanvas(true, true);
// Also mark the graph as dirty
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
}
};
// Override onBounding to ensure proper updates
const originalOnBounding = this.onBounding;
this.onBounding = function (out) {
if (originalOnBounding) {
originalOnBounding.call(this, out);
}
// Force redraw when bounds change
this.setDirtyCanvas(true, true);
};
};
const onDrawForeground = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function (ctx) {
if (onDrawForeground) {
onDrawForeground.apply(this, arguments);
}
// Create swap button as DOM widget
nodeType.prototype.createSwapButton = function () {
// Create button container
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
padding: 4px;
text-align: center;
`;
if (this.flags.collapsed) return;
// Create swap button
const swapButton = document.createElement("button");
swapButton.innerHTML = "↔️ Swap W×H";
swapButton.style.cssText = `
background: #4A90E2;
color: white;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-size: 11px;
font-weight: bold;
transition: background 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
`;
// Draw swap button with consistent spacing from widgets
const swapButtonSize = 24;
const margin = 6;
const swapButtonX = this.size[0] - swapButtonSize - margin;
// Button hover effects
swapButton.addEventListener("mouseenter", () => {
swapButton.style.background = "#5BA0F2";
swapButton.style.transform = "translateY(-1px)";
swapButton.style.boxShadow = "0 3px 6px rgba(0,0,0,0.3)";
});
// Calculate button position based on widget spacing rather than bottom margin
// Estimate widget area height and add consistent spacing
const estimatedWidgetHeight = 90; // Approximate height for 3 widgets
const topMargin = 35; // Space from top to first widget
const buttonSpacing = 40; // Space between last widget and button (moved down 5)
const swapButtonY = topMargin + estimatedWidgetHeight + buttonSpacing;
swapButton.addEventListener("mouseleave", () => {
swapButton.style.background = "#4A90E2";
swapButton.style.transform = "translateY(0)";
swapButton.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
});
// Button background - change color based on pressed state
if (this.swapButtonPressed) {
// Darker when pressed
ctx.fillStyle = "rgba(30, 120, 200, 0.9)"; // Darker blue when clicked
} else {
// Normal state
ctx.fillStyle = "rgba(66, 165, 245, 0.8)"; // Material blue
}
ctx.beginPath();
ctx.roundRect(
swapButtonX,
swapButtonY,
swapButtonSize,
swapButtonSize,
4,
);
ctx.fill();
// Button click effect and functionality
swapButton.addEventListener("mousedown", () => {
swapButton.style.background = "#3A80D2";
swapButton.style.transform = "translateY(1px)";
swapButton.style.boxShadow = "0 1px 2px rgba(0,0,0,0.2)";
});
// Button border with subtle highlight
ctx.strokeStyle = this.swapButtonPressed
? "rgba(20, 100, 180, 1.0)"
: "rgba(33, 150, 243, 0.9)";
ctx.lineWidth = 1;
ctx.stroke();
swapButton.addEventListener("mouseup", () => {
swapButton.style.background = "#5BA0F2";
swapButton.style.transform = "translateY(-1px)";
swapButton.style.boxShadow = "0 3px 6px rgba(0,0,0,0.3)";
});
// Draw swap icon - modern double arrow design
ctx.strokeStyle = "rgba(255, 255, 255, 0.95)";
ctx.lineWidth = 2;
ctx.lineCap = "round";
const centerX = swapButtonX + 12;
const centerY = swapButtonY + 12;
// Top arrow (pointing right) - width to height
ctx.beginPath();
ctx.moveTo(centerX - 7, centerY - 3);
ctx.lineTo(centerX + 5, centerY - 3);
ctx.stroke();
// Top arrow head
ctx.beginPath();
ctx.moveTo(centerX + 5, centerY - 3);
ctx.lineTo(centerX + 2, centerY - 5);
ctx.moveTo(centerX + 5, centerY - 3);
ctx.lineTo(centerX + 2, centerY - 1);
ctx.stroke();
// Bottom arrow (pointing left) - height to width
ctx.beginPath();
ctx.moveTo(centerX + 5, centerY + 3);
ctx.lineTo(centerX - 7, centerY + 3);
ctx.stroke();
// Bottom arrow head
ctx.beginPath();
ctx.moveTo(centerX - 7, centerY + 3);
ctx.lineTo(centerX - 4, centerY + 1);
ctx.moveTo(centerX - 7, centerY + 3);
ctx.lineTo(centerX - 4, centerY + 5);
ctx.stroke();
};
const onMouseDown = nodeType.prototype.onMouseDown;
nodeType.prototype.onMouseDown = function (e) {
// Check if click is on swap button
const swapButtonSize = 24;
const margin = 6;
const swapButtonX =
this.pos[0] + this.size[0] - swapButtonSize - margin;
// Use same positioning logic as drawing
const estimatedWidgetHeight = 90;
const topMargin = 35;
const buttonSpacing = 40;
const swapButtonY =
this.pos[1] + topMargin + estimatedWidgetHeight + buttonSpacing;
if (
e.canvasX >= swapButtonX &&
e.canvasX <= swapButtonX + swapButtonSize &&
e.canvasY >= swapButtonY &&
e.canvasY <= swapButtonY + swapButtonSize
) {
// Visual feedback - set button as pressed
this.swapButtonPressed = true;
this.setDirtyCanvas(true, true);
// Execute swap
// Main click functionality
swapButton.addEventListener("click", () => {
this.swapDimensions();
});
// Reset button state after a short delay for visual feedback
setTimeout(() => {
this.swapButtonPressed = false;
this.setDirtyCanvas(true, true);
}, 150);
buttonContainer.appendChild(swapButton);
return true; // Consume the event
}
// Call original onMouseDown if not clicking swap button
if (onMouseDown) {
return onMouseDown.apply(this, arguments);
}
// Add as DOM widget
this.swapButtonWidget = this.addDOMWidget(
"swap_button",
"div",
buttonContainer
);
};
// Optional: Add hover effect for better user feedback
const onMouseMove = nodeType.prototype.onMouseMove;
nodeType.prototype.onMouseMove = function (e) {
// Check if hovering over swap button
const swapButtonSize = 24;
const margin = 6;
const swapButtonX =
this.pos[0] + this.size[0] - swapButtonSize - margin;
// Use same positioning logic as drawing
const estimatedWidgetHeight = 90;
const topMargin = 35;
const buttonSpacing = 40;
const swapButtonY =
this.pos[1] + topMargin + estimatedWidgetHeight + buttonSpacing;
const isHovering =
e.canvasX >= swapButtonX &&
e.canvasX <= swapButtonX + swapButtonSize &&
e.canvasY >= swapButtonY &&
e.canvasY <= swapButtonY + swapButtonSize;
// Update cursor style for better UX (safely)
if (
isHovering &&
this.graph &&
this.graph.canvas &&
this.graph.canvas.canvas
) {
this.graph.canvas.canvas.style.cursor = "pointer";
} else if (
this.graph &&
this.graph.canvas &&
this.graph.canvas.canvas
) {
this.graph.canvas.canvas.style.cursor = "default";
}
// Call original onMouseMove
if (onMouseMove) {
return onMouseMove.apply(this, arguments);
}
};
}
},
});