- Restore getExtraMenuOptions context menu in advanced_comparer.ts:
- Layout Mode submenu with all 8 comparison modes
- Reset to Default Size option
- Consistent with video_comparer.ts implementation
- Create COMPARER_MODES constant to eliminate duplicate arrays:
- Used in @comparer_mode property definition
- Used in layoutWidget combo values
- Used in context menu submenu
- Share AnimationCache type in gradient_title.ts:
- Import AnimationCache from golden_eyeball.js
- Remove duplicate RenderCache interface definition
- Note: gradient_title.ts intentionally has its own drawGradientTitle
for equation-styled titles (different visual from golden eyeball)
All 85 tests pass.
192 lines
9.3 KiB
JavaScript
192 lines
9.3 KiB
JavaScript
// Cache for rendering optimization (uses shared AnimationCache interface)
|
||
const CACHE = {
|
||
lastTime: 0,
|
||
frameCount: 0,
|
||
frameSkip: 2, // Only update animation every X frames
|
||
};
|
||
// Import app from ComfyUI at runtime (this import is resolved by the browser)
|
||
// @ts-ignore - ComfyUI provides this at runtime
|
||
import { app } from '../../scripts/app.js';
|
||
// Define the extension
|
||
const extension = {
|
||
name: 'ShaderNoiseKSampler.GradientTitle',
|
||
async beforeRegisterNodeDef(nodeType, nodeData, _app) {
|
||
// Apply to both shader noise ksampler nodes
|
||
if (nodeData.name === 'ShaderNoiseKSampler' ||
|
||
nodeData.name === 'ShaderNoiseKSamplerDirect') {
|
||
// Store the original onDrawForeground function if it exists
|
||
const origOnDrawForeground = nodeType.prototype.onDrawForeground;
|
||
// Add our own onDrawForeground function
|
||
nodeType.prototype.onDrawForeground = function (ctx) {
|
||
// Call the original onDrawForeground if it exists
|
||
if (origOnDrawForeground) {
|
||
origOnDrawForeground.call(this, ctx);
|
||
}
|
||
// Draw a custom gradient title
|
||
drawGradientTitle(this, ctx);
|
||
};
|
||
// Clean up resources when node is removed
|
||
const origOnRemoved = nodeType.prototype.onRemoved;
|
||
nodeType.prototype.onRemoved = function () {
|
||
if (origOnRemoved) {
|
||
origOnRemoved.call(this);
|
||
}
|
||
// Note: CACHE only holds animation timing state, no cleanup needed
|
||
};
|
||
}
|
||
},
|
||
};
|
||
// Register the extension
|
||
app.registerExtension(extension);
|
||
/**
|
||
* Draws a gradient title directly on the canvas
|
||
* @param node - The node to apply the gradient to
|
||
* @param ctx - The canvas context
|
||
*/
|
||
function drawGradientTitle(node, ctx) {
|
||
// Get title area dimensions
|
||
const titleHeight = node.flags.collapsed ? 20 : 30; // Smaller height when collapsed
|
||
const width = node.flags.collapsed ? 190 : node.size[0]; // Smaller width when collapsed
|
||
const fullHeight = node.size[1]; // Get actual node height
|
||
const equationY = 45; // Y position for the equation, moved lower
|
||
// Choose appropriate equation based on node type
|
||
let equation;
|
||
let collapsedEquation;
|
||
if (node.type === 'ShaderNoiseKSamplerDirect') {
|
||
equation = 'Lt = Sα(N) ∘ Kβ(t) ⟿';
|
||
collapsedEquation = 'Lt = Sα(N) ∘ Kβ(t) ⟿';
|
||
}
|
||
else {
|
||
equation = 'Lt = Sα(N) ∘ Kβ(t)';
|
||
collapsedEquation = 'Lt = Sα(N) ∘ Kβ(t)';
|
||
}
|
||
// Update animation frame counter (performance optimization)
|
||
CACHE.frameCount = (CACHE.frameCount + 1) % (CACHE.frameSkip + 1);
|
||
const shouldUpdateAnimation = CACHE.frameCount === 0;
|
||
// Save current state
|
||
ctx.save();
|
||
// Reset shadow properties for gradient drawing
|
||
ctx.shadowColor = 'transparent';
|
||
ctx.shadowBlur = 0;
|
||
ctx.shadowOffsetX = 0;
|
||
ctx.shadowOffsetY = 0;
|
||
// Create vertical black gradient for entire background that fills node height
|
||
const gradient = ctx.createLinearGradient(0, 0, 0, fullHeight);
|
||
gradient.addColorStop(0, '#000000'); // Pure black at top
|
||
gradient.addColorStop(0.2, '#101010'); // Transition to very dark gray
|
||
gradient.addColorStop(1, '#101010'); // Very dark gray at bottom
|
||
// Create smooth shimmer effect for mathematical formula - only calculate if animation should update
|
||
let shimmerPosition = 0.5; // Default middle position
|
||
if (shouldUpdateAnimation) {
|
||
const time = Date.now() / 3000; // Faster time factor (changed from 4000)
|
||
shimmerPosition = (Math.sin(time) + 1) / 2; // Changed from cos to sin for left-to-right only
|
||
// Store for later use if needed
|
||
CACHE.lastTime = time;
|
||
}
|
||
else {
|
||
// Reuse last calculation for animation frames we're skipping
|
||
const time = CACHE.lastTime || Date.now() / 3000;
|
||
shimmerPosition = (Math.sin(time) + 1) / 2;
|
||
}
|
||
// Add collapse button handler
|
||
if (node.flags.collapsed) {
|
||
// If node is collapsed, adjust the title rendering
|
||
ctx.fillStyle = gradient;
|
||
ctx.fillRect(0, 0, width, titleHeight);
|
||
// Draw etched shadow for collapsed version
|
||
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
||
ctx.font = 'italic 11px Arial'; // Smaller font for collapsed state
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(collapsedEquation, width / 2 + 1, titleHeight / 2 + 1);
|
||
// Create base golden gradient for collapsed version
|
||
const baseGradient = ctx.createLinearGradient(0, titleHeight / 2 - 5, 0, titleHeight / 2 + 5);
|
||
baseGradient.addColorStop(0, '#B8860B'); // Darker gold
|
||
baseGradient.addColorStop(0.5, '#FFD700'); // Bright gold
|
||
baseGradient.addColorStop(1, '#B8860B'); // Darker gold
|
||
// Draw base golden text
|
||
ctx.fillStyle = baseGradient;
|
||
ctx.font = 'italic 11px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(collapsedEquation, width / 2, titleHeight / 2);
|
||
// Create moving highlight effect for collapsed version
|
||
const highlightWidth = width * 0.4; // Width of the highlight
|
||
const highlightX = -highlightWidth + (width + highlightWidth) * shimmerPosition; // Adjusted range
|
||
const shimmerGradient = ctx.createLinearGradient(highlightX - highlightWidth / 2, 0, highlightX + highlightWidth / 2, 0);
|
||
// Create smooth highlight transition
|
||
shimmerGradient.addColorStop(0, 'rgba(255, 255, 200, 0)');
|
||
shimmerGradient.addColorStop(0.1, 'rgba(255, 255, 200, 0)');
|
||
shimmerGradient.addColorStop(0.5, 'rgba(255, 255, 200, 0.3)');
|
||
shimmerGradient.addColorStop(0.9, 'rgba(255, 255, 200, 0)');
|
||
shimmerGradient.addColorStop(1, 'rgba(255, 255, 200, 0)');
|
||
// Apply highlight
|
||
ctx.fillStyle = shimmerGradient;
|
||
ctx.fillText(collapsedEquation, width / 2, titleHeight / 2);
|
||
// Add outline glow that follows the highlight
|
||
const glowIntensity = Math.max(0, 1 - Math.abs(width / 2 - highlightX) / (width / 4));
|
||
ctx.shadowColor = `rgba(255, 255, 200, ${glowIntensity * 0.3})`;
|
||
ctx.shadowBlur = 4; // Less blur for collapsed version
|
||
ctx.shadowOffsetX = 0;
|
||
ctx.shadowOffsetY = 0;
|
||
ctx.fillText(collapsedEquation, width / 2, titleHeight / 2);
|
||
// Skip the rest of the rendering when collapsed
|
||
ctx.restore();
|
||
return;
|
||
}
|
||
// Draw background that fills the entire node (non-collapsed state, reached after early return above)
|
||
ctx.fillStyle = gradient;
|
||
// Use rounded rectangle for the background with rounded corners at the bottom
|
||
const cornerRadius = 8; // Adjust radius as needed
|
||
// Create path for rounded rectangle
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, 0); // Start at top-left
|
||
ctx.lineTo(width, 0); // Top edge
|
||
ctx.lineTo(width, fullHeight - cornerRadius); // Right edge before bottom-right corner
|
||
ctx.arcTo(width, fullHeight, width - cornerRadius, fullHeight, cornerRadius); // Bottom-right rounded corner
|
||
ctx.lineTo(cornerRadius, fullHeight); // Bottom edge before bottom-left corner
|
||
ctx.arcTo(0, fullHeight, 0, fullHeight - cornerRadius, cornerRadius); // Bottom-left rounded corner
|
||
ctx.lineTo(0, 0); // Left edge back to top
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
// Draw etched shadow
|
||
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
||
ctx.font = 'italic 14px Arial'; // Smaller font for equation
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(equation, width / 2 + 2, equationY + 2);
|
||
// Create base golden gradient
|
||
const baseGradient = ctx.createLinearGradient(0, equationY - 7, 0, equationY + 7);
|
||
baseGradient.addColorStop(0, '#B8860B'); // Darker gold
|
||
baseGradient.addColorStop(0.5, '#FFD700'); // Bright gold
|
||
baseGradient.addColorStop(1, '#B8860B'); // Darker gold
|
||
// Draw base golden text
|
||
ctx.fillStyle = baseGradient;
|
||
ctx.font = 'italic 14px Arial'; // Smaller font for equation
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(equation, width / 2, equationY);
|
||
// Create moving highlight effect
|
||
const highlightWidth = width * 0.4; // Width of the highlight
|
||
const highlightX = -highlightWidth + (width + highlightWidth) * shimmerPosition; // Adjusted range
|
||
const shimmerGradient = ctx.createLinearGradient(highlightX - highlightWidth / 2, 0, highlightX + highlightWidth / 2, 0);
|
||
// Create smooth highlight transition
|
||
shimmerGradient.addColorStop(0, 'rgba(255, 255, 200, 0)');
|
||
shimmerGradient.addColorStop(0.1, 'rgba(255, 255, 200, 0)');
|
||
shimmerGradient.addColorStop(0.5, 'rgba(255, 255, 200, 0.3)');
|
||
shimmerGradient.addColorStop(0.9, 'rgba(255, 255, 200, 0)');
|
||
shimmerGradient.addColorStop(1, 'rgba(255, 255, 200, 0)');
|
||
// Apply highlight
|
||
ctx.fillStyle = shimmerGradient;
|
||
ctx.fillText(equation, width / 2, equationY);
|
||
// Add outline glow that follows the highlight
|
||
const glowIntensity = Math.max(0, 1 - Math.abs(width / 2 - highlightX) / (width / 4));
|
||
ctx.shadowColor = `rgba(255, 255, 200, ${glowIntensity * 0.3})`;
|
||
ctx.shadowBlur = 8;
|
||
ctx.shadowOffsetX = 0;
|
||
ctx.shadowOffsetY = 0;
|
||
ctx.fillText(equation, width / 2, equationY);
|
||
// Restore context state
|
||
ctx.restore();
|
||
}
|
||
//# sourceMappingURL=gradient_title.js.map
|