feat: Implement hybrid rendering for magnify glass with direct capture and virtual zoom.

This commit is contained in:
AEmotionStudio
2026-01-04 23:25:00 -08:00
parent 27b6e5c4aa
commit 5b05cd973d
13 changed files with 56 additions and 11 deletions
+10 -3
View File
@@ -46,17 +46,17 @@ This release brings a complete overhaul to the rendering engine for **native fid
* **WebGL-Powered**: Smooth, high-performance rendering at any zoom level.
* **Smart Interactions**: Follows your cursor or stays fixed. Toggles transparently for click-through.
* **Customizable**: Adjust zoom (up to 10x), size, border, and shape (Circle/Square/Rounded).
[HERE]
### ℹ️ Inspector Panel
* **Deep Analysis**: View node parameters (Seed, CFG, Steps), text content, and image details on hover.
* **Dockable Interface**: Pin the panel to keep it stable, or let it follow the glass.
* **Themed**: Automatically matches any ComfyUI theme (Dark, Light, Solarized, Arc, Nord, GitHub).
[HERE]
### ⚙️ Sidebar Integration
* **Organized Settings**: All Magnify Glass and Info Panel settings in one place.
* **Live Previews**: Changes apply instantly without needing to refresh.
* **Reset Options**: Quickly restore defaults with individual or global reset buttons.
[HERE]x
### 🖥️ Multi-Monitor Pop-Out
* **Detachable Viewer**: Open the magnified view in a separate browser tab.
* **Inspector Sidebar**: Real-time node details, cursor position, and canvas scale.
@@ -90,6 +90,7 @@ git clone https://github.com/AEmotionStudio/ComfyUI-MagnifyGlass.git
| **`Shift+P`** | Open Pop-Out Viewer in New Tab |
| **`U`** | Pin/Unpin Inspector Panel |
| **`O`** | Reset Offsets |
| **`D`** | Force Direct Capture (accurate cursor at low zoom) |
| **Arrows** | Nudge Glass Position |
> **Pro Tip:** Toggle the **Glass Preview (`G`)** off to keep the Inspector Panel active but hide the magnifying preview.
@@ -124,6 +125,12 @@ This project is licensed under the [GPL-3.0](LICENSE) License - see the [LICENSE
---
## 🐛 Known Issues
* **Virtual Zoom Cursor Alignment**: When using "Virtual Zoom" (canvas zoom < 100%), the mini cursor preview in the glass may not perfectly align with the actual content due to a fundamental drift in the coordinate capture system. **Workaround**: Press `D` to toggle Direct Capture mode for accurate cursor alignment (at the cost of lower magnification quality).
---
## 🤝 Contributing
Contributions are welcome! Please submit a Pull Request or open an Issue on GitHub.
+12 -1
View File
@@ -1,4 +1,4 @@
/**
x/**
* ComfyUI MagnifyGlass - ConfigManager (TypeScript)
*
* Manages configuration and settings for the magnifying glass.
@@ -26,6 +26,8 @@ const DEFAULT_GLASS_SETTINGS = {
"🔍MagnifyGlass.AlwaysActiveMode": true,
"🔍MagnifyGlass.ToggleFollowCursorKey": "h",
"🔍MagnifyGlass.GlassPreviewToggleHotkey": "g",
"🔍MagnifyGlass.ShowCursorPreview": true,
"🔍MagnifyGlass.ForceDirectCaptureKey": "d",
};
/**
@@ -86,6 +88,12 @@ export class ConfigManager {
/** Show cursor preview in glass */
showCursorPreview: boolean;
/** Force Direct Capture key */
forceDirectCaptureKey: string;
/** Runtime flag: Force Direct Capture mode (not persisted) */
forceDirectCapture: boolean;
/** Manual offset X in graph units */
offsetX: number;
@@ -112,6 +120,8 @@ export class ConfigManager {
this.toggleFollowCursorKey = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.ToggleFollowCursorKey"];
this.toggleGlassPreviewKey = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.GlassPreviewToggleHotkey"];
this.showCursorPreview = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.ShowCursorPreview"];
this.forceDirectCaptureKey = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.ForceDirectCaptureKey"];
this.forceDirectCapture = false; // Runtime flag, not persisted
// Alignment adjustment parameters - managed separately
this.offsetX = 0;
@@ -139,6 +149,7 @@ export class ConfigManager {
this.alwaysActiveMode = getSettingValue<boolean>("🔍MagnifyGlass.AlwaysActiveMode", this.alwaysActiveMode);
this.toggleFollowCursorKey = getSettingValue<string>("🔍MagnifyGlass.ToggleFollowCursorKey", this.toggleFollowCursorKey);
this.showCursorPreview = getSettingValue<boolean>("🔍MagnifyGlass.ShowCursorPreview", this.showCursorPreview);
this.forceDirectCaptureKey = getSettingValue<string>("🔍MagnifyGlass.ForceDirectCaptureKey", this.forceDirectCaptureKey);
this.toggleGlassPreviewKey = getSettingValue<string>("🔍MagnifyGlass.GlassPreviewToggleHotkey", this.toggleGlassPreviewKey);
}
+9
View File
@@ -153,6 +153,15 @@ export class EventHandler {
this.magnifyGlass.updateMagnifiedView();
}
// Force Direct Capture Toggle Key
if (e.key.toLowerCase() === config.forceDirectCaptureKey &&
(!config.altRequired || e.altKey)) {
config.forceDirectCapture = !config.forceDirectCapture;
this.magnifyGlass.debugger.log(`Force Direct Capture: ${config.forceDirectCapture ? 'ON' : 'OFF'}`);
this.magnifyGlass.updateMagnifiedView();
e.preventDefault();
}
// Pop-out tab toggle (Shift+P)
if (e.shiftKey && e.key.toLowerCase() === 'p') {
this.magnifyGlass.popOutManager.toggle();
+2 -1
View File
@@ -63,7 +63,8 @@ export class OffscreenRenderer {
// and it guarantees perfect alignment with zero extra draws.
// If we are zoomed out (< 100%), we use High Res Virtual Zoom for crisp details.
// Note: Image preview nodes are handled by drawImagePreviewsNatively() during Virtual Zoom.
const useDirectCapture = currentScale >= 1.0;
// If forceDirectCapture is enabled (via hotkey), always use Direct Capture for accurate cursor alignment.
const useDirectCapture = currentScale >= 1.0 || this.config.forceDirectCapture;
if (useDirectCapture) {
// Direct Capture: Full speed, no extra draws
+1
View File
@@ -36,6 +36,7 @@ export const DEFAULT_GLASS_SETTINGS: GlassSettings = {
"🔍MagnifyGlass.AlwaysActiveMode": true,
"🔍MagnifyGlass.ToggleFollowCursorKey": "h",
"🔍MagnifyGlass.ShowCursorPreview": true,
"🔍MagnifyGlass.ForceDirectCaptureKey": "d",
};
/**
+1
View File
@@ -24,6 +24,7 @@ export interface GlassSettings {
"🔍MagnifyGlass.AlwaysActiveMode": boolean;
"🔍MagnifyGlass.ToggleFollowCursorKey": string;
"🔍MagnifyGlass.ShowCursorPreview": boolean;
"🔍MagnifyGlass.ForceDirectCaptureKey": string;
}
/**
+10 -1
View File
@@ -19,7 +19,9 @@ const DEFAULT_GLASS_SETTINGS = {
"🔍MagnifyGlass.TextureFiltering": "Linear",
"🔍MagnifyGlass.AlwaysActiveMode": true,
"🔍MagnifyGlass.ToggleFollowCursorKey": "h",
"🔍MagnifyGlass.GlassPreviewToggleHotkey": "g"
"🔍MagnifyGlass.GlassPreviewToggleHotkey": "g",
"🔍MagnifyGlass.ShowCursorPreview": true,
"🔍MagnifyGlass.ForceDirectCaptureKey": "d"
};
class ConfigManager {
constructor() {
@@ -57,6 +59,10 @@ class ConfigManager {
__publicField(this, "toggleGlassPreviewKey");
/** Show cursor preview in glass */
__publicField(this, "showCursorPreview");
/** Force Direct Capture key */
__publicField(this, "forceDirectCaptureKey");
/** Runtime flag: Force Direct Capture mode (not persisted) */
__publicField(this, "forceDirectCapture");
/** Manual offset X in graph units */
__publicField(this, "offsetX");
/** Manual offset Y in graph units */
@@ -78,6 +84,8 @@ class ConfigManager {
this.toggleFollowCursorKey = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.ToggleFollowCursorKey"];
this.toggleGlassPreviewKey = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.GlassPreviewToggleHotkey"];
this.showCursorPreview = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.ShowCursorPreview"];
this.forceDirectCaptureKey = DEFAULT_GLASS_SETTINGS["🔍MagnifyGlass.ForceDirectCaptureKey"];
this.forceDirectCapture = false;
this.offsetX = 0;
this.offsetY = 0;
}
@@ -101,6 +109,7 @@ class ConfigManager {
this.alwaysActiveMode = getSettingValue("🔍MagnifyGlass.AlwaysActiveMode", this.alwaysActiveMode);
this.toggleFollowCursorKey = getSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", this.toggleFollowCursorKey);
this.showCursorPreview = getSettingValue("🔍MagnifyGlass.ShowCursorPreview", this.showCursorPreview);
this.forceDirectCaptureKey = getSettingValue("🔍MagnifyGlass.ForceDirectCaptureKey", this.forceDirectCaptureKey);
this.toggleGlassPreviewKey = getSettingValue("🔍MagnifyGlass.GlassPreviewToggleHotkey", this.toggleGlassPreviewKey);
}
/**
File diff suppressed because one or more lines are too long
+6
View File
@@ -117,6 +117,12 @@ class EventHandler {
config.saveOffsets();
this.magnifyGlass.updateMagnifiedView();
}
if (e.key.toLowerCase() === config.forceDirectCaptureKey && (!config.altRequired || e.altKey)) {
config.forceDirectCapture = !config.forceDirectCapture;
this.magnifyGlass.debugger.log(`Force Direct Capture: ${config.forceDirectCapture ? "ON" : "OFF"}`);
this.magnifyGlass.updateMagnifiedView();
e.preventDefault();
}
if (e.shiftKey && e.key.toLowerCase() === "p") {
this.magnifyGlass.popOutManager.toggle();
e.preventDefault();
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -35,7 +35,7 @@ const _OffscreenRenderer = class _OffscreenRenderer {
const currentScale = lgCanvas.ds.scale;
const rect = targetCanvas.getBoundingClientRect();
const dpr = rect.width > 0 ? targetCanvas.width / rect.width : 1;
const useDirectCapture = currentScale >= 1;
const useDirectCapture = currentScale >= 1 || this.config.forceDirectCapture;
if (useDirectCapture) {
this.cachedVirtualZoomResult = null;
return this.renderDirectCapture(targetCanvas, renderSize, dpr);
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"defaults.js","sources":["../../../src/shared/settings/defaults.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Default Settings Values\n * \n * Default values for all magnify glass and info panel settings.\n */\n\nimport {\n DEFAULT_GLASS_SIZE,\n DEFAULT_ZOOM_FACTOR,\n DEFAULT_BORDER_WIDTH,\n DEFAULT_BORDER_COLOR,\n DEFAULT_OFFSET_STEP,\n DEFAULT_PANEL_WIDTH,\n DEFAULT_PANEL_MAX_HEIGHT,\n DEFAULT_PANEL_OPACITY\n} from '../constants';\nimport type { GlassSettings, PanelSettings } from './types';\n\n/**\n * Default settings for the magnify glass component.\n */\nexport const DEFAULT_GLASS_SETTINGS: GlassSettings = {\n \"🔍MagnifyGlass.ZoomFactor\": DEFAULT_ZOOM_FACTOR,\n \"🔍MagnifyGlass.GlassSize\": DEFAULT_GLASS_SIZE,\n \"🔍MagnifyGlass.BorderColor\": DEFAULT_BORDER_COLOR,\n \"🔍MagnifyGlass.BorderWidth\": DEFAULT_BORDER_WIDTH,\n \"🔍MagnifyGlass.ActivationKey\": \"x\",\n \"🔍MagnifyGlass.AltRequired\": false,\n \"🔍MagnifyGlass.FollowCursor\": false,\n \"🔍MagnifyGlass.OffsetStep\": DEFAULT_OFFSET_STEP,\n \"🔍MagnifyGlass.GlassPosition\": \"Top-Right\",\n \"🔍MagnifyGlass.ResetKey\": \"o\",\n \"🔍MagnifyGlass.GlassShape\": \"Rounded Square\",\n \"🔍MagnifyGlass.BorderEnabled\": true,\n \"🔍MagnifyGlass.TextureFiltering\": \"Linear\",\n \"🔍MagnifyGlass.AlwaysActiveMode\": true,\n \"🔍MagnifyGlass.ToggleFollowCursorKey\": \"h\",\n \"🔍MagnifyGlass.ShowCursorPreview\": true,\n};\n\n/**\n * Default settings for the info panel component.\n */\nexport const DEFAULT_PANEL_SETTINGS: PanelSettings = {\n \"🔍MagnifyGlass.InfoPanelEnabled\": true,\n \"🔍MagnifyGlass.InfoPanelPosition\": \"Bottom\",\n \"🔍MagnifyGlass.InfoPanelWidth\": DEFAULT_PANEL_WIDTH,\n \"🔍MagnifyGlass.InfoPanelOpacity\": DEFAULT_PANEL_OPACITY,\n \"🔍MagnifyGlass.InfoPanelMaxHeight\": DEFAULT_PANEL_MAX_HEIGHT,\n \"🔍MagnifyGlass.InfoPanelAnimations\": false,\n \"🔍MagnifyGlass.ShowInspectorTab\": false,\n \"🔍MagnifyGlass.ToggleHotkey\": \"i\",\n \"🔍MagnifyGlass.GlassPreviewToggleHotkey\": \"g\",\n \"🔍MagnifyGlass.PinPanelHotkey\": \"u\",\n \"🔍MagnifyGlass.ShowHoveringControls\": true,\n \"🔍MagnifyGlass.ControlsPosition\": \"left\",\n \"🔍MagnifyGlass.InfoPanelTextColor\": \"#6b7280\",\n \"🔍MagnifyGlass.InfoPanelAccentColor\": \"#3b82f6\",\n \"🔍MagnifyGlass.InfoPanelFontSize\": 14,\n \"🔍MagnifyGlass.InfoPanelFontFamily\": \"System Default\",\n \"🔍MagnifyGlass.InfoPanelPersist\": false,\n};\n"],"names":[],"mappings":";AAqBO,MAAM,yBAAwC;AAAA,EACjD,6BAA6B;AAAA,EAC7B,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA,EAC9B,8BAA8B;AAAA,EAC9B,gCAAgC;AAAA,EAChC,8BAA8B;AAAA,EAC9B,+BAA+B;AAAA,EAC/B,6BAA6B;AAAA,EAC7B,gCAAgC;AAAA,EAChC,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,gCAAgC;AAAA,EAChC,mCAAmC;AAAA,EACnC,mCAAmC;AAAA,EACnC,wCAAwC;AAAA,EACxC,oCAAoC;AACxC;AAKO,MAAM,yBAAwC;AAAA,EACjD,mCAAmC;AAAA,EACnC,oCAAoC;AAAA,EACpC,iCAAiC;AAAA,EACjC,mCAAmC;AAAA,EACnC,qCAAqC;AAAA,EACrC,sCAAsC;AAAA,EACtC,mCAAmC;AAAA,EACnC,+BAA+B;AAAA,EAC/B,2CAA2C;AAAA,EAC3C,iCAAiC;AAAA,EACjC,uCAAuC;AAAA,EACvC,mCAAmC;AAAA,EACnC,qCAAqC;AAAA,EACrC,uCAAuC;AAAA,EACvC,oCAAoC;AAAA,EACpC,sCAAsC;AAAA,EACtC,mCAAmC;AACvC;"}
{"version":3,"file":"defaults.js","sources":["../../../src/shared/settings/defaults.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Default Settings Values\n * \n * Default values for all magnify glass and info panel settings.\n */\n\nimport {\n DEFAULT_GLASS_SIZE,\n DEFAULT_ZOOM_FACTOR,\n DEFAULT_BORDER_WIDTH,\n DEFAULT_BORDER_COLOR,\n DEFAULT_OFFSET_STEP,\n DEFAULT_PANEL_WIDTH,\n DEFAULT_PANEL_MAX_HEIGHT,\n DEFAULT_PANEL_OPACITY\n} from '../constants';\nimport type { GlassSettings, PanelSettings } from './types';\n\n/**\n * Default settings for the magnify glass component.\n */\nexport const DEFAULT_GLASS_SETTINGS: GlassSettings = {\n \"🔍MagnifyGlass.ZoomFactor\": DEFAULT_ZOOM_FACTOR,\n \"🔍MagnifyGlass.GlassSize\": DEFAULT_GLASS_SIZE,\n \"🔍MagnifyGlass.BorderColor\": DEFAULT_BORDER_COLOR,\n \"🔍MagnifyGlass.BorderWidth\": DEFAULT_BORDER_WIDTH,\n \"🔍MagnifyGlass.ActivationKey\": \"x\",\n \"🔍MagnifyGlass.AltRequired\": false,\n \"🔍MagnifyGlass.FollowCursor\": false,\n \"🔍MagnifyGlass.OffsetStep\": DEFAULT_OFFSET_STEP,\n \"🔍MagnifyGlass.GlassPosition\": \"Top-Right\",\n \"🔍MagnifyGlass.ResetKey\": \"o\",\n \"🔍MagnifyGlass.GlassShape\": \"Rounded Square\",\n \"🔍MagnifyGlass.BorderEnabled\": true,\n \"🔍MagnifyGlass.TextureFiltering\": \"Linear\",\n \"🔍MagnifyGlass.AlwaysActiveMode\": true,\n \"🔍MagnifyGlass.ToggleFollowCursorKey\": \"h\",\n \"🔍MagnifyGlass.ShowCursorPreview\": true,\n \"🔍MagnifyGlass.ForceDirectCaptureKey\": \"d\",\n};\n\n/**\n * Default settings for the info panel component.\n */\nexport const DEFAULT_PANEL_SETTINGS: PanelSettings = {\n \"🔍MagnifyGlass.InfoPanelEnabled\": true,\n \"🔍MagnifyGlass.InfoPanelPosition\": \"Bottom\",\n \"🔍MagnifyGlass.InfoPanelWidth\": DEFAULT_PANEL_WIDTH,\n \"🔍MagnifyGlass.InfoPanelOpacity\": DEFAULT_PANEL_OPACITY,\n \"🔍MagnifyGlass.InfoPanelMaxHeight\": DEFAULT_PANEL_MAX_HEIGHT,\n \"🔍MagnifyGlass.InfoPanelAnimations\": false,\n \"🔍MagnifyGlass.ShowInspectorTab\": false,\n \"🔍MagnifyGlass.ToggleHotkey\": \"i\",\n \"🔍MagnifyGlass.GlassPreviewToggleHotkey\": \"g\",\n \"🔍MagnifyGlass.PinPanelHotkey\": \"u\",\n \"🔍MagnifyGlass.ShowHoveringControls\": true,\n \"🔍MagnifyGlass.ControlsPosition\": \"left\",\n \"🔍MagnifyGlass.InfoPanelTextColor\": \"#6b7280\",\n \"🔍MagnifyGlass.InfoPanelAccentColor\": \"#3b82f6\",\n \"🔍MagnifyGlass.InfoPanelFontSize\": 14,\n \"🔍MagnifyGlass.InfoPanelFontFamily\": \"System Default\",\n \"🔍MagnifyGlass.InfoPanelPersist\": false,\n};\n"],"names":[],"mappings":";AAqBO,MAAM,yBAAwC;AAAA,EACjD,6BAA6B;AAAA,EAC7B,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA,EAC9B,8BAA8B;AAAA,EAC9B,gCAAgC;AAAA,EAChC,8BAA8B;AAAA,EAC9B,+BAA+B;AAAA,EAC/B,6BAA6B;AAAA,EAC7B,gCAAgC;AAAA,EAChC,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,gCAAgC;AAAA,EAChC,mCAAmC;AAAA,EACnC,mCAAmC;AAAA,EACnC,wCAAwC;AAAA,EACxC,oCAAoC;AAExC;AAKO,MAAM,yBAAwC;AAAA,EACjD,mCAAmC;AAAA,EACnC,oCAAoC;AAAA,EACpC,iCAAiC;AAAA,EACjC,mCAAmC;AAAA,EACnC,qCAAqC;AAAA,EACrC,sCAAsC;AAAA,EACtC,mCAAmC;AAAA,EACnC,+BAA+B;AAAA,EAC/B,2CAA2C;AAAA,EAC3C,iCAAiC;AAAA,EACjC,uCAAuC;AAAA,EACvC,mCAAmC;AAAA,EACnC,qCAAqC;AAAA,EACrC,uCAAuC;AAAA,EACvC,oCAAoC;AAAA,EACpC,sCAAsC;AAAA,EACtC,mCAAmC;AACvC;"}