feat: Full UI internationalization, pool hashrate stats, and layout caching

- Replace all hardcoded English strings with TR() translation keys across
  every tab, dialog, and component (~20 UI files)
- Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with
  complete translations (~37k lines added)
- Improve i18n loader with exe-relative path fallback and English base
  fallback for missing keys
- Add pool-side hashrate polling via pool stats API in xmrig_manager
- Introduce Layout::beginFrame() per-frame caching and refresh balance
  layout config only on schema generation change
- Offload daemon output parsing to worker thread
- Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
This commit is contained in:
dan_s
2026-03-11 00:40:50 -05:00
parent cc617dd5be
commit 96c27bb949
71 changed files with 43567 additions and 5267 deletions

View File

@@ -4,6 +4,7 @@
#include "mining_tab.h"
#include "../../app.h"
#include "../../util/i18n.h"
#include "../../config/version.h"
#include "../../data/wallet_state.h"
#include "../../config/settings.h"
@@ -38,6 +39,9 @@ static bool s_threads_initialized = false;
static bool s_drag_active = false;
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;
// Pool mode state
static bool s_pool_mode = false;
static char s_pool_url[256] = "pool.dragonx.is:3433";
@@ -126,7 +130,13 @@ void RenderMiningTab(App* app)
int max_threads = GetMaxMiningThreads();
if (!s_threads_initialized) {
s_selected_threads = mining.generate ? std::max(1, mining.genproclimit) : 1;
int saved = app->settings()->getPoolThreads();
if (mining.generate)
s_selected_threads = std::max(1, mining.genproclimit);
else if (saved > 0)
s_selected_threads = std::min(saved, max_threads);
else
s_selected_threads = 1;
s_threads_initialized = true;
}
@@ -234,7 +244,7 @@ void RenderMiningTab(App* app)
dl->AddRectFilled(soloMin, soloMax, WithAlpha(OnSurface(), 20), toggleRnd);
}
{
const char* label = "SOLO";
const char* label = TR("mining_solo");
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
float lx = soloMin.x + (toggleW - sz.x) * 0.5f;
float ly = soloMin.y + (toggleH - sz.y) * 0.5f;
@@ -255,7 +265,7 @@ void RenderMiningTab(App* app)
dl->AddRectFilled(poolMin, poolMax, WithAlpha(OnSurface(), 20), toggleRnd);
}
{
const char* label = "POOL";
const char* label = TR("mining_pool");
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
float lx = poolMin.x + (toggleW - sz.x) * 0.5f;
float ly = poolMin.y + (toggleH - sz.y) * 0.5f;
@@ -287,7 +297,7 @@ void RenderMiningTab(App* app)
}
if (poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (poolHov && soloMiningActive && !s_pool_mode) {
ImGui::SetTooltip("Stop solo mining to use pool mining");
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
}
ImGui::SetCursorScreenPos(ImVec2(tMin.x, tMax.y));
@@ -307,7 +317,7 @@ void RenderMiningTab(App* app)
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingSm());
ImGui::PushFont(capFont);
ImGui::TextUnformatted("Stop solo mining to configure pool settings");
ImGui::TextUnformatted(TR("mining_stop_solo_for_pool_settings"));
ImGui::PopFont();
ImGui::PopStyleColor();
} else if (s_pool_mode) {
@@ -339,7 +349,7 @@ void RenderMiningTab(App* app)
// === Pool URL input ===
ImGui::SetNextItemWidth(urlW);
if (ImGui::InputTextWithHint("##PoolURL", "Pool URL", s_pool_url, sizeof(s_pool_url))) {
if (ImGui::InputTextWithHint("##PoolURL", TR("mining_pool_url"), s_pool_url, sizeof(s_pool_url))) {
s_pool_settings_dirty = true;
}
@@ -357,7 +367,7 @@ void RenderMiningTab(App* app)
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
StateHover(), 4.0f * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Saved pools");
ImGui::SetTooltip("%s", TR("mining_saved_pools"));
}
ImFont* icoFont = Type().iconSmall();
const char* dropIcon = ICON_MD_ARROW_DROP_DOWN;
@@ -389,7 +399,7 @@ void RenderMiningTab(App* app)
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
StateHover(), 4.0f * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip(alreadySaved ? "Already saved" : "Save pool URL");
ImGui::SetTooltip("%s", alreadySaved ? TR("mining_already_saved") : TR("mining_save_pool_url"));
}
ImFont* icoFont = Type().iconSmall();
const char* saveIcon = alreadySaved ? ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER;
@@ -416,11 +426,19 @@ void RenderMiningTab(App* app)
if (savedUrls.empty()) {
ImGui::SetCursorPosX(8 * dp);
ImGui::PushFont(Type().caption());
ImGui::TextDisabled("No saved pools");
ImGui::TextDisabled("%s", TR("mining_no_saved_pools"));
ImGui::PopFont();
ImGui::SetCursorPosX(8 * dp);
ImGui::PushFont(Type().caption());
ImGui::TextDisabled("Click " ICON_MD_BOOKMARK_BORDER " to save");
ImGui::TextDisabled("%s", TR("mining_click"));
ImGui::PopFont();
ImGui::SameLine(0, 2 * dp);
ImGui::PushFont(Type().iconSmall());
ImGui::TextDisabled(ICON_MD_BOOKMARK_BORDER);
ImGui::PopFont();
ImGui::SameLine(0, 2 * dp);
ImGui::PushFont(Type().caption());
ImGui::TextDisabled("%s", TR("mining_to_save"));
ImGui::PopFont();
} else {
std::string urlToRemove;
@@ -457,7 +475,7 @@ void RenderMiningTab(App* app)
if (inXZone) {
pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Remove");
ImGui::SetTooltip("%s", TR("mining_remove"));
} else if (rowHov) {
// Show faint X when row is hovered
ImFont* icoF = Type().iconSmall();
@@ -508,11 +526,11 @@ void RenderMiningTab(App* app)
float wrkGroupW = wrkW + perGroupExtra;
ImGui::SetNextItemWidth(wrkW);
if (ImGui::InputTextWithHint("##PoolWorker", "Payout Address", s_pool_worker, sizeof(s_pool_worker))) {
if (ImGui::InputTextWithHint("##PoolWorker", TR("mining_payout_address"), s_pool_worker, sizeof(s_pool_worker))) {
s_pool_settings_dirty = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Your DRAGONX address for receiving pool payouts");
ImGui::SetTooltip("%s", TR("mining_payout_tooltip"));
}
// --- Worker: Dropdown arrow button ---
@@ -529,7 +547,7 @@ void RenderMiningTab(App* app)
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
StateHover(), 4.0f * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Saved addresses");
ImGui::SetTooltip("%s", TR("mining_saved_addresses"));
}
ImFont* icoFont = Type().iconSmall();
const char* dropIcon = ICON_MD_ARROW_DROP_DOWN;
@@ -561,7 +579,7 @@ void RenderMiningTab(App* app)
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
StateHover(), 4.0f * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip(alreadySaved ? "Already saved" : "Save payout address");
ImGui::SetTooltip("%s", alreadySaved ? TR("mining_already_saved") : TR("mining_save_payout_address"));
}
ImFont* icoFont = Type().iconSmall();
const char* saveIcon = alreadySaved ? ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER;
@@ -589,11 +607,19 @@ void RenderMiningTab(App* app)
if (savedWorkers.empty()) {
ImGui::SetCursorPosX(8 * dp);
ImGui::PushFont(Type().caption());
ImGui::TextDisabled("No saved addresses");
ImGui::TextDisabled("%s", TR("mining_no_saved_addresses"));
ImGui::PopFont();
ImGui::SetCursorPosX(8 * dp);
ImGui::PushFont(Type().caption());
ImGui::TextDisabled("Click " ICON_MD_BOOKMARK_BORDER " to save");
ImGui::TextDisabled("%s", TR("mining_click"));
ImGui::PopFont();
ImGui::SameLine(0, 2 * dp);
ImGui::PushFont(Type().iconSmall());
ImGui::TextDisabled(ICON_MD_BOOKMARK_BORDER);
ImGui::PopFont();
ImGui::SameLine(0, 2 * dp);
ImGui::PushFont(Type().caption());
ImGui::TextDisabled("%s", TR("mining_to_save"));
ImGui::PopFont();
} else {
std::string addrToRemove;
@@ -633,7 +659,7 @@ void RenderMiningTab(App* app)
if (inXZone) {
pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Remove");
ImGui::SetTooltip("%s", TR("mining_remove"));
} else if (rowHov) {
ImFont* icoF = Type().iconSmall();
const char* xIcon = ICON_MD_CLOSE;
@@ -694,7 +720,7 @@ void RenderMiningTab(App* app)
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
StateHover(), 4.0f * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Reset to defaults");
ImGui::SetTooltip("%s", TR("mining_reset_defaults"));
}
// Icon
@@ -781,9 +807,9 @@ void RenderMiningTab(App* app)
// --- Header row: "THREADS 4 / 16" + RAM est + active indicator ---
{
ImVec2 labelPos(cardMin.x + pad, curY);
dl->AddText(ovFont, ovFont->LegacySize, labelPos, OnSurfaceMedium(), "THREADS");
dl->AddText(ovFont, ovFont->LegacySize, labelPos, OnSurfaceMedium(), TR("mining_threads"));
float labelW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, "THREADS").x;
float labelW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, TR("mining_threads")).x;
snprintf(buf, sizeof(buf), " %d / %d", s_selected_threads, max_threads);
ImVec2 countPos(labelPos.x + labelW, curY);
dl->AddText(sub1, sub1->LegacySize, countPos, OnSurface(), buf);
@@ -804,17 +830,58 @@ void RenderMiningTab(App* app)
OnSurfaceDisabled(), buf);
}
// Active mining indicator (top-right)
// Idle mining toggle (top-right corner of card)
float idleRightEdge = cardMax.x - pad;
{
bool idleOn = app->settings()->getMineWhenIdle();
ImFont* icoFont = Type().iconSmall();
const char* idleIcon = ICON_MD_SCHEDULE;
float icoH = icoFont->LegacySize;
float btnSz = icoH + 8.0f * dp;
float btnX = idleRightEdge - btnSz;
float btnY = curY + (headerH - btnSz) * 0.5f;
// Pill background when active
if (idleOn) {
dl->AddRectFilled(ImVec2(btnX, btnY), ImVec2(btnX + btnSz, btnY + btnSz),
WithAlpha(Primary(), 60), btnSz * 0.5f);
}
// Icon centered in button
ImVec2 icoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, idleIcon);
ImU32 icoCol = idleOn ? Primary() : OnSurfaceDisabled();
dl->AddText(icoFont, icoFont->LegacySize,
ImVec2(btnX + (btnSz - icoSz.x) * 0.5f, btnY + (btnSz - icoSz.y) * 0.5f),
icoCol, idleIcon);
// Click target (save/restore cursor so layout is unaffected)
ImVec2 savedCur = ImGui::GetCursorScreenPos();
ImGui::SetCursorScreenPos(ImVec2(btnX, btnY));
ImGui::InvisibleButton("##IdleMining", ImVec2(btnSz, btnSz));
if (ImGui::IsItemClicked()) {
app->settings()->setMineWhenIdle(!idleOn);
app->settings()->save();
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("%s", idleOn ? TR("mining_idle_on_tooltip") : TR("mining_idle_off_tooltip"));
}
ImGui::SetCursorScreenPos(savedCur);
idleRightEdge = btnX - 4.0f * dp;
}
// Active mining indicator (left of idle toggle)
if (mining.generate) {
float pulse = effects::isLowSpecMode()
? schema::UI().drawElement("animations", "pulse-base-normal").size
: schema::UI().drawElement("animations", "pulse-base-normal").size + schema::UI().drawElement("animations", "pulse-amp-normal").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-normal").size);
ImU32 pulseCol = WithAlpha(Success(), (int)(255 * pulse));
float dotR = schema::UI().drawElement("tabs.mining", "active-dot-radius").size + 2.0f * hs;
dl->AddCircleFilled(ImVec2(cardMax.x - pad - dotR * 2, curY + dotR + 1 * dp), dotR, pulseCol);
dl->AddCircleFilled(ImVec2(idleRightEdge - dotR, curY + dotR + 1 * dp), dotR, pulseCol);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(cardMax.x - pad - dotR * 2 - 60 * hs, curY),
WithAlpha(Success(), 200), "Mining");
ImVec2(idleRightEdge - dotR - dotR - 60 * hs, curY),
WithAlpha(Success(), 200), TR("mining_active"));
}
curY += headerH + secGap;
}
@@ -930,12 +997,16 @@ void RenderMiningTab(App* app)
dl->AddText(capFont, capFont->LegacySize, txtPos, txtCol, buf);
}
if (threads_changed && mining.generate) {
app->startMining(s_selected_threads);
}
if (threads_changed && s_pool_mode && state.pool_mining.xmrig_running) {
app->stopPoolMining();
app->startPoolMining(s_selected_threads);
if (threads_changed) {
app->settings()->setPoolThreads(s_selected_threads);
app->settings()->save();
if (mining.generate) {
app->startMining(s_selected_threads);
}
if (s_pool_mode && state.pool_mining.xmrig_running) {
app->stopPoolMining();
app->startPoolMining(s_selected_threads);
}
}
curY += gridH + secGap;
@@ -1059,20 +1130,20 @@ void RenderMiningTab(App* app)
const char* label;
ImU32 lblCol;
if (isToggling) {
label = isMiningActive ? "STOPPING" : "STARTING";
label = isMiningActive ? TR("mining_stopping") : TR("mining_starting");
// Animated dots effect via alpha pulse
float pulse = effects::isLowSpecMode()
? 0.7f
: 0.5f + 0.5f * (float)std::sin((double)ImGui::GetTime() * 3.0);
lblCol = WithAlpha(Primary(), (int)(120 + 135 * pulse));
} else if (isMiningActive) {
label = "STOP";
label = TR("mining_stop");
lblCol = WithAlpha(Error(), 220);
} else if (disabled) {
label = "MINE";
label = TR("mining_mine");
lblCol = WithAlpha(OnSurface(), 50);
} else {
label = "MINE";
label = TR("mining_mine");
lblCol = WithAlpha(OnSurface(), 160);
}
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
@@ -1086,13 +1157,13 @@ void RenderMiningTab(App* app)
if (!disabled)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (isToggling)
ImGui::SetTooltip(isMiningActive ? "Stopping miner..." : "Starting miner...");
ImGui::SetTooltip("%s", isMiningActive ? TR("mining_stopping_tooltip") : TR("mining_starting_tooltip"));
else if (isSyncing && !s_pool_mode)
ImGui::SetTooltip("Syncing blockchain... (%.1f%%)", state.sync.verification_progress * 100.0);
ImGui::SetTooltip(TR("mining_syncing_tooltip"), state.sync.verification_progress * 100.0);
else if (poolBlockedBySolo)
ImGui::SetTooltip("Stop solo mining before starting pool mining");
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
else
ImGui::SetTooltip(isMiningActive ? "Stop Mining" : "Start Mining");
ImGui::SetTooltip("%s", isMiningActive ? TR("stop_mining") : TR("start_mining"));
}
// Click action — pool or solo
@@ -1201,23 +1272,15 @@ void RenderMiningTab(App* app)
int numStats = 3;
if (s_pool_mode) {
col1Label = "POOL HASHRATE";
col1Label = TR("mining_local_hashrate");
col1Str = FormatHashrate(state.pool_mining.hashrate_10s);
col1Col = state.pool_mining.xmrig_running ? greenCol : OnSurfaceDisabled();
col2Label = "THREADS / MEM";
{
char buf[64];
int64_t memMB = state.pool_mining.memory_used / (1024 * 1024);
if (memMB > 0)
snprintf(buf, sizeof(buf), "%d / %lld MB", state.pool_mining.threads_active, (long long)memMB);
else
snprintf(buf, sizeof(buf), "%d / --", state.pool_mining.threads_active);
col2Str = buf;
}
col2Col = OnSurface();
col2Label = TR("mining_pool_hashrate");
col2Str = FormatHashrate(state.pool_mining.pool_hashrate);
col2Col = state.pool_mining.pool_hashrate > 0 ? OnSurface() : OnSurfaceDisabled();
col3Label = "SHARES";
col3Label = TR("mining_shares");
char sharesBuf[64];
snprintf(sharesBuf, sizeof(sharesBuf), "%lld / %lld",
(long long)state.pool_mining.accepted,
@@ -1225,7 +1288,7 @@ void RenderMiningTab(App* app)
col3Str = sharesBuf;
col3Col = OnSurface();
col4Label = "UPTIME";
col4Label = TR("mining_uptime");
int64_t up = state.pool_mining.uptime_sec;
char uptBuf[64];
if (up <= 0)
@@ -1240,15 +1303,15 @@ void RenderMiningTab(App* app)
} else {
double est_hours = EstimateHoursToBlock(mining.localHashrate, mining.networkHashrate, mining.difficulty);
col1Label = "LOCAL HASHRATE";
col1Label = TR("mining_local_hashrate");
col1Str = FormatHashrate(mining.localHashrate);
col1Col = mining.generate ? greenCol : OnSurfaceDisabled();
col2Label = "NETWORK";
col2Label = TR("mining_network");
col2Str = FormatHashrate(mining.networkHashrate);
col2Col = OnSurface();
col3Label = "EST. BLOCK";
col3Label = TR("mining_est_block");
col3Str = FormatEstTime(est_hours);
col3Col = OnSurface();
}
@@ -1402,9 +1465,9 @@ void RenderMiningTab(App* app)
dl->AddText(capFont, capFont->LegacySize,
ImVec2(plotLeft, chartBot - capFont->LegacySize - 2 * dp),
OnSurfaceDisabled(),
chartHistory.size() >= 300 ? "5m ago" :
chartHistory.size() >= 60 ? "1m ago" : "start");
std::string nowLbl = "now";
chartHistory.size() >= 300 ? TR("mining_chart_5m_ago") :
chartHistory.size() >= 60 ? TR("mining_chart_1m_ago") : TR("mining_chart_start"));
std::string nowLbl = TR("mining_chart_now");
ImVec2 nowSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, nowLbl.c_str());
dl->AddText(capFont, capFont->LegacySize,
ImVec2(plotRight - nowSz.x, chartBot - capFont->LegacySize - 2 * dp),
@@ -1421,7 +1484,7 @@ void RenderMiningTab(App* app)
if (hasLogContent || hasChartContent) {
ImFont* iconFont = Type().iconSmall();
const char* toggleIcon = showLogFlag ? ICON_MD_SHOW_CHART : ICON_MD_ARTICLE;
const char* toggleTip = showLogFlag ? "Show hashrate chart" : "Show mining log";
const char* toggleTip = showLogFlag ? TR("mining_show_chart") : TR("mining_show_log");
ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, toggleIcon);
float btnSize = iconSz.y + 8 * dp;
float btnX = cardMax.x - pad - btnSize;
@@ -1486,6 +1549,10 @@ void RenderMiningTab(App* app)
&& !tx.memo.empty()
&& tx.memo.find("Mining Pool payout") != std::string::npos);
if (isSoloMined || isPoolPayout) {
// Apply earnings filter
if (s_earnings_filter == 1 && !isSoloMined) continue;
if (s_earnings_filter == 2 && !isPoolPayout) continue;
double amt = std::abs(tx.amount);
minedAllTime += amt;
minedAllTimeCount++;
@@ -1526,6 +1593,63 @@ void RenderMiningTab(App* app)
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + combinedCardH);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
// === Earnings filter toggle button (top-right of card) ===
{
const char* filterLabels[] = { TR("mining_filter_all"), TR("mining_solo"), TR("mining_pool") };
const char* filterIcons[] = { ICON_MD_FUNCTIONS, ICON_MD_MEMORY, ICON_MD_CLOUD };
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) % 3;
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
const char* tips[] = {
TR("mining_filter_tip_all"),
TR("mining_filter_tip_solo"),
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;
@@ -1561,10 +1685,10 @@ void RenderMiningTab(App* app)
snprintf(estVal, sizeof(estVal), "N/A");
EarningsEntry entries[] = {
{ "TODAY", todayVal, todaySub, greenCol2 },
{ "YESTERDAY", yesterdayVal, yesterdaySub, OnSurface() },
{ "ALL TIME", allVal, allSub, OnSurface() },
{ "EST. DAILY", estVal, nullptr, estActive ? greenCol2 : OnSurfaceDisabled() },
{ 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++) {
@@ -1619,7 +1743,7 @@ void RenderMiningTab(App* app)
float col3X = cx + colW * 2.0f;
// -- Difficulty --
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X, cy), OnSurfaceMedium(), "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);
@@ -1628,19 +1752,19 @@ void RenderMiningTab(App* app)
ImGui::InvisibleButton("##DiffCopy", ImVec2(diffSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Click to copy difficulty");
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("Difficulty copied");
Notifications::instance().info(TR("mining_difficulty_copied"));
}
}
// -- Block --
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X, cy), OnSurfaceMedium(), "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);
@@ -1649,19 +1773,19 @@ void RenderMiningTab(App* app)
ImGui::InvisibleButton("##BlockCopy", ImVec2(blkSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Click to copy block height");
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("Block height copied");
Notifications::instance().info(TR("mining_block_copied"));
}
}
// -- Mining Address --
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), "Mining Addr");
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()) {
@@ -1685,14 +1809,14 @@ void RenderMiningTab(App* app)
ImGui::InvisibleButton("##MiningAddrCopy", ImVec2(addrTextSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("Click to copy mining address");
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("Mining address copied");
Notifications::instance().info(TR("mining_address_copied"));
}
}
}
@@ -1859,15 +1983,15 @@ void RenderMiningTab(App* app)
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (selfRAM >= 1024.0)
ImGui::Text("Wallet: %.1f GB", selfRAM / 1024.0);
ImGui::Text(TR("ram_wallet_gb"), selfRAM / 1024.0);
else
ImGui::Text("Wallet: %.0f MB", selfRAM);
ImGui::Text(TR("ram_wallet_mb"), selfRAM);
if (daemonRAM >= 1024.0)
ImGui::Text("Daemon: %.1f GB (%s)", daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
ImGui::Text(TR("ram_daemon_gb"), daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
else
ImGui::Text("Daemon: %.0f MB (%s)", daemonRAM, app->getDaemonMemDiag().c_str());
ImGui::Text(TR("ram_daemon_mb"), daemonRAM, app->getDaemonMemDiag().c_str());
ImGui::Separator();
ImGui::Text("System: %.1f / %.0f GB", usedRAM / 1024.0, totalRAM / 1024.0);
ImGui::Text(TR("ram_system_gb"), usedRAM / 1024.0, totalRAM / 1024.0);
ImGui::EndTooltip();
}
}
@@ -1881,7 +2005,7 @@ void RenderMiningTab(App* app)
// ============================================================
if (!recentMined.empty() || s_pool_mode) {
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
s_pool_mode ? "RECENT POOL PAYOUTS" : "RECENT BLOCKS");
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);
@@ -1924,8 +2048,8 @@ void RenderMiningTab(App* app)
ImVec2(centerX - iSz.x * 0.5f, emptyY),
OnSurfaceDisabled(), emptyIcon);
const char* emptyMsg = s_pool_mode
? "No pool payouts yet"
: "No blocks found yet";
? 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()),
@@ -1966,13 +2090,13 @@ void RenderMiningTab(App* app)
// Time
int64_t diff = now - mtx.timestamp;
if (diff < 60)
snprintf(buf, sizeof(buf), "%llds ago", (long long)diff);
snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff);
else if (diff < 3600)
snprintf(buf, sizeof(buf), "%lldm ago", (long long)(diff / 60));
snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60));
else if (diff < 86400)
snprintf(buf, sizeof(buf), "%lldh ago", (long long)(diff / 3600));
snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600));
else
snprintf(buf, sizeof(buf), "%lldd ago", (long long)(diff / 86400));
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
@@ -1984,9 +2108,9 @@ void RenderMiningTab(App* app)
float badgeX = rMax.x - pad - Layout::spacingXl() * 3.5f;
if (mtx.mature) {
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
WithAlpha(Success(), 180), "Mature");
WithAlpha(Success(), 180), TR("mature"));
} else {
snprintf(buf, sizeof(buf), "%d conf", mtx.confirmations);
snprintf(buf, sizeof(buf), TR("conf_count"), mtx.confirmations);
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
WithAlpha(Warning(), 200), buf);
}
@@ -2001,7 +2125,7 @@ void RenderMiningTab(App* app)
dragonx::util::Platform::openUrl(url);
}
if (ImGui::IsItemHovered() && !mtx.txid.empty()) {
ImGui::SetTooltip("Open in explorer");
ImGui::SetTooltip("%s", TR("mining_open_in_explorer"));
}
}
@@ -2026,8 +2150,8 @@ void RenderMiningTab(App* app)
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() ? "Connected" : state.pool_mining.pool_url.c_str())
: "Connecting...";
? (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);