From e6c8dd583f5107c7b2b85a8b27cb7b19ceea6675 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Tue, 19 Aug 2025 11:48:53 -0700 Subject: [PATCH] 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. --- .../xyz_helpers/plot_sampler_params/logic.py | 21 +- web/empty_latent_batch_swap.js | 242 +++++------------- 2 files changed, 77 insertions(+), 186 deletions(-) diff --git a/kikotools/tools/xyz_helpers/plot_sampler_params/logic.py b/kikotools/tools/xyz_helpers/plot_sampler_params/logic.py index 519bc3b..efd7887 100644 --- a/kikotools/tools/xyz_helpers/plot_sampler_params/logic.py +++ b/kikotools/tools/xyz_helpers/plot_sampler_params/logic.py @@ -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']}]" diff --git a/web/empty_latent_batch_swap.js b/web/empty_latent_batch_swap.js index de76a66..221b054 100644 --- a/web/empty_latent_batch_swap.js +++ b/web/empty_latent_batch_swap.js @@ -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); - } - }; } }, });