refine(daemon-update): pad info surface, right-aligned icon badges, centered install

- Fix the info surface padding: a borderless BeginChild ignores WindowPadding
  unless ImGuiChildFlags_AlwaysUseWindowPadding is set, so the content was
  touching the surface edges. Add the flag + generous padding, and make the
  surface a touch more visible so it reads as a container.
- Version list: replace the inline text badges with a single right-aligned icon
  badge per item — check (installed/active), science flask (pre-release), or
  new-releases (newest available) — each with a tooltip.
- Center the Download & install button (still sized to its text).

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:33:18 -05:00
parent bedb482855
commit 96a1c54556
2 changed files with 24 additions and 29 deletions

View File

@@ -272,30 +272,21 @@ private:
mdl->AddRectFilled(rmn, rmx, fill, round); mdl->AddRectFilled(rmn, rmx, fill, round);
if (sel) mdl->AddRect(rmn, rmx, WithAlpha(Primary(), 150), round, 0, 1.6f * dp); if (sel) mdl->AddRect(rmn, rmx, WithAlpha(Primary(), 150), round, 0, 1.6f * dp);
if (hov || held) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (hov || held) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
// Active-daemon marker: a check on the right edge for the currently-installed version. // Right-aligned status icon badge — active (installed), pre-release, or newest.
float rightLimit = rmx.x - Layout::spacingMd(); const char* ico = nullptr; ImU32 icoC = 0; const char* tt = nullptr;
if (r.installed) { if (r.installed) { ico = ICON_MD_CHECK_CIRCLE; icoC = Success(); tt = TR("daemon_update_active_tt"); }
float cs = bodyF->LegacySize * 1.15f; else if (r.prerelease) { ico = ICON_MD_SCIENCE; icoC = Warning(); tt = TR("daemon_update_prerelease_tt"); }
else if (i == 0) { ico = ICON_MD_NEW_RELEASES; icoC = Primary(); tt = TR("daemon_update_latest_tt"); }
if (ico) {
float cs = bodyF->LegacySize * 1.3f;
ImVec2 cp(rmx.x - Layout::spacingMd() - cs, (rmn.y + rmx.y) * 0.5f - cs * 0.5f); 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); mdl->AddText(icoF, cs, cp, icoC, ico);
rightLimit = cp.x - Layout::spacingSm(); if ((hov || sel) && tt) Tooltip("%s", tt);
if (hov || sel) Tooltip("%s", TR("daemon_update_active_tt"));
} }
float x = rmn.x + Layout::spacingMd(); float x = rmn.x + Layout::spacingMd();
float tagY = rmn.y + Layout::spacingMd(); float tagY = rmn.y + Layout::spacingMd();
mdl->AddText(bodyF, bodyF->LegacySize, ImVec2(x, tagY), mdl->AddText(bodyF, bodyF->LegacySize, ImVec2(x, tagY),
r.hasAsset ? OnSurface() : OnSurfaceDisabled(), r.tag.c_str()); r.hasAsset ? OnSurface() : OnSurfaceDisabled(), r.tag.c_str());
float bx = x + bodyF->CalcTextSizeA(bodyF->LegacySize, FLT_MAX, 0, r.tag.c_str()).x + Layout::spacingSm();
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 > rightLimit) return; // no room: skip (long tag / active icon)
mdl->AddText(capF, capF->LegacySize, ImVec2(bx, by), c, t);
bx += tw + Layout::spacingSm();
};
if (r.installed) badge(TR("upd_installed_badge"), Success());
else if (i == 0) badge(TR("daemon_update_latest_badge"), Primary());
if (r.prerelease) badge(TR("upd_prerelease"), Warning());
if (!r.date.empty()) if (!r.date.empty())
mdl->AddText(capF, capF->LegacySize, mdl->AddText(capF, capF->LegacySize,
ImVec2(x, rmx.y - Layout::spacingMd() - capF->LegacySize), ImVec2(x, rmx.y - Layout::spacingMd() - capF->LegacySize),
@@ -357,11 +348,13 @@ private:
const float reserve = noteH + btnH + Layout::spacingMd() * 3.0f; const float reserve = noteH + btnH + Layout::spacingMd() * 3.0f;
const float infoH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - reserve); const float infoH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - reserve);
// ---- Info card: ONE rounded Material surface wrapping the version header + release notes ---- // ---- Info card: ONE rounded Material surface wrapping the version header + release notes.
ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 14)); // AlwaysUseWindowPadding is required or a borderless child ignores WindowPadding (which
// let the content touch the surface edges). ----
ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 22));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f * dp); // slightly smaller than the notes had ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f * dp); // slightly smaller than the notes had
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingMd())); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingLg()));
ImGui::BeginChild("##daemonInfo", ImVec2(0, infoH), false); ImGui::BeginChild("##daemonInfo", ImVec2(0, infoH), ImGuiChildFlags_AlwaysUseWindowPadding);
{ {
// Header (pinned): tag + status + installed/released — no divider, just a gap. // Header (pinned): tag + status + installed/released — no divider, just a gap.
Type().text(TypeStyle::H6, rel.tag.c_str()); Type().text(TypeStyle::H6, rel.tag.c_str());
@@ -385,18 +378,16 @@ private:
(std::string(TR("daemon_update_released")) + " " + row.date).c_str()); (std::string(TR("daemon_update_released")) + " " + row.date).c_str());
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// Notes: markdown, scrolls within the info card (header stays pinned above it). A little // Notes: markdown, scrolls within the info card (header stays pinned above it). The info
// inner padding keeps the text off the surface edges. // card's WindowPadding already insets it from the surface edges.
const std::string notes = cleanNotes(rel.body); const std::string notes = cleanNotes(rel.body);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); 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); ImGui::BeginChild("##daemonNotes", ImVec2(0, std::max(0.0f, ImGui::GetContentRegionAvail().y)), false);
if (notes.empty()) if (notes.empty())
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("daemon_update_no_notes")); Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("daemon_update_no_notes"));
else else
RenderMarkdown(notes, ImGui::GetContentRegionAvail().x - Layout::spacingSm()); RenderMarkdown(notes);
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
ImGui::EndChild(); ImGui::EndChild();
@@ -407,9 +398,11 @@ private:
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
Type().textColored(TypeStyle::Caption, downgrade ? Warning() : OnSurfaceMedium(), noteStr); Type().textColored(TypeStyle::Caption, downgrade ? Warning() : OnSurfaceMedium(), noteStr);
ImGui::Spacing(); ImGui::Spacing();
// Install button sized to its text (not the full pane width). // Install button sized to its text and centered in the pane.
const float bw = ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, (ImGui::GetContentRegionAvail().x - bw) * 0.5f));
ImGui::BeginDisabled(!row.hasAsset); ImGui::BeginDisabled(!row.hasAsset);
if (TactileButton(label, ImVec2(0.0f, btnH))) startInstallSelected(); if (TactileButton(label, ImVec2(bw, btnH))) startInstallSelected();
ImGui::EndDisabled(); ImGui::EndDisabled();
} }

View File

@@ -1489,6 +1489,8 @@ void I18n::loadBuiltinEnglish()
strings_["daemon_update_no_notes"] = "No release notes for this version."; strings_["daemon_update_no_notes"] = "No release notes for this version.";
strings_["daemon_update_install_this"] = "Install this version"; strings_["daemon_update_install_this"] = "Install this version";
strings_["daemon_update_active_tt"] = "The currently installed (active) node"; strings_["daemon_update_active_tt"] = "The currently installed (active) node";
strings_["daemon_update_latest_tt"] = "Newest available version";
strings_["daemon_update_prerelease_tt"] = "Pre-release / testing build";
// --- Lite Network tab (server browser) --- // --- Lite Network tab (server browser) ---
strings_["lite_net_title"] = "Lite Servers"; strings_["lite_net_title"] = "Lite Servers";