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) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 17:18:13 -05:00
parent c352364bdc
commit bedb482855
2 changed files with 85 additions and 78 deletions

View File

@@ -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();
}

View File

@@ -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";