UX improvements
This commit is contained in:
95
src/main.cpp
95
src/main.cpp
@@ -778,36 +778,12 @@ int main(int argc, char* argv[])
|
|||||||
dragonx::util::PerfLog::instance().init(perfPath);
|
dragonx::util::PerfLog::instance().init(perfPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user had a font scale > 1.0 saved, app.init() rebuilt fonts
|
// NOTE: No font-scale startup resize here. The saved window size
|
||||||
// at that scale. Resize the window now so the larger UI fits.
|
// already includes any font-scale inflation from the previous session
|
||||||
{
|
// (save divides by dpiScale but not fontScale). Multiplying by
|
||||||
float fs = dragonx::ui::Layout::userFontScale();
|
// fontScale again would compound the effect on every restart.
|
||||||
if (fs > 1.01f) {
|
// The per-frame font-scale block in the main loop handles resizing
|
||||||
int curW = 0, curH = 0;
|
// when the user actually changes the slider.
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pending payment URI from command line
|
// Handle pending payment URI from command line
|
||||||
if (!pendingURI.empty()) {
|
if (!pendingURI.empty()) {
|
||||||
@@ -1057,6 +1033,11 @@ int main(int argc, char* argv[])
|
|||||||
lastKnownH = waitEvent.window.data2;
|
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)
|
// Handle DPI change that arrived while idle (same logic as poll loop)
|
||||||
if (waitEvent.type == SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED &&
|
if (waitEvent.type == SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED &&
|
||||||
waitEvent.window.windowID == SDL_GetWindowID(window)) {
|
waitEvent.window.windowID == SDL_GetWindowID(window)) {
|
||||||
@@ -1193,6 +1174,11 @@ int main(int argc, char* argv[])
|
|||||||
lastKnownH = event.window.data2;
|
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
|
// Handle DPI/display scale changes (e.g. window dragged to a
|
||||||
// different-DPI monitor, or user changes Windows scaling)
|
// different-DPI monitor, or user changes Windows scaling)
|
||||||
if (event.type == SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED &&
|
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
|
// Pre-frame: font atlas rebuilds and schema hot-reload must
|
||||||
// happen BEFORE NewFrame() because NewFrame() caches font ptrs.
|
// happen BEFORE NewFrame() because NewFrame() caches font ptrs.
|
||||||
float prevFontScale = dragonx::ui::Layout::userFontScale();
|
|
||||||
app.preFrame();
|
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();
|
float curFS = dragonx::ui::Layout::userFontScale();
|
||||||
if (std::fabs(curFS - prevFontScale) > 0.001f) {
|
|
||||||
int curW = 0, curH = 0;
|
if (anchorFS < 0.001f) {
|
||||||
SDL_GetWindowSize(window, &curW, &curH);
|
// First frame: the current window IS the reference for
|
||||||
float ratio = curFS / prevFontScale;
|
// whatever font scale is loaded — no resize.
|
||||||
int newW = (int)lroundf(curW * ratio);
|
anchorFS = curFS;
|
||||||
int newH = (int)lroundf(curH * ratio);
|
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
|
// Clamp to display work area
|
||||||
SDL_DisplayID did = SDL_GetDisplayForWindow(window);
|
SDL_DisplayID did = SDL_GetDisplayForWindow(window);
|
||||||
@@ -1319,14 +1325,21 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
float hwDpi = dragonx::ui::Layout::rawDpiScale();
|
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,
|
SDL_SetWindowMinimumSize(window,
|
||||||
(int)(1024 * hwDpi * curFS),
|
(int)(1024 * hwDpi * curFS),
|
||||||
(int)(720 * hwDpi * curFS));
|
(int)(720 * hwDpi * curFS));
|
||||||
|
SDL_SetWindowSize(window, newW, newH);
|
||||||
lastKnownW = newW;
|
lastKnownW = newW;
|
||||||
lastKnownH = newH;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,18 +43,30 @@ namespace Layout {
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
inline float& userFontScaleRef() { static float s = 1.0f; return s; }
|
inline float& userFontScaleRef() { static float s = 1.0f; return s; }
|
||||||
inline bool& fontReloadNeededRef() { static bool s = false; 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.
|
* Multiplied into font loading so glyphs render at the chosen size.
|
||||||
*/
|
*/
|
||||||
inline float userFontScale() { return detail::userFontScaleRef(); }
|
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.
|
* @brief Set the user's font scale and flag a font reload.
|
||||||
* Called from the settings UI; the main loop detects the flag and
|
* Called on slider release for a crisp atlas rebuild.
|
||||||
* calls Typography::reload().
|
|
||||||
*/
|
*/
|
||||||
inline void setUserFontScale(float v) {
|
inline void setUserFontScale(float v) {
|
||||||
v = std::max(1.0f, std::min(1.5f, 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).
|
* @brief Consume the pending font-reload flag (returns true once).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -206,7 +206,9 @@ bool Typography::load(ImGuiIO& io, float dpiScale)
|
|||||||
io.FontDefault = fonts_[static_cast<int>(TypeStyle::Body1)];
|
io.FontDefault = fonts_[static_cast<int>(TypeStyle::Body1)];
|
||||||
|
|
||||||
loaded_ = true;
|
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;
|
return allLoaded;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -888,7 +888,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Card and sidebar opacity (100%% = fully opaque, lower = more see-through)");
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Card and sidebar opacity (100%% = fully opaque, lower = more see-through)");
|
||||||
|
|
||||||
// Window Opacity slider (label above)
|
// Window Opacity slider (label above)
|
||||||
ImGui::TextUnformatted("Window");
|
ImGui::TextUnformatted("Window Opacity");
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
{
|
{
|
||||||
char winop_fmt[16];
|
char winop_fmt[16];
|
||||||
@@ -912,20 +912,26 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImGui::PushFont(body2);
|
ImGui::PushFont(body2);
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::TextUnformatted("Font Scale");
|
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);
|
ImGui::SetNextItemWidth(fontSliderW);
|
||||||
float prev_font_scale = sp_font_scale;
|
float prev_font_scale = sp_font_scale;
|
||||||
{
|
{
|
||||||
char fs_fmt[16];
|
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,
|
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
|
||||||
ImGuiSliderFlags_AlwaysClamp);
|
ImGuiSliderFlags_AlwaysClamp);
|
||||||
}
|
}
|
||||||
// Snap to nearest 0.1 and apply live as the user drags.
|
// Smooth continuous scaling while dragging.
|
||||||
// Font atlas rebuild is deferred to preFrame() (before NewFrame),
|
// Visual scaling uses FontScaleMain (no atlas rebuild),
|
||||||
// so updating every tick is safe — no dangling font pointers.
|
// atlas rebuild is deferred to slider release for crisp text.
|
||||||
sp_font_scale = std::round(sp_font_scale * 10.0f) / 10.0f;
|
sp_font_scale = std::max(1.0f, std::min(1.5f, sp_font_scale));
|
||||||
if (sp_font_scale != prev_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);
|
Layout::setUserFontScale(sp_font_scale);
|
||||||
saveSettingsPageState(app->settings());
|
saveSettingsPageState(app->settings());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
// Draw a Material Design icon centered at (cx, cy) with the given color.
|
||||||
// Uses the medium (18px) icon font from Typography.
|
// Uses the medium (18px) icon font from Typography.
|
||||||
inline void DrawNavIcon(ImDrawList* dl, NavPage page, float cx, float cy, float /*s*/, ImU32 col)
|
inline void DrawNavIcon(ImDrawList* dl, NavPage page, float cx, float cy, float /*s*/, ImU32 col)
|
||||||
{
|
{
|
||||||
ImFont* iconFont = material::Type().iconMed();
|
ImFont* iconFont = material::Type().iconMed();
|
||||||
const char* icon = GetNavIconMD(page);
|
const char* icon = GetNavIconMD(page);
|
||||||
ImVec2 sz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, icon);
|
float fsz = ScaledFontSize(iconFont);
|
||||||
dl->AddText(iconFont, iconFont->LegacySize,
|
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);
|
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();
|
ImFont* iconFont = Type().iconSmall();
|
||||||
const char* chevIcon = collapsed ? ICON_MD_CHEVRON_RIGHT : ICON_MD_CHEVRON_LEFT;
|
const char* chevIcon = collapsed ? ICON_MD_CHEVRON_RIGHT : ICON_MD_CHEVRON_LEFT;
|
||||||
ImVec2 chevSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, chevIcon);
|
float chevFsz = ScaledFontSize(iconFont);
|
||||||
dl->AddText(iconFont, iconFont->LegacySize,
|
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);
|
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;
|
float labelY = ImGui::GetCursorScreenPos().y;
|
||||||
ImVec4 olCol = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
|
ImVec4 olCol = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
|
||||||
olCol.w *= expandFrac;
|
olCol.w *= expandFrac;
|
||||||
dl->AddText(olFont, olFont->LegacySize,
|
float olFsz = ScaledFontSize(olFont);
|
||||||
|
dl->AddText(olFont, olFsz,
|
||||||
ImVec2(wp.x + sbSectionLabelPadLeft, labelY),
|
ImVec2(wp.x + sbSectionLabelPadLeft, labelY),
|
||||||
ImGui::ColorConvertFloat4ToU32(olCol), item.section_label);
|
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) {
|
} else if (item.section_label && !showLabels) {
|
||||||
// Collapsed: thin separator instead of label
|
// Collapsed: thin separator instead of label
|
||||||
ImGui::Dummy(ImVec2(0, sbSectionGap * 0.4f));
|
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
|
// Measure total width of icon + gap + label, then center
|
||||||
ImFont* font = selected ? Type().subtitle2() : Type().body2();
|
ImFont* font = selected ? Type().subtitle2() : Type().body2();
|
||||||
float gap = iconLabelGap;
|
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 totalW = iconS * 2.0f + gap + labelSz.x;
|
||||||
float btnCX = (indMin.x + indMax.x) * 0.5f;
|
float btnCX = (indMin.x + indMax.x) * 0.5f;
|
||||||
float startX = btnCX - totalW * 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;
|
float labelX = startX + iconS * 2.0f + gap;
|
||||||
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol);
|
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol);
|
||||||
lc.w *= expandFrac;
|
lc.w *= expandFrac;
|
||||||
dl->AddText(font, font->LegacySize, ImVec2(labelX, textY),
|
dl->AddText(font, lblFsz, ImVec2(labelX, textY),
|
||||||
ImGui::ColorConvertFloat4ToU32(lc), item.label);
|
ImGui::ColorConvertFloat4ToU32(lc), item.label);
|
||||||
} else {
|
} else {
|
||||||
float iconCX = (indMin.x + indMax.x) * 0.5f;
|
float iconCX = (indMin.x + indMax.x) * 0.5f;
|
||||||
@@ -656,8 +668,9 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
char buf[16];
|
char buf[16];
|
||||||
snprintf(buf, sizeof(buf), "%d", badgeCount > 99 ? 99 : badgeCount);
|
snprintf(buf, sizeof(buf), "%d", badgeCount > 99 ? 99 : badgeCount);
|
||||||
ImFont* capFont = Type().caption();
|
ImFont* capFont = Type().caption();
|
||||||
ImVec2 ts = capFont->CalcTextSizeA(capFont->LegacySize, 1000.0f, 0.0f, buf);
|
float capFsz = ScaledFontSize(capFont);
|
||||||
dl->AddText(capFont, capFont->LegacySize,
|
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),
|
ImVec2(badgeX - ts.x * 0.5f, badgeY - ts.y * 0.5f),
|
||||||
badgeTextCol, buf);
|
badgeTextCol, buf);
|
||||||
}
|
}
|
||||||
@@ -777,25 +790,28 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
ImFont* iconFont = Type().iconSmall();
|
ImFont* iconFont = Type().iconSmall();
|
||||||
ImFont* font = Type().caption();
|
ImFont* font = Type().caption();
|
||||||
const char* exitIcon = ICON_MD_EXIT_TO_APP;
|
const char* exitIcon = ICON_MD_EXIT_TO_APP;
|
||||||
ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, exitIcon);
|
float eIconFsz = ScaledFontSize(iconFont);
|
||||||
ImVec2 labelSz = font->CalcTextSizeA(font->LegacySize, 1000.0f, 0.0f, "Exit");
|
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 gap = exitIconGap;
|
||||||
float totalW = iconSz.x + gap + labelSz.x;
|
float totalW = iconSz.x + gap + labelSz.x;
|
||||||
float startX = cx - totalW * 0.5f;
|
float startX = cx - totalW * 0.5f;
|
||||||
|
|
||||||
dl->AddText(iconFont, iconFont->LegacySize,
|
dl->AddText(iconFont, eIconFsz,
|
||||||
ImVec2(startX, cy - iconSz.y * 0.5f), exitCol, exitIcon);
|
ImVec2(startX, cy - iconSz.y * 0.5f), exitCol, exitIcon);
|
||||||
|
|
||||||
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(exitCol);
|
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(exitCol);
|
||||||
lc.w *= expandFrac;
|
lc.w *= expandFrac;
|
||||||
dl->AddText(font, font->LegacySize,
|
dl->AddText(font, eLblFsz,
|
||||||
ImVec2(startX + iconSz.x + gap, cy - labelSz.y * 0.5f),
|
ImVec2(startX + iconSz.x + gap, cy - labelSz.y * 0.5f),
|
||||||
ImGui::ColorConvertFloat4ToU32(lc), "Exit");
|
ImGui::ColorConvertFloat4ToU32(lc), "Exit");
|
||||||
} else {
|
} else {
|
||||||
ImFont* iconFont = Type().iconSmall();
|
ImFont* iconFont = Type().iconSmall();
|
||||||
const char* exitIcon = ICON_MD_EXIT_TO_APP;
|
const char* exitIcon = ICON_MD_EXIT_TO_APP;
|
||||||
ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, exitIcon);
|
float eIconFsz = ScaledFontSize(iconFont);
|
||||||
dl->AddText(iconFont, iconFont->LegacySize,
|
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),
|
ImVec2(cx - iconSz.x * 0.5f, cy - iconSz.y * 0.5f),
|
||||||
exitCol, exitIcon);
|
exitCol, exitIcon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,12 +46,6 @@ static bool s_pool_settings_dirty = false;
|
|||||||
static bool s_pool_state_loaded = false;
|
static bool s_pool_state_loaded = false;
|
||||||
static bool s_show_pool_log = false; // Toggle: false=chart, true=log
|
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
|
// Get max threads based on hardware
|
||||||
static int GetMaxMiningThreads()
|
static int GetMaxMiningThreads()
|
||||||
{
|
{
|
||||||
@@ -989,33 +983,15 @@ void RenderMiningTab(App* app)
|
|||||||
float plotW = plotRight - plotLeft;
|
float plotW = plotRight - plotLeft;
|
||||||
float plotH = std::max(1.0f, plotBottom - plotTop);
|
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();
|
size_t n = chartHistory.size();
|
||||||
double newestVal = chartHistory.back();
|
float stepW = (n > 1) ? plotW / (float)(n - 1) : plotW;
|
||||||
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;
|
|
||||||
|
|
||||||
std::vector<ImVec2> rawPts(n);
|
std::vector<ImVec2> rawPts(n);
|
||||||
for (size_t i = 0; i < n; i++) {
|
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;
|
float y = plotBottom - (float)((chartHistory[i] - yMin) / (yMax - yMin)) * plotH;
|
||||||
rawPts[i] = ImVec2(x, y);
|
rawPts[i] = ImVec2(x, y);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user