From bedb482855e98a4c3021d8ecd07c48bbd499d128 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 10 Jul 2026 17:18:13 -0500 Subject: [PATCH] refine(daemon-update): taller card, unified info surface, tactile card polish Design pass on the two-pane node updater: - Taller card (a generous share of the window) so release notes get vertical room and usually don't need to scroll. - Wrap the version header + release notes in ONE rounded Material surface (drop the divider between them); the notes markdown gets inner padding so it doesn't touch the edges, and the header stays pinned while long notes scroll within. - The verify note + install button now sit OUTSIDE that surface; the install button is sized to its text instead of full width. - Version cards: slightly smaller radius, accent bar removed, and the currently installed (active) node is marked with a check on the right of its card. - Footer: dropped the divider above Close and enlarged the Close button. Verified 100% + 150%, dark + light. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/daemon_download_dialog.h | 162 ++++++++++++------------ src/util/i18n.cpp | 1 + 2 files changed, 85 insertions(+), 78 deletions(-) diff --git a/src/ui/windows/daemon_download_dialog.h b/src/ui/windows/daemon_download_dialog.h index 0c3d3d4..5180e45 100644 --- a/src/ui/windows/daemon_download_dialog.h +++ b/src/ui/windows/daemon_download_dialog.h @@ -104,7 +104,9 @@ public: ov.p_open = active ? nullptr : &s_open; ov.style = OverlayStyle::BlurFloat; ov.cardWidth = 880.0f; - ov.cardHeight = 540.0f; + // Tall card (a generous share of the window) so the release notes get vertical room and + // usually don't need to scroll; the layout is GetContentRegionAvail-based, so it adapts. + ov.cardHeight = std::min(760.0f, ImGui::GetMainViewport()->Size.y * 0.84f / dp); ov.idSuffix = "daemonupd"; if (BeginOverlayDialog(ov)) { switch (p.state) { @@ -226,11 +228,10 @@ private: ImFont* capF = Type().caption(); const bool showFooter = (p.state == St::ReleaseList); - // Budget the whole footer block — the Spacing + Separator + Spacing + button row and every - // inter-item gap — so the two-pane body leaves exact room and nothing overflows the card. + // Budget the footer block (a gap + the enlarged Close button, no divider) so the two-pane + // body leaves exact room and nothing overflows the card. const float footerH = showFooter - ? (ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 3.0f - + Layout::spacingSm() * 2.0f + 1.0f) + ? (ImGui::GetFrameHeight() * 1.25f + ImGui::GetStyle().ItemSpacing.y * 2.0f + Layout::spacingSm()) : 0.0f; const float bodyH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - footerH); const float contentW = ImGui::GetContentRegionAvail().x; @@ -248,9 +249,10 @@ private: ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0, 0, 0, 0)); ImDrawList* mdl = ImGui::GetWindowDrawList(); + ImFont* icoF = Type().iconSmall(); const float rowH = Layout::spacingMd() * 2.0f + bodyF->LegacySize + Layout::spacingXs() + capF->LegacySize; const float rowW = ImGui::GetContentRegionAvail().x; - const float round = 10.0f * dp; + const float round = 8.0f * dp; for (int i = 0; i < (int)s_rows.size(); ++i) { const ReleaseRow& r = s_rows[i]; ImGui::PushID(i); @@ -270,11 +272,16 @@ private: mdl->AddRectFilled(rmn, rmx, fill, round); if (sel) mdl->AddRect(rmn, rmx, WithAlpha(Primary(), 150), round, 0, 1.6f * dp); if (hov || held) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - // Left accent bar (rounded), keyed to the version's status. - ImU32 accent = r.installed ? Success() : (i == 0 ? Primary() : WithAlpha(OnSurface(), 70)); - mdl->AddRectFilled(ImVec2(rmn.x + 4.0f * dp, rmn.y + 8.0f * dp), - ImVec2(rmn.x + 7.0f * dp, rmx.y - 8.0f * dp), accent, 2.0f * dp); - float x = rmn.x + Layout::spacingMd() + 5.0f * dp; + // Active-daemon marker: a check on the right edge for the currently-installed version. + float rightLimit = rmx.x - Layout::spacingMd(); + if (r.installed) { + float cs = bodyF->LegacySize * 1.15f; + ImVec2 cp(rmx.x - Layout::spacingMd() - cs, (rmn.y + rmx.y) * 0.5f - cs * 0.5f); + mdl->AddText(icoF, cs, cp, Success(), ICON_MD_CHECK_CIRCLE); + rightLimit = cp.x - Layout::spacingSm(); + if (hov || sel) Tooltip("%s", TR("daemon_update_active_tt")); + } + float x = rmn.x + Layout::spacingMd(); float tagY = rmn.y + Layout::spacingMd(); mdl->AddText(bodyF, bodyF->LegacySize, ImVec2(x, tagY), r.hasAsset ? OnSurface() : OnSurfaceDisabled(), r.tag.c_str()); @@ -282,7 +289,7 @@ private: float by = tagY + (bodyF->LegacySize - capF->LegacySize) * 0.5f; auto badge = [&](const char* t, ImU32 c) { float tw = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, t).x; - if (bx + tw > rmx.x - Layout::spacingSm()) return; // no room: skip (long tag) + if (bx + tw > rightLimit) return; // no room: skip (long tag / active icon) mdl->AddText(capF, capF->LegacySize, ImVec2(bx, by), c, t); bx += tw + Layout::spacingSm(); }; @@ -316,14 +323,13 @@ private: } ImGui::EndChild(); - // ---- Footer: Close (only while browsing; transient states carry their own actions) ---- + // ---- Footer: Close (only while browsing; transient states carry their own actions). No + // divider above it — a larger, right-aligned button reads as the modal-level dismiss. ---- if (showFooter) { ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - const float bw = 130.0f * dp; + const float bw = 170.0f * dp, bh = ImGui::GetFrameHeight() * 1.25f; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, ImGui::GetContentRegionAvail().x - bw)); - if (TactileButton(TR("close"), ImVec2(bw, 0))) s_open = false; + if (TactileButton(TR("close"), ImVec2(bw, bh))) s_open = false; } } @@ -335,75 +341,75 @@ private: const ReleaseRow& row = s_rows[s_sel]; const std::string instCore = util::daemonVersionCore(s_installed_version); const bool haveInstalled = !instCore.empty(); - - // Header: version tag. - Type().text(TypeStyle::H6, rel.tag.c_str()); - - // Status line — installed / newer / older, colored. - if (row.installed) { - Type().textColored(TypeStyle::Subtitle2, Success(), TR("daemon_update_status_current")); - } else if (s_sel == 0) { - Type().textColored(TypeStyle::Subtitle2, Primary(), - haveInstalled ? TR("daemon_update_available") : TR("daemon_update_status_latest")); - } else if (haveInstalled) { - Type().textColored(TypeStyle::Subtitle2, Warning(), TR("daemon_update_status_older")); - } else { - Type().text(TypeStyle::Subtitle2, rel.name.empty() ? rel.tag.c_str() : rel.name.c_str()); - } - - // Meta: installed comparison + date. - const std::string installed = s_installed_version.empty() ? std::string(TR("xmrig_none")) - : s_installed_version; - ImGui::Spacing(); - Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), (std::string(TR("daemon_update_installed")) + " " + installed).c_str()); - if (!row.date.empty()) - Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), (std::string(TR("daemon_update_released")) + " " + row.date).c_str()); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Reserve the bottom for the verify/downgrade note + the install button; notes fill the rest. - const float btnH = ImGui::GetFrameHeight(); const bool downgrade = !row.installed && s_sel != 0 && haveInstalled; + const char* noteStr = !row.hasAsset ? TR("upd_no_build_platform") : downgrade ? TR("daemon_update_downgrade_note") : TR("daemon_update_verify_note"); - const float noteW = ImGui::GetContentRegionAvail().x; - const float noteH = ImGui::CalcTextSize(noteStr, nullptr, false, noteW).y; - const float reserve = noteH + btnH + Layout::spacingMd() * 2.0f; - // Notes fill whatever's left ABOVE the reserved verify-note + install button. If the pane is - // too short to fit a useful notes box, drop it entirely so the install button stays visible - // (never let a min-height notes child push the button into a scroll region). - const float roomForNotes = ImGui::GetContentRegionAvail().y - reserve; - if (roomForNotes >= 44.0f * dp) { - // Filled, rounded Material surface (no thin outline); notes rendered as markdown. - ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 14)); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 12.0f * dp); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingMd(), Layout::spacingMd())); - ImGui::BeginChild("##daemonNotes", ImVec2(0, roomForNotes), false); - { - const std::string notes = cleanNotes(rel.body); - if (notes.empty()) - Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("daemon_update_no_notes")); - else - RenderMarkdown(notes); - } - ImGui::EndChild(); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); - ImGui::Spacing(); - } - - Type().textColored(TypeStyle::Caption, downgrade ? Warning() : OnSurfaceMedium(), noteStr); - ImGui::Spacing(); - - // Install button (full detail width). const char* label = row.installed ? TR("daemon_update_reinstall") : (s_sel == 0) ? TR("daemon_update_download_install") : TR("daemon_update_install_this"); + + // Reserve the bottom for the verify/downgrade note + the install button, so the info card + // fills the space above them. + const float btnH = ImGui::GetFrameHeight() * 1.15f; + const float noteH = ImGui::CalcTextSize(noteStr, nullptr, false, ImGui::GetContentRegionAvail().x).y; + const float reserve = noteH + btnH + Layout::spacingMd() * 3.0f; + const float infoH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - reserve); + + // ---- Info card: ONE rounded Material surface wrapping the version header + release notes ---- + ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 14)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f * dp); // slightly smaller than the notes had + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingMd())); + ImGui::BeginChild("##daemonInfo", ImVec2(0, infoH), false); + { + // Header (pinned): tag + status + installed/released — no divider, just a gap. + Type().text(TypeStyle::H6, rel.tag.c_str()); + if (row.installed) + Type().textColored(TypeStyle::Subtitle2, Success(), TR("daemon_update_status_current")); + else if (s_sel == 0) + Type().textColored(TypeStyle::Subtitle2, Primary(), + haveInstalled ? TR("daemon_update_available") : TR("daemon_update_status_latest")); + else if (haveInstalled) + Type().textColored(TypeStyle::Subtitle2, Warning(), TR("daemon_update_status_older")); + else + Type().text(TypeStyle::Subtitle2, rel.name.empty() ? rel.tag.c_str() : rel.name.c_str()); + + const std::string installed = s_installed_version.empty() ? std::string(TR("xmrig_none")) + : s_installed_version; + ImGui::Spacing(); + Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), + (std::string(TR("daemon_update_installed")) + " " + installed).c_str()); + if (!row.date.empty()) + Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), + (std::string(TR("daemon_update_released")) + " " + row.date).c_str()); + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + // Notes: markdown, scrolls within the info card (header stays pinned above it). A little + // inner padding keeps the text off the surface edges. + const std::string notes = cleanNotes(rel.body); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, Layout::spacingXs())); + ImGui::BeginChild("##daemonNotes", ImVec2(0, std::max(0.0f, ImGui::GetContentRegionAvail().y)), false); + if (notes.empty()) + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("daemon_update_no_notes")); + else + RenderMarkdown(notes, ImGui::GetContentRegionAvail().x - Layout::spacingSm()); + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + + // ---- Below the info card (outside the surface): verify note + install button ---- + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + Type().textColored(TypeStyle::Caption, downgrade ? Warning() : OnSurfaceMedium(), noteStr); + ImGui::Spacing(); + // Install button sized to its text (not the full pane width). ImGui::BeginDisabled(!row.hasAsset); - if (TactileButton(label, ImVec2(fullW(), 0))) startInstallSelected(); + if (TactileButton(label, ImVec2(0.0f, btnH))) startInstallSelected(); ImGui::EndDisabled(); } diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 7536b1e..7aa3151 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1488,6 +1488,7 @@ void I18n::loadBuiltinEnglish() strings_["daemon_update_released"] = "Released:"; strings_["daemon_update_no_notes"] = "No release notes for this version."; strings_["daemon_update_install_this"] = "Install this version"; + strings_["daemon_update_active_tt"] = "The currently installed (active) node"; // --- Lite Network tab (server browser) --- strings_["lite_net_title"] = "Lite Servers";