From 306038de9a7fa97336e2edf0071b18b01562404b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86motion?= <163354043+AEmotionStudio@users.noreply.github.com> Date: Tue, 26 Aug 2025 01:18:50 -0700 Subject: [PATCH] Update version to 1.4.1 and enhance README with recent improvements. Adjust magnifying glass settings for better UI integration and smart input detection. Refine info panel features including theme detection and customizable colors. --- README.md | 24 +- pyproject.toml | 2 +- web/magnify_glass.js | 164 +++++++++++-- web/magnify_info_panel.js | 488 +++++++++++++++++++++++++++++++++----- 4 files changed, 593 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 644e05b..c8d8145 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ComfyUI-MagnifyGlass -![Version](https://img.shields.io/badge/version-1.4.0-blue.svg) +![Version](https://img.shields.io/badge/version-1.4.1-blue.svg) ![ComfyUI](https://img.shields.io/badge/ComfyUI-compatible-green) ![License](https://img.shields.io/badge/license-GPL--3.0-brightgreen.svg) ![Dependencies](https://img.shields.io/badge/dependencies-none-brightgreen.svg) @@ -14,6 +14,28 @@ ComfyUI-MagnifyGlass adds an intuitive magnifying glass to your ComfyUI canvas, ![Magnify Glass in Action Wepb](images/magnify_glass_action.webp) +## 🔥 Recent Improvements + +**Latest Session Updates:** + +- **🎯 Smart Input Detection**: Hotkeys now intelligently detect when you're typing in input fields (similar to ComfyUI's native 'F' key behavior) and won't activate, eliminating the need for Alt key requirements +- **🔓 Enhanced Panel Controls**: + - Pin button now properly toggles between unlocked (🔓) and locked (🔒) states + - 'U' key unlocks panel to mouse location without requiring Alt + - 'G' key intelligently respects manual pin/lock decisions and only auto-unlocks when appropriate +- **🎨 Visual Improvements**: + - Updated default border colors to gray (#6b7280) for better UI integration + - Added customizable info panel text and accent colors with smart defaults for light/dark modes + - Automatic theme synchronization with main ComfyUI frontend for dark and light modes +- **⚙️ Enhanced Settings**: + - Percentage-based controls for opacity (10-100%) and zoom factor (100-1000%) for finer control + - Fixed color picker persistence issues +- **🔄 Improved Reset Functionality**: Reset key ('O') now properly resets both magnify glass and inspector panel to default top-right positions +- **📱 UI Polish**: + - Inspector panel always appears above other UI elements with proper z-index layering + - Fixed blank settings dropdowns and improved tooltip descriptions + - Updated button tooltips to reflect current hotkey behavior + ## 🚀 Latest Updates **6/12/25 (v1.3.0)** diff --git a/pyproject.toml b/pyproject.toml index e440258..9fc17a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-magnifyglass" description = "ComfyUI-MagnifyGlass: A powerful & customizable magnifying glass for ComfyUI. Zoom into canvas details with smooth controls, configurable activation, custom styles (shape, size, border) & WebGL performance." -version = "1.4.0" +version = "1.4.1" license = {file = "LICENSE"} [project.urls] diff --git a/web/magnify_glass.js b/web/magnify_glass.js index 2d0026d..165582e 100644 --- a/web/magnify_glass.js +++ b/web/magnify_glass.js @@ -4,7 +4,7 @@ import { app } from "../../../scripts/app.js"; * ComfyUI Magnifying Glass * * This script adds a magnifying glass feature to ComfyUI. - * Hold Alt+X to activate the magnifying glass and see a zoomed view of the canvas. + * Press X to activate the magnifying glass and see a zoomed view of the canvas. */ app.registerExtension({ @@ -12,13 +12,13 @@ app.registerExtension({ async setup() { // Default settings configuration const DEFAULT_SETTINGS = { - "🔍MagnifyGlass.ZoomFactor": 3, + "🔍MagnifyGlass.ZoomFactor": 300, "🔍MagnifyGlass.GlassSize": 300, - "🔍MagnifyGlass.BorderColor": "#ffffff", - "🔍MagnifyGlass.BorderWidth": 2, + "🔍MagnifyGlass.BorderColor": "#6b7280", + "🔍MagnifyGlass.BorderWidth": 1, "🔍MagnifyGlass.ActivationKey": "x", "🔍MagnifyGlass.AltRequired": false, - "🔍MagnifyGlass.FollowCursor": true, + "🔍MagnifyGlass.FollowCursor": false, "🔍MagnifyGlass.DebugMode": false, "🔍MagnifyGlass.OffsetStep": 5, "🔍MagnifyGlass.GlassPosition": "Top-Right", @@ -26,7 +26,7 @@ app.registerExtension({ "🔍MagnifyGlass.GlassShape": "Rounded Square", "🔍MagnifyGlass.BorderEnabled": true, "🔍MagnifyGlass.TextureFiltering": "Linear", - "🔍MagnifyGlass.AlwaysActiveMode": false, + "🔍MagnifyGlass.AlwaysActiveMode": true, "🔍MagnifyGlass.ToggleFollowCursorKey": "h", }; @@ -94,7 +94,7 @@ app.registerExtension({ // Attach event handlers this.eventHandler.attachListeners(); - this.debugger.log(`Initialized (WebGL). Hold ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate. Arrow keys to adjust offset, R to reset.`); + this.debugger.log(`Initialized (WebGL) with Smart Input Detection. Press ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate. Hotkeys disabled while typing in inputs.`); } findLiteGraphCanvas() { @@ -345,7 +345,7 @@ app.registerExtension({ // Method to update config from settings (can be called from callbacks) updateConfigFromSettings() { - this.config.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"]); + this.config.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"]) / 100; this.config.glassSize = getSettingValue("🔍MagnifyGlass.GlassSize", DEFAULT_SETTINGS["🔍MagnifyGlass.GlassSize"]); this.config.borderColor = getSettingValue("🔍MagnifyGlass.BorderColor", DEFAULT_SETTINGS["🔍MagnifyGlass.BorderColor"]); this.config.borderWidth = getSettingValue("🔍MagnifyGlass.BorderWidth", DEFAULT_SETTINGS["🔍MagnifyGlass.BorderWidth"]); @@ -415,7 +415,40 @@ app.registerExtension({ if (this.state.active) { this.updateMagnifiedView(); // Update if active } - this.debugger.log("Offsets reset to 0, 0"); + + // Reset magnify glass position to top-right corner + if (this.ui.glassDiv) { + const glassSize = this.config.glassSize; + const padding = 20; // Same padding as initial positioning + this.ui.glassDiv.style.left = `${window.innerWidth - glassSize - padding}px`; + this.ui.glassDiv.style.top = `${padding}px`; + // Reset the activation flag so next time it uses default positioning + this.state.wasActivatedBefore = false; + } + + // Reset inspector panel position to default + if (window.comfyUIMagnifyGlassExtensions && window.comfyUIMagnifyGlassExtensions.length > 0) { + window.comfyUIMagnifyGlassExtensions.forEach(extension => { + if (extension && extension.stateManager) { + // Reset panel to default position + extension.stateManager.state.isPanelPinned = false; + extension.stateManager.state.isPanelLocked = false; + extension.stateManager.state.isAutoPinned = false; + extension.stateManager.state.pinnedPosition = { x: 0, y: 0 }; + extension.stateManager.state.lastPinnedPosition = null; + + // Update panel position and controls + if (extension.positionManager) { + extension.positionManager.positionPanel(); + } + if (extension.uiManager && extension.uiManager.updateControlStates) { + extension.uiManager.updateControlStates(); + } + } + }); + } + + this.debugger.log("Reset: Magnify glass and inspector panel positions restored to top-right defaults"); } } @@ -423,7 +456,7 @@ app.registerExtension({ class ConfigManager { constructor() { // Initialize properties that will be set by reading settings - this.zoomFactor = DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"]; + this.zoomFactor = DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"] / 100; this.glassSize = DEFAULT_SETTINGS["🔍MagnifyGlass.GlassSize"]; this.borderColor = DEFAULT_SETTINGS["🔍MagnifyGlass.BorderColor"]; this.borderWidth = DEFAULT_SETTINGS["🔍MagnifyGlass.BorderWidth"]; @@ -447,7 +480,7 @@ app.registerExtension({ // Call this after settings are registered and potentially read by MagnifyGlass loadSettings() { - this.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", this.zoomFactor); + this.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", this.zoomFactor * 100) / 100; this.glassSize = getSettingValue("🔍MagnifyGlass.GlassSize", this.glassSize); this.borderColor = getSettingValue("🔍MagnifyGlass.BorderColor", this.borderColor); this.borderWidth = getSettingValue("🔍MagnifyGlass.BorderWidth", this.borderWidth); @@ -543,7 +576,7 @@ app.registerExtension({ border: ${this.config.borderEnabled ? `${this.config.borderWidth}px solid ${this.config.borderColor}` : 'none'}; overflow: hidden; pointer-events: none; - z-index: 9999; + z-index: 98999; display: none; box-shadow: 0 5px 15px rgba(0,0,0,0.3); background-color: rgba(255,255,255,0.1); @@ -589,7 +622,7 @@ app.registerExtension({ right: 10px; background: rgba(0,0,0,0.7); border: 1px solid #fff; - z-index: 10000; + z-index: 99000; pointer-events: none; color: white; font-family: monospace; @@ -931,10 +964,43 @@ app.registerExtension({ document.addEventListener("mousemove", this.handleMouseMove.bind(this)); } + // Helper function to check if user is typing in an input field + isUserTyping() { + const activeElement = document.activeElement; + if (!activeElement) return false; + + // Check if the active element is an input, textarea, or contenteditable + const tagName = activeElement.tagName.toLowerCase(); + if (tagName === 'input' || tagName === 'textarea') { + return true; + } + + // Check for contenteditable elements + if (activeElement.contentEditable === 'true') { + return true; + } + + // Check if it's inside a form or has input-like classes + if (activeElement.closest('form') || + activeElement.classList.contains('cm-editor') || // CodeMirror editor + activeElement.classList.contains('monaco-editor') || // Monaco editor + activeElement.closest('.cm-editor') || + activeElement.closest('.monaco-editor')) { + return true; + } + + return false; + } + handleKeyDown(e) { const config = this.magnifyGlass.config; const state = this.magnifyGlass.state; + // Don't handle hotkeys if user is typing in an input field (Smart Input Detection) + if (this.isUserTyping()) { + return; + } + // Magnifier activation if (e.key.toLowerCase() === config.activationKey && (!config.altRequired || e.altKey)) { @@ -982,10 +1048,43 @@ app.registerExtension({ e.preventDefault(); } else if (e.key.toLowerCase() === config.resetKey.toLowerCase() && (!config.altRequired || e.altKey)) { // Use configured reset key & check Alt - // Reset offsets to zero + // Reset magnify glass offsets to zero config.offsetX = 0; config.offsetY = 0; offsetChanged = true; + + // Reset magnify glass position to top-right corner + if (this.magnifyGlass.ui.glassDiv) { + const glassSize = config.glassSize; + const padding = 20; // Same padding as initial positioning + this.magnifyGlass.ui.glassDiv.style.left = `${window.innerWidth - glassSize - padding}px`; + this.magnifyGlass.ui.glassDiv.style.top = `${padding}px`; + // Reset the activation flag so next time it uses default positioning + this.magnifyGlass.state.wasActivatedBefore = false; + } + + // Reset inspector panel position to default + if (window.comfyUIMagnifyGlassExtensions && window.comfyUIMagnifyGlassExtensions.length > 0) { + window.comfyUIMagnifyGlassExtensions.forEach(extension => { + if (extension && extension.stateManager) { + // Reset panel to default position + extension.stateManager.state.isPanelPinned = false; + extension.stateManager.state.isPanelLocked = false; + extension.stateManager.state.pinnedPosition = { x: 0, y: 0 }; + extension.stateManager.state.lastPinnedPosition = null; + + // Update panel position and controls + if (extension.positionManager) { + extension.positionManager.positionPanel(); + } + if (extension.uiManager && extension.uiManager.updateControlStates) { + extension.uiManager.updateControlStates(); + } + } + }); + } + + this.magnifyGlass.debugger.log("Reset: Magnify glass and inspector panel positions restored to top-right defaults"); e.preventDefault(); } @@ -1276,16 +1375,17 @@ app.registerExtension({ // Add settings to the ComfyUI settings dialog app.ui.settings.addSetting({ id: "🔍MagnifyGlass.ZoomFactor", - name: "🔍 Magnify Glass: Zoom Factor", + name: "🔍 Magnify Glass: Zoom Factor (%)", type: "slider", defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"], - min: 1.0, - max: 10.0, - step: 0.1, - tooltip: "Magnification level (e.g., 2.5 means 2.5x zoom).", + min: 100, + max: 1000, + step: 25, + tooltip: "Magnification level as a percentage (e.g., 300 = 3x zoom, 150 = 1.5x zoom).", onChange: (value) => { if (magnifyGlass && magnifyGlass.config) { - magnifyGlass.config.zoomFactor = parseFloat(value); + // Convert percentage to decimal (e.g., 300 -> 3.0) + magnifyGlass.config.zoomFactor = value / 100; if (magnifyGlass.state.active) { magnifyGlass.updateMagnifiedView(); } @@ -1320,11 +1420,11 @@ app.registerExtension({ defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.BorderWidth"], min: 0, max: 10, - step: 1, - tooltip: "Width of the border around the magnifying glass.", + step: 0.1, + tooltip: "Width of the border around the magnifying glass (supports decimal values like 0.5, 1.5, etc.).", onChange: (value) => { if (magnifyGlass && magnifyGlass.config) { - magnifyGlass.config.borderWidth = parseInt(value, 10); + magnifyGlass.config.borderWidth = parseFloat(value); magnifyGlass.applyUiChanges(); // Update border style } } @@ -1338,7 +1438,19 @@ app.registerExtension({ tooltip: "Color of the border around the magnifying glass.", onChange: (value) => { if (magnifyGlass && magnifyGlass.config) { - magnifyGlass.config.borderColor = value; + // Ensure color value includes # symbol for valid CSS + const normalizedColor = value && !value.startsWith('#') ? `#${value}` : value; + magnifyGlass.config.borderColor = normalizedColor; + + // Only save back if we actually normalized the color (to prevent recursion) + if (value !== normalizedColor) { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.BorderColor", normalizedColor); + } catch (e) { + console.warn('Failed to save normalized border color:', e); + } + } + magnifyGlass.applyUiChanges(); // Update border style } } @@ -1365,7 +1477,7 @@ app.registerExtension({ type: "combo", options: [ { value: true, text: "Yes" }, { value: false, text: "No" } ], defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.AltRequired"], - tooltip: "If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation and for reset.", + tooltip: "If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation and for reset. Not needed when 'Smart Input Detection' prevents hotkeys during typing.", onChange: (value) => { if (magnifyGlass && magnifyGlass.config) { magnifyGlass.config.altRequired = value; @@ -1410,7 +1522,7 @@ app.registerExtension({ type: "combo", options: ["r", "o", "p", "k", "l"], // Example keys, can be expanded defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.ResetKey"], - tooltip: "The key (case-insensitive) to press to reset the view offset while the magnifier is active. Works with Alt/Option if 'Require Alt for Reset' is Yes.", + tooltip: "The key (case-insensitive) to press to reset the magnify glass offset and inspector panel position to their default locations while the magnifier is active. Works with Alt/Option if 'Require Alt for Reset' is Yes.", onChange: (value) => { if (magnifyGlass && magnifyGlass.config) { magnifyGlass.config.resetKey = value.toLowerCase(); diff --git a/web/magnify_info_panel.js b/web/magnify_info_panel.js index 85bc2eb..c388522 100644 --- a/web/magnify_info_panel.js +++ b/web/magnify_info_panel.js @@ -42,18 +42,19 @@ app.registerExtension({ // Default settings configuration const DEFAULT_SETTINGS = { "🔍MagnifyGlass.InfoPanelEnabled": true, - "🔍MagnifyGlass.InfoPanelPosition": "Left", + "🔍MagnifyGlass.InfoPanelPosition": "Bottom", "🔍MagnifyGlass.InfoPanelWidth": 320, - "🔍MagnifyGlass.InfoPanelOpacity": 0.95, + "🔍MagnifyGlass.InfoPanelOpacity": 100, "🔍MagnifyGlass.InfoPanelMaxHeight": 500, - "🔍MagnifyGlass.InfoPanelTheme": "Dark", - "🔍MagnifyGlass.InfoPanelAnimations": true, + "🔍MagnifyGlass.InfoPanelAnimations": false, "🔍MagnifyGlass.ShowInspectorTab": false, "🔍MagnifyGlass.ToggleHotkey": "i", "🔍MagnifyGlass.GlassPreviewToggleHotkey": "g", - "🔍MagnifyGlass.PinPanelHotkey": "p", + "🔍MagnifyGlass.PinPanelHotkey": "u", "🔍MagnifyGlass.ShowHoveringControls": true, - "🔍MagnifyGlass.ControlsPosition": "bottom-centered", + "🔍MagnifyGlass.ControlsPosition": "bottom", + "🔍MagnifyGlass.InfoPanelTextColor": "#6b7280", + "🔍MagnifyGlass.InfoPanelAccentColor": "#3b82f6", }; const getSettingValue = (key, defaultValue) => { @@ -78,6 +79,7 @@ app.registerExtension({ isPanelMinimized: false, isPanelPinned: false, isPanelLocked: false, // New lock state + isAutoPinned: false, // Track if panel was auto-pinned by glass hide pinnedPosition: { x: 0, y: 0 }, lastPinnedPosition: null, // Remember last pinned location @@ -103,16 +105,265 @@ app.registerExtension({ currentInfo: {}, // Settings cache - settings: {} + settings: {}, + + // Auto-detected theme + currentTheme: 'dark' // Will be auto-detected }; + this.initThemeDetection(); this.loadSettings(); } + initThemeDetection() { + // Immediate forced detection + console.log('ComfyUI Magnify Glass: Starting theme detection...'); + + // Detect ComfyUI's current theme + this.detectCurrentTheme(); + + // Force an immediate check in case we missed the initial state + setTimeout(() => { + console.log('ComfyUI Magnify Glass: Delayed theme check...'); + if (this.detectCurrentTheme()) { + this.notifyThemeChange(); + } + }, 1000); + + // Set up mutation observer to watch for theme changes + this.setupThemeObserver(); + + // Listen for system theme changes as fallback + if (window.matchMedia) { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); + prefersDark.addListener(() => this.detectCurrentTheme()); + } + + // Add click listener to theme toggle buttons for immediate detection + this.setupThemeButtonListeners(); + } + + detectCurrentTheme() { + let detectedTheme = 'dark'; // Default fallback + + const body = document.body; + const html = document.documentElement; + const vueApp = document.querySelector('#vue-app'); + const sidebar = document.querySelector('.comfy-menu, .sidebar, .menu, [class*="sidebar"], [class*="menu"]'); + + // Get all possible theme-related elements + const allElements = [body, html, vueApp, sidebar].filter(Boolean); + + // Debug: Log current classes and attributes with more detail + const debugInfo = { + bodyClasses: Array.from(body.classList), + htmlClasses: Array.from(html.classList), + bodyDataTheme: body.getAttribute('data-theme'), + htmlDataTheme: html.getAttribute('data-theme'), + vueAppClasses: vueApp ? Array.from(vueApp.classList) : null, + sidebarClasses: sidebar ? Array.from(sidebar.classList) : null, + bodyBgColor: window.getComputedStyle(body).backgroundColor, + htmlBgColor: window.getComputedStyle(html).backgroundColor, + allComputedVars: {} + }; + + // Check CSS variables + const rootStyles = window.getComputedStyle(html); + const bodyStyles = window.getComputedStyle(body); + + // Get all CSS custom properties + for (let i = 0; i < rootStyles.length; i++) { + const prop = rootStyles[i]; + if (prop.startsWith('--')) { + debugInfo.allComputedVars[prop] = rootStyles.getPropertyValue(prop).trim(); + } + } + + console.log('ComfyUI Magnify Glass: Comprehensive theme detection debug:', debugInfo); + + // Method 1: Look for specific light theme indicators in CSS variables + const primarySurface = rootStyles.getPropertyValue('--p-primary-color') || + rootStyles.getPropertyValue('--primary-color') || + bodyStyles.getPropertyValue('--p-primary-color') || + bodyStyles.getPropertyValue('--primary-color'); + + const surfaceGround = rootStyles.getPropertyValue('--p-surface-ground') || + rootStyles.getPropertyValue('--surface-ground') || + bodyStyles.getPropertyValue('--p-surface-ground') || + bodyStyles.getPropertyValue('--surface-ground'); + + const textColor = rootStyles.getPropertyValue('--p-text-color') || + rootStyles.getPropertyValue('--text-color') || + bodyStyles.getPropertyValue('--p-text-color') || + bodyStyles.getPropertyValue('--text-color'); + + console.log('ComfyUI Magnify Glass: CSS Variables:', { primarySurface, surfaceGround, textColor }); + + // Method 2: Analyze background colors of multiple elements + const backgrounds = allElements.map(el => { + const styles = window.getComputedStyle(el); + return { + element: el.tagName || el.className, + backgroundColor: styles.backgroundColor, + color: styles.color + }; + }); + + console.log('ComfyUI Magnify Glass: Background analysis:', backgrounds); + + // Method 3: Check for light backgrounds + for (const bg of backgrounds) { + if (bg.backgroundColor && bg.backgroundColor !== 'rgba(0, 0, 0, 0)' && bg.backgroundColor !== 'transparent') { + const rgbMatch = bg.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (rgbMatch) { + const [, r, g, b] = rgbMatch.map(Number); + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + console.log(`ComfyUI Magnify Glass: Element ${bg.element} brightness: ${brightness}`); + if (brightness > 180) { // Higher threshold for light detection + detectedTheme = 'light'; + console.log(`ComfyUI Magnify Glass: Light theme detected via background analysis of ${bg.element}`); + break; + } + } + } + } + + // Method 4: Check for theme toggle buttons state (they might indicate current theme) + const themeButtons = document.querySelectorAll('[class*="theme"], button[title*="theme"], button[title*="Theme"], .p-button[title*="light"], .p-button[title*="Light"]'); + themeButtons.forEach(btn => { + console.log('ComfyUI Magnify Glass: Found theme button:', { + classes: Array.from(btn.classList), + text: btn.textContent, + title: btn.title, + active: btn.classList.contains('active') || btn.classList.contains('selected') + }); + }); + + // Method 5: Force light theme detection if we see light backgrounds + if (detectedTheme === 'dark') { + // Look for any element with very light background + const lightElements = document.querySelectorAll('*'); + for (let i = 0; i < Math.min(lightElements.length, 50); i++) { // Check first 50 elements + const el = lightElements[i]; + const styles = window.getComputedStyle(el); + const bgColor = styles.backgroundColor; + + if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') { + const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (rgbMatch) { + const [, r, g, b] = rgbMatch.map(Number); + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + if (brightness > 200) { // Very light elements + detectedTheme = 'light'; + console.log(`ComfyUI Magnify Glass: Light theme forced via element ${el.tagName}.${el.className} with brightness ${brightness}`); + break; + } + } + } + } + } + + // Update theme if it changed + if (this.state.currentTheme !== detectedTheme) { + console.log(`ComfyUI Magnify Glass: Theme changed from ${this.state.currentTheme} to ${detectedTheme}`); + this.state.currentTheme = detectedTheme; + return true; // Theme changed + } + + console.log(`ComfyUI Magnify Glass: Current theme remains: ${detectedTheme}`); + return false; // No change + } + + setupThemeObserver() { + // Watch for changes to body/html classes and attributes + const observer = new MutationObserver((mutations) => { + let shouldCheck = false; + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && + (mutation.attributeName === 'class' || + mutation.attributeName === 'data-theme' || + mutation.attributeName === 'style')) { + shouldCheck = true; + console.log('ComfyUI Magnify Glass: DOM change detected:', { + target: mutation.target.tagName, + attribute: mutation.attributeName, + oldValue: mutation.oldValue, + newValue: mutation.target.getAttribute(mutation.attributeName) + }); + } + }); + + if (shouldCheck && this.detectCurrentTheme()) { + // Theme changed, update UI immediately + this.notifyThemeChange(); + } + }); + + // Observe changes to body, html, and vue app + observer.observe(document.body, { attributes: true, attributeOldValue: true }); + observer.observe(document.documentElement, { attributes: true, attributeOldValue: true }); + + const vueApp = document.querySelector('#vue-app'); + if (vueApp) { + observer.observe(vueApp, { attributes: true, attributeOldValue: true }); + } + + // Also watch for changes to any element with theme-related classes + const themeElements = document.querySelectorAll('[class*="theme"], [class*="dark"], [class*="light"], [data-theme]'); + themeElements.forEach(el => { + observer.observe(el, { attributes: true, attributeOldValue: true }); + }); + + // Periodic fallback check every 2 seconds + setInterval(() => { + if (this.detectCurrentTheme()) { + this.notifyThemeChange(); + } + }, 2000); + } + + setupThemeButtonListeners() { + // Listen for clicks on theme toggle buttons + document.addEventListener('click', (e) => { + // Check if clicked element might be a theme toggle + const target = e.target; + const isThemeButton = target.textContent?.toLowerCase().includes('theme') || + target.textContent?.toLowerCase().includes('light') || + target.textContent?.toLowerCase().includes('dark') || + target.title?.toLowerCase().includes('theme') || + target.className?.toLowerCase().includes('theme'); + + if (isThemeButton) { + console.log('ComfyUI Magnify Glass: Theme button clicked, triggering detection'); + setTimeout(() => { + if (this.detectCurrentTheme()) { + this.notifyThemeChange(); + } + }, 100); + } + }); + } + + notifyThemeChange() { + // Update settings to reflect new theme + this.state.settings["🔍MagnifyGlass.InfoPanelTheme"] = this.state.currentTheme; + + // Notify UI components of theme change immediately + if (window.infoPanelManager && window.infoPanelManager.uiManager) { + window.infoPanelManager.uiManager.updateTheme(this.state.currentTheme); + } + } + loadSettings() { Object.keys(DEFAULT_SETTINGS).forEach(key => { - this.state.settings[key] = getSettingValue(key, DEFAULT_SETTINGS[key]); + // Skip the theme setting since we auto-detect it + if (key !== "🔍MagnifyGlass.InfoPanelTheme") { + this.state.settings[key] = getSettingValue(key, DEFAULT_SETTINGS[key]); + } }); + + // Set theme to auto-detected value + this.state.settings["🔍MagnifyGlass.InfoPanelTheme"] = this.state.currentTheme; } updateSettings() { @@ -149,9 +400,10 @@ app.registerExtension({ const wasPinned = this.state.isPanelPinned; this.state.isPanelPinned = !this.state.isPanelPinned; - // When unpinning, also unlock + // When unpinning, also unlock and clear auto-pin flag if (!this.state.isPanelPinned) { this.state.isPanelLocked = false; + this.state.isAutoPinned = false; // Clear auto-pin flag when manually unpinning } if (this.state.isPanelPinned && this.state.lastPinnedPosition) { @@ -266,7 +518,7 @@ app.registerExtension({ // Main panel container this.elements.panel = document.createElement("div"); this.elements.panel.id = "comfyui-magnify-info-panel-pro-v2"; - this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelTheme"].toLowerCase()}`; + this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`; // Header this.elements.header = document.createElement("div"); @@ -304,10 +556,10 @@ app.registerExtension({ this.elements.controls = document.createElement("div"); this.elements.controls.className = "floating-controls vertical-layout"; // Default to vertical this.elements.controls.innerHTML = ` - + - - + + `; // Insert before the panel in the document body, not as a child @@ -329,6 +581,7 @@ app.registerExtension({ if (pinBtn) { pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned); pinBtn.title = this.stateManager.state.isPanelPinned ? "Lock Panel" : "Unlock Panel"; + pinBtn.textContent = this.stateManager.state.isPanelPinned ? "🔒" : "🔓"; } if (lockBtn) { @@ -367,11 +620,33 @@ app.registerExtension({ applyStyles() { const settings = this.stateManager.state.settings; + // Apply custom text color via CSS variable if provided + const textColor = settings["🔍MagnifyGlass.InfoPanelTextColor"]; + if (textColor) { + // Ensure color has # symbol + const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`; + this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor); + } + + // Apply custom accent color via CSS variable if provided + const accentColor = settings["🔍MagnifyGlass.InfoPanelAccentColor"]; + if (accentColor) { + // Ensure color has # symbol + const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`; + this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor); + } + + // Apply opacity setting if panel is visible (convert percentage to decimal) + if (this.stateManager.state.isPanelVisible) { + const opacityPercent = settings["🔍MagnifyGlass.InfoPanelOpacity"]; + this.elements.panel.style.opacity = opacityPercent / 100; + } + this.elements.panel.style.cssText = ` position: absolute; width: ${settings["🔍MagnifyGlass.InfoPanelWidth"]}px; max-height: ${settings["🔍MagnifyGlass.InfoPanelMaxHeight"]}px; - z-index: 10001; + z-index: 99999; display: none; opacity: 0; transform: translateY(-10px); @@ -379,6 +654,8 @@ app.registerExtension({ pointer-events: auto; user-select: none; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + ${textColor ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''} + ${accentColor ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''} `; } @@ -388,6 +665,9 @@ app.registerExtension({ if (this.elements.controls) { this.elements.controls.style.display = "flex"; } + // Apply user's opacity setting when showing (convert percentage to decimal) + const opacityPercent = this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelOpacity"]; + this.elements.panel.style.opacity = opacityPercent / 100; // Trigger reflow this.elements.panel.offsetHeight; this.elements.panel.classList.add('visible'); @@ -424,7 +704,10 @@ app.registerExtension({ } updateTheme(newTheme) { - this.elements.panel.className = this.elements.panel.className.replace(/theme-\w+/, `theme-${newTheme.toLowerCase()}`); + if (this.elements.panel) { + this.elements.panel.className = this.elements.panel.className.replace(/theme-\w+/, `theme-${newTheme.toLowerCase()}`); + console.log(`ComfyUI Magnify Glass: Panel theme updated to ${newTheme}`); + } } displayInfo(info) { @@ -571,15 +854,16 @@ app.registerExtension({ updateHeaderSubtitle(info) { const subtitleElement = this.elements.header.querySelector('.header-subtitle'); if (subtitleElement) { + const accentColor = this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelAccentColor"]; if (info.hoveredNode) { subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title}`; - subtitleElement.style.color = '#74b9ff'; + subtitleElement.style.color = accentColor; } else if (info.media) { subtitleElement.textContent = `Media: ${info.media.tagName}`; - subtitleElement.style.color = '#a0d468'; + subtitleElement.style.color = accentColor; } else if (info.connection) { subtitleElement.textContent = `Connection: ${info.connection.type}`; - subtitleElement.style.color = '#fdcb6e'; + subtitleElement.style.color = accentColor; } else { subtitleElement.textContent = 'Real-time analysis'; subtitleElement.style.color = ''; @@ -763,18 +1047,17 @@ app.registerExtension({ .magnify-info-panel.theme-dark { background: linear-gradient(135deg, rgba(0, 0, 0, 0.95), rgba(10, 10, 15, 0.95)); - color: #e0e0e0; + color: var(--info-panel-text-color, #e0e0e0); border: 1px solid rgba(100, 100, 120, 0.3); } .magnify-info-panel.theme-light { background: linear-gradient(135deg, rgba(250, 250, 255, 0.95), rgba(240, 240, 250, 0.95)); - color: #2a2a2a; + color: var(--info-panel-text-color, #2a2a2a); border: 1px solid rgba(200, 200, 220, 0.4); } .magnify-info-panel.visible { - opacity: 1 !important; transform: translateY(0) !important; } @@ -803,7 +1086,7 @@ app.registerExtension({ cursor: grabbing; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 215, 0, 0.7); transform: scale(1.02) !important; - z-index: 10010 !important; + z-index: 100001 !important; transition: none !important; } @@ -839,7 +1122,7 @@ app.registerExtension({ border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 4px; opacity: 1; - z-index: 10003; + z-index: 100000; backdrop-filter: blur(8px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); } @@ -1008,7 +1291,7 @@ app.registerExtension({ .section-header.expanded { background: rgba(116, 185, 255, 0.1); - border-left: 2px solid #74b9ff; + border-left: 2px solid var(--info-panel-accent-color, #74b9ff); } .info-section[data-section="node"] { @@ -1030,7 +1313,7 @@ app.registerExtension({ } .info-section[data-section="node"] .section-title { - color: #74b9ff; + color: var(--info-panel-accent-color, #74b9ff); font-weight: 700; } @@ -1064,7 +1347,7 @@ app.registerExtension({ } .info-section[data-section="media"] .section-title { - color: #fdcb6e; + color: var(--info-panel-accent-color, #fdcb6e); font-weight: 700; } @@ -1098,12 +1381,12 @@ app.registerExtension({ .section-header:hover .expand-icon { opacity: 1; - color: #74b9ff; + color: var(--info-panel-accent-color, #74b9ff); } .section-header.expanded .expand-icon { transform: rotate(90deg); - color: #74b9ff; + color: var(--info-panel-accent-color, #74b9ff); } .section-content { @@ -1141,7 +1424,7 @@ app.registerExtension({ .info-value { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 14px; - color: #a0d468; + color: var(--info-panel-accent-color, #a0d468); background: rgba(160, 212, 104, 0.1); padding: 3px 8px; border-radius: 4px; @@ -1442,29 +1725,64 @@ app.registerExtension({ }); } + // Helper function to check if user is typing in an input field + isUserTyping() { + const activeElement = document.activeElement; + if (!activeElement) return false; + + // Check if the active element is an input, textarea, or contenteditable + const tagName = activeElement.tagName.toLowerCase(); + if (tagName === 'input' || tagName === 'textarea') { + return true; + } + + // Check for contenteditable elements + if (activeElement.contentEditable === 'true') { + return true; + } + + // Check if it's inside a form or has input-like classes + if (activeElement.closest('form') || + activeElement.classList.contains('cm-editor') || // CodeMirror editor + activeElement.classList.contains('monaco-editor') || // Monaco editor + activeElement.closest('.cm-editor') || + activeElement.closest('.monaco-editor')) { + return true; + } + + return false; + } + setupHotkeyEvents() { document.addEventListener('keydown', (e) => { + // Don't handle hotkeys if user is typing in an input field (Smart Input Detection) + if (this.isUserTyping()) { + return; + } + // Only handle hotkeys when magnifying glass is active if (!magnifyGlass.state.active) return; const settings = this.stateManager.state.settings; // Toggle info panel - if (e.key.toLowerCase() === settings["🔍MagnifyGlass.ToggleHotkey"].toLowerCase() && !e.repeat) { + if (e.key.toLowerCase() === settings["🔍MagnifyGlass.ToggleHotkey"].toLowerCase() && + (!magnifyGlass.config.altRequired || e.altKey) && !e.repeat) { e.preventDefault(); e.stopPropagation(); this.handleControlAction('toggle-panel'); } // Toggle glass preview - if (e.key.toLowerCase() === settings["🔍MagnifyGlass.GlassPreviewToggleHotkey"].toLowerCase() && !e.repeat) { + if (e.key.toLowerCase() === settings["🔍MagnifyGlass.GlassPreviewToggleHotkey"].toLowerCase() && + (!magnifyGlass.config.altRequired || e.altKey) && !e.repeat) { e.preventDefault(); e.stopPropagation(); this.handleControlAction('toggle-glass'); } - // Pin at mouse location with Alt + configured key - if (e.key.toLowerCase() === settings["🔍MagnifyGlass.PinPanelHotkey"].toLowerCase() && e.altKey && !e.repeat) { + // Unlock panel from magnify glass and move to current mouse location (no Alt required due to Smart Input Detection) + if (e.key.toLowerCase() === settings["🔍MagnifyGlass.PinPanelHotkey"].toLowerCase() && !e.repeat) { e.preventDefault(); e.stopPropagation(); this.handleControlAction('pin-at-mouse'); @@ -1754,6 +2072,14 @@ app.registerExtension({ if (!isVisible && !this.stateManager.state.isPanelPinned) { this.captureCurrentPanelPosition(); // Capture current position before pinning this.stateManager.togglePinning(); + this.stateManager.state.isAutoPinned = true; // Mark as auto-pinned + this.uiManager.updatePinnedState(); + } + + // When the glass preview is shown again, only auto-unlock if NOT manually locked with 📌 button + if (isVisible && this.stateManager.state.isPanelPinned && !this.stateManager.state.isPanelLocked) { + this.stateManager.togglePinning(); // Unpin the panel + this.stateManager.state.isAutoPinned = false; // Clear auto-pin flag this.uiManager.updatePinnedState(); } @@ -2170,10 +2496,8 @@ app.registerExtension({ updateSettings() { const changes = this.stateManager.updateSettings(); - // React to setting changes - if (changes["🔍MagnifyGlass.InfoPanelTheme"]) { - this.uiManager.updateTheme(changes["🔍MagnifyGlass.InfoPanelTheme"].new); - } + // React to setting changes (theme is now auto-detected) + // Theme changes are handled automatically by the observer if (changes["🔍MagnifyGlass.ControlsPosition"]) { // Update controls layout and position when position setting changes @@ -2199,12 +2523,15 @@ app.registerExtension({ // Create the main manager instance const infoPanelManager = new ProfessionalInfoPanelManager(magnifyGlass); - // Store reference globally for cleanup + // Store reference globally for cleanup and theme updates if (!window.comfyUIMagnifyGlassExtensions) { window.comfyUIMagnifyGlassExtensions = []; } window.comfyUIMagnifyGlassExtensions.push(infoPanelManager); + // Store main manager reference for theme updates + window.infoPanelManager = infoPanelManager; + // Register settings with ComfyUI Object.keys(DEFAULT_SETTINGS).forEach(settingKey => { const settingConfig = getSettingConfig(settingKey, DEFAULT_SETTINGS[settingKey]); @@ -2256,13 +2583,20 @@ app.registerExtension({ }, "🔍MagnifyGlass.InfoPanelOpacity": { id: key, - name: "📋 Magnify Glass: Info Panel Opacity", + name: "📋 Magnify Glass: Info Panel Opacity (%)", type: "slider", defaultValue, - min: 0.7, - max: 1.0, - step: 0.05, - tooltip: "Opacity of the information panel background." + min: 10, + max: 100, + step: 5, + tooltip: "Opacity of the information panel background as a percentage (10 = very transparent, 100 = fully opaque).", + onChange: (value) => { + // Convert percentage to decimal and apply opacity immediately + const opacity = value / 100; + if (window.infoPanelManager && window.infoPanelManager.uiManager && window.infoPanelManager.uiManager.elements.panel) { + window.infoPanelManager.uiManager.elements.panel.style.opacity = opacity; + } + } }, "🔍MagnifyGlass.InfoPanelMaxHeight": { id: key, @@ -2274,17 +2608,7 @@ app.registerExtension({ step: 25, tooltip: "Maximum height of the information panel in pixels." }, - "🔍MagnifyGlass.InfoPanelTheme": { - id: key, - name: "🎨 Magnify Glass: Info Panel Theme", - type: "combo", - options: [ - { value: "Dark", text: "Dark" }, - { value: "Light", text: "Light" } - ], - defaultValue, - tooltip: "Color theme for the information panel." - }, + "🔍MagnifyGlass.InfoPanelAnimations": { id: key, name: "🎬 Magnify Glass: Info Panel Animations", @@ -2325,11 +2649,11 @@ app.registerExtension({ }, "🔍MagnifyGlass.PinPanelHotkey": { id: key, - name: "📌 Magnify Glass: Pin Panel Hotkey", + name: "🔓 Magnify Glass: Unlock Panel to Mouse Location", type: "combo", - options: ["p", "f", "x", "z", "h"], + options: ["u", "p", "f", "x", "z"], defaultValue, - tooltip: "Key to use with Alt to pin the info panel at mouse location (e.g., Alt+P)." + tooltip: "Key to unlock the info panel from the magnify glass and move it to your current mouse position (e.g., U). Panel remains movable - use the pin button to lock it in place. Alt not required due to Smart Input Detection." }, "🔍MagnifyGlass.ShowHoveringControls": { id: key, @@ -2358,6 +2682,56 @@ app.registerExtension({ ], defaultValue, tooltip: "Position of the floating control buttons relative to the info panel." + }, + "🔍MagnifyGlass.InfoPanelTextColor": { + id: key, + name: "🎨 Magnify Glass: Info Panel Text Color", + type: "color", + defaultValue, + tooltip: "Color of the text in the info panel. This affects all text content including titles, values, and labels.", + onChange: (value) => { + // Ensure color value includes # symbol for valid CSS and persistence + const normalizedColor = value && !value.startsWith('#') ? `#${value}` : value; + + // Only save back if we actually normalized the color (to prevent recursion) + if (value !== normalizedColor) { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelTextColor", normalizedColor); + } catch (e) { + console.warn('Failed to save normalized text color:', e); + } + } + + // Apply the color immediately + if (window.infoPanelManager && window.infoPanelManager.uiManager) { + window.infoPanelManager.uiManager.applyStyles(); + } + } + }, + "🔍MagnifyGlass.InfoPanelAccentColor": { + id: key, + name: "🎨 Magnify Glass: Info Panel Accent Color", + type: "color", + defaultValue, + tooltip: "Color used for highlighted elements like values, section titles, and active states in the info panel.", + onChange: (value) => { + // Ensure color value includes # symbol for valid CSS and persistence + const normalizedColor = value && !value.startsWith('#') ? `#${value}` : value; + + // Only save back if we actually normalized the color (to prevent recursion) + if (value !== normalizedColor) { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelAccentColor", normalizedColor); + } catch (e) { + console.warn('Failed to save normalized accent color:', e); + } + } + + // Apply the color immediately + if (window.infoPanelManager && window.infoPanelManager.uiManager) { + window.infoPanelManager.uiManager.applyStyles(); + } + } } };