Final slice of decomposing mining_tab.cpp. The ~529-line "Mode toggle" section (SOLO | POOL segmented control + pool URL/worker inputs) is moved verbatim into RenderMiningModeToggle(). mining_tab.cpp is now 311 lines (was 2628) — just the tab dispatch, thread-sync glue, benchmark advance, section-budget setup, and four card calls. State the toggle mutates is passed BY REFERENCE so behaviour is identical: the pool-mode flag, the settings-dirty flag, and the pool URL / worker char[256] buffers (the text inputs write into them) — passed as char(&)[256] references and named with their original identifiers so the body stays byte-identical. Verified: full-node + Windows + lite build, tests, hygiene. Audit #10 complete: the 2628-line monolith is now five focused files (earnings, stats, controls, mode-toggle + the 311-line shell). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
312 lines
13 KiB
C++
312 lines
13 KiB
C++
// DragonX Wallet - ImGui Edition
|
||
// Copyright 2024-2026 The Hush Developers
|
||
// Released under the GPLv3
|
||
|
||
#include "mining_tab.h"
|
||
#include "mining_benchmark.h"
|
||
#include "mining_tab_helpers.h"
|
||
#include "mining_pool_panel.h"
|
||
#include "mining_earnings.h"
|
||
#include "mining_stats.h"
|
||
#include "mining_controls.h"
|
||
#include "mining_mode_toggle.h"
|
||
#include "xmrig_download_dialog.h"
|
||
#include "../../util/xmrig_updater.h"
|
||
#include "../../app.h"
|
||
#include "../../util/i18n.h"
|
||
#include "../../config/version.h"
|
||
#include "../../data/wallet_state.h"
|
||
#include "../../config/settings.h"
|
||
#include "../../util/platform.h"
|
||
#include "../schema/ui_schema.h"
|
||
#include "../material/type.h"
|
||
#include "../material/draw_helpers.h"
|
||
#include "../material/colors.h"
|
||
#include "../material/components/buttons.h"
|
||
#include "../effects/low_spec.h"
|
||
#include "../layout.h"
|
||
#include "../notifications.h"
|
||
#include "../../embedded/IconsMaterialDesign.h"
|
||
#include "imgui.h"
|
||
#include "imgui_internal.h"
|
||
|
||
#include <thread>
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <ctime>
|
||
#include <vector>
|
||
|
||
namespace dragonx {
|
||
namespace ui {
|
||
|
||
using namespace material;
|
||
|
||
// Local UI state for thread grid
|
||
static int s_selected_threads = 0;
|
||
static bool s_threads_initialized = false;
|
||
|
||
// Drag-to-select state
|
||
static bool s_drag_active = false;
|
||
static int s_drag_anchor_thread = 0; // thread# where drag started
|
||
|
||
|
||
static ThreadBenchmark s_benchmark;
|
||
|
||
bool IsMiningBenchmarkActive() {
|
||
return s_benchmark.active();
|
||
}
|
||
|
||
// Miner-update version check (one shot per session): fetches the latest DRG-XMRig release tag in
|
||
// the background so the "Update" button can show it. Network call to the project Gitea, started
|
||
// the first time the pool section is shown.
|
||
static util::XmrigUpdater s_xmrig_version_check;
|
||
static bool s_xmrig_check_started = false;
|
||
static std::string s_xmrig_latest_tag;
|
||
|
||
// Pool mode state
|
||
static bool s_pool_mode = false;
|
||
static char s_pool_url[256] = "pool.dragonx.is:3433";
|
||
static char s_pool_worker[256] = "x";
|
||
static bool s_pool_settings_dirty = false;
|
||
static bool s_pool_state_loaded = false;
|
||
|
||
static void RenderMiningTabContent(App* app);
|
||
|
||
void RenderMiningTab(App* app)
|
||
{
|
||
// Scrollable child to contain all content within available space
|
||
ImVec2 miningAvail = ImGui::GetContentRegionAvail();
|
||
ImGui::BeginChild("##MiningScroll", miningAvail, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
|
||
|
||
ImGuiErrorRecoveryState erState;
|
||
ImGui::ErrorRecoveryStoreState(&erState);
|
||
try {
|
||
RenderMiningTabContent(app);
|
||
} catch (const std::exception& e) {
|
||
DEBUG_LOGF("[MiningTab] Exception: %s\n", e.what());
|
||
ImGui::ErrorRecoveryTryToRecoverState(&erState);
|
||
} catch (...) {
|
||
DEBUG_LOGF("[MiningTab] Unknown exception\n");
|
||
ImGui::ErrorRecoveryTryToRecoverState(&erState);
|
||
}
|
||
|
||
ImGui::EndChild(); // ##MiningScroll
|
||
}
|
||
|
||
static void RenderMiningTabContent(App* app)
|
||
{
|
||
auto& S = schema::UI();
|
||
ImVec2 miningAvail = ImGui::GetContentRegionAvail();
|
||
auto sliderInput = S.input("tabs.mining", "thread-slider");
|
||
auto startBtn = S.button("tabs.mining", "start-button");
|
||
auto lbl = S.label("tabs.mining", "label-column");
|
||
const auto& state = app->getWalletState();
|
||
const auto& mining = state.mining;
|
||
|
||
// Responsive: scale factors per frame
|
||
float availWidth = ImGui::GetContentRegionAvail().x;
|
||
float hs = Layout::hScale(availWidth);
|
||
float vs = Layout::vScale(miningAvail.y);
|
||
float pad = Layout::cardInnerPadding();
|
||
float gap = Layout::cardGap();
|
||
const float dp = Layout::dpiScale();
|
||
auto tier = Layout::currentTier(availWidth, miningAvail.y);
|
||
(void)tier;
|
||
|
||
int max_threads = GetMaxMiningThreads();
|
||
|
||
if (!s_threads_initialized) {
|
||
int saved = app->settings()->getPoolThreads();
|
||
if (mining.generate)
|
||
s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
|
||
else if (saved > 0)
|
||
s_selected_threads = ClampMiningThreads(saved, max_threads);
|
||
else
|
||
s_selected_threads = 1;
|
||
s_threads_initialized = true;
|
||
}
|
||
|
||
// Sync thread grid with actual count when idle thread scaling adjusts threads
|
||
// Skip during benchmark — the benchmark controls thread counts directly
|
||
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active && !IsMiningBenchmarkActive()) {
|
||
if (s_pool_mode && state.pool_mining.xmrig_running) {
|
||
// Use the requested thread count (available immediately) rather
|
||
// than threads_active from the xmrig API which lags during restarts.
|
||
int reqThreads = app->getXmrigRequestedThreads();
|
||
if (reqThreads > 0)
|
||
s_selected_threads = ClampMiningThreads(reqThreads, max_threads);
|
||
else if (state.pool_mining.threads_active > 0)
|
||
s_selected_threads = ClampMiningThreads(state.pool_mining.threads_active, max_threads);
|
||
} else if (mining.generate && mining.genproclimit > 0) {
|
||
s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
|
||
}
|
||
}
|
||
|
||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||
GlassPanelSpec glassSpec;
|
||
glassSpec.rounding = Layout::glassRounding();
|
||
ImFont* ovFont = Type().overline();
|
||
ImFont* capFont = Type().caption();
|
||
ImFont* sub1 = Type().subtitle1();
|
||
if (!ovFont || !capFont || !sub1) {
|
||
return;
|
||
}
|
||
char buf[128];
|
||
|
||
// Load pool state from settings on first frame
|
||
if (!s_pool_state_loaded) {
|
||
s_pool_mode = app->settings()->getPoolMode();
|
||
strncpy(s_pool_url, app->settings()->getPoolUrl().c_str(), sizeof(s_pool_url) - 1);
|
||
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
|
||
s_pool_state_loaded = true;
|
||
}
|
||
// Kick off a one-shot miner-version check the first time the pool section is shown, then cache
|
||
// the latest tag once it arrives (so the "Update" button can display it). Network call.
|
||
if (s_pool_mode && !s_xmrig_check_started) {
|
||
s_xmrig_version_check.startCheck(app->settings()->getXmrigVersion());
|
||
s_xmrig_check_started = true;
|
||
}
|
||
if (s_xmrig_check_started && s_xmrig_latest_tag.empty()) {
|
||
const auto _vp = s_xmrig_version_check.getProgress();
|
||
if (!_vp.latest_tag.empty()) s_xmrig_latest_tag = _vp.latest_tag;
|
||
}
|
||
|
||
if (!app->supportsSoloMining() && !s_pool_mode) {
|
||
s_pool_mode = true;
|
||
app->settings()->setPoolMode(true);
|
||
app->settings()->save();
|
||
}
|
||
|
||
// Default pool worker to user's first shielded (z) address once available.
|
||
// For new wallets without a z-address, leave the field blank so the user
|
||
// is prompted to generate one before mining.
|
||
{
|
||
static bool s_pool_worker_defaulted = false;
|
||
std::string workerStr(s_pool_worker);
|
||
if (shouldDefaultPoolWorker(workerStr, s_pool_worker_defaulted) && !state.addresses.empty()) {
|
||
std::string defaultAddr = defaultPoolWorkerAddress(state.addresses);
|
||
if (!defaultAddr.empty()) {
|
||
strncpy(s_pool_worker, defaultAddr.c_str(), sizeof(s_pool_worker) - 1);
|
||
s_pool_worker[sizeof(s_pool_worker) - 1] = '\0';
|
||
s_pool_settings_dirty = true;
|
||
} else {
|
||
// No z-address yet — clear the placeholder "x" so field shows empty
|
||
s_pool_worker[0] = '\0';
|
||
s_pool_settings_dirty = true;
|
||
}
|
||
s_pool_worker_defaulted = true;
|
||
}
|
||
}
|
||
|
||
// Persist pool settings when dirty and no field is active
|
||
if (s_pool_settings_dirty && !ImGui::IsAnyItemActive()) {
|
||
app->settings()->setPoolUrl(s_pool_url);
|
||
app->settings()->setPoolWorker(s_pool_worker);
|
||
app->settings()->save();
|
||
s_pool_settings_dirty = false;
|
||
|
||
// Auto-restart pool miner if it is currently running so the new
|
||
// URL / worker address takes effect immediately.
|
||
if (state.pool_mining.xmrig_running) {
|
||
app->stopPoolMining();
|
||
app->startPoolMining(s_selected_threads);
|
||
}
|
||
}
|
||
|
||
// Determine active mining state for UI
|
||
// Include pool mining running state even when user just switched to solo,
|
||
// so the button shows STOP/STOPPING while xmrig shuts down.
|
||
bool isMiningActive = IsPoolMiningActive(s_pool_mode,
|
||
state.pool_mining.xmrig_running,
|
||
mining.generate);
|
||
|
||
// ================================================================
|
||
// Thread Benchmark state machine — runs pool mining at each candidate
|
||
// thread count to find the optimal setting for this CPU.
|
||
// ================================================================
|
||
if (s_benchmark.active()) {
|
||
auto benchmarkUpdate = AdvanceThreadBenchmark(
|
||
s_benchmark, ImGui::GetIO().DeltaTime, state.pool_mining.hashrate_10s);
|
||
if (benchmarkUpdate.stopPoolMining) {
|
||
app->stopPoolMining();
|
||
}
|
||
if (benchmarkUpdate.saveOptimalThreads) {
|
||
s_selected_threads = benchmarkUpdate.optimalThreads;
|
||
app->settings()->setPoolThreads(s_selected_threads);
|
||
app->settings()->save();
|
||
}
|
||
if (benchmarkUpdate.startPoolMining) {
|
||
app->startPoolMining(benchmarkUpdate.startThreads);
|
||
}
|
||
}
|
||
|
||
// ================================================================
|
||
// Proportional section budget — ensures all content fits without
|
||
// scrolling at the minimum window size (1024×775).
|
||
// ================================================================
|
||
float sHdr = ovFont->LegacySize + Layout::spacingXs()
|
||
+ ImGui::GetStyle().ItemSpacing.y * 2.0f;
|
||
float gapOver = gap + ImGui::GetStyle().ItemSpacing.y;
|
||
// 3 sections with headers (CHART+STATS, DETAILS+EARNINGS, BLOCKS)
|
||
float totalOverhead = 3.0f * (sHdr + gapOver) + 1.0f * gapOver;
|
||
float cardBudget = std::max(schema::UI().drawElement("tabs.mining", "card-budget-min").size, miningAvail.y - totalOverhead);
|
||
|
||
Layout::SectionBudget cb(cardBudget);
|
||
float controlsBudgetH = cb.allocate(0.26f, 80.0f * dp);
|
||
float chartBudgetH = cb.allocate(0.22f, 60.0f * dp);
|
||
(void)cb; // remaining budget used by combined earnings+details card
|
||
|
||
// ================================================================
|
||
// MODE TOGGLE — SOLO | POOL segmented control
|
||
// ================================================================
|
||
RenderMiningModeToggle(app, state, mining, dl, capFont, ovFont, dp, hs, gap, availWidth,
|
||
s_pool_mode, s_pool_url, s_pool_worker, s_pool_settings_dirty);
|
||
|
||
// ================================================================
|
||
// CONTROLS — Glass card with CPU core grid (no heading)
|
||
// ================================================================
|
||
RenderMiningControls(app, state, mining, dl, capFont, sub1, ovFont, dp, hs, vs, gap, pad,
|
||
availWidth, glassSpec, controlsBudgetH, max_threads, isMiningActive,
|
||
s_pool_mode, s_pool_worker, s_xmrig_latest_tag, s_benchmark,
|
||
s_selected_threads, s_drag_active, s_drag_anchor_thread);
|
||
|
||
// (The miner download/update control lives in the mining-control header row, next to the
|
||
// benchmark button — see the "Miner update" block above.)
|
||
|
||
// ================================================================
|
||
// HASHRATE + STATS — Combined glass card: stat values on top, chart below
|
||
// (Or full-card log view when toggled in pool mode)
|
||
// ================================================================
|
||
RenderMiningStats(state, mining, dl, capFont, sub1, ovFont, dp, vs, gap, pad,
|
||
availWidth, glassSpec, chartBudgetH, s_pool_mode);
|
||
|
||
// ================================================================
|
||
// EARNINGS — Horizontal row card (Today | Yesterday | All Time | Est. Daily)
|
||
// ================================================================
|
||
RenderMiningEarnings(app, state, mining, dl, capFont, sub1, ovFont, dp, vs, gap, pad,
|
||
isMiningActive, s_pool_mode, availWidth, glassSpec, sHdr, gapOver);
|
||
|
||
// ================================================================
|
||
// POOL CONNECTION STATUS — inline indicator (pool mode, no log)
|
||
// ================================================================
|
||
if (s_pool_mode && state.pool_mining.log_lines.empty() && state.pool_mining.xmrig_running) {
|
||
ImFont* iconFont = Type().iconSmall();
|
||
const char* dotIcon = ICON_MD_CIRCLE;
|
||
ImU32 dotCol = state.pool_mining.connected ? WithAlpha(Success(), 200) : WithAlpha(Error(), 200);
|
||
const char* statusText = state.pool_mining.connected
|
||
? (state.pool_mining.pool_url.empty() ? TR("mining_connected") : state.pool_mining.pool_url.c_str())
|
||
: TR("mining_connecting");
|
||
|
||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||
ImVec2 dotSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, dotIcon);
|
||
dl->AddText(iconFont, iconFont->LegacySize, pos, dotCol, dotIcon);
|
||
dl->AddText(capFont, capFont->LegacySize,
|
||
ImVec2(pos.x + dotSz.x + 4 * dp, pos.y + (dotSz.y - capFont->LegacySize) * 0.5f),
|
||
OnSurfaceMedium(), statusText);
|
||
ImGui::Dummy(ImVec2(availWidth, capFont->LegacySize + 4 * dp));
|
||
}
|
||
}
|
||
|
||
} // namespace ui
|
||
} // namespace dragonx
|