From c7ff755fdef7b4863d7c4224adb0ffa7885a269e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86motion?= <163354043+AEmotionStudio@users.noreply.github.com> Date: Thu, 15 May 2025 05:28:23 -0700 Subject: [PATCH] Added a toggle follow key to the magnifying glass. --- README.md | 11 ++-- web/magnify_glass.js | 135 ++++++++++++++++++++++++++++--------------- 2 files changed, 97 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index d9736a0..d3975df 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ ComfyUI-MagnifyGlass adds an intuitive magnifying glass to your ComfyUI canvas, - **⚙️ Settings Integration**: All options are configurable through the ComfyUI settings dialog. - **💾 Persistent Offsets**: Manually adjusted offsets are saved and loaded across sessions. - **🖼️ Texture Filtering**: Choose between Linear (smooth) and Nearest (pixelated) texture filtering for the magnified view. +- **🔒 Always Active Mode**: Toggle the magnifier to stay on/off without holding keys. ![Magnify Glass Settings PNG](images/magnify_glass_settings.png) @@ -60,7 +61,7 @@ Restart ComfyUI after installation. No additional `pip install` steps are typica ## 🚀 Usage 1. **Activation**: - - By default, press and hold `Alt + X` to activate the magnifying glass. + - By default, press and hold `Alt + X` to activate the magnifying glass. If "Always Active Mode" is enabled, this combination toggles the magnifier on or off. - The activation key and whether `Alt/Option` is required can be changed in the settings. 2. **Using the Magnifier**: - While active, move your mouse over the ComfyUI canvas to see the magnified view. @@ -68,7 +69,8 @@ Restart ComfyUI after installation. No additional `pip install` steps are typica 3. **Controls (while active)**: - **Arrow Keys**: Nudge the magnified view (adjusts `offsetX`/`offsetY`). - **Shift + Arrow Keys**: Nudge the magnified view by a larger step. - - **Reset Key (default 'R')**: Resets `offsetX` and `offsetY` to zero. (Configurable, may require Alt/Option). + - **Reset Key (default 'R')**: Resets `offsetX` and `offsetY` to zero. (Configurable, respects the global "Require Alt/Option Key" setting). + - **Toggle Follow Key (default 'H')**: Toggles the `Follow Cursor` behavior on/off. (Configurable, respects the global "Require Alt/Option Key" setting). 4. **Configuration**: - Access all settings by clicking the ⚙️ (Settings) icon in ComfyUI, then find the "🔍 Magnify Glass" section. @@ -86,16 +88,17 @@ All options are available in the ComfyUI settings dialog under the "🔍 Magnify | **Border Width (px)** | Width of the border around the magnifying glass. | `2` | | **Border Color** | Color of the border around the magnifying glass. | `#ffffff` | | **Activation Key** | The key (case-insensitive) to hold down to activate the magnifier. | `x` | -| **Require Alt/Option Key** | If Yes, Alt (Windows/Linux) or Option (Mac) must be held along with the activation key. | `Yes (true)` | +| **Require Alt/Option Key** | If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation and for reset. | `Yes (true)` | | **Follow Cursor Position** | If Yes, the magnifier window moves with the cursor. If No, it stays where activated. | `Yes (true)` | | **Offset Adjust Step (Graph Units)** | How many graph units the view shifts when pressing arrow keys (Shift+Arrow = 5x). | `5` | | **Reset Offset Key** | The key to press to reset the view offset while active. | `r` | -| **Require Alt/Option for Reset** | If Yes, Alt/Option must be held with the reset key. | `Yes (true)` | | **Debug Mode** | Show detailed logging and the debug visualization overlay. | `Disabled (false)`| | **Glass Position** | Position of the magnifying glass relative to the cursor. | `Bottom` | | **Shape** | Shape of the magnifying glass (Circle, Square, Rounded Square). | `Circle` | | **Show Border** | Enable or disable the border around the magnifying glass. | `Yes (true)` | | **Texture Filtering** | Controls how the magnified image is scaled. Linear is smoother, Nearest is sharper/pixelated. | `Linear` | +| **Always Active Mode** | If Yes, activating the magnifier keeps it on until activated again. If No, it deactivates on key release. | `No (false)` | +| **Toggle Follow Key** | The key to toggle 'Follow Cursor' behavior. Works with Alt/Option if 'Require Alt/Option Key' is Yes. | `h` | ## ❓ Troubleshooting diff --git a/web/magnify_glass.js b/web/magnify_glass.js index ea488e9..7abc41f 100644 --- a/web/magnify_glass.js +++ b/web/magnify_glass.js @@ -17,16 +17,17 @@ app.registerExtension({ "🔍MagnifyGlass.BorderColor": "#ffffff", "🔍MagnifyGlass.BorderWidth": 2, "🔍MagnifyGlass.ActivationKey": "x", - "🔍MagnifyGlass.AltRequired": true, + "🔍MagnifyGlass.AltRequired": false, "🔍MagnifyGlass.FollowCursor": true, "🔍MagnifyGlass.DebugMode": false, "🔍MagnifyGlass.OffsetStep": 5, "🔍MagnifyGlass.GlassPosition": "Bottom", - "🔍MagnifyGlass.ResetKey": "r", - "🔍MagnifyGlass.ResetAltRequired": true, - "🔍MagnifyGlass.GlassShape": "Circle", + "🔍MagnifyGlass.ResetKey": "o", + "🔍MagnifyGlass.GlassShape": "Rounded Square", "🔍MagnifyGlass.BorderEnabled": true, "🔍MagnifyGlass.TextureFiltering": "Linear", + "🔍MagnifyGlass.AlwaysActiveMode": false, + "🔍MagnifyGlass.ToggleFollowCursorKey": "h", }; // Function to safely get settings values @@ -355,10 +356,11 @@ app.registerExtension({ this.config.offsetStep = getSettingValue("🔍MagnifyGlass.OffsetStep", DEFAULT_SETTINGS["🔍MagnifyGlass.OffsetStep"]); this.config.glassPosition = getSettingValue("🔍MagnifyGlass.GlassPosition", DEFAULT_SETTINGS["🔍MagnifyGlass.GlassPosition"]); this.config.resetKey = getSettingValue("🔍MagnifyGlass.ResetKey", DEFAULT_SETTINGS["🔍MagnifyGlass.ResetKey"]); - this.config.resetAltRequired = getSettingValue("🔍MagnifyGlass.ResetAltRequired", DEFAULT_SETTINGS["🔍MagnifyGlass.ResetAltRequired"]); this.config.glassShape = getSettingValue("🔍MagnifyGlass.GlassShape", DEFAULT_SETTINGS["🔍MagnifyGlass.GlassShape"]); this.config.borderEnabled = getSettingValue("🔍MagnifyGlass.BorderEnabled", DEFAULT_SETTINGS["🔍MagnifyGlass.BorderEnabled"]); this.config.textureFiltering = getSettingValue("🔍MagnifyGlass.TextureFiltering", DEFAULT_SETTINGS["🔍MagnifyGlass.TextureFiltering"]); + this.config.alwaysActiveMode = getSettingValue("🔍MagnifyGlass.AlwaysActiveMode", DEFAULT_SETTINGS["🔍MagnifyGlass.AlwaysActiveMode"]); + this.config.toggleFollowCursorKey = getSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", DEFAULT_SETTINGS["🔍MagnifyGlass.ToggleFollowCursorKey"]); // Offsets X/Y are managed separately via load/save/arrow keys } @@ -432,10 +434,11 @@ app.registerExtension({ this.offsetStep = DEFAULT_SETTINGS["🔍MagnifyGlass.OffsetStep"]; this.glassPosition = DEFAULT_SETTINGS["🔍MagnifyGlass.GlassPosition"]; this.resetKey = DEFAULT_SETTINGS["🔍MagnifyGlass.ResetKey"]; - this.resetAltRequired = DEFAULT_SETTINGS["🔍MagnifyGlass.ResetAltRequired"]; this.glassShape = DEFAULT_SETTINGS["🔍MagnifyGlass.GlassShape"]; this.borderEnabled = DEFAULT_SETTINGS["🔍MagnifyGlass.BorderEnabled"]; this.textureFiltering = DEFAULT_SETTINGS["🔍MagnifyGlass.TextureFiltering"]; + this.alwaysActiveMode = DEFAULT_SETTINGS["🔍MagnifyGlass.AlwaysActiveMode"]; + this.toggleFollowCursorKey = DEFAULT_SETTINGS["🔍MagnifyGlass.ToggleFollowCursorKey"]; // Alignment adjustment parameters - managed separately this.offsetX = 0; // Default before loading saved @@ -455,10 +458,11 @@ app.registerExtension({ this.offsetStep = getSettingValue("🔍MagnifyGlass.OffsetStep", this.offsetStep); this.glassPosition = getSettingValue("🔍MagnifyGlass.GlassPosition", this.glassPosition); this.resetKey = getSettingValue("🔍MagnifyGlass.ResetKey", this.resetKey); - this.resetAltRequired = getSettingValue("🔍MagnifyGlass.ResetAltRequired", this.resetAltRequired); this.glassShape = getSettingValue("🔍MagnifyGlass.GlassShape", this.glassShape); this.borderEnabled = getSettingValue("🔍MagnifyGlass.BorderEnabled", this.borderEnabled); this.textureFiltering = getSettingValue("🔍MagnifyGlass.TextureFiltering", this.textureFiltering); + this.alwaysActiveMode = getSettingValue("🔍MagnifyGlass.AlwaysActiveMode", this.alwaysActiveMode); + this.toggleFollowCursorKey = getSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", this.toggleFollowCursorKey); } loadSavedOffsets() { @@ -933,12 +937,21 @@ app.registerExtension({ // Magnifier activation if (e.key.toLowerCase() === config.activationKey && (!config.altRequired || e.altKey)) { - if (!state.active) { - state.active = true; - this.magnifyGlass.ui.show(); - - // On activation, immediately update cursor position from current mouse position - this.updateInitialPosition(); + if (config.alwaysActiveMode) { + if (state.active) { + state.active = false; + this.magnifyGlass.ui.hide(); + } else { + state.active = true; + this.magnifyGlass.ui.show(); + this.updateInitialPosition(); // Update position when activating + } + } else { + if (!state.active) { + state.active = true; + this.magnifyGlass.ui.show(); + this.updateInitialPosition(); // Update position when activating + } } } @@ -967,7 +980,7 @@ app.registerExtension({ offsetChanged = true; e.preventDefault(); } else if (e.key.toLowerCase() === config.resetKey.toLowerCase() && - (!config.resetAltRequired || e.altKey)) { // Use configured reset key & check Alt + (!config.altRequired || e.altKey)) { // Use configured reset key & check Alt // Reset offsets to zero config.offsetX = 0; config.offsetY = 0; @@ -975,6 +988,19 @@ app.registerExtension({ e.preventDefault(); } + // Toggle Follow Cursor Key + if (e.key.toLowerCase() === config.toggleFollowCursorKey && + (!config.altRequired || e.altKey)) { + config.followCursor = !config.followCursor; + this.magnifyGlass.debugger.log(`Follow Cursor Toggled: ${config.followCursor ? 'ON' : 'OFF'}`); + if (state.active && config.followCursor) { + // If toggled ON and active, update glass position to current cursor + this.magnifyGlass.ui.positionGlass(this.magnifyGlass.lastKnownMousePosition.x, this.magnifyGlass.lastKnownMousePosition.y); + } + // We might also want to trigger an update of the settings UI if it shows this value directly, but that's more complex. + e.preventDefault(); + } + // If offset was changed, update the view if (offsetChanged) { // Save the changes to localStorage @@ -989,14 +1015,12 @@ app.registerExtension({ const state = this.magnifyGlass.state; // Check if the released key is the activation key OR if Alt was required and Alt was released. - if (e.key.toLowerCase() === config.activationKey || - (config.altRequired && e.key === "Alt")) { + if (!config.alwaysActiveMode && + (e.key.toLowerCase() === config.activationKey || + (config.altRequired && e.key === "Alt"))) { // Only deactivate if the *other* key required for activation is NOT still pressed. // This handles cases like: Alt+X, release X (deactivate), or Alt+X, release Alt (deactivate). - // But if Alt+X, release X, but Alt is still held for another purpose, it shouldn't deactivate if activationKey is still held (though less common). - // The primary goal is to ensure releasing *either* key (if both were used) deactivates. - let shouldDeactivate = false; if (config.altRequired) { // If Alt is required, releasing either Alt or the activation key deactivates. @@ -1056,21 +1080,24 @@ app.registerExtension({ updateInitialPosition() { if (!this.magnifyGlass.litegraphCanvas) return; - + const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect(); const clientX = this.magnifyGlass.lastKnownMousePosition.x; const clientY = this.magnifyGlass.lastKnownMousePosition.y; - - // Only use the position if it's over the canvas - if (clientX >= rect.left && clientX <= rect.right && - clientY >= rect.top && clientY <= rect.bottom) { - + + // If alwaysActiveMode is true, we want to set the initial position + // even if the cursor is not strictly over the canvas at the moment of activation. + // However, we still need a valid canvas context for calculations. + // The current logic in handleMouseMove which sets state.x/y only when over canvas remains crucial + // for continuous updates. This initial positioning is a one-shot for activation. + const isOverCanvas = clientX >= rect.left && clientX <= rect.right && + clientY >= rect.top && clientY <= rect.bottom; + + if (this.magnifyGlass.config.alwaysActiveMode || isOverCanvas) { const cssMouseXOnCanvas = clientX - rect.left; const cssMouseYOnCanvas = clientY - rect.top; - // Convert CSS coordinates on canvas to actual canvas pixel coordinates const canvasElement = this.magnifyGlass.litegraphCanvas; - // Ensure rect.width and rect.height are not zero to prevent division by zero const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1; const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1; @@ -1079,7 +1106,7 @@ app.registerExtension({ this.magnifyGlass.state.x = pixelX; this.magnifyGlass.state.y = pixelY; - + // Position the glass immediately based on current cursor this.magnifyGlass.ui.positionGlass(clientX, clientY); this.magnifyGlass.updateMagnifiedView(); @@ -1324,11 +1351,11 @@ 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 along with the activation key.", + tooltip: "If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation and for reset.", onChange: (value) => { if (magnifyGlass && magnifyGlass.config) { magnifyGlass.config.altRequired = value; - magnifyGlass.debugger.log(`Activation key set to ${magnifyGlass.config.altRequired ? 'Alt/Option+' : ''}${magnifyGlass.config.activationKey.toUpperCase()}`); + magnifyGlass.debugger.log(`Require Alt/Option set to ${value}. Activation: ${magnifyGlass.config.altRequired ? 'Alt/Option+' : ''}${magnifyGlass.config.activationKey.toUpperCase()}, Reset: ${magnifyGlass.config.altRequired ? 'Alt/Option+' : ''}${magnifyGlass.config.resetKey.toUpperCase()}`); } } }); @@ -1378,21 +1405,6 @@ app.registerExtension({ } }); - app.ui.settings.addSetting({ - id: "🔍MagnifyGlass.ResetAltRequired", - name: "⌨️ Magnify Glass: Require Alt/Option for Reset", - type: "combo", - options: [ { value: true, text: "Yes" }, { value: false, text: "No" } ], - defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.ResetAltRequired"], - tooltip: "If Yes, Alt (Windows/Linux) or Option (Mac) must be held along with the reset key.", - onChange: (value) => { - if (magnifyGlass && magnifyGlass.config) { - magnifyGlass.config.resetAltRequired = value; - magnifyGlass.debugger.log(`Require Alt/Option for Reset set to ${value}. Reset key combination is now ${magnifyGlass.config.resetAltRequired ? 'Alt/Option+' : ''}${magnifyGlass.config.resetKey.toUpperCase()}`); - } - } - }); - app.ui.settings.addSetting({ id: "🔍MagnifyGlass.DebugMode", name: "🐞 Magnify Glass: Debug Mode", @@ -1497,6 +1509,39 @@ app.registerExtension({ } }); + app.ui.settings.addSetting({ + id: "🔍MagnifyGlass.AlwaysActiveMode", + name: "🔒 Magnify Glass: Always Active Mode", + type: "combo", + options: [ + { value: true, text: "Yes" }, + { value: false, text: "No" } + ], + defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.AlwaysActiveMode"], + tooltip: "If Yes, activating the magnifier keeps it on until activated again. If No, it deactivates on key release.", + onChange: (value) => { + if (magnifyGlass && magnifyGlass.config) { + magnifyGlass.config.alwaysActiveMode = value; + // No immediate UI change needed other than behavior change + } + } + }); + + app.ui.settings.addSetting({ + id: "🔍MagnifyGlass.ToggleFollowCursorKey", + name: "⌨️ Magnify Glass: Toggle Follow Key", + type: "combo", + options: ["f", "g", "h", "j", "k"], // Example keys, can be expanded + defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.ToggleFollowCursorKey"], + tooltip: "The key (case-insensitive) to toggle the 'Follow Cursor' behavior. Works with Alt/Option if 'Require Alt/Option Key' is Yes.", + onChange: (value) => { + if (magnifyGlass && magnifyGlass.config) { + magnifyGlass.config.toggleFollowCursorKey = value.toLowerCase(); + magnifyGlass.debugger.log(`Toggle Follow Cursor key set to ${magnifyGlass.config.altRequired ? 'Alt/Option+' : ''}${magnifyGlass.config.toggleFollowCursorKey.toUpperCase()}`); + } + } + }); + // --- Initialization --- // Load settings into the config object *after* settings are registered