diff --git a/src/main.cpp b/src/main.cpp index 562fd6a..ab98a71 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -778,36 +778,12 @@ int main(int argc, char* argv[]) dragonx::util::PerfLog::instance().init(perfPath); } - // If the user had a font scale > 1.0 saved, app.init() rebuilt fonts - // at that scale. Resize the window now so the larger UI fits. - { - float fs = dragonx::ui::Layout::userFontScale(); - if (fs > 1.01f) { - int curW = 0, curH = 0; - SDL_GetWindowSize(window, &curW, &curH); - int newW = (int)lroundf(curW * fs); - int newH = (int)lroundf(curH * fs); - - // Clamp to display work area - 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); - } - } - - float hwDpi = dragonx::ui::Layout::rawDpiScale(); - SDL_SetWindowSize(window, newW, newH); - SDL_SetWindowMinimumSize(window, - (int)(1024 * hwDpi * fs), - (int)(720 * hwDpi * fs)); - SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - DEBUG_LOGF("Font-scale startup: window %dx%d -> %dx%d (fontScale %.1f)\n", - curW, curH, newW, newH, fs); - } - } + // NOTE: No font-scale startup resize here. The saved window size + // already includes any font-scale inflation from the previous session + // (save divides by dpiScale but not fontScale). Multiplying by + // fontScale again would compound the effect on every restart. + // The per-frame font-scale block in the main loop handles resizing + // when the user actually changes the slider. // Handle pending payment URI from command line if (!pendingURI.empty()) { @@ -1057,6 +1033,11 @@ int main(int argc, char* argv[]) lastKnownH = waitEvent.window.data2; } } + // Window restored from minimized — trigger immediate data refresh + if (waitEvent.type == SDL_EVENT_WINDOW_RESTORED && + waitEvent.window.windowID == SDL_GetWindowID(window)) { + app.refreshNow(); + } // Handle DPI change that arrived while idle (same logic as poll loop) if (waitEvent.type == SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED && waitEvent.window.windowID == SDL_GetWindowID(window)) { @@ -1193,6 +1174,11 @@ int main(int argc, char* argv[]) lastKnownH = event.window.data2; } } + // Window restored from minimized — trigger immediate data refresh + if (event.type == SDL_EVENT_WINDOW_RESTORED && + event.window.windowID == SDL_GetWindowID(window)) { + app.refreshNow(); + } // Handle DPI/display scale changes (e.g. window dragged to a // different-DPI monitor, or user changes Windows scaling) if (event.type == SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED && @@ -1295,18 +1281,38 @@ int main(int argc, char* argv[]) // Pre-frame: font atlas rebuilds and schema hot-reload must // happen BEFORE NewFrame() because NewFrame() caches font ptrs. - float prevFontScale = dragonx::ui::Layout::userFontScale(); app.preFrame(); - // If font scale changed (user dragged the slider), resize window + // Smooth font-scale: compensate visual font size via FontScaleMain + // when the atlas hasn't been rebuilt yet (during slider drag). + // After atlas rebuild, atlasScale == userFontScale so this is 1.0. { + float userFS = dragonx::ui::Layout::userFontScale(); + float atlasFS = dragonx::ui::Layout::fontAtlasScale(); + if (atlasFS > 0.001f) + ImGui::GetStyle().FontScaleMain = userFS / atlasFS; + } + + // If font scale changed, resize window proportionally. + // anchorW/H = the window size at the moment the anchor was set. + // anchorFS = the font scale at that moment. + // target = anchorW * curFS / anchorFS (symmetric, no drift). + { + static float anchorFS = 0.0f; + static int anchorW = 0, anchorH = 0; float curFS = dragonx::ui::Layout::userFontScale(); - if (std::fabs(curFS - prevFontScale) > 0.001f) { - int curW = 0, curH = 0; - SDL_GetWindowSize(window, &curW, &curH); - float ratio = curFS / prevFontScale; - int newW = (int)lroundf(curW * ratio); - int newH = (int)lroundf(curH * ratio); + + if (anchorFS < 0.001f) { + // First frame: the current window IS the reference for + // whatever font scale is loaded — no resize. + anchorFS = curFS; + SDL_GetWindowSize(window, &anchorW, &anchorH); + } + + if (std::fabs(curFS - anchorFS) > 0.001f) { + float ratio = curFS / anchorFS; + int newW = (int)lroundf((float)anchorW * ratio); + int newH = (int)lroundf((float)anchorH * ratio); // Clamp to display work area SDL_DisplayID did = SDL_GetDisplayForWindow(window); @@ -1319,14 +1325,21 @@ int main(int argc, char* argv[]) } float hwDpi = dragonx::ui::Layout::rawDpiScale(); - SDL_SetWindowSize(window, newW, newH); + // Update minimum size BEFORE resizing so the window can + // actually shrink when the font scale decreases. SDL_SetWindowMinimumSize(window, (int)(1024 * hwDpi * curFS), (int)(720 * hwDpi * curFS)); + SDL_SetWindowSize(window, newW, newH); lastKnownW = newW; lastKnownH = newH; - DEBUG_LOGF("Font-scale resize: %dx%d -> %dx%d (%.1fx -> %.1fx)\n", - curW, curH, newW, newH, prevFontScale, curFS); + + // Update anchor so we don't re-fire every frame. + // The new anchor becomes the current size/scale so + // subsequent slider movements are relative to here. + anchorW = newW; + anchorH = newH; + anchorFS = curFS; } } diff --git a/src/ui/layout.h b/src/ui/layout.h index 1551186..de50aaa 100644 --- a/src/ui/layout.h +++ b/src/ui/layout.h @@ -43,18 +43,30 @@ namespace Layout { namespace detail { inline float& userFontScaleRef() { static float s = 1.0f; return s; } inline bool& fontReloadNeededRef() { static bool s = false; return s; } + inline float& fontAtlasScaleRef() { static float s = 1.0f; return s; } } /** - * @brief Get the user's font scale preference (1.0–3.0). + * @brief Get the user's font scale preference (1.0–1.5). * Multiplied into font loading so glyphs render at the chosen size. */ inline float userFontScale() { return detail::userFontScaleRef(); } +/** + * @brief Get the user font scale at which the font atlas was last built. + * Used to compute FontScaleMain compensation during smooth slider drag. + */ +inline float fontAtlasScale() { return detail::fontAtlasScaleRef(); } + +/** + * @brief Record the font atlas scale after a rebuild. + * Called from Typography::load() after the atlas is built. + */ +inline void setFontAtlasScale(float v) { detail::fontAtlasScaleRef() = v; } + /** * @brief Set the user's font scale and flag a font reload. - * Called from the settings UI; the main loop detects the flag and - * calls Typography::reload(). + * Called on slider release for a crisp atlas rebuild. */ inline void setUserFontScale(float v) { v = std::max(1.0f, std::min(1.5f, v)); @@ -64,6 +76,16 @@ inline void setUserFontScale(float v) { } } +/** + * @brief Set the user's font scale WITHOUT flagging a font reload. + * Called every frame while the slider is being dragged for smooth + * visual scaling via FontScaleMain compensation. + */ +inline void setUserFontScaleVisual(float v) { + v = std::max(1.0f, std::min(1.5f, v)); + detail::userFontScaleRef() = v; +} + /** * @brief Consume the pending font-reload flag (returns true once). */ diff --git a/src/ui/material/typography.cpp b/src/ui/material/typography.cpp index ebd30e8..1e587c3 100644 --- a/src/ui/material/typography.cpp +++ b/src/ui/material/typography.cpp @@ -206,7 +206,9 @@ bool Typography::load(ImGuiIO& io, float dpiScale) io.FontDefault = fonts_[static_cast(TypeStyle::Body1)]; loaded_ = true; - DEBUG_LOGF("Typography: Loaded %d font styles (default=Body1)\n", allLoaded ? kNumStyles : -1); + Layout::setFontAtlasScale(Layout::userFontScale()); + DEBUG_LOGF("Typography: Loaded %d font styles (default=Body1, atlasScale=%.2f)\n", + allLoaded ? kNumStyles : -1, Layout::fontAtlasScale()); return allLoaded; } diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 099f083..f020cc6 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -888,7 +888,7 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemHovered()) ImGui::SetTooltip("Card and sidebar opacity (100%% = fully opaque, lower = more see-through)"); // Window Opacity slider (label above) - ImGui::TextUnformatted("Window"); + ImGui::TextUnformatted("Window Opacity"); ImGui::SetNextItemWidth(ctrlW); { char winop_fmt[16]; @@ -912,20 +912,26 @@ void RenderSettingsPage(App* app) { ImGui::PushFont(body2); ImGui::Spacing(); ImGui::TextUnformatted("Font Scale"); - float fontSliderW = std::min(availWidth - pad * 2, 260.0f * dp); + float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, + availWidth - pad * 2); ImGui::SetNextItemWidth(fontSliderW); float prev_font_scale = sp_font_scale; { char fs_fmt[16]; - snprintf(fs_fmt, sizeof(fs_fmt), "%.1fx", sp_font_scale); + snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale); ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt, ImGuiSliderFlags_AlwaysClamp); } - // Snap to nearest 0.1 and apply live as the user drags. - // Font atlas rebuild is deferred to preFrame() (before NewFrame), - // so updating every tick is safe — no dangling font pointers. - sp_font_scale = std::round(sp_font_scale * 10.0f) / 10.0f; + // Smooth continuous scaling while dragging. + // Visual scaling uses FontScaleMain (no atlas rebuild), + // atlas rebuild is deferred to slider release for crisp text. + sp_font_scale = std::max(1.0f, std::min(1.5f, sp_font_scale)); if (sp_font_scale != prev_font_scale) { + // While dragging: update layout scale without atlas rebuild + Layout::setUserFontScaleVisual(sp_font_scale); + } + if (ImGui::IsItemDeactivatedAfterEdit()) { + // On release: rebuild font atlas at final size Layout::setUserFontScale(sp_font_scale); saveSettingsPageState(app->settings()); } diff --git a/src/ui/sidebar.h b/src/ui/sidebar.h index 1dc9ecd..603dc79 100644 --- a/src/ui/sidebar.h +++ b/src/ui/sidebar.h @@ -70,14 +70,23 @@ inline const char* GetNavIconMD(NavPage page) } } +// Compute the effective draw-list font size for a given font. +// During smooth font-scale drag, FontScaleMain compensates for the atlas +// not yet being rebuilt. drawList->AddText bypasses that, so we apply +// the factor manually to keep sidebar text in sync with the rest of the UI. +inline float ScaledFontSize(ImFont* f) { + return f->LegacySize * ImGui::GetStyle().FontScaleMain; +} + // Draw a Material Design icon centered at (cx, cy) with the given color. // Uses the medium (18px) icon font from Typography. inline void DrawNavIcon(ImDrawList* dl, NavPage page, float cx, float cy, float /*s*/, ImU32 col) { ImFont* iconFont = material::Type().iconMed(); const char* icon = GetNavIconMD(page); - ImVec2 sz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, icon); - dl->AddText(iconFont, iconFont->LegacySize, + float fsz = ScaledFontSize(iconFont); + ImVec2 sz = iconFont->CalcTextSizeA(fsz, 1000.0f, 0.0f, icon); + dl->AddText(iconFont, fsz, ImVec2(cx - sz.x * 0.5f, cy - sz.y * 0.5f), col, icon); } @@ -506,8 +515,9 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei { ImFont* iconFont = Type().iconSmall(); const char* chevIcon = collapsed ? ICON_MD_CHEVRON_RIGHT : ICON_MD_CHEVRON_LEFT; - ImVec2 chevSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, chevIcon); - dl->AddText(iconFont, iconFont->LegacySize, + float chevFsz = ScaledFontSize(iconFont); + ImVec2 chevSz = iconFont->CalcTextSizeA(chevFsz, 1000.0f, 0.0f, chevIcon); + dl->AddText(iconFont, chevFsz, ImVec2(cx - chevSz.x * 0.5f, cy - chevSz.y * 0.5f), iconCol, chevIcon); } @@ -528,10 +538,11 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei float labelY = ImGui::GetCursorScreenPos().y; ImVec4 olCol = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()); olCol.w *= expandFrac; - dl->AddText(olFont, olFont->LegacySize, + float olFsz = ScaledFontSize(olFont); + dl->AddText(olFont, olFsz, ImVec2(wp.x + sbSectionLabelPadLeft, labelY), ImGui::ColorConvertFloat4ToU32(olCol), item.section_label); - ImGui::Dummy(ImVec2(0, olFont->LegacySize + 2.0f)); + ImGui::Dummy(ImVec2(0, olFsz + 2.0f)); } else if (item.section_label && !showLabels) { // Collapsed: thin separator instead of label ImGui::Dummy(ImVec2(0, sbSectionGap * 0.4f)); @@ -601,7 +612,8 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei // Measure total width of icon + gap + label, then center ImFont* font = selected ? Type().subtitle2() : Type().body2(); float gap = iconLabelGap; - ImVec2 labelSz = font->CalcTextSizeA(font->LegacySize, 1000.0f, 0.0f, item.label); + float lblFsz = ScaledFontSize(font); + ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, item.label); float totalW = iconS * 2.0f + gap + labelSz.x; float btnCX = (indMin.x + indMax.x) * 0.5f; float startX = btnCX - totalW * 0.5f; @@ -612,7 +624,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei float labelX = startX + iconS * 2.0f + gap; ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol); lc.w *= expandFrac; - dl->AddText(font, font->LegacySize, ImVec2(labelX, textY), + dl->AddText(font, lblFsz, ImVec2(labelX, textY), ImGui::ColorConvertFloat4ToU32(lc), item.label); } else { float iconCX = (indMin.x + indMax.x) * 0.5f; @@ -656,8 +668,9 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei char buf[16]; snprintf(buf, sizeof(buf), "%d", badgeCount > 99 ? 99 : badgeCount); ImFont* capFont = Type().caption(); - ImVec2 ts = capFont->CalcTextSizeA(capFont->LegacySize, 1000.0f, 0.0f, buf); - dl->AddText(capFont, capFont->LegacySize, + float capFsz = ScaledFontSize(capFont); + ImVec2 ts = capFont->CalcTextSizeA(capFsz, 1000.0f, 0.0f, buf); + dl->AddText(capFont, capFsz, ImVec2(badgeX - ts.x * 0.5f, badgeY - ts.y * 0.5f), badgeTextCol, buf); } @@ -777,25 +790,28 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei ImFont* iconFont = Type().iconSmall(); ImFont* font = Type().caption(); const char* exitIcon = ICON_MD_EXIT_TO_APP; - ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, exitIcon); - ImVec2 labelSz = font->CalcTextSizeA(font->LegacySize, 1000.0f, 0.0f, "Exit"); + float eIconFsz = ScaledFontSize(iconFont); + float eLblFsz = ScaledFontSize(font); + ImVec2 iconSz = iconFont->CalcTextSizeA(eIconFsz, 1000.0f, 0.0f, exitIcon); + ImVec2 labelSz = font->CalcTextSizeA(eLblFsz, 1000.0f, 0.0f, "Exit"); float gap = exitIconGap; float totalW = iconSz.x + gap + labelSz.x; float startX = cx - totalW * 0.5f; - dl->AddText(iconFont, iconFont->LegacySize, + dl->AddText(iconFont, eIconFsz, ImVec2(startX, cy - iconSz.y * 0.5f), exitCol, exitIcon); ImVec4 lc = ImGui::ColorConvertU32ToFloat4(exitCol); lc.w *= expandFrac; - dl->AddText(font, font->LegacySize, + dl->AddText(font, eLblFsz, ImVec2(startX + iconSz.x + gap, cy - labelSz.y * 0.5f), ImGui::ColorConvertFloat4ToU32(lc), "Exit"); } else { ImFont* iconFont = Type().iconSmall(); const char* exitIcon = ICON_MD_EXIT_TO_APP; - ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, exitIcon); - dl->AddText(iconFont, iconFont->LegacySize, + float eIconFsz = ScaledFontSize(iconFont); + ImVec2 iconSz = iconFont->CalcTextSizeA(eIconFsz, 1000.0f, 0.0f, exitIcon); + dl->AddText(iconFont, eIconFsz, ImVec2(cx - iconSz.x * 0.5f, cy - iconSz.y * 0.5f), exitCol, exitIcon); } diff --git a/src/ui/windows/mining_tab.cpp b/src/ui/windows/mining_tab.cpp index f96ac33..8cfa03b 100644 --- a/src/ui/windows/mining_tab.cpp +++ b/src/ui/windows/mining_tab.cpp @@ -46,12 +46,6 @@ static bool s_pool_settings_dirty = false; static bool s_pool_state_loaded = false; static bool s_show_pool_log = false; // Toggle: false=chart, true=log -// Chart smooth-scroll state -static size_t s_chart_last_n = 0; -static double s_chart_last_newest = -1.0; -static double s_chart_update_time = 0.0; -static float s_chart_interval = 1.0f; // measured seconds between data updates - // Get max threads based on hardware static int GetMaxMiningThreads() { @@ -989,33 +983,15 @@ void RenderMiningTab(App* app) float plotW = plotRight - plotLeft; float plotH = std::max(1.0f, plotBottom - plotTop); - // --- Smooth scroll: detect new data and measure interval --- + // Build raw data points — evenly spaced across the plot. + // No smooth-scroll animation: the chart updates in-place + // when new data arrives without any interim compression. size_t n = chartHistory.size(); - double newestVal = chartHistory.back(); - double nowTime = ImGui::GetTime(); - bool dataChanged = (n != s_chart_last_n) || (newestVal != s_chart_last_newest); - if (dataChanged) { - float dt = (float)(nowTime - s_chart_update_time); - if (dt > 0.3f && dt < 10.0f) - s_chart_interval = s_chart_interval * 0.6f + dt * 0.4f; // smoothed - s_chart_last_n = n; - s_chart_last_newest = newestVal; - s_chart_update_time = nowTime; - } - float elapsed = (float)(nowTime - s_chart_update_time); - float scrollFrac = std::clamp(elapsed / s_chart_interval, 0.0f, 1.0f); - - // Build raw data points with smooth scroll offset. - // Newest point is anchored at plotRight; as scrollFrac grows - // the spacing compresses by one virtual slot so the next - // incoming point will appear seamlessly at plotRight. - float virtualSlots = (float)(n - 1) + scrollFrac; - if (virtualSlots < 1.0f) virtualSlots = 1.0f; - float stepW = plotW / virtualSlots; + float stepW = (n > 1) ? plotW / (float)(n - 1) : plotW; std::vector rawPts(n); for (size_t i = 0; i < n; i++) { - float x = plotRight - (float)(n - 1 - i) * stepW; + float x = plotLeft + (float)i * stepW; float y = plotBottom - (float)((chartHistory[i] - yMin) / (yMax - yMin)) * plotH; rawPts[i] = ImVec2(x, y); }