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:
467
src/main.cpp
467
src/main.cpp
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user