diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index d4c32b4ea..24f465271 100644 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/lod.spec.ts b/browser_tests/tests/vueNodes/lod.spec.ts new file mode 100644 index 000000000..9011f91b1 --- /dev/null +++ b/browser_tests/tests/vueNodes/lod.spec.ts @@ -0,0 +1,44 @@ +import { expect } from '@playwright/test' + +import { comfyPageFixture as test } from '../../fixtures/ComfyPage' + +test.describe('Vue Nodes - LOD', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) + await comfyPage.setup() + await comfyPage.loadWorkflow('default') + }) + + test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => { + await comfyPage.vueNodes.waitForNodes() + + const initialNodeCount = await comfyPage.vueNodes.getNodeCount() + expect(initialNodeCount).toBeGreaterThan(0) + + await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png') + + const vueNodesContainer = comfyPage.vueNodes.nodes + const textboxesInNodes = vueNodesContainer.getByRole('textbox') + const buttonsInNodes = vueNodesContainer.getByRole('button') + + await expect(textboxesInNodes.first()).toBeVisible() + await expect(buttonsInNodes.first()).toBeVisible() + + await comfyPage.zoom(120, 10) + await comfyPage.nextFrame() + + await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png') + + await expect(textboxesInNodes.first()).toBeHidden() + await expect(buttonsInNodes.first()).toBeHidden() + + await comfyPage.zoom(-120, 10) + await comfyPage.nextFrame() + + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-nodes-lod-inactive.png' + ) + await expect(textboxesInNodes.first()).toBeVisible() + await expect(buttonsInNodes.first()).toBeVisible() + }) +}) diff --git a/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png new file mode 100644 index 000000000..8e93b88f3 Binary files /dev/null and b/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png new file mode 100644 index 000000000..5dfa61c19 Binary files /dev/null and b/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png new file mode 100644 index 000000000..59802088f Binary files /dev/null and b/browser_tests/tests/vueNodes/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/src/assets/css/style.css b/src/assets/css/style.css index cad8a1b3b..21d526bac 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -929,48 +929,6 @@ audio.comfy-audio.empty-audio-widget { } /* End of [Desktop] Electron window specific styles */ -/* Vue Node LOD (Level of Detail) System */ -/* These classes control rendering detail based on zoom level */ - -/* Minimal LOD (zoom <= 0.4) - Title only for performance */ -.lg-node--lod-minimal { - min-height: 32px; - transition: min-height 0.2s ease; - /* Performance optimizations */ - text-shadow: none; - backdrop-filter: none; -} - -.lg-node--lod-minimal .lg-node-body { - display: none !important; -} - -/* Reduced LOD (0.4 < zoom <= 0.8) - Essential widgets, simplified styling */ -.lg-node--lod-reduced { - transition: opacity 0.1s ease; - /* Performance optimizations */ - text-shadow: none; -} - -.lg-node--lod-reduced .lg-widget-label, -.lg-node--lod-reduced .lg-slot-label { - display: none; -} - -.lg-node--lod-reduced .lg-slot { - opacity: 0.6; - font-size: 0.75rem; -} - -.lg-node--lod-reduced .lg-widget { - margin: 2px 0; - font-size: 0.875rem; -} - -/* Full LOD (zoom > 0.8) - Complete detail rendering */ -.lg-node--lod-full { - /* Uses default styling - no overrides needed */ -} .lg-node { /* Disable text selection on all nodes */ @@ -996,23 +954,52 @@ audio.comfy-audio.empty-audio-widget { will-change: transform; } -/* Global performance optimizations for LOD */ -.lg-node--lod-minimal, -.lg-node--lod-reduced { - /* Remove ALL expensive paint effects */ - box-shadow: none !important; - filter: none !important; - backdrop-filter: none !important; - text-shadow: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - clip-path: none !important; +/* START LOD specific styles */ +/* LOD styles - Custom CSS avoids 100+ Tailwind selectors that would slow style recalculation when .isLOD toggles */ + +.isLOD .lg-node { + box-shadow: none; + filter: none; + backdrop-filter: none; + text-shadow: none; + -webkit-mask-image: none; + mask-image: none; + clip-path: none; + background-image: none; + text-rendering: optimizeSpeed; + border-radius: 0; + contain: layout style; + transition: none; + } -/* Reduce paint complexity for minimal LOD */ -.lg-node--lod-minimal { - /* Skip complex borders */ - border-radius: 0 !important; - /* Use solid colors only */ - background-image: none !important; +.isLOD .lg-node > * { + pointer-events: none; } + +.lod-toggle { + visibility: visible; +} + +.isLOD .lod-toggle { + visibility: hidden; +} + + +.lod-fallback { + display: none; +} + +.isLOD .lod-fallback { + display: block; +} + +.isLOD .image-preview img { + image-rendering: pixelated; +} + + +.isLOD .slot-dot { + border-radius: 0; +} +/* END LOD specific styles */ diff --git a/src/renderer/core/layout/transform/TransformPane.vue b/src/renderer/core/layout/transform/TransformPane.vue index 29abc1262..43cc0e328 100644 --- a/src/renderer/core/layout/transform/TransformPane.vue +++ b/src/renderer/core/layout/transform/TransformPane.vue @@ -1,7 +1,12 @@ @@ -119,6 +122,8 @@ import { downloadFile } from '@/base/common/downloadUtil' import { useCommandStore } from '@/stores/commandStore' import { useNodeOutputStore } from '@/stores/imagePreviewStore' +import LODFallback from './LODFallback.vue' + interface ImagePreviewProps { /** Array of image URLs to display */ readonly imageUrls: readonly string[] diff --git a/src/renderer/extensions/vueNodes/components/InputSlot.vue b/src/renderer/extensions/vueNodes/components/InputSlot.vue index ef38c0754..1e8387335 100644 --- a/src/renderer/extensions/vueNodes/components/InputSlot.vue +++ b/src/renderer/extensions/vueNodes/components/InputSlot.vue @@ -10,12 +10,15 @@ /> - - {{ slotData.localized_name || slotData.name || `Input ${index}` }} - +
+ + {{ slotData.localized_name || slotData.name || `Input ${index}` }} + + +
@@ -38,6 +41,7 @@ import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composabl import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction' import { cn } from '@/utils/tailwindUtil' +import LODFallback from './LODFallback.vue' import SlotConnectionDot from './SlotConnectionDot.vue' interface InputSlotProps { diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index f764a82bf..08ff30d3b 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -23,7 +23,7 @@ bypassed, 'will-change-transform': isDragging }, - lodCssClass, + shouldHandleNodePointerEvents ? 'pointer-events-auto' : 'pointer-events-none' @@ -48,10 +48,9 @@
-