refactor(audit): de-duplicate the main.cpp backdrop draw + DPI transition

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 21:00:40 -05:00
parent cb74fbbff0
commit af2db4366c

View File

@@ -382,6 +382,184 @@ static std::string findPaymentURI(int argc, char* argv[])
return ""; 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<int,std::pair<int,int>>& 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[]) int main(int argc, char* argv[])
{ {
// Ensure ObsidianDragon config directory exists early (before any file I/O) // 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(); ImDrawList* bgDL = ImGui::GetBackgroundDrawList();
ImVec2 p0 = vp->Pos; ImVec2 p0 = vp->Pos;
ImVec2 p1 = ImVec2(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y); ImVec2 p1 = ImVec2(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y);
ImTextureID curGradTex2 = ctx->app->getGradientTexture(); // Resize path historically always sampled the texture and always
float winOp2 = ctx->app->settings() ? ctx->app->settings()->getWindowOpacity() : 1.0f; // added the capture callback — pass lowSpec = false to preserve
// that behavior exactly.
// Scale only background layer alpha by window opacity so the drawWindowBackdrop(*ctx->app, bgDL, p0, p1, ctx->backdrop_active, /*lowSpec=*/false);
// 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;
}
} }
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@@ -1143,76 +1272,9 @@ int main(int argc, char* argv[])
#ifdef __APPLE__ #ifdef __APPLE__
newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale
#endif #endif
auto& typo = dragonx::ui::material::Typography::instance(); handleDisplayScaleChange(window, newScale, savedSizeForScale,
float oldScale = typo.getDpiScale(); lastKnownW, lastKnownH, dpiTargetW, dpiTargetH,
if (std::abs(newScale - oldScale) > 0.01f) { dpiResizeRetries, dpiResizePending);
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);
}
}
} }
needsRedraw = true; // Got an event — redraw needsRedraw = true; // Got an event — redraw
} }
@@ -1323,84 +1385,9 @@ int main(int argc, char* argv[])
#ifdef __APPLE__ #ifdef __APPLE__
newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale
#endif #endif
auto& typo = dragonx::ui::material::Typography::instance(); handleDisplayScaleChange(window, newScale, savedSizeForScale,
float oldScale = typo.getDpiScale(); lastKnownW, lastKnownH, dpiTargetW, dpiTargetH,
if (std::abs(newScale - oldScale) > 0.01f) { dpiResizeRetries, dpiResizePending);
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");
}
} }
} }
@@ -1503,82 +1490,8 @@ int main(int argc, char* argv[])
ImDrawList* bgDL = ImGui::GetBackgroundDrawList(); ImDrawList* bgDL = ImGui::GetBackgroundDrawList();
ImVec2 p0 = vp->Pos; ImVec2 p0 = vp->Pos;
ImVec2 p1 = ImVec2(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y); ImVec2 p1 = ImVec2(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y);
drawWindowBackdrop(app, bgDL, p0, p1, backdrop_active,
// Read the current gradient texture from the app each frame dragonx::ui::effects::isLowSpecMode());
// 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;
}
} }
PERF_END("Backdrop", _perfBackdrop); PERF_END("Backdrop", _perfBackdrop);