feat(xmrig-update): two-pane version picker matching the node updater
Bring the miner (DRG-XMRig) updater to the same design as the daemon updater: two-pane layout with tactile version cards (text label + right-aligned icon badge: installed check / pre-release flask / newest new-releases, hover-only tooltips) on the left, and the selected version's detail on the right — a unified rounded info surface holding the version header + markdown-rendered release notes, with the verify note and a centered, text-fit install button below it, and a larger Close in the footer. Fetches the full release list up front; Downloading/Done/Failed render in the right pane. Preserves the miner specifics: installs are refused while the miner is running (button disabled + a stop-mining note), and the installed tag is persisted via setXmrigVersion on success. Adds a modal-xmrig-update sweep surface (fake releases). Verified 100% + 150%, dark + light. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "ui/windows/send_tab.h"
|
||||
#include "ui/windows/wallets_dialog.h"
|
||||
#include "ui/windows/daemon_download_dialog.h"
|
||||
#include "ui/windows/xmrig_download_dialog.h"
|
||||
#include "util/platform.h"
|
||||
#include "wallet/wallet_capabilities.h"
|
||||
|
||||
@@ -476,6 +477,33 @@ void App::buildSweepCatalog()
|
||||
},
|
||||
[](App&) { ui::DaemonUpdateDialog::sweepClose(); });
|
||||
|
||||
// Miner (xmrig) updater — same two-pane version picker, seeded with fake releases for the sweep.
|
||||
add("modal-xmrig-update", ui::NavPage::Mining,
|
||||
[](App& a) {
|
||||
auto mk = [](const char* tag, const char* name, const char* date, const char* body, bool pre) {
|
||||
util::XmrigRelease r; r.ok = true; r.tag = tag; r.name = name; r.body = body;
|
||||
r.prerelease = pre; r.publishedAt = std::string(date) + "T10:00:00Z";
|
||||
util::XmrigReleaseAsset as;
|
||||
as.name = std::string("drg-xmrig-") + tag + "-linux-x64.zip";
|
||||
as.downloadUrl = "https://git.dragonx.is/" + as.name; as.size = 8000000;
|
||||
r.assets.push_back(as);
|
||||
return r;
|
||||
};
|
||||
std::vector<util::XmrigRelease> rels;
|
||||
rels.push_back(mk("v6.25.3", "DRG-XMRig v6.25.3", "2026-06-20",
|
||||
"## What's new\n- Rebased on upstream XMRig 6.25.3\n- **RandomX** JIT speedups on modern CPUs\n"
|
||||
"- Fix `--cpu-priority` parsing on Windows\n\n## Checksums\n| File | SHA-256 |\n"
|
||||
"|---|---|\n| drg-xmrig-v6.25.3-linux-x64.zip | `ab12cd34` |\n", false));
|
||||
rels.push_back(mk("v6.24.0", "DRG-XMRig v6.24.0", "2026-04-30",
|
||||
"## Notes\n- Pool TLS fixes\n- Lower idle CPU\n", false));
|
||||
rels.push_back(mk("v6.23.0", "DRG-XMRig v6.23.0", "2026-03-11",
|
||||
"- First DRG-XMRig build\n", false));
|
||||
rels.push_back(mk("v6.26.0-rc1", "DRG-XMRig v6.26.0-rc1", "2026-07-02",
|
||||
"Release candidate. **Testing only.**\n", true));
|
||||
ui::XmrigDownloadDialog::sweepSeed(&a, rels, util::XmrigUpdater::State::ReleaseList, "v6.24.0");
|
||||
},
|
||||
[](App&) { ui::XmrigDownloadDialog::sweepClose(); });
|
||||
|
||||
// In-app folder picker (Wallets → Scan another folder). Populate a small demo tree so the
|
||||
// list shows sub-folders + wallet files, then open the wallets dialog + the picker over it.
|
||||
add("modal-folder-picker", ui::NavPage::Settings,
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Modal dialog to download / update the DRG-XMRig miner from the project Gitea, driven by
|
||||
// util::XmrigUpdater. States follow the updater: Checking -> UpToDate/UpdateAvailable ->
|
||||
// Downloading/Verifying/Extracting -> Done / Failed. Header-only (static inline state), mirroring
|
||||
// BootstrapDownloadDialog. The installed release tag is persisted to settings on success.
|
||||
// util::XmrigUpdater. Two-pane layout (mirrors the node updater / Manage-Portfolio editor): the list
|
||||
// of miner versions on the LEFT, the selected version's detail + install action on the RIGHT.
|
||||
// States: Listing -> ReleaseList -> Downloading/Verifying/Extracting -> Done / Failed. Header-only.
|
||||
// The installed release tag is persisted to settings on success. Installs are refused while the
|
||||
// miner is running (you can't replace a running binary).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -18,9 +23,10 @@
|
||||
#include "../../resources/embedded_resources.h" // resources::getDaemonDirectory()
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/xmrig_updater.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/markdown.h"
|
||||
#include "../material/type.h"
|
||||
#include "../theme.h"
|
||||
#include "release_list_view.h"
|
||||
#include "imgui.h"
|
||||
@@ -30,151 +36,345 @@ namespace ui {
|
||||
|
||||
class XmrigDownloadDialog {
|
||||
public:
|
||||
using Progress = util::XmrigUpdater::Progress;
|
||||
using St = util::XmrigUpdater::State;
|
||||
|
||||
static void show(App* app) {
|
||||
if (!app) return;
|
||||
s_app = app;
|
||||
s_open = true;
|
||||
s_persisted = false;
|
||||
s_sel = 0;
|
||||
s_sweep = false;
|
||||
s_installed_tag = app->settings() ? app->settings()->getXmrigVersion() : std::string();
|
||||
s_rows.clear();
|
||||
s_releases.clear();
|
||||
s_updater = std::make_unique<util::XmrigUpdater>();
|
||||
s_updater->startCheck(s_installed_tag);
|
||||
s_updater->startListReleases(); // fetch every version up front for the two-pane picker
|
||||
}
|
||||
|
||||
static bool isOpen() { return s_open; }
|
||||
|
||||
// Sweep-only: seed fake releases + a forced state so the offline UI sweep can render the modal.
|
||||
static void sweepSeed(App* app, std::vector<util::XmrigRelease> releases, St state,
|
||||
const std::string& installedTag) {
|
||||
s_app = app;
|
||||
s_open = true;
|
||||
s_persisted = false;
|
||||
s_installed_tag = installedTag;
|
||||
s_sel = 0;
|
||||
s_sweep = true;
|
||||
s_releases = std::move(releases);
|
||||
s_rows.clear();
|
||||
s_updater.reset();
|
||||
s_sweepProgress = Progress{};
|
||||
s_sweepProgress.state = state;
|
||||
if (!s_releases.empty()) s_sweepProgress.latest_tag = s_releases.front().tag;
|
||||
s_sweepProgress.installed_tag = installedTag;
|
||||
s_sweepProgress.percent = 62.0f;
|
||||
s_sweepProgress.status_text = "drg-xmrig-linux-x64.zip";
|
||||
}
|
||||
static void sweepClose() { s_open = false; s_sweep = false; s_releases.clear(); s_rows.clear(); }
|
||||
|
||||
static void render() {
|
||||
if (!s_open || !s_app || !s_updater) {
|
||||
if (!s_open) s_updater.reset(); // closed: drop the updater (dtor joins the worker)
|
||||
if (!s_open || !s_app) {
|
||||
if (!s_open) s_updater.reset();
|
||||
return;
|
||||
}
|
||||
using namespace material;
|
||||
const float dp = Layout::dpiScale();
|
||||
const auto p = s_updater->getProgress();
|
||||
// No window X during an active download/verify/extract — closing would orphan/block on the
|
||||
// worker; the in-dialog Cancel is the intended way to abort.
|
||||
const Progress p = s_sweep ? s_sweepProgress
|
||||
: (s_updater ? s_updater->getProgress() : Progress{});
|
||||
const bool active = (p.state == St::Downloading || p.state == St::Verifying || p.state == St::Extracting);
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("xmrig_update_title"); ov.p_open = active ? nullptr : &s_open;
|
||||
ov.style = OverlayStyle::BlurFloat; ov.cardWidth = 480.0f; ov.cardBottomViewportRatio = 0.94f;
|
||||
ov.title = TR("xmrig_update_title");
|
||||
ov.p_open = active ? nullptr : &s_open;
|
||||
ov.style = OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 880.0f;
|
||||
ov.cardHeight = std::min(760.0f, ImGui::GetMainViewport()->Size.y * 0.84f / dp);
|
||||
ov.idSuffix = "xmrigupd";
|
||||
if (BeginOverlayDialog(ov)) {
|
||||
switch (p.state) {
|
||||
case St::Checking: renderChecking(dp, p); break;
|
||||
case St::Idle:
|
||||
case St::Checking:
|
||||
case St::UpToDate:
|
||||
case St::UpdateAvailable: renderPrompt(dp, p); break;
|
||||
case St::Unavailable: renderUnavailable(dp, p); break;
|
||||
case St::Listing: renderListing(dp, p); break;
|
||||
case St::ReleaseList: renderReleaseList(dp, p); break;
|
||||
case St::Downloading:
|
||||
case St::Verifying:
|
||||
case St::Extracting: renderProgress(dp, p); break;
|
||||
case St::Done: renderDone(dp, p); break;
|
||||
case St::Failed: renderFailed(dp, p); break;
|
||||
default: break;
|
||||
case St::UpdateAvailable:
|
||||
case St::Listing: renderCentered(TR("xmrig_loading_releases")); break;
|
||||
case St::Unavailable: renderUnavailable(p); break;
|
||||
case St::Failed:
|
||||
if (s_releases.empty()) { renderListFailed(p); break; }
|
||||
renderTwoPane(dp, p, active); break;
|
||||
default: renderTwoPane(dp, p, active); break;
|
||||
}
|
||||
EndOverlayDialog();
|
||||
}
|
||||
if (!s_open) s_updater.reset();
|
||||
if (!s_open) { s_updater.reset(); s_sweep = false; }
|
||||
}
|
||||
|
||||
private:
|
||||
using Progress = util::XmrigUpdater::Progress;
|
||||
using St = util::XmrigUpdater::State;
|
||||
|
||||
static float fullW() { return ImGui::GetContentRegionAvail().x; }
|
||||
|
||||
// Install button that refuses (with a note) while the miner is running, re-checked live so a
|
||||
// dialog opened before mining started can't replace a now-running binary (TOCTOU guard).
|
||||
static void installAction(const char* label) {
|
||||
static void renderCentered(const char* msg) {
|
||||
using namespace material;
|
||||
if (s_app->isPoolMinerRunning()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
|
||||
TR("xmrig_stop_mining_first"));
|
||||
return;
|
||||
}
|
||||
if (TactileButton(label, ImVec2(fullW(), 0)))
|
||||
s_updater->startInstall(resources::getDaemonDirectory());
|
||||
ImVec2 av = ImGui::GetContentRegionAvail();
|
||||
ImFont* f = Type().body2();
|
||||
ImVec2 ts = f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, msg);
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + std::max(0.0f, (av.x - ts.x) * 0.5f),
|
||||
ImGui::GetCursorPosY() + av.y * 0.45f));
|
||||
Type().text(TypeStyle::Body2, msg);
|
||||
}
|
||||
|
||||
static void renderChecking(float, const Progress&) {
|
||||
using namespace material;
|
||||
Type().text(TypeStyle::Body2, TR("xmrig_checking"));
|
||||
}
|
||||
|
||||
static void renderUnavailable(float, const Progress& p) {
|
||||
static void renderUnavailable(const Progress& p) {
|
||||
using namespace material;
|
||||
Type().text(TypeStyle::Subtitle2, TR("xmrig_unavailable_title"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", p.status_text.empty()
|
||||
? TR("xmrig_unavailable_body")
|
||||
: p.status_text.c_str());
|
||||
ImGui::TextWrapped("%s", p.status_text.empty() ? TR("xmrig_unavailable_body")
|
||||
: p.status_text.c_str());
|
||||
ImGui::Spacing();
|
||||
if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false;
|
||||
if (TactileButton(TR("close"), ImVec2(std::min(fullW(), 200.0f * Layout::dpiScale()), 0)))
|
||||
s_open = false;
|
||||
}
|
||||
|
||||
static void renderPrompt(float, const Progress& p) {
|
||||
static void renderListFailed(const Progress& p) {
|
||||
using namespace material;
|
||||
const std::string installed = p.installed_tag.empty() ? "none" : p.installed_tag;
|
||||
if (p.state == St::UpdateAvailable) {
|
||||
Type().text(TypeStyle::Subtitle2, TR("xmrig_update_available"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("%s %s", TR("xmrig_latest"), p.latest_tag.c_str());
|
||||
ImGui::Text("%s %s", TR("xmrig_installed"), installed.c_str());
|
||||
ImGui::Spacing();
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("xmrig_verify_note"));
|
||||
ImGui::Spacing();
|
||||
installAction(TR("xmrig_download_install"));
|
||||
} else {
|
||||
Type().text(TypeStyle::Subtitle2, TR("xmrig_up_to_date"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("%s %s", TR("xmrig_installed"), p.latest_tag.c_str());
|
||||
ImGui::Spacing();
|
||||
installAction(TR("xmrig_reinstall"));
|
||||
}
|
||||
const float bw = std::min(fullW(), 220.0f * Layout::dpiScale());
|
||||
Type().textColored(TypeStyle::Subtitle2, Error(), TR("xmrig_update_failed"));
|
||||
ImGui::Spacing();
|
||||
// Browse all releases (pre-releases / older versions).
|
||||
if (TactileButton(TR("xmrig_browse_releases"), ImVec2(fullW(), 0))) {
|
||||
ImGui::TextWrapped("%s", p.error.empty() ? TR("xmrig_unknown_error") : p.error.c_str());
|
||||
ImGui::Spacing();
|
||||
if (TactileButton(TR("retry"), ImVec2(bw, 0)) && s_updater) {
|
||||
s_releases.clear();
|
||||
s_rows.clear();
|
||||
s_updater->startListReleases();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false;
|
||||
if (TactileButton(TR("close"), ImVec2(bw, 0))) s_open = false;
|
||||
}
|
||||
|
||||
static void renderListing(float, const Progress&) {
|
||||
using namespace material;
|
||||
Type().text(TypeStyle::Body2, TR("xmrig_loading_releases"));
|
||||
static void buildRows() {
|
||||
if (!s_rows.empty() || s_releases.empty()) return;
|
||||
const std::string token = util::currentXmrigPlatformToken();
|
||||
s_rows.reserve(s_releases.size());
|
||||
for (const auto& r : s_releases) {
|
||||
ReleaseRow row;
|
||||
row.tag = r.tag;
|
||||
row.title = r.name;
|
||||
row.date = r.publishedAt.size() >= 10 ? r.publishedAt.substr(0, 10) : r.publishedAt;
|
||||
row.prerelease = r.prerelease;
|
||||
row.hasAsset = util::selectXmrigAsset(r, token) >= 0;
|
||||
row.installed = !s_installed_tag.empty() && r.tag == s_installed_tag;
|
||||
s_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
static void renderReleaseList(float dp, const Progress&) {
|
||||
static std::string cleanNotes(const std::string& body) {
|
||||
std::istringstream ss(body);
|
||||
std::string line, out;
|
||||
while (std::getline(ss, line)) {
|
||||
size_t a = line.find_first_not_of(" \t");
|
||||
std::string lt = (a == std::string::npos) ? std::string() : line.substr(a);
|
||||
std::string low = lt;
|
||||
std::transform(low.begin(), low.end(), low.begin(),
|
||||
[](unsigned char c) { return (char)std::tolower(c); });
|
||||
if (!lt.empty() && lt[0] == '|') break;
|
||||
if (low.rfind("## checksum", 0) == 0 || low.rfind("#### checksum", 0) == 0 ||
|
||||
low.rfind("checksums", 0) == 0 || low.rfind("### sha", 0) == 0) break;
|
||||
out += line;
|
||||
out += '\n';
|
||||
}
|
||||
while (!out.empty() && (out.back() == '\n' || out.back() == '\r' ||
|
||||
out.back() == ' ' || out.back() == '\t')) out.pop_back();
|
||||
return out;
|
||||
}
|
||||
|
||||
static void startInstallSelected() {
|
||||
if (!s_updater || s_app->isPoolMinerRunning()) return; // can't replace a running miner
|
||||
if (s_sel < 0 || s_sel >= (int)s_releases.size()) return;
|
||||
const util::XmrigRelease rel = s_releases[s_sel];
|
||||
s_updater->startInstallRelease(resources::getDaemonDirectory(), rel);
|
||||
}
|
||||
|
||||
// ================= Two-pane: version list (left) + detail/progress (right) =================
|
||||
static void renderTwoPane(float dp, const Progress& p, bool active) {
|
||||
using namespace material;
|
||||
// Snapshot + build rows once per listing (not every frame). Cleared on browse / show.
|
||||
if (s_rows.empty()) {
|
||||
s_releases = s_updater->getReleases();
|
||||
const std::string token = util::currentXmrigPlatformToken();
|
||||
s_rows.reserve(s_releases.size());
|
||||
for (const auto& r : s_releases) {
|
||||
ReleaseRow row;
|
||||
row.tag = r.tag;
|
||||
row.title = r.name;
|
||||
row.date = r.publishedAt.size() >= 10 ? r.publishedAt.substr(0, 10) : r.publishedAt;
|
||||
row.prerelease = r.prerelease;
|
||||
row.hasAsset = util::selectXmrigAsset(r, token) >= 0;
|
||||
row.installed = !s_installed_tag.empty() && r.tag == s_installed_tag;
|
||||
s_rows.push_back(std::move(row));
|
||||
if (!s_sweep && s_releases.empty() && s_updater) s_releases = s_updater->getReleases();
|
||||
buildRows();
|
||||
if (s_sel >= (int)s_rows.size()) s_sel = s_rows.empty() ? 0 : (int)s_rows.size() - 1;
|
||||
|
||||
ImFont* bodyF = Type().body2();
|
||||
ImFont* capF = Type().caption();
|
||||
|
||||
const bool showFooter = (p.state == St::ReleaseList);
|
||||
const float footerH = showFooter
|
||||
? (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;
|
||||
const float gap = Layout::spacingLg();
|
||||
const float masterW = std::min(std::max(contentW * 0.34f, 240.0f * dp), 340.0f * dp);
|
||||
const float detailW = contentW - masterW - gap;
|
||||
|
||||
// ---- LEFT: borderless list of tactile version cards ----
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
ImGui::BeginChild("##xmrigVersions", ImVec2(masterW, bodyH), false);
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0));
|
||||
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 = 8.0f * dp;
|
||||
for (int i = 0; i < (int)s_rows.size(); ++i) {
|
||||
const ReleaseRow& r = s_rows[i];
|
||||
ImGui::PushID(i);
|
||||
bool sel = (s_sel == i);
|
||||
ImVec2 rmn = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Selectable("##ver", sel, ImGuiSelectableFlags_SpanAvailWidth, ImVec2(0, rowH))) {
|
||||
if (!active) s_sel = i;
|
||||
}
|
||||
bool hov = ImGui::IsItemHovered();
|
||||
bool held = ImGui::IsItemActive();
|
||||
ImVec2 rmx(rmn.x + rowW, rmn.y + rowH);
|
||||
ImU32 fill = sel ? WithAlpha(Primary(), held ? 55 : 42)
|
||||
: held ? WithAlpha(OnSurface(), 34)
|
||||
: hov ? WithAlpha(OnSurface(), 24)
|
||||
: WithAlpha(OnSurface(), 12);
|
||||
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);
|
||||
const char* ico = nullptr; ImU32 statusC = 0; const char* tt = nullptr; const char* lbl = nullptr;
|
||||
if (r.installed) { ico = ICON_MD_CHECK_CIRCLE; statusC = Success(); tt = TR("xmrig_active_tt"); lbl = TR("upd_installed_badge"); }
|
||||
else if (r.prerelease) { ico = ICON_MD_SCIENCE; statusC = Warning(); tt = TR("xmrig_prerelease_tt"); lbl = TR("upd_prerelease"); }
|
||||
else if (i == 0) { ico = ICON_MD_NEW_RELEASES; statusC = Primary(); tt = TR("xmrig_latest_tt"); lbl = TR("xmrig_latest_badge"); }
|
||||
float rightLimit = rmx.x - Layout::spacingMd();
|
||||
if (ico) {
|
||||
float cs = bodyF->LegacySize * 1.3f;
|
||||
ImVec2 cp(rmx.x - Layout::spacingMd() - cs, (rmn.y + rmx.y) * 0.5f - cs * 0.5f);
|
||||
mdl->AddText(icoF, cs, cp, statusC, ico);
|
||||
if (tt && hov && ImGui::IsMouseHoveringRect(cp, ImVec2(cp.x + cs, cp.y + cs)))
|
||||
Tooltip("%s", tt);
|
||||
rightLimit = cp.x - Layout::spacingSm();
|
||||
}
|
||||
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());
|
||||
if (lbl) {
|
||||
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;
|
||||
if (bx + capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, lbl).x <= rightLimit)
|
||||
mdl->AddText(capF, capF->LegacySize, ImVec2(bx, by), statusC, lbl);
|
||||
}
|
||||
if (!r.date.empty())
|
||||
mdl->AddText(capF, capF->LegacySize,
|
||||
ImVec2(x, rmx.y - Layout::spacingMd() - capF->LegacySize),
|
||||
OnSurfaceMedium(), r.date.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
// Can't replace a running miner binary — disable installs while mining (TOCTOU guard).
|
||||
const char* disabledNote = s_app->isPoolMinerRunning() ? TR("xmrig_stop_mining_first") : nullptr;
|
||||
bool back = false;
|
||||
const int idx = RenderReleaseList(s_rows, dp, &back, disabledNote);
|
||||
if (back) { s_rows.clear(); s_updater->startCheck(s_installed_tag); return; }
|
||||
if (idx >= 0 && !s_app->isPoolMinerRunning() && idx < static_cast<int>(s_releases.size())) {
|
||||
const util::XmrigRelease rel = s_releases[idx];
|
||||
s_rows.clear();
|
||||
s_updater->startInstallRelease(resources::getDaemonDirectory(), rel);
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SameLine(0.0f, gap);
|
||||
|
||||
// ---- RIGHT: detail / progress / done / failed for the selected version ----
|
||||
ImGui::BeginChild("##xmrigDetail", ImVec2(detailW, bodyH), false);
|
||||
switch (p.state) {
|
||||
case St::ReleaseList: renderDetail(dp, p); break;
|
||||
case St::Downloading:
|
||||
case St::Verifying:
|
||||
case St::Extracting: renderProgress(dp, p); break;
|
||||
case St::Done: renderDone(dp, p); break;
|
||||
case St::Failed: renderFailed(dp, p); break;
|
||||
default: break;
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (showFooter) {
|
||||
ImGui::Spacing();
|
||||
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, bh))) s_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void renderDetail(float dp, const Progress&) {
|
||||
using namespace material;
|
||||
if (s_sel < 0 || s_sel >= (int)s_releases.size() || s_sel >= (int)s_rows.size()) return;
|
||||
const util::XmrigRelease& rel = s_releases[s_sel];
|
||||
const ReleaseRow& row = s_rows[s_sel];
|
||||
const bool haveInstalled = !s_installed_tag.empty();
|
||||
const bool downgrade = !row.installed && s_sel != 0 && haveInstalled;
|
||||
const bool mining = s_app->isPoolMinerRunning();
|
||||
|
||||
const char* noteStr = mining ? TR("xmrig_stop_mining_first")
|
||||
: !row.hasAsset ? TR("upd_no_build_platform")
|
||||
: downgrade ? TR("xmrig_downgrade_note")
|
||||
: TR("xmrig_verify_note");
|
||||
const char* label = row.installed ? TR("xmrig_reinstall")
|
||||
: (s_sel == 0) ? TR("xmrig_download_install")
|
||||
: TR("xmrig_install_this");
|
||||
|
||||
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 (version header + release notes markdown) ----
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 22));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f * dp);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingLg()));
|
||||
ImGui::BeginChild("##xmrigInfo", ImVec2(0, infoH), ImGuiChildFlags_AlwaysUseWindowPadding);
|
||||
{
|
||||
Type().text(TypeStyle::H6, rel.tag.c_str());
|
||||
if (row.installed)
|
||||
Type().textColored(TypeStyle::Subtitle2, Success(), TR("xmrig_status_current"));
|
||||
else if (s_sel == 0)
|
||||
Type().textColored(TypeStyle::Subtitle2, Primary(),
|
||||
haveInstalled ? TR("xmrig_update_available") : TR("xmrig_status_latest"));
|
||||
else if (haveInstalled)
|
||||
Type().textColored(TypeStyle::Subtitle2, Warning(), TR("xmrig_status_older"));
|
||||
else
|
||||
Type().text(TypeStyle::Subtitle2, rel.name.empty() ? rel.tag.c_str() : rel.name.c_str());
|
||||
|
||||
const std::string installed = s_installed_tag.empty() ? std::string(TR("xmrig_none"))
|
||||
: s_installed_tag;
|
||||
ImGui::Spacing();
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
|
||||
(std::string(TR("xmrig_installed")) + " " + installed).c_str());
|
||||
if (!row.date.empty())
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
|
||||
(std::string(TR("xmrig_released")) + " " + row.date).c_str());
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
const std::string notes = cleanNotes(rel.body);
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
|
||||
ImGui::BeginChild("##xmrigNotes", ImVec2(0, std::max(0.0f, ImGui::GetContentRegionAvail().y)), false);
|
||||
if (notes.empty())
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("xmrig_no_notes"));
|
||||
else
|
||||
RenderMarkdown(notes);
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// ---- Below the info card: verify / stop-mining note + install button (centered, text-fit) ----
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
Type().textColored(TypeStyle::Caption, (mining || downgrade) ? Warning() : OnSurfaceMedium(), noteStr);
|
||||
ImGui::Spacing();
|
||||
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 || mining);
|
||||
if (TactileButton(label, ImVec2(bw, btnH))) startInstallSelected();
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
static void renderProgress(float dp, const Progress& p) {
|
||||
@@ -186,11 +386,10 @@ private:
|
||||
ImGui::Spacing();
|
||||
material::ProgressBar(fullW(), 8.0f * dp, p.percent / 100.0f);
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("%s", p.status_text.c_str());
|
||||
ImGui::TextWrapped("%s", p.status_text.c_str());
|
||||
ImGui::Spacing();
|
||||
// Cancel aborts the in-flight transfer promptly (the curl progress callback returns abort).
|
||||
if (TactileButton(TR("cancel"), ImVec2(fullW(), 0))) {
|
||||
s_updater->cancel();
|
||||
if (s_updater) s_updater->cancel();
|
||||
s_open = false;
|
||||
}
|
||||
}
|
||||
@@ -217,16 +416,21 @@ private:
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", p.error.empty() ? TR("xmrig_unknown_error") : p.error.c_str());
|
||||
ImGui::Spacing();
|
||||
installAction(TR("retry"));
|
||||
ImGui::BeginDisabled(s_app->isPoolMinerRunning());
|
||||
if (TactileButton(TR("retry"), ImVec2(fullW(), 0))) startInstallSelected();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Spacing();
|
||||
if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false;
|
||||
}
|
||||
|
||||
static inline bool s_open = false;
|
||||
static inline bool s_persisted = false;
|
||||
static inline bool s_sweep = false;
|
||||
static inline int s_sel = 0;
|
||||
static inline std::string s_installed_tag;
|
||||
static inline std::vector<ReleaseRow> s_rows; // built once per listing (UI rows)
|
||||
static inline std::vector<util::XmrigRelease> s_releases; // matching releases (install by index)
|
||||
static inline Progress s_sweepProgress;
|
||||
static inline App* s_app = nullptr;
|
||||
static inline std::unique_ptr<util::XmrigUpdater> s_updater;
|
||||
};
|
||||
|
||||
@@ -1444,6 +1444,17 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["xmrig_unknown_error"] = "Unknown error.";
|
||||
strings_["xmrig_browse_releases"] = "Browse all releases…";
|
||||
strings_["xmrig_loading_releases"] = "Loading releases…";
|
||||
strings_["xmrig_latest_badge"] = "latest";
|
||||
strings_["xmrig_status_current"] = "This version is installed";
|
||||
strings_["xmrig_status_latest"] = "Latest version";
|
||||
strings_["xmrig_status_older"] = "Older than your installed version";
|
||||
strings_["xmrig_released"] = "Released:";
|
||||
strings_["xmrig_no_notes"] = "No release notes for this version.";
|
||||
strings_["xmrig_install_this"] = "Install this version";
|
||||
strings_["xmrig_downgrade_note"] = "Installing a different miner version replaces the current one; stop mining first, then it takes effect on the next start.";
|
||||
strings_["xmrig_active_tt"] = "The currently installed (active) miner";
|
||||
strings_["xmrig_latest_tt"] = "Newest available version";
|
||||
strings_["xmrig_prerelease_tt"] = "Pre-release / testing build";
|
||||
|
||||
// --- Shared release picker ("Browse all releases") ---
|
||||
strings_["upd_select_version"] = "Select a version to install";
|
||||
|
||||
Reference in New Issue
Block a user