refactor(mining): extract the Earnings card into mining_earnings.{h,cpp} (audit #10, slice 1)
First incremental slice of decomposing the 2628-line mining_tab.cpp monolith (one giant RenderMiningTabContent function). The ~636-line "Earnings" card section is moved verbatim into RenderMiningEarnings(); mining_tab.cpp is now 1992 lines and calls it with the immediate-mode layout context as parameters (draw list, fonts, scale/spacing, glass spec, pool-mode flag). Behavior-preserving by construction: the body is byte-identical (the only additions are a `const bool s_pool_mode = poolMode` alias and a local scratch `buf` so the moved code keeps its original identifiers). The earnings-filter static moved with the card it belongs to. The compiler surfaced every enclosing dependency, which became explicit parameters. Verified: full-node + Windows + lite build, tests, hygiene, clean smoke start. Pending hands-on visual check of the Earnings card before extracting the next section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -442,6 +442,7 @@ set(APP_SOURCES
|
|||||||
src/ui/windows/receive_tab.cpp
|
src/ui/windows/receive_tab.cpp
|
||||||
src/ui/windows/transactions_tab.cpp
|
src/ui/windows/transactions_tab.cpp
|
||||||
src/ui/windows/mining_tab.cpp
|
src/ui/windows/mining_tab.cpp
|
||||||
|
src/ui/windows/mining_earnings.cpp
|
||||||
src/ui/windows/mining_benchmark.cpp
|
src/ui/windows/mining_benchmark.cpp
|
||||||
src/ui/windows/mining_pool_panel.cpp
|
src/ui/windows/mining_pool_panel.cpp
|
||||||
src/ui/windows/mining_tab_helpers.cpp
|
src/ui/windows/mining_tab_helpers.cpp
|
||||||
|
|||||||
683
src/ui/windows/mining_earnings.cpp
Normal file
683
src/ui/windows/mining_earnings.cpp
Normal file
@@ -0,0 +1,683 @@
|
|||||||
|
// DragonX Wallet - ImGui Edition
|
||||||
|
// Copyright 2024-2026 The Hush Developers
|
||||||
|
// Released under the GPLv3
|
||||||
|
|
||||||
|
#include "mining_earnings.h"
|
||||||
|
#include "mining_tab_helpers.h" // EstimateHoursToBlock
|
||||||
|
|
||||||
|
#include "../../app.h"
|
||||||
|
#include "../../config/version.h"
|
||||||
|
#include "../../util/i18n.h"
|
||||||
|
#include "../../util/platform.h"
|
||||||
|
#include "../../config/settings.h"
|
||||||
|
#include "../schema/ui_schema.h"
|
||||||
|
#include "../material/type.h"
|
||||||
|
#include "../material/draw_helpers.h"
|
||||||
|
#include "../material/colors.h"
|
||||||
|
#include "../layout.h"
|
||||||
|
#include "../notifications.h"
|
||||||
|
#include "../../embedded/IconsMaterialDesign.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
using namespace material;
|
||||||
|
|
||||||
|
// Earnings filter: 0 = All, 1 = Solo, 2 = Pool. (Moved here with the card it belongs to.)
|
||||||
|
static int s_earnings_filter = 0;
|
||||||
|
|
||||||
|
void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& mining,
|
||||||
|
ImDrawList* dl, ImFont* capFont, ImFont* sub1, ImFont* ovFont,
|
||||||
|
float dp, float vs, float gap, float pad, bool isMiningActive,
|
||||||
|
bool poolMode, float availWidth,
|
||||||
|
const GlassPanelSpec& glassSpec, float sHdr, float gapOver)
|
||||||
|
{
|
||||||
|
const bool s_pool_mode = poolMode; // alias: keep the moved body's identifier verbatim
|
||||||
|
char buf[128]; // section-local scratch (was shared in the parent)
|
||||||
|
// Gather mining transactions from state
|
||||||
|
double minedToday = 0.0, minedYesterday = 0.0, minedAllTime = 0.0;
|
||||||
|
int minedTodayCount = 0, minedYesterdayCount = 0, minedAllTimeCount = 0;
|
||||||
|
int64_t now = (int64_t)std::time(nullptr);
|
||||||
|
|
||||||
|
// Calendar-day boundaries (local time)
|
||||||
|
time_t nowT = (time_t)now;
|
||||||
|
struct tm local;
|
||||||
|
#ifdef _WIN32
|
||||||
|
localtime_s(&local, &nowT);
|
||||||
|
#else
|
||||||
|
localtime_r(&nowT, &local);
|
||||||
|
#endif
|
||||||
|
local.tm_hour = 0;
|
||||||
|
local.tm_min = 0;
|
||||||
|
local.tm_sec = 0;
|
||||||
|
int64_t todayStart = (int64_t)mktime(&local);
|
||||||
|
int64_t yesterdayStart = todayStart - 86400;
|
||||||
|
|
||||||
|
struct MinedTx {
|
||||||
|
int64_t timestamp;
|
||||||
|
double amount;
|
||||||
|
int confirmations;
|
||||||
|
bool mature;
|
||||||
|
std::string txid;
|
||||||
|
bool isPoolPayout;
|
||||||
|
};
|
||||||
|
std::vector<MinedTx> recentMined;
|
||||||
|
|
||||||
|
for (const auto& tx : state.transactions) {
|
||||||
|
bool isSoloMined = (tx.type == "generate" || tx.type == "immature" || tx.type == "mined");
|
||||||
|
bool isPoolPayout = (tx.type == "receive"
|
||||||
|
&& !tx.memo.empty()
|
||||||
|
&& tx.memo.find("Mining Pool payout") != std::string::npos);
|
||||||
|
if (isSoloMined || isPoolPayout) {
|
||||||
|
// Apply earnings filter
|
||||||
|
if (!app->supportsSoloMining()) {
|
||||||
|
if (s_earnings_filter == 1 && !isPoolPayout) continue;
|
||||||
|
} else {
|
||||||
|
if (s_earnings_filter == 1 && !isSoloMined) continue;
|
||||||
|
if (s_earnings_filter == 2 && !isPoolPayout) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double amt = std::abs(tx.amount);
|
||||||
|
minedAllTime += amt;
|
||||||
|
minedAllTimeCount++;
|
||||||
|
if (tx.timestamp >= todayStart) {
|
||||||
|
minedToday += amt;
|
||||||
|
minedTodayCount++;
|
||||||
|
} else if (tx.timestamp >= yesterdayStart) {
|
||||||
|
minedYesterday += amt;
|
||||||
|
minedYesterdayCount++;
|
||||||
|
}
|
||||||
|
// Separate solo blocks from pool payouts based on current mode
|
||||||
|
bool showInCurrentMode = s_pool_mode ? isPoolPayout : isSoloMined;
|
||||||
|
if (showInCurrentMode && recentMined.size() < 4) {
|
||||||
|
recentMined.push_back({tx.timestamp, amt, tx.confirmations, tx.confirmations >= 100, tx.txid, isPoolPayout});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use pool hashrate for EST. DAILY when in pool mode
|
||||||
|
double estHashrate = s_pool_mode ? state.pool_mining.hashrate_10s : mining.localHashrate;
|
||||||
|
double est_hours_2 = EstimateHoursToBlock(estHashrate, mining.networkHashrate, mining.difficulty);
|
||||||
|
double estDailyBlocks = (est_hours_2 > 0) ? (24.0 / est_hours_2) : 0.0;
|
||||||
|
double blockReward = schema::UI().drawElement("business", "block-reward").size;
|
||||||
|
double estDaily = estDailyBlocks * blockReward;
|
||||||
|
bool estActive = isMiningActive && estDaily > 0;
|
||||||
|
|
||||||
|
ImU32 greenCol2 = Success();
|
||||||
|
|
||||||
|
// --- Combined Earnings + Details card ---
|
||||||
|
float earningsRowH = ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + pad * 1.5f;
|
||||||
|
float detailsContentH = pad * 0.5f + capFont->LegacySize + pad * 0.5f;
|
||||||
|
float barH_est = capFont->LegacySize + Layout::spacingMd() * 2.0f;
|
||||||
|
float combinedCardH = earningsRowH + detailsContentH + barH_est + pad;
|
||||||
|
combinedCardH = std::max(combinedCardH,
|
||||||
|
schema::UI().drawElement("tabs.mining", "details-card-min-height").size + earningsRowH);
|
||||||
|
|
||||||
|
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + combinedCardH);
|
||||||
|
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||||
|
|
||||||
|
// === Earnings filter toggle button (top-right of card) ===
|
||||||
|
{
|
||||||
|
const bool soloMiningAvailable = app->supportsSoloMining();
|
||||||
|
const char* filterLabels[] = { TR("mining_filter_all"), soloMiningAvailable ? TR("mining_solo") : TR("mining_pool"), TR("mining_pool") };
|
||||||
|
const char* filterIcons[] = { ICON_MD_FUNCTIONS, soloMiningAvailable ? ICON_MD_MEMORY : ICON_MD_CLOUD, ICON_MD_CLOUD };
|
||||||
|
const int filterCount = soloMiningAvailable ? 3 : 2;
|
||||||
|
const char* curIcon = filterIcons[s_earnings_filter];
|
||||||
|
const char* curLabel = filterLabels[s_earnings_filter];
|
||||||
|
|
||||||
|
ImFont* icoFont = Type().iconSmall();
|
||||||
|
float icoH = icoFont->LegacySize;
|
||||||
|
ImVec2 icoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, curIcon);
|
||||||
|
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, curLabel);
|
||||||
|
float padH = Layout::spacingSm();
|
||||||
|
float btnW = padH + icoSz.x + Layout::spacingXs() + lblSz.x + padH;
|
||||||
|
float btnH = icoH + 8.0f * dp;
|
||||||
|
float btnX = cardMax.x - pad - btnW;
|
||||||
|
float btnY = cardMin.y + (earningsRowH - btnH) * 0.5f;
|
||||||
|
|
||||||
|
ImVec2 bMin(btnX, btnY), bMax(btnX + btnW, btnY + btnH);
|
||||||
|
bool hov = material::IsRectHovered(bMin, bMax);
|
||||||
|
|
||||||
|
// Pill background
|
||||||
|
ImU32 pillBg = s_earnings_filter != 0
|
||||||
|
? WithAlpha(Primary(), 60)
|
||||||
|
: WithAlpha(OnSurface(), hov ? 25 : 12);
|
||||||
|
dl->AddRectFilled(bMin, bMax, pillBg, btnH * 0.5f);
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
ImU32 icoCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceDisabled());
|
||||||
|
float cx = bMin.x + padH;
|
||||||
|
float cy = bMin.y + (btnH - icoSz.y) * 0.5f;
|
||||||
|
dl->AddText(icoFont, icoFont->LegacySize, ImVec2(cx, cy), icoCol, curIcon);
|
||||||
|
|
||||||
|
// Label
|
||||||
|
ImU32 lblCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceMedium());
|
||||||
|
float lx = cx + icoSz.x + Layout::spacingXs();
|
||||||
|
float ly = bMin.y + (btnH - lblSz.y) * 0.5f;
|
||||||
|
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(lx, ly), lblCol, curLabel);
|
||||||
|
|
||||||
|
// Click target
|
||||||
|
ImVec2 savedCur = ImGui::GetCursorScreenPos();
|
||||||
|
ImGui::SetCursorScreenPos(bMin);
|
||||||
|
ImGui::InvisibleButton("##EarningsFilter", ImVec2(btnW, btnH));
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
s_earnings_filter = (s_earnings_filter + 1) % filterCount;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
|
const char* tips[] = {
|
||||||
|
TR("mining_filter_tip_all"),
|
||||||
|
soloMiningAvailable ? TR("mining_filter_tip_solo") : TR("mining_filter_tip_pool"),
|
||||||
|
TR("mining_filter_tip_pool")
|
||||||
|
};
|
||||||
|
ImGui::SetTooltip("%s", tips[s_earnings_filter]);
|
||||||
|
}
|
||||||
|
ImGui::SetCursorScreenPos(savedCur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Earnings section (top of combined card) ===
|
||||||
|
{
|
||||||
|
const int numCols = 4;
|
||||||
|
float colW = (availWidth - pad * 2) / (float)numCols;
|
||||||
|
float ey = cardMin.y + pad * 0.5f;
|
||||||
|
char valBuf[64], subBuf2[64];
|
||||||
|
|
||||||
|
struct EarningsEntry {
|
||||||
|
const char* label;
|
||||||
|
const char* value;
|
||||||
|
const char* sub;
|
||||||
|
ImU32 col;
|
||||||
|
};
|
||||||
|
|
||||||
|
snprintf(valBuf, sizeof(valBuf), "+%.4f", minedToday);
|
||||||
|
snprintf(subBuf2, sizeof(subBuf2), "(%d txn)", minedTodayCount);
|
||||||
|
char todayVal[64], todaySub[64];
|
||||||
|
strncpy(todayVal, valBuf, sizeof(todayVal));
|
||||||
|
strncpy(todaySub, subBuf2, sizeof(todaySub));
|
||||||
|
|
||||||
|
char yesterdayVal[64], yesterdaySub[64];
|
||||||
|
snprintf(yesterdayVal, sizeof(yesterdayVal), "+%.4f", minedYesterday);
|
||||||
|
snprintf(yesterdaySub, sizeof(yesterdaySub), "(%d txn)", minedYesterdayCount);
|
||||||
|
|
||||||
|
char allVal[64], allSub[64];
|
||||||
|
snprintf(allVal, sizeof(allVal), "+%.4f", minedAllTime);
|
||||||
|
snprintf(allSub, sizeof(allSub), "(%d txn)", minedAllTimeCount);
|
||||||
|
|
||||||
|
char estVal[64];
|
||||||
|
if (estActive)
|
||||||
|
snprintf(estVal, sizeof(estVal), "~%.4f", estDaily);
|
||||||
|
else
|
||||||
|
snprintf(estVal, sizeof(estVal), "N/A");
|
||||||
|
|
||||||
|
EarningsEntry entries[] = {
|
||||||
|
{ TR("mining_today"), todayVal, todaySub, greenCol2 },
|
||||||
|
{ TR("mining_yesterday"), yesterdayVal, yesterdaySub, OnSurface() },
|
||||||
|
{ TR("mining_all_time"), allVal, allSub, OnSurface() },
|
||||||
|
{ TR("mining_est_daily"), estVal, nullptr, estActive ? greenCol2 : OnSurfaceDisabled() },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int ei = 0; ei < numCols; ei++) {
|
||||||
|
float sx = cardMin.x + pad + ei * colW;
|
||||||
|
float centerX = sx + colW * 0.5f;
|
||||||
|
|
||||||
|
// Overline label (centered)
|
||||||
|
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, entries[ei].label);
|
||||||
|
dl->AddText(ovFont, ovFont->LegacySize,
|
||||||
|
ImVec2(centerX - lblSz.x * 0.5f, ey), OnSurfaceMedium(), entries[ei].label);
|
||||||
|
|
||||||
|
// Value (centered)
|
||||||
|
float valY = ey + ovFont->LegacySize + Layout::spacingXs();
|
||||||
|
ImVec2 valSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, entries[ei].value);
|
||||||
|
|
||||||
|
if (entries[ei].sub) {
|
||||||
|
// Value + sub annotation side by side, centered together
|
||||||
|
ImVec2 subSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, entries[ei].sub);
|
||||||
|
float totalW = valSz.x + 4 * dp + subSz.x;
|
||||||
|
float startX = centerX - totalW * 0.5f;
|
||||||
|
dl->AddText(sub1, sub1->LegacySize, ImVec2(startX, valY), entries[ei].col, entries[ei].value);
|
||||||
|
dl->AddText(capFont, capFont->LegacySize,
|
||||||
|
ImVec2(startX + valSz.x + 4 * dp, valY + (sub1->LegacySize - capFont->LegacySize) * 0.5f),
|
||||||
|
OnSurfaceDisabled(), entries[ei].sub);
|
||||||
|
} else {
|
||||||
|
dl->AddText(sub1, sub1->LegacySize,
|
||||||
|
ImVec2(centerX - valSz.x * 0.5f, valY), entries[ei].col, entries[ei].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Separator between earnings & details ===
|
||||||
|
float earningsSepY = cardMin.y + earningsRowH;
|
||||||
|
{
|
||||||
|
float rnd = glassSpec.rounding;
|
||||||
|
dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, earningsSepY),
|
||||||
|
ImVec2(cardMax.x - rnd * 0.5f, earningsSepY),
|
||||||
|
WithAlpha(OnSurface(), 15), 1.0f * dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Details section (below separator) ===
|
||||||
|
{
|
||||||
|
float cx = cardMin.x + pad;
|
||||||
|
float cy = earningsSepY + pad * 0.5f;
|
||||||
|
|
||||||
|
// Three equal columns: Difficulty | Block | Mining Address
|
||||||
|
float colW = availWidth / 3.0f;
|
||||||
|
float valOffX = availWidth * schema::UI().drawElement("tabs.mining", "stats-col1-value-x-ratio").size;
|
||||||
|
|
||||||
|
float col1X = cx;
|
||||||
|
float col2X = cx + colW;
|
||||||
|
float col3X = cx + colW * 2.0f;
|
||||||
|
|
||||||
|
// -- Difficulty --
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X, cy), OnSurfaceMedium(), TR("difficulty"));
|
||||||
|
if (mining.difficulty > 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "%.4f", mining.difficulty);
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X + valOffX, cy), OnSurface(), buf);
|
||||||
|
ImVec2 diffSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(col1X + valOffX, cy));
|
||||||
|
ImGui::InvisibleButton("##DiffCopy", ImVec2(diffSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
|
ImGui::SetTooltip("%s", TR("mining_click_copy_difficulty"));
|
||||||
|
dl->AddLine(ImVec2(col1X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
||||||
|
ImVec2(col1X + valOffX + diffSz.x, cy + capFont->LegacySize + 1 * dp),
|
||||||
|
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
ImGui::SetClipboardText(buf);
|
||||||
|
Notifications::instance().info(TR("mining_difficulty_copied"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Block --
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X, cy), OnSurfaceMedium(), TR("block"));
|
||||||
|
if (mining.blocks > 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "%d", mining.blocks);
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X + valOffX, cy), OnSurface(), buf);
|
||||||
|
ImVec2 blkSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(col2X + valOffX, cy));
|
||||||
|
ImGui::InvisibleButton("##BlockCopy", ImVec2(blkSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
|
ImGui::SetTooltip("%s", TR("mining_click_copy_block"));
|
||||||
|
dl->AddLine(ImVec2(col2X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
||||||
|
ImVec2(col2X + valOffX + blkSz.x, cy + capFont->LegacySize + 1 * dp),
|
||||||
|
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
ImGui::SetClipboardText(buf);
|
||||||
|
Notifications::instance().info(TR("mining_block_copied"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Mining Address --
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), TR("mining_mining_addr"));
|
||||||
|
std::string mining_address = "";
|
||||||
|
for (const auto& addr : state.addresses) {
|
||||||
|
if (addr.type == "transparent" && !addr.address.empty()) {
|
||||||
|
mining_address = addr.address;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mining_address.empty()) {
|
||||||
|
float addrAvailW = colW - pad - valOffX;
|
||||||
|
float charW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "M").x;
|
||||||
|
if (charW <= 0.0f) charW = 8.0f;
|
||||||
|
int maxChars = std::max(8, (int)(addrAvailW / charW));
|
||||||
|
std::string truncAddr = mining_address;
|
||||||
|
if ((int)truncAddr.length() > maxChars && maxChars > 5) {
|
||||||
|
int half = (maxChars - 3) / 2;
|
||||||
|
if (half > 0 && (size_t)half < truncAddr.length())
|
||||||
|
truncAddr = truncAddr.substr(0, half) + "..." + truncAddr.substr(truncAddr.length() - half);
|
||||||
|
}
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X + valOffX, cy), OnSurface(), truncAddr.c_str());
|
||||||
|
|
||||||
|
ImVec2 addrTextSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, truncAddr.c_str());
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(col3X + valOffX, cy));
|
||||||
|
ImGui::InvisibleButton("##MiningAddrCopy", ImVec2(addrTextSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
|
ImGui::SetTooltip("%s", TR("mining_click_copy_address"));
|
||||||
|
dl->AddLine(ImVec2(col3X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
||||||
|
ImVec2(col3X + valOffX + addrTextSz.x, cy + capFont->LegacySize + 1 * dp),
|
||||||
|
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
ImGui::SetClipboardText(mining_address.c_str());
|
||||||
|
Notifications::instance().info(TR("mining_address_copied"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Memory bar — centered in remaining space below details ----
|
||||||
|
{
|
||||||
|
double totalRAM = dragonx::util::Platform::getTotalSystemRAM_MB();
|
||||||
|
double usedRAM = dragonx::util::Platform::getUsedSystemRAM_MB();
|
||||||
|
double selfRAM = dragonx::util::Platform::getSelfMemoryUsageMB();
|
||||||
|
double daemonRAM = app->getDaemonMemoryUsageMB();
|
||||||
|
// Include xmrig memory when pool mining
|
||||||
|
double xmrigRAM = state.pool_mining.memory_used / (1024.0 * 1024.0); // bytes -> MB
|
||||||
|
double appRAM = selfRAM + daemonRAM + xmrigRAM; // App + daemon + xmrig combined
|
||||||
|
|
||||||
|
// Fixed bar height (text + padding)
|
||||||
|
float barH = capFont->LegacySize + Layout::spacingMd() * 2.0f;
|
||||||
|
float barRnd = barH * 0.5f; // fully rounded corners
|
||||||
|
|
||||||
|
// Details content ends here
|
||||||
|
float detailsEndY = earningsSepY + detailsContentH;
|
||||||
|
|
||||||
|
// Subtle top separator at the boundary
|
||||||
|
float rnd = glassSpec.rounding;
|
||||||
|
float stripY = detailsEndY;
|
||||||
|
dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, stripY),
|
||||||
|
ImVec2(cardMax.x - rnd * 0.5f, stripY),
|
||||||
|
WithAlpha(OnSurface(), 15), 1.0f * dp);
|
||||||
|
float remainingH = cardMax.y - stripY;
|
||||||
|
float barX = cardMin.x + pad;
|
||||||
|
float barW = cardMax.x - pad - barX;
|
||||||
|
float barY = stripY + (remainingH - barH) * 0.5f;
|
||||||
|
float textY = barY + (barH - capFont->LegacySize) * 0.5f;
|
||||||
|
float textPadX = Layout::spacingMd();
|
||||||
|
|
||||||
|
// Background track
|
||||||
|
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
|
||||||
|
WithAlpha(OnSurface(), 20), barRnd);
|
||||||
|
|
||||||
|
float sysFrac = 0.0f, appFrac = 0.0f;
|
||||||
|
float sysFillW = 0.0f, appFillW = 0.0f;
|
||||||
|
|
||||||
|
// Helper: draw a fill bar that perfectly matches the track's rounded
|
||||||
|
// left edge regardless of fill width. We draw a full-width rounded
|
||||||
|
// rect (same shape as the track) but clip it to just the fill portion.
|
||||||
|
auto drawFillBar = [&](float fillW, ImU32 col) {
|
||||||
|
if (fillW <= 1.0f) return;
|
||||||
|
dl->PushClipRect(ImVec2(barX, barY), ImVec2(barX + fillW, barY + barH), true);
|
||||||
|
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
|
||||||
|
col, barRnd);
|
||||||
|
dl->PopClipRect();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (usedRAM > 0 && totalRAM > 0) {
|
||||||
|
sysFrac = std::clamp((float)(usedRAM / totalRAM), 0.0f, 1.0f);
|
||||||
|
sysFillW = barW * sysFrac;
|
||||||
|
|
||||||
|
// System memory bar (subtle fill)
|
||||||
|
ImU32 ramBarCol = schema::UI().resolveColor("var(--ram-bar-system)",
|
||||||
|
IsDarkTheme() ? IM_COL32(255, 255, 255, 46) : IM_COL32(0, 0, 0, 46));
|
||||||
|
drawFillBar(sysFillW, ramBarCol);
|
||||||
|
|
||||||
|
// App+daemon memory bar (saturated accent)
|
||||||
|
if (appRAM > 0) {
|
||||||
|
appFrac = std::clamp((float)(appRAM / totalRAM), 0.0f, sysFrac);
|
||||||
|
appFillW = barW * appFrac;
|
||||||
|
ImU32 appBarCol = schema::UI().resolveColor("var(--ram-bar-app)",
|
||||||
|
ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]));
|
||||||
|
drawFillBar(appFillW, appBarCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Text overlaying the bar ---
|
||||||
|
float accentEdge = barX + appFillW;
|
||||||
|
float whiteEdge = barX + sysFillW;
|
||||||
|
|
||||||
|
// Determine text colors for bar segments
|
||||||
|
// On dark themes: white text on empty, dark on filled bars (both system and app)
|
||||||
|
// On light themes: dark text on empty/system, white on app accent bar
|
||||||
|
ImU32 barTextOnEmpty = IsDarkTheme() ? IM_COL32(255, 255, 255, 230)
|
||||||
|
: IM_COL32(30, 30, 30, 230);
|
||||||
|
ImU32 barTextOnFill = IsDarkTheme() ? IM_COL32(30, 30, 30, 230)
|
||||||
|
: IM_COL32(255, 255, 255, 230);
|
||||||
|
ImU32 barTextOnAccent = IsDarkTheme() ? IM_COL32(0, 0, 0, 210)
|
||||||
|
: IM_COL32(255, 255, 255, 230);
|
||||||
|
|
||||||
|
// App usage on the left
|
||||||
|
char appBuf[64] = {};
|
||||||
|
if (appRAM > 0) {
|
||||||
|
if (appRAM >= 1024.0)
|
||||||
|
snprintf(appBuf, sizeof(appBuf), "%.1f GB", appRAM / 1024.0);
|
||||||
|
else
|
||||||
|
snprintf(appBuf, sizeof(appBuf), "%.0f MB", appRAM);
|
||||||
|
|
||||||
|
float appTextX = barX + textPadX;
|
||||||
|
float appTextW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, appBuf).x;
|
||||||
|
float appTextEnd = appTextX + appTextW;
|
||||||
|
|
||||||
|
// Inside accent bar: white | inside white bar: dark | outside: white
|
||||||
|
if (appTextEnd <= accentEdge) {
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
||||||
|
barTextOnAccent, appBuf);
|
||||||
|
} else if (appTextX >= whiteEdge) {
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
||||||
|
barTextOnEmpty, appBuf);
|
||||||
|
} else {
|
||||||
|
// Part in accent bar: white
|
||||||
|
if (accentEdge > appTextX) {
|
||||||
|
dl->PushClipRect(ImVec2(appTextX, barY), ImVec2(accentEdge, barY + barH));
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
||||||
|
barTextOnAccent, appBuf);
|
||||||
|
dl->PopClipRect();
|
||||||
|
}
|
||||||
|
// Part in white bar (past accent): dark
|
||||||
|
float dkS = std::max(appTextX, accentEdge);
|
||||||
|
float dkE = std::min(appTextEnd, whiteEdge);
|
||||||
|
if (dkE > dkS) {
|
||||||
|
dl->PushClipRect(ImVec2(dkS, barY), ImVec2(dkE, barY + barH));
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
||||||
|
barTextOnFill, appBuf);
|
||||||
|
dl->PopClipRect();
|
||||||
|
}
|
||||||
|
// Part outside white bar: white
|
||||||
|
if (appTextEnd > whiteEdge) {
|
||||||
|
dl->PushClipRect(ImVec2(whiteEdge, barY), ImVec2(appTextEnd + 1, barY + barH));
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
||||||
|
barTextOnEmpty, appBuf);
|
||||||
|
dl->PopClipRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System usage on the right
|
||||||
|
char sysBuf[64] = {};
|
||||||
|
if (usedRAM > 0 && totalRAM > 0)
|
||||||
|
snprintf(sysBuf, sizeof(sysBuf), "%.1f / %.0f GB", usedRAM / 1024.0, totalRAM / 1024.0);
|
||||||
|
else if (totalRAM > 0)
|
||||||
|
snprintf(sysBuf, sizeof(sysBuf), "-- / %.0f GB", totalRAM / 1024.0);
|
||||||
|
else
|
||||||
|
snprintf(sysBuf, sizeof(sysBuf), "N/A");
|
||||||
|
|
||||||
|
float sysTextW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, sysBuf).x;
|
||||||
|
float sysTextX = barX + barW - textPadX - sysTextW;
|
||||||
|
|
||||||
|
if (sysTextX >= whiteEdge) {
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
||||||
|
barTextOnEmpty, sysBuf);
|
||||||
|
} else if (sysTextX + sysTextW <= whiteEdge) {
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
||||||
|
barTextOnFill, sysBuf);
|
||||||
|
} else {
|
||||||
|
dl->PushClipRect(ImVec2(sysTextX, barY), ImVec2(whiteEdge, barY + barH));
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
||||||
|
barTextOnFill, sysBuf);
|
||||||
|
dl->PopClipRect();
|
||||||
|
dl->PushClipRect(ImVec2(whiteEdge, barY), ImVec2(sysTextX + sysTextW + 1, barY + barH));
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
||||||
|
barTextOnEmpty, sysBuf);
|
||||||
|
dl->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invisible button over the bar for tooltip interaction
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(barX, barY));
|
||||||
|
ImGui::InvisibleButton("##rambar", ImVec2(barW, barH));
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
if (selfRAM >= 1024.0)
|
||||||
|
ImGui::Text(TR("ram_wallet_gb"), selfRAM / 1024.0);
|
||||||
|
else
|
||||||
|
ImGui::Text(TR("ram_wallet_mb"), selfRAM);
|
||||||
|
if (daemonRAM >= 1024.0)
|
||||||
|
ImGui::Text(TR("ram_daemon_gb"), daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
|
||||||
|
else
|
||||||
|
ImGui::Text(TR("ram_daemon_mb"), daemonRAM, app->getDaemonMemDiag().c_str());
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text(TR("ram_system_gb"), usedRAM / 1024.0, totalRAM / 1024.0);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||||
|
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||||
|
ImGui::Dummy(ImVec2(0, gap));
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// RECENT BLOCKS — last 4 mined blocks (always shown in pool mode)
|
||||||
|
// ============================================================
|
||||||
|
if (!recentMined.empty() || s_pool_mode) {
|
||||||
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
|
||||||
|
s_pool_mode ? TR("mining_recent_payouts") : TR("mining_recent_blocks"));
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
|
||||||
|
float rowH_blocks = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
|
||||||
|
// Size to remaining space — proportional budget ensures fit
|
||||||
|
float recentAvailH = ImGui::GetContentRegionAvail().y - sHdr - gapOver;
|
||||||
|
float minRows = recentMined.empty() ? 2.0f : (float)recentMined.size();
|
||||||
|
float contentH_blocks = rowH_blocks * minRows + pad * 2.5f;
|
||||||
|
float recentH = std::clamp(contentH_blocks, 30.0f * dp, std::max(30.0f * dp, recentAvailH));
|
||||||
|
|
||||||
|
// Glass panel wrapping the list + scroll-edge mask state
|
||||||
|
ImVec2 recentPanelMin = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 recentPanelMax(recentPanelMin.x + availWidth, recentPanelMin.y + recentH);
|
||||||
|
GlassPanelSpec recentGlass;
|
||||||
|
recentGlass.rounding = Layout::glassRounding();
|
||||||
|
DrawGlassPanel(dl, recentPanelMin, recentPanelMax, recentGlass);
|
||||||
|
|
||||||
|
float miningScrollY = 0.0f, miningScrollMaxY = 0.0f;
|
||||||
|
int miningParentVtx = dl->VtxBuffer.Size;
|
||||||
|
|
||||||
|
ImGui::BeginChild("##RecentBlocks", ImVec2(availWidth, recentH), false,
|
||||||
|
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
|
||||||
|
ImDrawList* miningChildDL = ImGui::GetWindowDrawList();
|
||||||
|
int miningChildVtx = miningChildDL->VtxBuffer.Size;
|
||||||
|
|
||||||
|
miningScrollY = ImGui::GetScrollY();
|
||||||
|
miningScrollMaxY = ImGui::GetScrollMaxY();
|
||||||
|
|
||||||
|
// Top padding inside glass card
|
||||||
|
ImGui::Dummy(ImVec2(0, pad * 0.5f));
|
||||||
|
|
||||||
|
if (recentMined.empty()) {
|
||||||
|
// Empty state — card is visible but no rows yet
|
||||||
|
float emptyY = ImGui::GetCursorScreenPos().y;
|
||||||
|
float emptyX = ImGui::GetCursorScreenPos().x;
|
||||||
|
float centerX = emptyX + availWidth * 0.5f;
|
||||||
|
ImFont* icoFont = Type().iconMed();
|
||||||
|
const char* emptyIcon = ICON_MD_HOURGLASS_EMPTY;
|
||||||
|
ImVec2 iSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, emptyIcon);
|
||||||
|
miningChildDL->AddText(icoFont, icoFont->LegacySize,
|
||||||
|
ImVec2(centerX - iSz.x * 0.5f, emptyY),
|
||||||
|
OnSurfaceDisabled(), emptyIcon);
|
||||||
|
const char* emptyMsg = s_pool_mode
|
||||||
|
? TR("mining_no_payouts_yet")
|
||||||
|
: TR("mining_no_blocks_yet");
|
||||||
|
ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, emptyMsg);
|
||||||
|
miningChildDL->AddText(capFont, capFont->LegacySize,
|
||||||
|
ImVec2(centerX - msgSz.x * 0.5f, emptyY + iSz.y + Layout::spacingXs()),
|
||||||
|
OnSurfaceDisabled(), emptyMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t mi = 0; mi < recentMined.size(); mi++) {
|
||||||
|
const auto& mtx = recentMined[mi];
|
||||||
|
|
||||||
|
ImVec2 rMin = ImGui::GetCursorScreenPos();
|
||||||
|
float rH = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
|
||||||
|
ImVec2 rMax(rMin.x + availWidth, rMin.y + rH);
|
||||||
|
|
||||||
|
// Subtle background on hover (inset from card edges)
|
||||||
|
bool hovered = material::IsRectHovered(rMin, rMax);
|
||||||
|
bool isClickable = !mtx.txid.empty();
|
||||||
|
if (hovered) {
|
||||||
|
dl->AddRectFilled(ImVec2(rMin.x + pad * 0.5f, rMin.y),
|
||||||
|
ImVec2(rMax.x - pad * 0.5f, rMax.y),
|
||||||
|
IM_COL32(255, 255, 255, 8), 3.0f * dp);
|
||||||
|
if (isClickable) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float rx = rMin.x + pad;
|
||||||
|
float ry = rMin.y + Layout::spacingXs();
|
||||||
|
|
||||||
|
// Mining icon — Material Design
|
||||||
|
ImFont* iconFont = Type().iconSmall();
|
||||||
|
const char* mIcon = ICON_MD_CONSTRUCTION;
|
||||||
|
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, mIcon);
|
||||||
|
float iconX = rx + 2 * dp, iconY = ry + 2 * dp;
|
||||||
|
dl->AddText(iconFont, iconFont->LegacySize,
|
||||||
|
ImVec2(iconX, iconY),
|
||||||
|
WithAlpha(Warning(), 200), mIcon);
|
||||||
|
|
||||||
|
// Time
|
||||||
|
int64_t diff = now - mtx.timestamp;
|
||||||
|
if (diff < 60)
|
||||||
|
snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff);
|
||||||
|
else if (diff < 3600)
|
||||||
|
snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60));
|
||||||
|
else if (diff < 86400)
|
||||||
|
snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600));
|
||||||
|
else
|
||||||
|
snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400));
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(rx + iSz.x + 8 * dp, ry), OnSurfaceDisabled(), buf);
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
snprintf(buf, sizeof(buf), "+%.8f %s", mtx.amount, DRAGONX_TICKER);
|
||||||
|
float amtX = rMin.x + pad + (availWidth - pad * 2) * 0.35f;
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(amtX, ry), greenCol2, buf);
|
||||||
|
|
||||||
|
// Maturity badge — inset from right edge
|
||||||
|
float badgeX = rMax.x - pad - Layout::spacingXl() * 3.5f;
|
||||||
|
if (mtx.mature) {
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
|
||||||
|
WithAlpha(Success(), 180), TR("mature"));
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), TR("conf_count"), mtx.confirmations);
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
|
||||||
|
WithAlpha(Warning(), 200), buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click to open in block explorer
|
||||||
|
ImGui::SetCursorScreenPos(rMin);
|
||||||
|
char blockBtnId[32];
|
||||||
|
snprintf(blockBtnId, sizeof(blockBtnId), "##RecentBlock%zu", mi);
|
||||||
|
ImGui::InvisibleButton(blockBtnId, ImVec2(availWidth, rH));
|
||||||
|
if (ImGui::IsItemClicked() && !mtx.txid.empty()) {
|
||||||
|
std::string url = app->settings()->getTxExplorerUrl() + mtx.txid;
|
||||||
|
dragonx::util::Platform::openUrl(url);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered() && !mtx.txid.empty()) {
|
||||||
|
ImGui::SetTooltip("%s", TR("mining_open_in_explorer"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild(); // ##RecentBlocks
|
||||||
|
|
||||||
|
// CSS-style clipping mask
|
||||||
|
{
|
||||||
|
float fadeZone = std::min(capFont->LegacySize * 3.0f, recentH * 0.18f);
|
||||||
|
ApplyScrollEdgeMask(dl, miningParentVtx, miningChildDL, miningChildVtx,
|
||||||
|
recentPanelMin.y, recentPanelMax.y, fadeZone, miningScrollY, miningScrollMaxY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0, gap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace dragonx
|
||||||
26
src/ui/windows/mining_earnings.h
Normal file
26
src/ui/windows/mining_earnings.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// DragonX Wallet - ImGui Edition
|
||||||
|
// Copyright 2024-2026 The Hush Developers
|
||||||
|
// Released under the GPLv3
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../data/wallet_state.h"
|
||||||
|
#include "../material/draw_helpers.h" // GlassPanelSpec
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
class App;
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
// Renders the mining "Earnings" card (Today | Yesterday | All Time | Est. Daily). Extracted
|
||||||
|
// verbatim from the monolithic mining tab; the immediate-mode layout context (draw list, fonts,
|
||||||
|
// scale/spacing, the glass-panel spec, and the parent's pool-mode flag) is passed in so the
|
||||||
|
// rendering flows in sequence exactly as before.
|
||||||
|
void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& mining,
|
||||||
|
ImDrawList* dl, ImFont* capFont, ImFont* sub1, ImFont* ovFont,
|
||||||
|
float dp, float vs, float gap, float pad, bool isMiningActive,
|
||||||
|
bool poolMode, float availWidth,
|
||||||
|
const material::GlassPanelSpec& glassSpec, float sHdr, float gapOver);
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace dragonx
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "mining_benchmark.h"
|
#include "mining_benchmark.h"
|
||||||
#include "mining_tab_helpers.h"
|
#include "mining_tab_helpers.h"
|
||||||
#include "mining_pool_panel.h"
|
#include "mining_pool_panel.h"
|
||||||
|
#include "mining_earnings.h"
|
||||||
#include "xmrig_download_dialog.h"
|
#include "xmrig_download_dialog.h"
|
||||||
#include "../../util/xmrig_updater.h"
|
#include "../../util/xmrig_updater.h"
|
||||||
#include "../../app.h"
|
#include "../../app.h"
|
||||||
@@ -45,8 +46,6 @@ static bool s_threads_initialized = false;
|
|||||||
static bool s_drag_active = false;
|
static bool s_drag_active = false;
|
||||||
static int s_drag_anchor_thread = 0; // thread# where drag started
|
static int s_drag_anchor_thread = 0; // thread# where drag started
|
||||||
|
|
||||||
// Earnings filter: 0 = All, 1 = Solo, 2 = Pool
|
|
||||||
static int s_earnings_filter = 0;
|
|
||||||
|
|
||||||
static ThreadBenchmark s_benchmark;
|
static ThreadBenchmark s_benchmark;
|
||||||
|
|
||||||
@@ -1966,642 +1965,8 @@ static void RenderMiningTabContent(App* app)
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
// EARNINGS — Horizontal row card (Today | Yesterday | All Time | Est. Daily)
|
// EARNINGS — Horizontal row card (Today | Yesterday | All Time | Est. Daily)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
RenderMiningEarnings(app, state, mining, dl, capFont, sub1, ovFont, dp, vs, gap, pad,
|
||||||
// Gather mining transactions from state
|
isMiningActive, s_pool_mode, availWidth, glassSpec, sHdr, gapOver);
|
||||||
double minedToday = 0.0, minedYesterday = 0.0, minedAllTime = 0.0;
|
|
||||||
int minedTodayCount = 0, minedYesterdayCount = 0, minedAllTimeCount = 0;
|
|
||||||
int64_t now = (int64_t)std::time(nullptr);
|
|
||||||
|
|
||||||
// Calendar-day boundaries (local time)
|
|
||||||
time_t nowT = (time_t)now;
|
|
||||||
struct tm local;
|
|
||||||
#ifdef _WIN32
|
|
||||||
localtime_s(&local, &nowT);
|
|
||||||
#else
|
|
||||||
localtime_r(&nowT, &local);
|
|
||||||
#endif
|
|
||||||
local.tm_hour = 0;
|
|
||||||
local.tm_min = 0;
|
|
||||||
local.tm_sec = 0;
|
|
||||||
int64_t todayStart = (int64_t)mktime(&local);
|
|
||||||
int64_t yesterdayStart = todayStart - 86400;
|
|
||||||
|
|
||||||
struct MinedTx {
|
|
||||||
int64_t timestamp;
|
|
||||||
double amount;
|
|
||||||
int confirmations;
|
|
||||||
bool mature;
|
|
||||||
std::string txid;
|
|
||||||
bool isPoolPayout;
|
|
||||||
};
|
|
||||||
std::vector<MinedTx> recentMined;
|
|
||||||
|
|
||||||
for (const auto& tx : state.transactions) {
|
|
||||||
bool isSoloMined = (tx.type == "generate" || tx.type == "immature" || tx.type == "mined");
|
|
||||||
bool isPoolPayout = (tx.type == "receive"
|
|
||||||
&& !tx.memo.empty()
|
|
||||||
&& tx.memo.find("Mining Pool payout") != std::string::npos);
|
|
||||||
if (isSoloMined || isPoolPayout) {
|
|
||||||
// Apply earnings filter
|
|
||||||
if (!app->supportsSoloMining()) {
|
|
||||||
if (s_earnings_filter == 1 && !isPoolPayout) continue;
|
|
||||||
} else {
|
|
||||||
if (s_earnings_filter == 1 && !isSoloMined) continue;
|
|
||||||
if (s_earnings_filter == 2 && !isPoolPayout) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
double amt = std::abs(tx.amount);
|
|
||||||
minedAllTime += amt;
|
|
||||||
minedAllTimeCount++;
|
|
||||||
if (tx.timestamp >= todayStart) {
|
|
||||||
minedToday += amt;
|
|
||||||
minedTodayCount++;
|
|
||||||
} else if (tx.timestamp >= yesterdayStart) {
|
|
||||||
minedYesterday += amt;
|
|
||||||
minedYesterdayCount++;
|
|
||||||
}
|
|
||||||
// Separate solo blocks from pool payouts based on current mode
|
|
||||||
bool showInCurrentMode = s_pool_mode ? isPoolPayout : isSoloMined;
|
|
||||||
if (showInCurrentMode && recentMined.size() < 4) {
|
|
||||||
recentMined.push_back({tx.timestamp, amt, tx.confirmations, tx.confirmations >= 100, tx.txid, isPoolPayout});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use pool hashrate for EST. DAILY when in pool mode
|
|
||||||
double estHashrate = s_pool_mode ? state.pool_mining.hashrate_10s : mining.localHashrate;
|
|
||||||
double est_hours_2 = EstimateHoursToBlock(estHashrate, mining.networkHashrate, mining.difficulty);
|
|
||||||
double estDailyBlocks = (est_hours_2 > 0) ? (24.0 / est_hours_2) : 0.0;
|
|
||||||
double blockReward = schema::UI().drawElement("business", "block-reward").size;
|
|
||||||
double estDaily = estDailyBlocks * blockReward;
|
|
||||||
bool estActive = isMiningActive && estDaily > 0;
|
|
||||||
|
|
||||||
ImU32 greenCol2 = Success();
|
|
||||||
|
|
||||||
// --- Combined Earnings + Details card ---
|
|
||||||
float earningsRowH = ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + pad * 1.5f;
|
|
||||||
float detailsContentH = pad * 0.5f + capFont->LegacySize + pad * 0.5f;
|
|
||||||
float barH_est = capFont->LegacySize + Layout::spacingMd() * 2.0f;
|
|
||||||
float combinedCardH = earningsRowH + detailsContentH + barH_est + pad;
|
|
||||||
combinedCardH = std::max(combinedCardH,
|
|
||||||
schema::UI().drawElement("tabs.mining", "details-card-min-height").size + earningsRowH);
|
|
||||||
|
|
||||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
|
||||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + combinedCardH);
|
|
||||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
|
||||||
|
|
||||||
// === Earnings filter toggle button (top-right of card) ===
|
|
||||||
{
|
|
||||||
const bool soloMiningAvailable = app->supportsSoloMining();
|
|
||||||
const char* filterLabels[] = { TR("mining_filter_all"), soloMiningAvailable ? TR("mining_solo") : TR("mining_pool"), TR("mining_pool") };
|
|
||||||
const char* filterIcons[] = { ICON_MD_FUNCTIONS, soloMiningAvailable ? ICON_MD_MEMORY : ICON_MD_CLOUD, ICON_MD_CLOUD };
|
|
||||||
const int filterCount = soloMiningAvailable ? 3 : 2;
|
|
||||||
const char* curIcon = filterIcons[s_earnings_filter];
|
|
||||||
const char* curLabel = filterLabels[s_earnings_filter];
|
|
||||||
|
|
||||||
ImFont* icoFont = Type().iconSmall();
|
|
||||||
float icoH = icoFont->LegacySize;
|
|
||||||
ImVec2 icoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, curIcon);
|
|
||||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, curLabel);
|
|
||||||
float padH = Layout::spacingSm();
|
|
||||||
float btnW = padH + icoSz.x + Layout::spacingXs() + lblSz.x + padH;
|
|
||||||
float btnH = icoH + 8.0f * dp;
|
|
||||||
float btnX = cardMax.x - pad - btnW;
|
|
||||||
float btnY = cardMin.y + (earningsRowH - btnH) * 0.5f;
|
|
||||||
|
|
||||||
ImVec2 bMin(btnX, btnY), bMax(btnX + btnW, btnY + btnH);
|
|
||||||
bool hov = material::IsRectHovered(bMin, bMax);
|
|
||||||
|
|
||||||
// Pill background
|
|
||||||
ImU32 pillBg = s_earnings_filter != 0
|
|
||||||
? WithAlpha(Primary(), 60)
|
|
||||||
: WithAlpha(OnSurface(), hov ? 25 : 12);
|
|
||||||
dl->AddRectFilled(bMin, bMax, pillBg, btnH * 0.5f);
|
|
||||||
|
|
||||||
// Icon
|
|
||||||
ImU32 icoCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceDisabled());
|
|
||||||
float cx = bMin.x + padH;
|
|
||||||
float cy = bMin.y + (btnH - icoSz.y) * 0.5f;
|
|
||||||
dl->AddText(icoFont, icoFont->LegacySize, ImVec2(cx, cy), icoCol, curIcon);
|
|
||||||
|
|
||||||
// Label
|
|
||||||
ImU32 lblCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceMedium());
|
|
||||||
float lx = cx + icoSz.x + Layout::spacingXs();
|
|
||||||
float ly = bMin.y + (btnH - lblSz.y) * 0.5f;
|
|
||||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(lx, ly), lblCol, curLabel);
|
|
||||||
|
|
||||||
// Click target
|
|
||||||
ImVec2 savedCur = ImGui::GetCursorScreenPos();
|
|
||||||
ImGui::SetCursorScreenPos(bMin);
|
|
||||||
ImGui::InvisibleButton("##EarningsFilter", ImVec2(btnW, btnH));
|
|
||||||
if (ImGui::IsItemClicked()) {
|
|
||||||
s_earnings_filter = (s_earnings_filter + 1) % filterCount;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
||||||
const char* tips[] = {
|
|
||||||
TR("mining_filter_tip_all"),
|
|
||||||
soloMiningAvailable ? TR("mining_filter_tip_solo") : TR("mining_filter_tip_pool"),
|
|
||||||
TR("mining_filter_tip_pool")
|
|
||||||
};
|
|
||||||
ImGui::SetTooltip("%s", tips[s_earnings_filter]);
|
|
||||||
}
|
|
||||||
ImGui::SetCursorScreenPos(savedCur);
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Earnings section (top of combined card) ===
|
|
||||||
{
|
|
||||||
const int numCols = 4;
|
|
||||||
float colW = (availWidth - pad * 2) / (float)numCols;
|
|
||||||
float ey = cardMin.y + pad * 0.5f;
|
|
||||||
char valBuf[64], subBuf2[64];
|
|
||||||
|
|
||||||
struct EarningsEntry {
|
|
||||||
const char* label;
|
|
||||||
const char* value;
|
|
||||||
const char* sub;
|
|
||||||
ImU32 col;
|
|
||||||
};
|
|
||||||
|
|
||||||
snprintf(valBuf, sizeof(valBuf), "+%.4f", minedToday);
|
|
||||||
snprintf(subBuf2, sizeof(subBuf2), "(%d txn)", minedTodayCount);
|
|
||||||
char todayVal[64], todaySub[64];
|
|
||||||
strncpy(todayVal, valBuf, sizeof(todayVal));
|
|
||||||
strncpy(todaySub, subBuf2, sizeof(todaySub));
|
|
||||||
|
|
||||||
char yesterdayVal[64], yesterdaySub[64];
|
|
||||||
snprintf(yesterdayVal, sizeof(yesterdayVal), "+%.4f", minedYesterday);
|
|
||||||
snprintf(yesterdaySub, sizeof(yesterdaySub), "(%d txn)", minedYesterdayCount);
|
|
||||||
|
|
||||||
char allVal[64], allSub[64];
|
|
||||||
snprintf(allVal, sizeof(allVal), "+%.4f", minedAllTime);
|
|
||||||
snprintf(allSub, sizeof(allSub), "(%d txn)", minedAllTimeCount);
|
|
||||||
|
|
||||||
char estVal[64];
|
|
||||||
if (estActive)
|
|
||||||
snprintf(estVal, sizeof(estVal), "~%.4f", estDaily);
|
|
||||||
else
|
|
||||||
snprintf(estVal, sizeof(estVal), "N/A");
|
|
||||||
|
|
||||||
EarningsEntry entries[] = {
|
|
||||||
{ TR("mining_today"), todayVal, todaySub, greenCol2 },
|
|
||||||
{ TR("mining_yesterday"), yesterdayVal, yesterdaySub, OnSurface() },
|
|
||||||
{ TR("mining_all_time"), allVal, allSub, OnSurface() },
|
|
||||||
{ TR("mining_est_daily"), estVal, nullptr, estActive ? greenCol2 : OnSurfaceDisabled() },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int ei = 0; ei < numCols; ei++) {
|
|
||||||
float sx = cardMin.x + pad + ei * colW;
|
|
||||||
float centerX = sx + colW * 0.5f;
|
|
||||||
|
|
||||||
// Overline label (centered)
|
|
||||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, entries[ei].label);
|
|
||||||
dl->AddText(ovFont, ovFont->LegacySize,
|
|
||||||
ImVec2(centerX - lblSz.x * 0.5f, ey), OnSurfaceMedium(), entries[ei].label);
|
|
||||||
|
|
||||||
// Value (centered)
|
|
||||||
float valY = ey + ovFont->LegacySize + Layout::spacingXs();
|
|
||||||
ImVec2 valSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, entries[ei].value);
|
|
||||||
|
|
||||||
if (entries[ei].sub) {
|
|
||||||
// Value + sub annotation side by side, centered together
|
|
||||||
ImVec2 subSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, entries[ei].sub);
|
|
||||||
float totalW = valSz.x + 4 * dp + subSz.x;
|
|
||||||
float startX = centerX - totalW * 0.5f;
|
|
||||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(startX, valY), entries[ei].col, entries[ei].value);
|
|
||||||
dl->AddText(capFont, capFont->LegacySize,
|
|
||||||
ImVec2(startX + valSz.x + 4 * dp, valY + (sub1->LegacySize - capFont->LegacySize) * 0.5f),
|
|
||||||
OnSurfaceDisabled(), entries[ei].sub);
|
|
||||||
} else {
|
|
||||||
dl->AddText(sub1, sub1->LegacySize,
|
|
||||||
ImVec2(centerX - valSz.x * 0.5f, valY), entries[ei].col, entries[ei].value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Separator between earnings & details ===
|
|
||||||
float earningsSepY = cardMin.y + earningsRowH;
|
|
||||||
{
|
|
||||||
float rnd = glassSpec.rounding;
|
|
||||||
dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, earningsSepY),
|
|
||||||
ImVec2(cardMax.x - rnd * 0.5f, earningsSepY),
|
|
||||||
WithAlpha(OnSurface(), 15), 1.0f * dp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Details section (below separator) ===
|
|
||||||
{
|
|
||||||
float cx = cardMin.x + pad;
|
|
||||||
float cy = earningsSepY + pad * 0.5f;
|
|
||||||
|
|
||||||
// Three equal columns: Difficulty | Block | Mining Address
|
|
||||||
float colW = availWidth / 3.0f;
|
|
||||||
float valOffX = availWidth * schema::UI().drawElement("tabs.mining", "stats-col1-value-x-ratio").size;
|
|
||||||
|
|
||||||
float col1X = cx;
|
|
||||||
float col2X = cx + colW;
|
|
||||||
float col3X = cx + colW * 2.0f;
|
|
||||||
|
|
||||||
// -- Difficulty --
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X, cy), OnSurfaceMedium(), TR("difficulty"));
|
|
||||||
if (mining.difficulty > 0) {
|
|
||||||
snprintf(buf, sizeof(buf), "%.4f", mining.difficulty);
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X + valOffX, cy), OnSurface(), buf);
|
|
||||||
ImVec2 diffSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(col1X + valOffX, cy));
|
|
||||||
ImGui::InvisibleButton("##DiffCopy", ImVec2(diffSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
||||||
ImGui::SetTooltip("%s", TR("mining_click_copy_difficulty"));
|
|
||||||
dl->AddLine(ImVec2(col1X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
|
||||||
ImVec2(col1X + valOffX + diffSz.x, cy + capFont->LegacySize + 1 * dp),
|
|
||||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemClicked()) {
|
|
||||||
ImGui::SetClipboardText(buf);
|
|
||||||
Notifications::instance().info(TR("mining_difficulty_copied"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Block --
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X, cy), OnSurfaceMedium(), TR("block"));
|
|
||||||
if (mining.blocks > 0) {
|
|
||||||
snprintf(buf, sizeof(buf), "%d", mining.blocks);
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X + valOffX, cy), OnSurface(), buf);
|
|
||||||
ImVec2 blkSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(col2X + valOffX, cy));
|
|
||||||
ImGui::InvisibleButton("##BlockCopy", ImVec2(blkSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
||||||
ImGui::SetTooltip("%s", TR("mining_click_copy_block"));
|
|
||||||
dl->AddLine(ImVec2(col2X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
|
||||||
ImVec2(col2X + valOffX + blkSz.x, cy + capFont->LegacySize + 1 * dp),
|
|
||||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemClicked()) {
|
|
||||||
ImGui::SetClipboardText(buf);
|
|
||||||
Notifications::instance().info(TR("mining_block_copied"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Mining Address --
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), TR("mining_mining_addr"));
|
|
||||||
std::string mining_address = "";
|
|
||||||
for (const auto& addr : state.addresses) {
|
|
||||||
if (addr.type == "transparent" && !addr.address.empty()) {
|
|
||||||
mining_address = addr.address;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mining_address.empty()) {
|
|
||||||
float addrAvailW = colW - pad - valOffX;
|
|
||||||
float charW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "M").x;
|
|
||||||
if (charW <= 0.0f) charW = 8.0f;
|
|
||||||
int maxChars = std::max(8, (int)(addrAvailW / charW));
|
|
||||||
std::string truncAddr = mining_address;
|
|
||||||
if ((int)truncAddr.length() > maxChars && maxChars > 5) {
|
|
||||||
int half = (maxChars - 3) / 2;
|
|
||||||
if (half > 0 && (size_t)half < truncAddr.length())
|
|
||||||
truncAddr = truncAddr.substr(0, half) + "..." + truncAddr.substr(truncAddr.length() - half);
|
|
||||||
}
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X + valOffX, cy), OnSurface(), truncAddr.c_str());
|
|
||||||
|
|
||||||
ImVec2 addrTextSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, truncAddr.c_str());
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(col3X + valOffX, cy));
|
|
||||||
ImGui::InvisibleButton("##MiningAddrCopy", ImVec2(addrTextSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
||||||
ImGui::SetTooltip("%s", TR("mining_click_copy_address"));
|
|
||||||
dl->AddLine(ImVec2(col3X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
|
||||||
ImVec2(col3X + valOffX + addrTextSz.x, cy + capFont->LegacySize + 1 * dp),
|
|
||||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemClicked()) {
|
|
||||||
ImGui::SetClipboardText(mining_address.c_str());
|
|
||||||
Notifications::instance().info(TR("mining_address_copied"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Memory bar — centered in remaining space below details ----
|
|
||||||
{
|
|
||||||
double totalRAM = dragonx::util::Platform::getTotalSystemRAM_MB();
|
|
||||||
double usedRAM = dragonx::util::Platform::getUsedSystemRAM_MB();
|
|
||||||
double selfRAM = dragonx::util::Platform::getSelfMemoryUsageMB();
|
|
||||||
double daemonRAM = app->getDaemonMemoryUsageMB();
|
|
||||||
// Include xmrig memory when pool mining
|
|
||||||
double xmrigRAM = state.pool_mining.memory_used / (1024.0 * 1024.0); // bytes -> MB
|
|
||||||
double appRAM = selfRAM + daemonRAM + xmrigRAM; // App + daemon + xmrig combined
|
|
||||||
|
|
||||||
// Fixed bar height (text + padding)
|
|
||||||
float barH = capFont->LegacySize + Layout::spacingMd() * 2.0f;
|
|
||||||
float barRnd = barH * 0.5f; // fully rounded corners
|
|
||||||
|
|
||||||
// Details content ends here
|
|
||||||
float detailsEndY = earningsSepY + detailsContentH;
|
|
||||||
|
|
||||||
// Subtle top separator at the boundary
|
|
||||||
float rnd = glassSpec.rounding;
|
|
||||||
float stripY = detailsEndY;
|
|
||||||
dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, stripY),
|
|
||||||
ImVec2(cardMax.x - rnd * 0.5f, stripY),
|
|
||||||
WithAlpha(OnSurface(), 15), 1.0f * dp);
|
|
||||||
float remainingH = cardMax.y - stripY;
|
|
||||||
float barX = cardMin.x + pad;
|
|
||||||
float barW = cardMax.x - pad - barX;
|
|
||||||
float barY = stripY + (remainingH - barH) * 0.5f;
|
|
||||||
float textY = barY + (barH - capFont->LegacySize) * 0.5f;
|
|
||||||
float textPadX = Layout::spacingMd();
|
|
||||||
|
|
||||||
// Background track
|
|
||||||
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
|
|
||||||
WithAlpha(OnSurface(), 20), barRnd);
|
|
||||||
|
|
||||||
float sysFrac = 0.0f, appFrac = 0.0f;
|
|
||||||
float sysFillW = 0.0f, appFillW = 0.0f;
|
|
||||||
|
|
||||||
// Helper: draw a fill bar that perfectly matches the track's rounded
|
|
||||||
// left edge regardless of fill width. We draw a full-width rounded
|
|
||||||
// rect (same shape as the track) but clip it to just the fill portion.
|
|
||||||
auto drawFillBar = [&](float fillW, ImU32 col) {
|
|
||||||
if (fillW <= 1.0f) return;
|
|
||||||
dl->PushClipRect(ImVec2(barX, barY), ImVec2(barX + fillW, barY + barH), true);
|
|
||||||
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
|
|
||||||
col, barRnd);
|
|
||||||
dl->PopClipRect();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (usedRAM > 0 && totalRAM > 0) {
|
|
||||||
sysFrac = std::clamp((float)(usedRAM / totalRAM), 0.0f, 1.0f);
|
|
||||||
sysFillW = barW * sysFrac;
|
|
||||||
|
|
||||||
// System memory bar (subtle fill)
|
|
||||||
ImU32 ramBarCol = schema::UI().resolveColor("var(--ram-bar-system)",
|
|
||||||
IsDarkTheme() ? IM_COL32(255, 255, 255, 46) : IM_COL32(0, 0, 0, 46));
|
|
||||||
drawFillBar(sysFillW, ramBarCol);
|
|
||||||
|
|
||||||
// App+daemon memory bar (saturated accent)
|
|
||||||
if (appRAM > 0) {
|
|
||||||
appFrac = std::clamp((float)(appRAM / totalRAM), 0.0f, sysFrac);
|
|
||||||
appFillW = barW * appFrac;
|
|
||||||
ImU32 appBarCol = schema::UI().resolveColor("var(--ram-bar-app)",
|
|
||||||
ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]));
|
|
||||||
drawFillBar(appFillW, appBarCol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Text overlaying the bar ---
|
|
||||||
float accentEdge = barX + appFillW;
|
|
||||||
float whiteEdge = barX + sysFillW;
|
|
||||||
|
|
||||||
// Determine text colors for bar segments
|
|
||||||
// On dark themes: white text on empty, dark on filled bars (both system and app)
|
|
||||||
// On light themes: dark text on empty/system, white on app accent bar
|
|
||||||
ImU32 barTextOnEmpty = IsDarkTheme() ? IM_COL32(255, 255, 255, 230)
|
|
||||||
: IM_COL32(30, 30, 30, 230);
|
|
||||||
ImU32 barTextOnFill = IsDarkTheme() ? IM_COL32(30, 30, 30, 230)
|
|
||||||
: IM_COL32(255, 255, 255, 230);
|
|
||||||
ImU32 barTextOnAccent = IsDarkTheme() ? IM_COL32(0, 0, 0, 210)
|
|
||||||
: IM_COL32(255, 255, 255, 230);
|
|
||||||
|
|
||||||
// App usage on the left
|
|
||||||
char appBuf[64] = {};
|
|
||||||
if (appRAM > 0) {
|
|
||||||
if (appRAM >= 1024.0)
|
|
||||||
snprintf(appBuf, sizeof(appBuf), "%.1f GB", appRAM / 1024.0);
|
|
||||||
else
|
|
||||||
snprintf(appBuf, sizeof(appBuf), "%.0f MB", appRAM);
|
|
||||||
|
|
||||||
float appTextX = barX + textPadX;
|
|
||||||
float appTextW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, appBuf).x;
|
|
||||||
float appTextEnd = appTextX + appTextW;
|
|
||||||
|
|
||||||
// Inside accent bar: white | inside white bar: dark | outside: white
|
|
||||||
if (appTextEnd <= accentEdge) {
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
|
||||||
barTextOnAccent, appBuf);
|
|
||||||
} else if (appTextX >= whiteEdge) {
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
|
||||||
barTextOnEmpty, appBuf);
|
|
||||||
} else {
|
|
||||||
// Part in accent bar: white
|
|
||||||
if (accentEdge > appTextX) {
|
|
||||||
dl->PushClipRect(ImVec2(appTextX, barY), ImVec2(accentEdge, barY + barH));
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
|
||||||
barTextOnAccent, appBuf);
|
|
||||||
dl->PopClipRect();
|
|
||||||
}
|
|
||||||
// Part in white bar (past accent): dark
|
|
||||||
float dkS = std::max(appTextX, accentEdge);
|
|
||||||
float dkE = std::min(appTextEnd, whiteEdge);
|
|
||||||
if (dkE > dkS) {
|
|
||||||
dl->PushClipRect(ImVec2(dkS, barY), ImVec2(dkE, barY + barH));
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
|
||||||
barTextOnFill, appBuf);
|
|
||||||
dl->PopClipRect();
|
|
||||||
}
|
|
||||||
// Part outside white bar: white
|
|
||||||
if (appTextEnd > whiteEdge) {
|
|
||||||
dl->PushClipRect(ImVec2(whiteEdge, barY), ImVec2(appTextEnd + 1, barY + barH));
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
|
|
||||||
barTextOnEmpty, appBuf);
|
|
||||||
dl->PopClipRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// System usage on the right
|
|
||||||
char sysBuf[64] = {};
|
|
||||||
if (usedRAM > 0 && totalRAM > 0)
|
|
||||||
snprintf(sysBuf, sizeof(sysBuf), "%.1f / %.0f GB", usedRAM / 1024.0, totalRAM / 1024.0);
|
|
||||||
else if (totalRAM > 0)
|
|
||||||
snprintf(sysBuf, sizeof(sysBuf), "-- / %.0f GB", totalRAM / 1024.0);
|
|
||||||
else
|
|
||||||
snprintf(sysBuf, sizeof(sysBuf), "N/A");
|
|
||||||
|
|
||||||
float sysTextW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, sysBuf).x;
|
|
||||||
float sysTextX = barX + barW - textPadX - sysTextW;
|
|
||||||
|
|
||||||
if (sysTextX >= whiteEdge) {
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
|
||||||
barTextOnEmpty, sysBuf);
|
|
||||||
} else if (sysTextX + sysTextW <= whiteEdge) {
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
|
||||||
barTextOnFill, sysBuf);
|
|
||||||
} else {
|
|
||||||
dl->PushClipRect(ImVec2(sysTextX, barY), ImVec2(whiteEdge, barY + barH));
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
|
||||||
barTextOnFill, sysBuf);
|
|
||||||
dl->PopClipRect();
|
|
||||||
dl->PushClipRect(ImVec2(whiteEdge, barY), ImVec2(sysTextX + sysTextW + 1, barY + barH));
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
|
|
||||||
barTextOnEmpty, sysBuf);
|
|
||||||
dl->PopClipRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invisible button over the bar for tooltip interaction
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(barX, barY));
|
|
||||||
ImGui::InvisibleButton("##rambar", ImVec2(barW, barH));
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::BeginTooltip();
|
|
||||||
if (selfRAM >= 1024.0)
|
|
||||||
ImGui::Text(TR("ram_wallet_gb"), selfRAM / 1024.0);
|
|
||||||
else
|
|
||||||
ImGui::Text(TR("ram_wallet_mb"), selfRAM);
|
|
||||||
if (daemonRAM >= 1024.0)
|
|
||||||
ImGui::Text(TR("ram_daemon_gb"), daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
|
|
||||||
else
|
|
||||||
ImGui::Text(TR("ram_daemon_mb"), daemonRAM, app->getDaemonMemDiag().c_str());
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text(TR("ram_system_gb"), usedRAM / 1024.0, totalRAM / 1024.0);
|
|
||||||
ImGui::EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
|
||||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
|
||||||
ImGui::Dummy(ImVec2(0, gap));
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// RECENT BLOCKS — last 4 mined blocks (always shown in pool mode)
|
|
||||||
// ============================================================
|
|
||||||
if (!recentMined.empty() || s_pool_mode) {
|
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
|
|
||||||
s_pool_mode ? TR("mining_recent_payouts") : TR("mining_recent_blocks"));
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
|
||||||
|
|
||||||
float rowH_blocks = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
|
|
||||||
// Size to remaining space — proportional budget ensures fit
|
|
||||||
float recentAvailH = ImGui::GetContentRegionAvail().y - sHdr - gapOver;
|
|
||||||
float minRows = recentMined.empty() ? 2.0f : (float)recentMined.size();
|
|
||||||
float contentH_blocks = rowH_blocks * minRows + pad * 2.5f;
|
|
||||||
float recentH = std::clamp(contentH_blocks, 30.0f * dp, std::max(30.0f * dp, recentAvailH));
|
|
||||||
|
|
||||||
// Glass panel wrapping the list + scroll-edge mask state
|
|
||||||
ImVec2 recentPanelMin = ImGui::GetCursorScreenPos();
|
|
||||||
ImVec2 recentPanelMax(recentPanelMin.x + availWidth, recentPanelMin.y + recentH);
|
|
||||||
GlassPanelSpec recentGlass;
|
|
||||||
recentGlass.rounding = Layout::glassRounding();
|
|
||||||
DrawGlassPanel(dl, recentPanelMin, recentPanelMax, recentGlass);
|
|
||||||
|
|
||||||
float miningScrollY = 0.0f, miningScrollMaxY = 0.0f;
|
|
||||||
int miningParentVtx = dl->VtxBuffer.Size;
|
|
||||||
|
|
||||||
ImGui::BeginChild("##RecentBlocks", ImVec2(availWidth, recentH), false,
|
|
||||||
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
|
|
||||||
ImDrawList* miningChildDL = ImGui::GetWindowDrawList();
|
|
||||||
int miningChildVtx = miningChildDL->VtxBuffer.Size;
|
|
||||||
|
|
||||||
miningScrollY = ImGui::GetScrollY();
|
|
||||||
miningScrollMaxY = ImGui::GetScrollMaxY();
|
|
||||||
|
|
||||||
// Top padding inside glass card
|
|
||||||
ImGui::Dummy(ImVec2(0, pad * 0.5f));
|
|
||||||
|
|
||||||
if (recentMined.empty()) {
|
|
||||||
// Empty state — card is visible but no rows yet
|
|
||||||
float emptyY = ImGui::GetCursorScreenPos().y;
|
|
||||||
float emptyX = ImGui::GetCursorScreenPos().x;
|
|
||||||
float centerX = emptyX + availWidth * 0.5f;
|
|
||||||
ImFont* icoFont = Type().iconMed();
|
|
||||||
const char* emptyIcon = ICON_MD_HOURGLASS_EMPTY;
|
|
||||||
ImVec2 iSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, emptyIcon);
|
|
||||||
miningChildDL->AddText(icoFont, icoFont->LegacySize,
|
|
||||||
ImVec2(centerX - iSz.x * 0.5f, emptyY),
|
|
||||||
OnSurfaceDisabled(), emptyIcon);
|
|
||||||
const char* emptyMsg = s_pool_mode
|
|
||||||
? TR("mining_no_payouts_yet")
|
|
||||||
: TR("mining_no_blocks_yet");
|
|
||||||
ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, emptyMsg);
|
|
||||||
miningChildDL->AddText(capFont, capFont->LegacySize,
|
|
||||||
ImVec2(centerX - msgSz.x * 0.5f, emptyY + iSz.y + Layout::spacingXs()),
|
|
||||||
OnSurfaceDisabled(), emptyMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t mi = 0; mi < recentMined.size(); mi++) {
|
|
||||||
const auto& mtx = recentMined[mi];
|
|
||||||
|
|
||||||
ImVec2 rMin = ImGui::GetCursorScreenPos();
|
|
||||||
float rH = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
|
|
||||||
ImVec2 rMax(rMin.x + availWidth, rMin.y + rH);
|
|
||||||
|
|
||||||
// Subtle background on hover (inset from card edges)
|
|
||||||
bool hovered = material::IsRectHovered(rMin, rMax);
|
|
||||||
bool isClickable = !mtx.txid.empty();
|
|
||||||
if (hovered) {
|
|
||||||
dl->AddRectFilled(ImVec2(rMin.x + pad * 0.5f, rMin.y),
|
|
||||||
ImVec2(rMax.x - pad * 0.5f, rMax.y),
|
|
||||||
IM_COL32(255, 255, 255, 8), 3.0f * dp);
|
|
||||||
if (isClickable) {
|
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float rx = rMin.x + pad;
|
|
||||||
float ry = rMin.y + Layout::spacingXs();
|
|
||||||
|
|
||||||
// Mining icon — Material Design
|
|
||||||
ImFont* iconFont = Type().iconSmall();
|
|
||||||
const char* mIcon = ICON_MD_CONSTRUCTION;
|
|
||||||
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, mIcon);
|
|
||||||
float iconX = rx + 2 * dp, iconY = ry + 2 * dp;
|
|
||||||
dl->AddText(iconFont, iconFont->LegacySize,
|
|
||||||
ImVec2(iconX, iconY),
|
|
||||||
WithAlpha(Warning(), 200), mIcon);
|
|
||||||
|
|
||||||
// Time
|
|
||||||
int64_t diff = now - mtx.timestamp;
|
|
||||||
if (diff < 60)
|
|
||||||
snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff);
|
|
||||||
else if (diff < 3600)
|
|
||||||
snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60));
|
|
||||||
else if (diff < 86400)
|
|
||||||
snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600));
|
|
||||||
else
|
|
||||||
snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400));
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(rx + iSz.x + 8 * dp, ry), OnSurfaceDisabled(), buf);
|
|
||||||
|
|
||||||
// Amount
|
|
||||||
snprintf(buf, sizeof(buf), "+%.8f %s", mtx.amount, DRAGONX_TICKER);
|
|
||||||
float amtX = rMin.x + pad + (availWidth - pad * 2) * 0.35f;
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(amtX, ry), greenCol2, buf);
|
|
||||||
|
|
||||||
// Maturity badge — inset from right edge
|
|
||||||
float badgeX = rMax.x - pad - Layout::spacingXl() * 3.5f;
|
|
||||||
if (mtx.mature) {
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
|
|
||||||
WithAlpha(Success(), 180), TR("mature"));
|
|
||||||
} else {
|
|
||||||
snprintf(buf, sizeof(buf), TR("conf_count"), mtx.confirmations);
|
|
||||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
|
|
||||||
WithAlpha(Warning(), 200), buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Click to open in block explorer
|
|
||||||
ImGui::SetCursorScreenPos(rMin);
|
|
||||||
char blockBtnId[32];
|
|
||||||
snprintf(blockBtnId, sizeof(blockBtnId), "##RecentBlock%zu", mi);
|
|
||||||
ImGui::InvisibleButton(blockBtnId, ImVec2(availWidth, rH));
|
|
||||||
if (ImGui::IsItemClicked() && !mtx.txid.empty()) {
|
|
||||||
std::string url = app->settings()->getTxExplorerUrl() + mtx.txid;
|
|
||||||
dragonx::util::Platform::openUrl(url);
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered() && !mtx.txid.empty()) {
|
|
||||||
ImGui::SetTooltip("%s", TR("mining_open_in_explorer"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndChild(); // ##RecentBlocks
|
|
||||||
|
|
||||||
// CSS-style clipping mask
|
|
||||||
{
|
|
||||||
float fadeZone = std::min(capFont->LegacySize * 3.0f, recentH * 0.18f);
|
|
||||||
ApplyScrollEdgeMask(dl, miningParentVtx, miningChildDL, miningChildVtx,
|
|
||||||
recentPanelMin.y, recentPanelMax.y, fadeZone, miningScrollY, miningScrollMaxY);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, gap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// POOL CONNECTION STATUS — inline indicator (pool mode, no log)
|
// POOL CONNECTION STATUS — inline indicator (pool mode, no log)
|
||||||
|
|||||||
Reference in New Issue
Block a user