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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user