From af2db4366cd4b2bb35b7d1f2ed2111adccf4b6f0 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 2 Jul 2026 21:00:40 -0500 Subject: [PATCH] refactor(audit): de-duplicate the main.cpp backdrop draw + DPI transition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two render-loop de-duplications, zero behavior change (verified byte-equivalent on both the GL and DX11 backends via the mingw Windows cross-build): - drawWindowBackdrop(app, bgDL, p0, p1, backdrop_active, lowSpec): the ~80-line gradient/texture/acrylic-capture/WindowBg-alpha block was copy-pasted in the main loop (with low-spec guards) and the resize-watcher lambda (without them). Extracted once; the main loop calls it with lowSpec=isLowSpecMode() and the resize watcher with lowSpec=false — each reproducing its CURRENT behavior exactly (pure dedup, no change). The intentional differences around it (resize's immediate present(0) vs the main loop's vsync, the main loop's try/catch, the BeginFrame low-spec guard) are left untouched. - handleDisplayScaleChange(window, newScale, savedSizeForScale, lastKnownW/H, dpiTargetW/H, dpiResizeRetries, dpiResizePending): the SDL display-scale-change handler was duplicated ~verbatim in the idle-wait and poll event paths (differing only in debug-log text). Moved the body into one helper called from both; the #ifdef DRAGONX_USE_DX11 g_borderlessDpi and #ifdef __APPLE__ scale normalization are preserved. Deliberately did NOT unify the full frame/present sequence — the resize path's immediate present and lack of try/catch are intentional. Full-node (GL) + Lite build clean; Windows/DX11 cross-build compiles main.cpp clean; ctest 1/1; hygiene clean. NEEDS RUNTIME VERIFICATION: a live window drag-resize (backdrop draws, no flicker) and a monitor DPI-scale change (window resizes + fonts reload). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/main.cpp | 467 +++++++++++++++++++++------------------------------ 1 file changed, 190 insertions(+), 277 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 55b4a31..1c25af3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -382,6 +382,184 @@ static std::string findPaymentURI(int argc, char* argv[]) return ""; } +// --------------------------------------------------------------- +// Shared render-loop helpers (used by both the main loop and the +// resize event-watcher). Extracted verbatim to de-duplicate the +// per-frame code — same calls, same order, same #ifdef branches. +// --------------------------------------------------------------- + +// Draw the window backdrop (base gradient / texture tint / acrylic +// capture callback / WindowBg alpha). The main loop passes lowSpec = +// isLowSpecMode() so low-spec skips texture sampling and the capture +// callback; the resize watcher passes lowSpec = false to reproduce its +// current always-sample-and-capture behavior. +static void drawWindowBackdrop(dragonx::App& app, ImDrawList* bgDL, ImVec2 p0, ImVec2 p1, + bool backdrop_active, bool lowSpec) +{ + // Read the current gradient texture from the app each frame + // so hot-reloaded / skin-changed images are picked up immediately. + ImTextureID curGradTex = app.getGradientTexture(); + + // Window opacity: scale only background layer alpha so the + // desktop shows through while UI stays fully opaque. + float winOpacity = app.settings() ? app.settings()->getWindowOpacity() : 1.0f; + + // Scale a single color's alpha channel by window opacity + auto scaleAlpha = [&](ImU32 col) -> ImU32 { + uint8_t a = (col >> IM_COL32_A_SHIFT) & 0xFF; + a = (uint8_t)(a * winOpacity); + return (col & ~(0xFFu << IM_COL32_A_SHIFT)) | ((ImU32)a << IM_COL32_A_SHIFT); + }; + + if (curGradTex != 0) { + // Base color gradient underneath the texture — read from ui.toml + const auto& S2 = dragonx::ui::schema::UI(); + ImU32 baseTop = S2.resolveColor(S2.drawElement("backdrop", "base-color-top").color, IM_COL32(18,28,65,200)); + ImU32 baseBottom = S2.resolveColor(S2.drawElement("backdrop", "base-color-bottom").color, IM_COL32(8,12,35,200)); + + if (lowSpec) { + // Low-spec: skip texture sampling — just draw the base gradient. + bgDL->AddRectFilledMultiColor(p0, p1, scaleAlpha(baseTop), scaleAlpha(baseTop), + scaleAlpha(baseBottom), scaleAlpha(baseBottom)); + } else { + int tintAlpha = (int)S2.drawElement("backdrop", "texture-tint-alpha").size; + if (tintAlpha <= 0) tintAlpha = 140; + + // Scale background gradient + texture tint alpha by + // window opacity — only affects the backdrop, not UI. + bgDL->AddRectFilledMultiColor(p0, p1, scaleAlpha(baseTop), scaleAlpha(baseTop), + scaleAlpha(baseBottom), scaleAlpha(baseBottom)); + int scaledTintAlpha = (int)(tintAlpha * winOpacity); + ImU32 tintCol = IM_COL32(255, 255, 255, scaledTintAlpha); + bgDL->AddImage(curGradTex, p0, p1, ImVec2(0, 0), ImVec2(1, 1), tintCol); + } + } else if (backdrop_active) { + // Programmatic gradient tint (fallback when texture unavailable) + const auto& S = dragonx::ui::schema::UI(); + auto bde = [&](const char* key, float fb) { + float v = S.drawElement("backdrop", key).size; + return v >= 0 ? v : fb; + }; + ImU32 colTop = IM_COL32((int)bde("gradient-top-r",8), (int)bde("gradient-top-g",12), + (int)bde("gradient-top-b",28), (int)(bde("gradient-top-a",80) * winOpacity)); + ImU32 colBottom = IM_COL32((int)bde("gradient-bottom-r",6), (int)bde("gradient-bottom-g",8), + (int)bde("gradient-bottom-b",18), (int)(bde("gradient-bottom-a",60) * winOpacity)); + bgDL->AddRectFilledMultiColor(p0, p1, colTop, colTop, colBottom, colBottom); + } + + // Insert acrylic capture callback BEFORE the noise overlay + // so the noise grain is not captured and blurred into the + // glass cards (the noise should only be a visual overlay). + // Skip in low-spec mode — acrylic is disabled so the FBO + // capture/blur callback is unnecessary GPU work. + if (!lowSpec) { + auto captureCb = dragonx::ui::effects::ImGuiAcrylic::GetBackgroundCaptureCallback(); + if (captureCb) { + bgDL->AddCallback(captureCb, nullptr); + bgDL->AddCallback(ImDrawCallback_ResetRenderState, nullptr); + } + } + + // WindowBg alpha stays at its base theme value — NOT scaled + // by window opacity. Only the background gradient/texture + // layers fade so UI elements remain fully readable. + if (backdrop_active || curGradTex != 0) { + const auto& S3 = dragonx::ui::schema::UI(); + float baseBgAlpha = S3.drawElement("backdrop", "background-alpha").sizeOr(0.40f); + ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = baseBgAlpha; + } +} + +// Handle SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: save the old-scale size, +// compute + clamp the new physical size, arm the deferred resize retry, +// and rebuild fonts / reset ImGui style at the new DPI scale. newScale +// is pre-normalized by the caller (<=0 -> 1.0f, and macOS -> 1.0f). +static void handleDisplayScaleChange(SDL_Window* window, float newScale, + std::map>& savedSizeForScale, + int& lastKnownW, int& lastKnownH, + int& dpiTargetW, int& dpiTargetH, + int& dpiResizeRetries, bool& dpiResizePending) +{ + auto& typo = dragonx::ui::material::Typography::instance(); + float oldScale = typo.getDpiScale(); + if (std::abs(newScale - oldScale) > 0.01f) { + float ratio = newScale / oldScale; + DEBUG_LOGF("Display scale changed: %.2f -> %.2f (ratio %.2f)\n", + oldScale, newScale, ratio); + ImGuiIO& io = ImGui::GetIO(); + +#ifdef DRAGONX_USE_DX11 + g_borderlessDpi = newScale; +#endif + + // Save the last known size (before auto-resize) under + // the OLD scale. We use lastKnownW/H instead of + // SDL_GetWindowSize() because by now WM_DPICHANGED + // may have already auto-resized the window. + { + int oldPct = (int)lroundf(oldScale * 100.0f); + savedSizeForScale[oldPct] = {lastKnownW, lastKnownH}; + DEBUG_LOGF(" Saved %dx%d for scale %d%%\n", + lastKnownW, lastKnownH, oldPct); + } + + // Compute new physical size: if we've been to this + // scale before, restore that exact size; otherwise + // proportionally scale from the current size. + int newPct = (int)lroundf(newScale * 100.0f); + int newW, newH; + auto it = savedSizeForScale.find(newPct); + if (it != savedSizeForScale.end()) { + newW = it->second.first; + newH = it->second.second; + } else { + // Use lastKnownW/H (pre-auto-resize) instead of + // SDL_GetWindowSize() which already reflects the + // WM_DPICHANGED auto-resize. + newW = (int)lroundf((float)lastKnownW * newScale / oldScale); + newH = (int)lroundf((float)lastKnownH * newScale / oldScale); + } + + // Clamp to the target display's work area so the + // window doesn't overflow a smaller/higher-density monitor. + SDL_DisplayID did = SDL_GetDisplayForWindow(window); + if (did) { + SDL_Rect usable; + if (SDL_GetDisplayUsableBounds(did, &usable)) { + newW = std::min(newW, usable.w); + newH = std::min(newH, usable.h); + } + } + + dpiResizePending = true; + SDL_SetWindowSize(window, newW, newH); + lastKnownW = newW; + lastKnownH = newH; + // Arm deferred retry — the SetWindowSize above may + // not stick if the user is mid-drag (modal loop). + dpiTargetW = newW; + dpiTargetH = newH; + dpiResizeRetries = 30; // retry for ~30 frames + SDL_SetWindowMinimumSize(window, + (int)(1024 * newScale), + (int)(720 * newScale)); + DEBUG_LOGF(" Window resized: %dx%d (scale %.2f, pct %d)\n", + newW, newH, newScale, newPct); + + // 2) Rebuild font atlas at the new DPI scale + typo.reload(io, newScale); + + // 3) Reset style → reapply base theme → scale by new DPI + ImGui::GetStyle() = ImGuiStyle(); + dragonx::ui::schema::UISchema::instance().reapplyColorsToImGui(); + if (newScale > 1.01f) { + ImGui::GetStyle().ScaleAllSizes(newScale); + } + + DEBUG_LOGF(" DPI transition complete\n"); + } +} + int main(int argc, char* argv[]) { // Ensure ObsidianDragon config directory exists early (before any file I/O) @@ -941,59 +1119,10 @@ int main(int argc, char* argv[]) ImDrawList* bgDL = ImGui::GetBackgroundDrawList(); ImVec2 p0 = vp->Pos; ImVec2 p1 = ImVec2(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y); - ImTextureID curGradTex2 = ctx->app->getGradientTexture(); - float winOp2 = ctx->app->settings() ? ctx->app->settings()->getWindowOpacity() : 1.0f; - - // Scale only background layer alpha by window opacity so the - // desktop shows through while UI elements stay fully opaque. - auto scaleA2 = [&](ImU32 col) -> ImU32 { - uint8_t a = (col >> IM_COL32_A_SHIFT) & 0xFF; - a = (uint8_t)(a * winOp2); - return (col & ~(0xFFu << IM_COL32_A_SHIFT)) | ((ImU32)a << IM_COL32_A_SHIFT); - }; - - if (curGradTex2 != 0) { - const auto& S1 = dragonx::ui::schema::UI(); - ImU32 baseTop1 = S1.resolveColor(S1.drawElement("backdrop", "base-color-top").color, IM_COL32(18,28,65,200)); - ImU32 baseBottom1 = S1.resolveColor(S1.drawElement("backdrop", "base-color-bottom").color, IM_COL32(8,12,35,200)); - int tintAlpha1 = (int)S1.drawElement("backdrop", "texture-tint-alpha").size; - if (tintAlpha1 <= 0) tintAlpha1 = 140; - bgDL->AddRectFilledMultiColor(p0, p1, scaleA2(baseTop1), scaleA2(baseTop1), - scaleA2(baseBottom1), scaleA2(baseBottom1)); - int scaledTA1 = (int)(tintAlpha1 * winOp2); - ImU32 tintCol1 = IM_COL32(255, 255, 255, scaledTA1); - bgDL->AddImage(curGradTex2, p0, p1, ImVec2(0, 0), ImVec2(1, 1), tintCol1); - } else if (ctx->backdrop_active) { - const auto& S = dragonx::ui::schema::UI(); - auto bde = [&](const char* key, float fb) { - float v = S.drawElement("backdrop", key).size; - return v >= 0 ? v : fb; - }; - ImU32 colTop = IM_COL32((int)bde("gradient-top-r",8), (int)bde("gradient-top-g",12), - (int)bde("gradient-top-b",28), (int)(bde("gradient-top-a",80) * winOp2)); - ImU32 colBottom = IM_COL32((int)bde("gradient-bottom-r",6), (int)bde("gradient-bottom-g",8), - (int)bde("gradient-bottom-b",18), (int)(bde("gradient-bottom-a",60) * winOp2)); - bgDL->AddRectFilledMultiColor(p0, p1, colTop, colTop, colBottom, colBottom); - } - // Acrylic capture callback — must fire BEFORE the noise - // overlay so the noise grain is not captured and blurred - // into the glass cards. - auto captureCb2 = dragonx::ui::effects::ImGuiAcrylic::GetBackgroundCaptureCallback(); - if (captureCb2) { - bgDL->AddCallback(captureCb2, nullptr); - bgDL->AddCallback(ImDrawCallback_ResetRenderState, nullptr); - } - - // WindowBg alpha stays at its base theme value — NOT scaled - // by window opacity. Only the background gradient/texture - // layers are scaled so the desktop shows through while UI - // elements (cards, text, buttons) remain fully readable. - if (ctx->backdrop_active || curGradTex2 != 0) { - const auto& S4 = dragonx::ui::schema::UI(); - float baseBgAlpha2 = S4.drawElement("backdrop", "background-alpha").sizeOr(0.40f); - ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = baseBgAlpha2; - } - + // Resize path historically always sampled the texture and always + // added the capture callback — pass lowSpec = false to preserve + // that behavior exactly. + drawWindowBackdrop(*ctx->app, bgDL, p0, p1, ctx->backdrop_active, /*lowSpec=*/false); } ImGuiIO& io = ImGui::GetIO(); @@ -1143,76 +1272,9 @@ int main(int argc, char* argv[]) #ifdef __APPLE__ newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale #endif - auto& typo = dragonx::ui::material::Typography::instance(); - float oldScale = typo.getDpiScale(); - if (std::abs(newScale - oldScale) > 0.01f) { - float ratio = newScale / oldScale; - DEBUG_LOGF("Display scale changed (idle): %.2f -> %.2f (ratio %.2f)\n", - oldScale, newScale, ratio); - ImGuiIO& io = ImGui::GetIO(); - -#ifdef DRAGONX_USE_DX11 - g_borderlessDpi = newScale; -#endif - - // Save the last known size (before auto-resize) under - // the OLD scale. We use lastKnownW/H instead of - // SDL_GetWindowSize() because by now WM_DPICHANGED - // may have already auto-resized the window. - { - int oldPct = (int)lroundf(oldScale * 100.0f); - savedSizeForScale[oldPct] = {lastKnownW, lastKnownH}; - DEBUG_LOGF(" Saved %dx%d for scale %d%%\n", - lastKnownW, lastKnownH, oldPct); - } - - // Compute new physical size: if we've been to this - // scale before, restore that exact size; otherwise - // proportionally scale from the current size. - int newPct = (int)lroundf(newScale * 100.0f); - int newW, newH; - auto it = savedSizeForScale.find(newPct); - if (it != savedSizeForScale.end()) { - newW = it->second.first; - newH = it->second.second; - } else { - // Use lastKnownW/H (pre-auto-resize) instead of - // SDL_GetWindowSize() which already reflects the - // WM_DPICHANGED auto-resize. - newW = (int)lroundf((float)lastKnownW * newScale / oldScale); - newH = (int)lroundf((float)lastKnownH * newScale / oldScale); - } - - // Clamp to the target display's work area so the - // window doesn't overflow a smaller/higher-density monitor. - SDL_DisplayID did = SDL_GetDisplayForWindow(window); - if (did) { - SDL_Rect usable; - if (SDL_GetDisplayUsableBounds(did, &usable)) { - newW = std::min(newW, usable.w); - newH = std::min(newH, usable.h); - } - } - - dpiResizePending = true; - SDL_SetWindowSize(window, newW, newH); - lastKnownW = newW; - lastKnownH = newH; - // Arm deferred retry — the SetWindowSize above may - // not stick if the user is mid-drag (modal loop). - dpiTargetW = newW; - dpiTargetH = newH; - dpiResizeRetries = 30; // retry for ~30 frames - SDL_SetWindowMinimumSize(window, - (int)(1024 * newScale), (int)(720 * newScale)); - - typo.reload(io, newScale); - ImGui::GetStyle() = ImGuiStyle(); - dragonx::ui::schema::UISchema::instance().reapplyColorsToImGui(); - if (newScale > 1.01f) { - ImGui::GetStyle().ScaleAllSizes(newScale); - } - } + handleDisplayScaleChange(window, newScale, savedSizeForScale, + lastKnownW, lastKnownH, dpiTargetW, dpiTargetH, + dpiResizeRetries, dpiResizePending); } needsRedraw = true; // Got an event — redraw } @@ -1323,84 +1385,9 @@ int main(int argc, char* argv[]) #ifdef __APPLE__ newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale #endif - auto& typo = dragonx::ui::material::Typography::instance(); - float oldScale = typo.getDpiScale(); - if (std::abs(newScale - oldScale) > 0.01f) { - float ratio = newScale / oldScale; - DEBUG_LOGF("Display scale changed: %.2f -> %.2f (ratio %.2f)\n", - oldScale, newScale, ratio); - ImGuiIO& io = ImGui::GetIO(); - -#ifdef DRAGONX_USE_DX11 - g_borderlessDpi = newScale; -#endif - - // Save the last known size (before auto-resize) under - // the OLD scale. We use lastKnownW/H instead of - // SDL_GetWindowSize() because by now WM_DPICHANGED - // may have already auto-resized the window. - { - int oldPct = (int)lroundf(oldScale * 100.0f); - savedSizeForScale[oldPct] = {lastKnownW, lastKnownH}; - DEBUG_LOGF(" Saved %dx%d for scale %d%%\n", - lastKnownW, lastKnownH, oldPct); - } - - // Compute new physical size: if we've been to this - // scale before, restore that exact size; otherwise - // proportionally scale from the current size. - int newPct = (int)lroundf(newScale * 100.0f); - int newW, newH; - auto it = savedSizeForScale.find(newPct); - if (it != savedSizeForScale.end()) { - newW = it->second.first; - newH = it->second.second; - } else { - // Use lastKnownW/H (pre-auto-resize) instead of - // SDL_GetWindowSize() which already reflects the - // WM_DPICHANGED auto-resize. - newW = (int)lroundf((float)lastKnownW * newScale / oldScale); - newH = (int)lroundf((float)lastKnownH * newScale / oldScale); - } - - // Clamp to the target display's work area so the - // window doesn't overflow a smaller/higher-density monitor. - SDL_DisplayID did = SDL_GetDisplayForWindow(window); - if (did) { - SDL_Rect usable; - if (SDL_GetDisplayUsableBounds(did, &usable)) { - newW = std::min(newW, usable.w); - newH = std::min(newH, usable.h); - } - } - - dpiResizePending = true; - SDL_SetWindowSize(window, newW, newH); - lastKnownW = newW; - lastKnownH = newH; - // Arm deferred retry — the SetWindowSize above may - // not stick if the user is mid-drag (modal loop). - dpiTargetW = newW; - dpiTargetH = newH; - dpiResizeRetries = 30; // retry for ~30 frames - SDL_SetWindowMinimumSize(window, - (int)(1024 * newScale), - (int)(720 * newScale)); - DEBUG_LOGF(" Window resized: %dx%d (scale %.2f, pct %d)\n", - newW, newH, newScale, newPct); - - // 2) Rebuild font atlas at the new DPI scale - typo.reload(io, newScale); - - // 3) Reset style → reapply base theme → scale by new DPI - ImGui::GetStyle() = ImGuiStyle(); - dragonx::ui::schema::UISchema::instance().reapplyColorsToImGui(); - if (newScale > 1.01f) { - ImGui::GetStyle().ScaleAllSizes(newScale); - } - - DEBUG_LOGF(" DPI transition complete\n"); - } + handleDisplayScaleChange(window, newScale, savedSizeForScale, + lastKnownW, lastKnownH, dpiTargetW, dpiTargetH, + dpiResizeRetries, dpiResizePending); } } @@ -1503,82 +1490,8 @@ int main(int argc, char* argv[]) ImDrawList* bgDL = ImGui::GetBackgroundDrawList(); ImVec2 p0 = vp->Pos; ImVec2 p1 = ImVec2(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y); - - // Read the current gradient texture from the app each frame - // so hot-reloaded / skin-changed images are picked up immediately. - ImTextureID curGradTex = app.getGradientTexture(); - - // Window opacity: scale only background layer alpha so the - // desktop shows through while UI stays fully opaque. - float winOpacity = app.settings() ? app.settings()->getWindowOpacity() : 1.0f; - - bool lowSpec = dragonx::ui::effects::isLowSpecMode(); - - // Scale a single color's alpha channel by window opacity - auto scaleAlpha = [&](ImU32 col) -> ImU32 { - uint8_t a = (col >> IM_COL32_A_SHIFT) & 0xFF; - a = (uint8_t)(a * winOpacity); - return (col & ~(0xFFu << IM_COL32_A_SHIFT)) | ((ImU32)a << IM_COL32_A_SHIFT); - }; - - if (curGradTex != 0) { - // Base color gradient underneath the texture — read from ui.toml - const auto& S2 = dragonx::ui::schema::UI(); - ImU32 baseTop = S2.resolveColor(S2.drawElement("backdrop", "base-color-top").color, IM_COL32(18,28,65,200)); - ImU32 baseBottom = S2.resolveColor(S2.drawElement("backdrop", "base-color-bottom").color, IM_COL32(8,12,35,200)); - - if (lowSpec) { - // Low-spec: skip texture sampling — just draw the base gradient. - bgDL->AddRectFilledMultiColor(p0, p1, scaleAlpha(baseTop), scaleAlpha(baseTop), - scaleAlpha(baseBottom), scaleAlpha(baseBottom)); - } else { - int tintAlpha = (int)S2.drawElement("backdrop", "texture-tint-alpha").size; - if (tintAlpha <= 0) tintAlpha = 140; - - // Scale background gradient + texture tint alpha by - // window opacity — only affects the backdrop, not UI. - bgDL->AddRectFilledMultiColor(p0, p1, scaleAlpha(baseTop), scaleAlpha(baseTop), - scaleAlpha(baseBottom), scaleAlpha(baseBottom)); - int scaledTintAlpha = (int)(tintAlpha * winOpacity); - ImU32 tintCol = IM_COL32(255, 255, 255, scaledTintAlpha); - bgDL->AddImage(curGradTex, p0, p1, ImVec2(0, 0), ImVec2(1, 1), tintCol); - } - } else if (backdrop_active) { - // Programmatic gradient tint (fallback when texture unavailable) - const auto& S = dragonx::ui::schema::UI(); - auto bde = [&](const char* key, float fb) { - float v = S.drawElement("backdrop", key).size; - return v >= 0 ? v : fb; - }; - ImU32 colTop = IM_COL32((int)bde("gradient-top-r",8), (int)bde("gradient-top-g",12), - (int)bde("gradient-top-b",28), (int)(bde("gradient-top-a",80) * winOpacity)); - ImU32 colBottom = IM_COL32((int)bde("gradient-bottom-r",6), (int)bde("gradient-bottom-g",8), - (int)bde("gradient-bottom-b",18), (int)(bde("gradient-bottom-a",60) * winOpacity)); - bgDL->AddRectFilledMultiColor(p0, p1, colTop, colTop, colBottom, colBottom); - } - - // Insert acrylic capture callback BEFORE the noise overlay - // so the noise grain is not captured and blurred into the - // glass cards (the noise should only be a visual overlay). - // Skip in low-spec mode — acrylic is disabled so the FBO - // capture/blur callback is unnecessary GPU work. - if (!lowSpec) { - auto captureCb = dragonx::ui::effects::ImGuiAcrylic::GetBackgroundCaptureCallback(); - if (captureCb) { - bgDL->AddCallback(captureCb, nullptr); - bgDL->AddCallback(ImDrawCallback_ResetRenderState, nullptr); - } - } - - // WindowBg alpha stays at its base theme value — NOT scaled - // by window opacity. Only the background gradient/texture - // layers fade so UI elements remain fully readable. - if (backdrop_active || curGradTex != 0) { - const auto& S3 = dragonx::ui::schema::UI(); - float baseBgAlpha = S3.drawElement("backdrop", "background-alpha").sizeOr(0.40f); - ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = baseBgAlpha; - } - + drawWindowBackdrop(app, bgDL, p0, p1, backdrop_active, + dragonx::ui::effects::isLowSpecMode()); } PERF_END("Backdrop", _perfBackdrop);