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:
@@ -104,7 +104,9 @@ public:
|
|||||||
ov.p_open = active ? nullptr : &s_open;
|
ov.p_open = active ? nullptr : &s_open;
|
||||||
ov.style = OverlayStyle::BlurFloat;
|
ov.style = OverlayStyle::BlurFloat;
|
||||||
ov.cardWidth = 880.0f;
|
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";
|
ov.idSuffix = "daemonupd";
|
||||||
if (BeginOverlayDialog(ov)) {
|
if (BeginOverlayDialog(ov)) {
|
||||||
switch (p.state) {
|
switch (p.state) {
|
||||||
@@ -226,11 +228,10 @@ private:
|
|||||||
ImFont* capF = Type().caption();
|
ImFont* capF = Type().caption();
|
||||||
|
|
||||||
const bool showFooter = (p.state == St::ReleaseList);
|
const bool showFooter = (p.state == St::ReleaseList);
|
||||||
// Budget the whole footer block — the Spacing + Separator + Spacing + button row and every
|
// Budget the footer block (a gap + the enlarged Close button, no divider) so the two-pane
|
||||||
// inter-item gap — so the two-pane body leaves exact room and nothing overflows the card.
|
// body leaves exact room and nothing overflows the card.
|
||||||
const float footerH = showFooter
|
const float footerH = showFooter
|
||||||
? (ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 3.0f
|
? (ImGui::GetFrameHeight() * 1.25f + ImGui::GetStyle().ItemSpacing.y * 2.0f + Layout::spacingSm())
|
||||||
+ Layout::spacingSm() * 2.0f + 1.0f)
|
|
||||||
: 0.0f;
|
: 0.0f;
|
||||||
const float bodyH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - footerH);
|
const float bodyH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - footerH);
|
||||||
const float contentW = ImGui::GetContentRegionAvail().x;
|
const float contentW = ImGui::GetContentRegionAvail().x;
|
||||||
@@ -248,9 +249,10 @@ private:
|
|||||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0));
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0));
|
||||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0, 0, 0, 0));
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0, 0, 0, 0));
|
||||||
ImDrawList* mdl = ImGui::GetWindowDrawList();
|
ImDrawList* mdl = ImGui::GetWindowDrawList();
|
||||||
|
ImFont* icoF = Type().iconSmall();
|
||||||
const float rowH = Layout::spacingMd() * 2.0f + bodyF->LegacySize + Layout::spacingXs() + capF->LegacySize;
|
const float rowH = Layout::spacingMd() * 2.0f + bodyF->LegacySize + Layout::spacingXs() + capF->LegacySize;
|
||||||
const float rowW = ImGui::GetContentRegionAvail().x;
|
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) {
|
for (int i = 0; i < (int)s_rows.size(); ++i) {
|
||||||
const ReleaseRow& r = s_rows[i];
|
const ReleaseRow& r = s_rows[i];
|
||||||
ImGui::PushID(i);
|
ImGui::PushID(i);
|
||||||
@@ -270,11 +272,16 @@ 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);
|
||||||
// Left accent bar (rounded), keyed to the version's status.
|
// Active-daemon marker: a check on the right edge for the currently-installed version.
|
||||||
ImU32 accent = r.installed ? Success() : (i == 0 ? Primary() : WithAlpha(OnSurface(), 70));
|
float rightLimit = rmx.x - Layout::spacingMd();
|
||||||
mdl->AddRectFilled(ImVec2(rmn.x + 4.0f * dp, rmn.y + 8.0f * dp),
|
if (r.installed) {
|
||||||
ImVec2(rmn.x + 7.0f * dp, rmx.y - 8.0f * dp), accent, 2.0f * dp);
|
float cs = bodyF->LegacySize * 1.15f;
|
||||||
float x = rmn.x + Layout::spacingMd() + 5.0f * dp;
|
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();
|
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());
|
||||||
@@ -282,7 +289,7 @@ private:
|
|||||||
float by = tagY + (bodyF->LegacySize - capF->LegacySize) * 0.5f;
|
float by = tagY + (bodyF->LegacySize - capF->LegacySize) * 0.5f;
|
||||||
auto badge = [&](const char* t, ImU32 c) {
|
auto badge = [&](const char* t, ImU32 c) {
|
||||||
float tw = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, t).x;
|
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);
|
mdl->AddText(capF, capF->LegacySize, ImVec2(bx, by), c, t);
|
||||||
bx += tw + Layout::spacingSm();
|
bx += tw + Layout::spacingSm();
|
||||||
};
|
};
|
||||||
@@ -316,14 +323,13 @@ private:
|
|||||||
}
|
}
|
||||||
ImGui::EndChild();
|
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) {
|
if (showFooter) {
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::Separator();
|
const float bw = 170.0f * dp, bh = ImGui::GetFrameHeight() * 1.25f;
|
||||||
ImGui::Spacing();
|
|
||||||
const float bw = 130.0f * dp;
|
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, ImGui::GetContentRegionAvail().x - bw));
|
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 ReleaseRow& row = s_rows[s_sel];
|
||||||
const std::string instCore = util::daemonVersionCore(s_installed_version);
|
const std::string instCore = util::daemonVersionCore(s_installed_version);
|
||||||
const bool haveInstalled = !instCore.empty();
|
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 bool downgrade = !row.installed && s_sel != 0 && haveInstalled;
|
||||||
|
|
||||||
const char* noteStr = !row.hasAsset ? TR("upd_no_build_platform")
|
const char* noteStr = !row.hasAsset ? TR("upd_no_build_platform")
|
||||||
: downgrade ? TR("daemon_update_downgrade_note")
|
: downgrade ? TR("daemon_update_downgrade_note")
|
||||||
: TR("daemon_update_verify_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")
|
const char* label = row.installed ? TR("daemon_update_reinstall")
|
||||||
: (s_sel == 0) ? TR("daemon_update_download_install")
|
: (s_sel == 0) ? TR("daemon_update_download_install")
|
||||||
: TR("daemon_update_install_this");
|
: 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);
|
ImGui::BeginDisabled(!row.hasAsset);
|
||||||
if (TactileButton(label, ImVec2(fullW(), 0))) startInstallSelected();
|
if (TactileButton(label, ImVec2(0.0f, btnH))) startInstallSelected();
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1488,6 +1488,7 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["daemon_update_released"] = "Released:";
|
strings_["daemon_update_released"] = "Released:";
|
||||||
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";
|
||||||
|
|
||||||
// --- Lite Network tab (server browser) ---
|
// --- Lite Network tab (server browser) ---
|
||||||
strings_["lite_net_title"] = "Lite Servers";
|
strings_["lite_net_title"] = "Lite Servers";
|
||||||
|
|||||||
Reference in New Issue
Block a user