upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/gui.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui.cpp')
-rw-r--r--src/gui.cpp701
1 files changed, 424 insertions, 277 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 2210296..a1a0698 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -1,4 +1,4 @@
1// Bitflash GUI — ImGui + GLFW + OpenGL3 1// Bitflash GUI -- ImGui + GLFW + OpenGL3
2// Same source on Linux and Windows. No platform ifdefs. 2// Same source on Linux and Windows. No platform ifdefs.
3 3
4#include "imgui/imgui.h" 4#include "imgui/imgui.h"
@@ -25,6 +25,7 @@ extern std::string strParticipantPool;
25extern CAddress addrLocalHost; 25extern CAddress addrLocalHost;
26 26
27void MainFrameRepaint(); 27void MainFrameRepaint();
28int GetDiscoveredPeerCount();
28string DateTimeStr(int64 nTime); 29string DateTimeStr(int64 nTime);
29bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew); 30bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew);
30int64 GetBalance(); 31int64 GetBalance();
@@ -34,15 +35,20 @@ void ThreadBitcoinMiner(void*);
34void ThreadRPCServer(void*); 35void ThreadRPCServer(void*);
35 36
36// --------------------------------------------------------------------------- 37// ---------------------------------------------------------------------------
37// State 38// Layout constants
39// ---------------------------------------------------------------------------
40static const float STATUS_BAR_H = 22.0f;
41
42// ---------------------------------------------------------------------------
43// GUI state
38// --------------------------------------------------------------------------- 44// ---------------------------------------------------------------------------
39static std::string g_myAddress; 45static std::string g_myAddress;
40static int64 g_balance = 0; 46static int64 g_balance = 0;
41static int64 g_lastWalletRefresh = 0; 47static int64 g_lastWalletRefresh = 0;
42static bool g_showSend = false; 48static bool g_showSend = false;
43static bool g_showOptions = false; 49static bool g_showOptions = false;
44static bool g_showAbout = false; 50static bool g_showAbout = false;
45static bool g_needRefresh = true; 51static bool g_needRefresh = true;
46 52
47static char g_sendAddr[128] = {}; 53static char g_sendAddr[128] = {};
48static char g_sendAmount[32] = {}; 54static char g_sendAmount[32] = {};
@@ -73,36 +79,34 @@ static std::string FmtMoney(int64 n)
73static std::string FmtAge(int64 createdAt) 79static std::string FmtAge(int64 createdAt)
74{ 80{
75 int64 age = GetTime() - createdAt; 81 int64 age = GetTime() - createdAt;
76 if (age < 0) 82 if (age < 0) age = 0;
77 age = 0; 83 if (age < 60) return strprintf("%llds", (long long)age);
78 if (age < 60) 84 if (age < 3600) return strprintf("%lldm", (long long)(age / 60));
79 return strprintf("%llds", (long long)age); 85 return strprintf("%lldh", (long long)(age / 3600));
80 if (age < 3600)
81 return strprintf("%lldm", (long long)(age / 60));
82 return strprintf("%lldh", (long long)(age / 3600));
83} 86}
84 87
85static ImVec4 ParticipantStatusColor(const std::string& status) 88static ImVec4 StatusColor(const std::string& s)
86{ 89{
87 if (status.find("failed") != std::string::npos || 90 if (s.find("failed") != std::string::npos ||
88 status.find("rejected") != std::string::npos || 91 s.find("rejected") != std::string::npos ||
89 status.find("stopped") != std::string::npos) 92 s.find("stopped") != std::string::npos)
90 return ImVec4(1.0f, 0.38f, 0.38f, 1.0f); 93 return ImVec4(1.0f, 0.38f, 0.38f, 1.0f); // red
91 if (status.find("hashing") != std::string::npos || 94 if (s.find("hashing") != std::string::npos ||
92 status.find("accepted") != std::string::npos || 95 s.find("accepted") != std::string::npos ||
93 status.find("connected") != std::string::npos) 96 s.find("connected")!= std::string::npos)
94 return ImVec4(0.35f, 1.0f, 0.45f, 1.0f); 97 return ImVec4(0.35f, 1.0f, 0.45f, 1.0f); // green
95 if (status.find("asking") != std::string::npos || 98 if (s.find("asking") != std::string::npos ||
96 status.find("authoriz") != std::string::npos || 99 s.find("authoriz") != std::string::npos ||
97 status.find("subscrib") != std::string::npos) 100 s.find("subscrib") != std::string::npos)
98 return ImVec4(1.0f, 0.75f, 0.25f, 1.0f); 101 return ImVec4(1.0f, 0.75f, 0.25f, 1.0f); // amber
99 return ImVec4(0.55f, 0.80f, 1.0f, 1.0f); 102 return ImVec4(0.55f, 0.80f, 1.0f, 1.0f); // blue-grey
100} 103}
101 104
102static std::string PoolLabel(const BtfPoolAnnouncement& ann) 105static std::string PoolLabel(const BtfPoolAnnouncement& ann)
103{ 106{
104 std::ostringstream o; 107 std::ostringstream o;
105 o << ann.poolName << " - " << std::fixed << std::setprecision(2) << ann.feePercent << "%"; 108 o << ann.poolName << " - "
109 << std::fixed << std::setprecision(2) << ann.feePercent << "%";
106 if (ann.connectedMiners > 0) 110 if (ann.connectedMiners > 0)
107 o << " - " << ann.connectedMiners << " miners"; 111 o << " - " << ann.connectedMiners << " miners";
108 return o.str(); 112 return o.str();
@@ -118,19 +122,47 @@ static void RefreshWallet()
118 g_txRows.clear(); 122 g_txRows.clear();
119 TRY_CRITICAL_BLOCK(cs_mapWallet) 123 TRY_CRITICAL_BLOCK(cs_mapWallet)
120 { 124 {
121 std::vector<std::pair<int64,uint256>> vs; 125 // Sort: unconfirmed first (depth 0), then by depth ascending (least
122 for (auto& kv : mapWallet) vs.push_back({kv.second.GetTxTime(), kv.first}); 126 // deep = most recent). We can't use nTimeReceived because a node
123 std::sort(vs.rbegin(), vs.rend()); 127 // resync re-stamps all txs with the same time. Depth is always correct.
128 std::vector<std::pair<int,uint256>> vs;
129 for (auto& kv : mapWallet)
130 vs.push_back({kv.second.GetDepthInMainChain(), kv.second.GetHash()});
131 std::sort(vs.begin(), vs.end(),
132 [](const std::pair<int,uint256>& a, const std::pair<int,uint256>& b) {
133 return a.first < b.first; // 0 (unconfirmed) first, then shallowest
134 });
124 for (auto& sv : vs) { 135 for (auto& sv : vs) {
125 auto mi = mapWallet.find(sv.second); 136 auto mi = mapWallet.find(sv.second);
126 if (mi == mapWallet.end()) continue; 137 if (mi == mapWallet.end()) continue;
127 CWalletTx& wtx = mi->second; 138 CWalletTx& wtx = mi->second;
128 int64 net = wtx.GetCredit() - wtx.GetDebit(); 139 int depth = wtx.GetDepthInMainChain();
129 int depth = wtx.GetDepthInMainChain(); 140 int toMature = wtx.IsCoinBase() ? wtx.GetBlocksToMaturity() : 0;
141 // An orphaned coinbase has hashBlock set but depth=0 (block not in main chain).
142 // A genuinely unconfirmed tx has hashBlock==0.
143 bool isOrphaned = wtx.IsCoinBase() && wtx.hashBlock != 0 && depth == 0;
144 int64 net;
130 std::string desc; 145 std::string desc;
131 if (wtx.IsCoinBase()) desc = depth<1 ? "Generated (unconfirmed)" : "Generated"; 146 if (wtx.IsCoinBase()) {
132 else if (wtx.GetDebit() > 0) desc = "Sent"; 147 if (isOrphaned) {
133 else desc = "Received"; 148 net = 0;
149 desc = "Generated (orphaned -- block not in main chain)";
150 } else if (toMature > 0) {
151 net = wtx.GetValueOut();
152 char buf[64];
153 snprintf(buf, sizeof(buf), "Generated (matures in %d blocks)", toMature);
154 desc = buf;
155 } else {
156 net = wtx.GetCredit() - wtx.GetDebit();
157 desc = depth < 1 ? "Generated (unconfirmed)" : "Generated";
158 }
159 } else if (wtx.GetDebit() > 0) {
160 net = wtx.GetCredit() - wtx.GetDebit();
161 desc = "Sent";
162 } else {
163 net = wtx.GetCredit() - wtx.GetDebit();
164 desc = "Received";
165 }
134 g_txRows.push_back({DateTimeStr(wtx.GetTxTime()), desc, net, depth}); 166 g_txRows.push_back({DateTimeStr(wtx.GetTxTime()), desc, net, depth});
135 } 167 }
136 } 168 }
@@ -149,18 +181,103 @@ string DateTimeStr(int64 nTime)
149} 181}
150 182
151// --------------------------------------------------------------------------- 183// ---------------------------------------------------------------------------
184// Status bar -- separate fullscreen-width overlay pinned to bottom edge.
185// Drawn last so it always sits on top of everything else.
186// ---------------------------------------------------------------------------
187static void DrawStatusBar()
188{
189 ImGuiIO& io = ImGui::GetIO();
190 ImGui::SetNextWindowPos(ImVec2(0.0f, io.DisplaySize.y - STATUS_BAR_H));
191 ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, STATUS_BAR_H));
192 ImGui::SetNextWindowBgAlpha(1.0f);
193 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 3.0f));
194 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
195 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
196 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13f, 0.13f, 0.13f, 1.0f));
197 ImGui::Begin("##statusbar", nullptr,
198 ImGuiWindowFlags_NoTitleBar |
199 ImGuiWindowFlags_NoResize |
200 ImGuiWindowFlags_NoMove |
201 ImGuiWindowFlags_NoScrollbar |
202 ImGuiWindowFlags_NoScrollWithMouse|
203 ImGuiWindowFlags_NoSavedSettings |
204 ImGuiWindowFlags_NoBringToFrontOnFocus);
205 ImGui::PopStyleVar(3);
206 ImGui::PopStyleColor();
207
208 int connected = (int)vNodes.size();
209 int discovered = GetDiscoveredPeerCount();
210
211 if (nMineMode == MINE_PARTICIPANT) {
212 uint64 sent = 0, accepted = 0;
213 double hashRate = 0.0;
214 std::string st = GetParticipantMiningStatus();
215 GetParticipantMiningStats(sent, accepted, hashRate);
216 if (sent > 0 || accepted > 0 || hashRate > 0.0)
217 ImGui::TextDisabled(
218 "Connected peers: %d Detected peers: %d Height: %d"
219 " Shares: %llu sent / %llu accepted %.2f H/s",
220 connected, discovered, nBestHeight,
221 (unsigned long long)sent, (unsigned long long)accepted, hashRate);
222 else if (!st.empty())
223 ImGui::TextDisabled(
224 "Connected peers: %d Detected peers: %d Height: %d %s",
225 connected, discovered, nBestHeight, st.c_str());
226 else
227 ImGui::TextDisabled(
228 "Connected peers: %d Detected peers: %d Height: %d",
229 connected, discovered, nBestHeight);
230 } else if (nMineMode == MINE_OPERATOR) {
231 int miners = 0, blocksFound = 0;
232 uint64 roundShares = 0;
233 double totalHashRate = 0.0;
234 GetPoolOperatorStats(miners, blocksFound, roundShares, totalHashRate);
235 ImGui::TextDisabled(
236 "Connected peers: %d Detected peers: %d Height: %d"
237 " Miners: %d Round shares: %llu Blocks found: %d %.2f H/s",
238 connected, discovered, nBestHeight,
239 miners, (unsigned long long)roundShares, blocksFound, totalHashRate);
240 } else {
241 ImGui::TextDisabled(
242 "Connected peers: %d Detected peers: %d Height: %d",
243 connected, discovered, nBestHeight);
244 }
245
246 ImGui::End();
247}
248
249// ---------------------------------------------------------------------------
152// Main window 250// Main window
251//
252// Layout:
253// [menu bar] -- ImGui built-in menu
254// [header strip] -- address, balance, send button, mine status
255// [separator]
256// [telemetry strip] -- mode-specific live stats (never scrolls)
257// [separator]
258// [scrollable content child] -- transactions + mode panels
259//
260// The scrollable child ends at the top of the status bar so content never
261// hides behind it. The child is explicitly sized to fill the remaining space.
153// --------------------------------------------------------------------------- 262// ---------------------------------------------------------------------------
154static void DrawMainWindow() 263static void DrawMainWindow()
155{ 264{
156 ImGuiIO& io = ImGui::GetIO(); 265 ImGuiIO& io = ImGui::GetIO();
157 ImGui::SetNextWindowPos(ImVec2(0,0)); 266
158 ImGui::SetNextWindowSize(io.DisplaySize); 267 // Full display height minus the status bar at the bottom.
159 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12,12)); 268 const float winH = io.DisplaySize.y - STATUS_BAR_H;
269
270 ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
271 ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, winH));
272 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 12.0f));
160 ImGui::Begin("##main", nullptr, 273 ImGui::Begin("##main", nullptr,
161 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | 274 ImGuiWindowFlags_NoTitleBar |
162 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_MenuBar | 275 ImGuiWindowFlags_NoResize |
163 ImGuiWindowFlags_NoFocusOnAppearing); 276 ImGuiWindowFlags_NoMove |
277 ImGuiWindowFlags_NoScrollbar |
278 ImGuiWindowFlags_NoScrollWithMouse |
279 ImGuiWindowFlags_MenuBar |
280 ImGuiWindowFlags_NoBringToFrontOnFocus);
164 ImGui::SetWindowFontScale(0.92f); 281 ImGui::SetWindowFontScale(0.92f);
165 ImGui::PopStyleVar(); 282 ImGui::PopStyleVar();
166 283
@@ -170,179 +287,209 @@ static void DrawMainWindow()
170 if (ImGui::MenuItem("Exit")) glfwSetWindowShouldClose(glfwGetCurrentContext(), true); 287 if (ImGui::MenuItem("Exit")) glfwSetWindowShouldClose(glfwGetCurrentContext(), true);
171 ImGui::EndMenu(); 288 ImGui::EndMenu();
172 } 289 }
173 if (ImGui::MenuItem("Options")) g_showOptions = true; 290 if (ImGui::MenuItem("Options")) g_showOptions = true;
174 if (ImGui::MenuItem("About")) g_showAbout = true; 291 if (ImGui::MenuItem("About")) g_showAbout = true;
175 ImGui::EndMenuBar(); 292 ImGui::EndMenuBar();
176 } 293 }
177 294
178 // ---- Address bar ---- 295 // ---- Header strip (never scrolls) ----
179 ImGui::Spacing(); 296 ImGui::Spacing();
180 ImGui::TextDisabled("Address:"); 297 ImGui::TextDisabled("Address:");
181 ImGui::SameLine(); 298 ImGui::SameLine();
182 ImGui::SetNextItemWidth(-80); 299 ImGui::SetNextItemWidth(-80.0f);
183 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f,0.14f,0.14f,1)); 300 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 1.0f));
184 ImGui::InputText("##addr", (char*)g_myAddress.c_str(), g_myAddress.size()+1, 301 ImGui::InputText("##addr", (char*)g_myAddress.c_str(), g_myAddress.size()+1,
185 ImGuiInputTextFlags_ReadOnly); 302 ImGuiInputTextFlags_ReadOnly);
186 ImGui::PopStyleColor(); 303 ImGui::PopStyleColor();
187 ImGui::SameLine(); 304 ImGui::SameLine();
188 if (ImGui::Button("Copy##a")) ImGui::SetClipboardText(g_myAddress.c_str()); 305 if (ImGui::Button("Copy##a")) ImGui::SetClipboardText(g_myAddress.c_str());
189 306
190 // ---- Balance on its own line ----
191 ImGui::TextDisabled("Balance:"); 307 ImGui::TextDisabled("Balance:");
192 ImGui::SameLine(); 308 ImGui::SameLine();
193 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.35f,1.0f,0.35f,1.0f)); 309 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.35f, 1.0f, 0.35f, 1.0f));
194 ImGui::Text("%s BTF", FmtMoney(g_balance).c_str()); 310 ImGui::Text("%s BTF", FmtMoney(g_balance).c_str());
195 ImGui::PopStyleColor(); 311 ImGui::PopStyleColor();
196 312
197 // ---- Toolbar ----
198 ImGui::Spacing(); 313 ImGui::Spacing();
199 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f,0.40f,0.15f,1)); 314 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.40f, 0.15f, 1.0f));
200 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.20f,0.55f,0.20f,1)); 315 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.20f, 0.55f, 0.20f, 1.0f));
201 if (ImGui::Button(" Send Coins ")) g_showSend = true; 316 if (ImGui::Button(" Send Coins ")) g_showSend = true;
202 ImGui::PopStyleColor(2); 317 ImGui::PopStyleColor(2);
203 318
204 // Mining status indicator 319 ImGui::SameLine(0.0f, 20.0f);
205 ImGui::SameLine(0, 20);
206 if (fGenerateBitcoins) { 320 if (fGenerateBitcoins) {
207 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f,0.6f,0.0f,1.0f)); 321 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.6f, 0.0f, 1.0f));
208 const char* modeLabel = nMineMode==MINE_OPERATOR ? "Mining (operator)" : 322 const char* modeLabel =
209 nMineMode==MINE_PARTICIPANT ? "Mining (pool)" : "Mining"; 323 nMineMode == MINE_OPERATOR ? "Mining (operator)" :
210 ImGui::Text("⬤ %s", modeLabel); 324 nMineMode == MINE_PARTICIPANT ? "Mining (pool)" :
325 nMineMode == MINE_RELAY ? "Relay" : "Mining";
326 ImGui::Text("* %s", modeLabel);
211 ImGui::PopStyleColor(); 327 ImGui::PopStyleColor();
212 if (nMineMode == MINE_PARTICIPANT) { 328 if (nMineMode == MINE_PARTICIPANT) {
213 std::string mineStatus = GetParticipantMiningStatus(); 329 std::string st = GetParticipantMiningStatus();
214 if (!mineStatus.empty()) { 330 if (!st.empty()) {
215 ImGui::SameLine(0, 12); 331 ImGui::SameLine(0.0f, 12.0f);
216 ImGui::PushStyleColor(ImGuiCol_Text, ParticipantStatusColor(mineStatus)); 332 ImGui::PushStyleColor(ImGuiCol_Text, StatusColor(st));
217 ImGui::Text("[%s]", mineStatus.c_str()); 333 ImGui::Text("[%s]", st.c_str());
218 ImGui::PopStyleColor(); 334 ImGui::PopStyleColor();
219 } 335 }
220 } 336 }
221 } else { 337 } else {
222 ImGui::TextDisabled("○ Not mining"); 338 ImGui::TextDisabled("Not mining");
223 } 339 }
224 340
225 ImGui::Spacing(); 341 ImGui::Spacing();
226 ImGui::Separator(); 342 ImGui::Separator();
227 ImGui::Spacing();
228 343
229 // ---- Live telemetry strip ---- 344 // ---- Telemetry strip (never scrolls) ----
230 { 345 {
231 const char* modeName = nMineMode==MINE_OPERATOR ? "Operator" : 346 ImGui::Spacing();
232 nMineMode==MINE_PARTICIPANT ? "Participant" : "Solo"; 347 const char* modeName =
233 int64 age = g_lastWalletRefresh > 0 ? (GetTime() - g_lastWalletRefresh) : -1; 348 nMineMode == MINE_OPERATOR ? "Operator" :
234 if (age < 0) age = 0; 349 nMineMode == MINE_PARTICIPANT ? "Participant" :
350 nMineMode == MINE_RELAY ? "Relay" : "Solo";
351 int64 age = g_lastWalletRefresh > 0 ? GetTime() - g_lastWalletRefresh : 0;
235 352
236 ImGui::Text("Mode: %s", modeName); 353 ImGui::Text("Mode: %s", modeName);
237 ImGui::SameLine(0, 16); 354 ImGui::SameLine(0.0f, 16.0f);
238 ImGui::Text("Wallet refresh: %llds ago", (long long)age); 355 ImGui::TextDisabled("Wallet refreshed %llds ago", (long long)age);
239 356
240 if (nMineMode == MINE_PARTICIPANT) { 357 if (nMineMode == MINE_PARTICIPANT) {
241 uint64 sent = 0, accepted = 0; 358 uint64 sent = 0, accepted = 0;
242 double hashRate = 0.0; 359 double hashRate = 0.0;
243 std::string mineStatus = GetParticipantMiningStatus(); 360 std::string st = GetParticipantMiningStatus();
244 GetParticipantMiningStats(sent, accepted, hashRate); 361 GetParticipantMiningStats(sent, accepted, hashRate);
245 if (sent > 0 || accepted > 0 || hashRate > 0.0) { 362 if (sent > 0 || accepted > 0 || hashRate > 0.0) {
246 double acceptPct = sent ? (100.0 * (double)accepted / (double)sent) : 0.0; 363 double pct = sent ? (100.0 * (double)accepted / (double)sent) : 0.0;
247 ImGui::TextDisabled("Your mining: sent %llu, accepted %llu (%.1f%%), %.2f H/s", 364 ImGui::TextDisabled(
248 (unsigned long long)sent, 365 "Sent %llu shares, accepted %llu (%.1f%%), %.2f H/s",
249 (unsigned long long)accepted, 366 (unsigned long long)sent, (unsigned long long)accepted, pct, hashRate);
250 acceptPct, 367 } else if (!st.empty()) {
251 hashRate); 368 ImGui::TextDisabled("Status: %s", st.c_str());
252 } else if (!mineStatus.empty()) {
253 ImGui::TextDisabled("Your mining: %s", mineStatus.c_str());
254 } else { 369 } else {
255 ImGui::TextDisabled("Your mining: asking pool for work"); 370 ImGui::TextDisabled("Waiting for pool work...");
256 } 371 }
257 } else if (nMineMode == MINE_OPERATOR) { 372 } else if (nMineMode == MINE_OPERATOR) {
258 int miners = 0, blocksFound = 0; 373 int miners = 0, blocksFound = 0;
259 uint64 roundShares = 0; 374 uint64 roundShares = 0;
260 GetPoolOperatorStats(miners, blocksFound, roundShares); 375 double totalHashRate = 0.0;
376 GetPoolOperatorStats(miners, blocksFound, roundShares, totalHashRate);
261 if (miners > 0 || blocksFound > 0 || roundShares > 0) 377 if (miners > 0 || blocksFound > 0 || roundShares > 0)
262 ImGui::TextDisabled("Pool runtime: %d authorized miners, %llu round shares, %d blocks found", miners, (unsigned long long)roundShares, blocksFound); 378 ImGui::TextDisabled(
379 "Authorized miners: %d Round shares: %llu Blocks found: %d %.2f H/s",
380 miners, (unsigned long long)roundShares, blocksFound, totalHashRate);
263 else 381 else
264 ImGui::TextDisabled("Pool runtime: waiting for miners"); 382 ImGui::TextDisabled("Pool server running, waiting for miners");
383 } else if (nMineMode == MINE_RELAY) {
384 ImGui::TextDisabled("Relay mode: syncing, not mining");
265 } else { 385 } else {
266 ImGui::TextDisabled("Solo runtime: local wallet mining telemetry"); 386 ImGui::TextDisabled("Solo mining");
267 } 387 }
268 ImGui::Spacing(); 388 ImGui::Spacing();
269 ImGui::Separator(); 389 ImGui::Separator();
270 ImGui::Spacing(); 390 ImGui::Spacing();
271 } 391 }
272 392
393 // ---- Scrollable content child ----
394 // Fills all remaining height in the window (cursor Y to bottom of window).
395 // Content that overflows scrolls; nothing leaks past the window edge.
396 // Size the scrollable child to fill exactly the remaining window height.
397 // GetContentRegionAvail() already accounts for padding/borders.
398 const float availH = ImGui::GetContentRegionAvail().y;
399 ImGui::BeginChild("##scroll", ImVec2(0.0f, availH), false, 0);
400
273 // ---- Transaction list ---- 401 // ---- Transaction list ----
274 float listHeight = io.DisplaySize.y * 0.26f; 402 float txH = io.DisplaySize.y * 0.26f;
275 if (listHeight < 190.0f) listHeight = 190.0f; 403 if (txH < 180.0f) txH = 180.0f;
276 if (listHeight > 260.0f) listHeight = 260.0f; 404 if (txH > 260.0f) txH = 260.0f;
405
277 if (ImGui::BeginTable("txlist", 4, 406 if (ImGui::BeginTable("txlist", 4,
278 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | 407 ImGuiTableFlags_Borders |
279 ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp, 408 ImGuiTableFlags_RowBg |
280 ImVec2(0, listHeight))) 409 ImGuiTableFlags_ScrollY |
410 ImGuiTableFlags_SizingStretchProp,
411 ImVec2(0.0f, txH)))
281 { 412 {
282 ImGui::TableSetupScrollFreeze(0, 1); 413 ImGui::TableSetupScrollFreeze(0, 1);
283 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 110); 414 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 110.0f);
284 ImGui::TableSetupColumn("Date", ImGuiTableColumnFlags_WidthFixed, 130); 415 ImGui::TableSetupColumn("Date", ImGuiTableColumnFlags_WidthFixed, 130.0f);
285 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch); 416 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch);
286 ImGui::TableSetupColumn("Amount", ImGuiTableColumnFlags_WidthFixed, 140); 417 ImGui::TableSetupColumn("Amount", ImGuiTableColumnFlags_WidthFixed, 140.0f);
287 ImGui::TableHeadersRow(); 418 ImGui::TableHeadersRow();
288 419
289 for (auto& row : g_txRows) { 420 for (auto& row : g_txRows) {
290 ImGui::TableNextRow(); 421 ImGui::TableNextRow();
291 ImGui::TableSetColumnIndex(0); 422 ImGui::TableSetColumnIndex(0);
292 if (row.depth < 1) 423 bool isImmatureCoinbase = (row.desc.find("matures in") != std::string::npos);
293 ImGui::TextColored(ImVec4(1,0.6f,0,1), "Unconfirmed"); 424 bool isOrphaned = (row.desc.find("orphaned") != std::string::npos);
425 if (isOrphaned)
426 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Orphaned");
427 else if (row.depth < 1)
428 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Unconfirmed");
429 else if (isImmatureCoinbase)
430 ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.25f, 1.0f), "Immature");
431 else if (row.depth < 6)
432 ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.3f, 1.0f), "%d confirm%s",
433 row.depth, row.depth == 1 ? "" : "s");
294 else 434 else
295 ImGui::Text("%d blocks deep", row.depth); 435 ImGui::TextColored(ImVec4(0.5f, 0.9f, 0.5f, 1.0f), "Confirmed");
296 ImGui::TableSetColumnIndex(1); 436 ImGui::TableSetColumnIndex(1);
297 ImGui::TextDisabled("%s", row.date.c_str()); 437 ImGui::TextDisabled("%s", row.date.c_str());
298 ImGui::TableSetColumnIndex(2); 438 ImGui::TableSetColumnIndex(2);
299 ImGui::TextUnformatted(row.desc.c_str()); 439 ImGui::TextUnformatted(row.desc.c_str());
300 ImGui::TableSetColumnIndex(3); 440 ImGui::TableSetColumnIndex(3);
301 if (row.amount >= 0) 441 if (row.amount >= 0)
302 ImGui::TextColored(ImVec4(0.35f,1,0.35f,1), "+%s BTF", FmtMoney(row.amount).c_str()); 442 ImGui::TextColored(ImVec4(0.35f, 1.0f, 0.35f, 1.0f),
443 "+%s BTF", FmtMoney(row.amount).c_str());
303 else 444 else
304 ImGui::TextColored(ImVec4(1,0.4f,0.4f,1), "%s BTF", FmtMoney(row.amount).c_str()); 445 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
446 "%s BTF", FmtMoney(row.amount).c_str());
305 } 447 }
306 ImGui::EndTable(); 448 ImGui::EndTable();
307 } 449 }
308 450
451 // ---- Mode panels ----
309 std::vector<BtfPoolAnnouncement> pools; 452 std::vector<BtfPoolAnnouncement> pools;
310 BtfGetPoolAnnouncements(pools); 453 BtfGetPoolAnnouncements(pools);
311 454
312 if (nMineMode == MINE_PARTICIPANT) { 455 if (nMineMode == MINE_PARTICIPANT) {
313 ImGui::Spacing(); 456 ImGui::Spacing();
314 ImGui::SeparatorText("Participant Mining"); 457 ImGui::SeparatorText("Participant Mining");
315 ImGui::Text("Selected pool:"); 458
459 ImGui::TextDisabled("Selected pool:");
460 ImGui::SameLine();
316 if (strParticipantPool.empty()) 461 if (strParticipantPool.empty())
317 ImGui::TextDisabled("No pool selected yet"); 462 ImGui::TextDisabled("none");
318 else 463 else
319 ImGui::TextWrapped("%s", strParticipantPool.c_str()); 464 ImGui::TextWrapped("%s", strParticipantPool.c_str());
320 465
321 const BtfPoolAnnouncement* selected = NULL; 466 const BtfPoolAnnouncement* sel = nullptr;
322 for (size_t i = 0; i < pools.size(); i++) 467 for (size_t i = 0; i < pools.size(); i++)
323 if (pools[i].btfAddress == strParticipantPool) { selected = &pools[i]; break; } 468 if (pools[i].btfAddress == strParticipantPool) { sel = &pools[i]; break; }
324 469
325 if (selected) { 470 if (sel) {
326 ImGui::Text("Pool name: %s", selected->poolName.c_str()); 471 ImGui::Text("Pool: %s Fee: %.2f%% Miners: %d Blocks: %d %.2f H/s",
327 ImGui::Text("Fee: %.2f%%", selected->feePercent); 472 sel->poolName.c_str(), sel->feePercent,
328 ImGui::Text("Live stats: %d miners, %d blocks", selected->connectedMiners, selected->blocksFound); 473 sel->connectedMiners, sel->blocksFound, sel->hashRate);
329 if (!selected->dashboardUrl.empty()) 474 if (!sel->dashboardUrl.empty())
330 ImGui::TextWrapped("Dashboard: %s", selected->dashboardUrl.c_str()); 475 ImGui::TextDisabled("Dashboard: %s", sel->dashboardUrl.c_str());
331 } else if (!strParticipantPool.empty()) { 476 } else if (!strParticipantPool.empty()) {
332 ImGui::TextDisabled("No fresh announcement found for this pool yet"); 477 ImGui::TextDisabled("No live announcement for this pool yet.");
333 } 478 }
334 479
335 if (!pools.empty()) { 480 if (!pools.empty()) {
336 ImGui::Spacing(); 481 ImGui::Spacing();
337 ImGui::SeparatorText("Live Pools"); 482 ImGui::SeparatorText("Live Pools");
338 if (ImGui::BeginTable("livepools", 5, 483 if (ImGui::BeginTable("livepools", 5,
339 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) 484 ImGuiTableFlags_Borders |
485 ImGuiTableFlags_RowBg |
486 ImGuiTableFlags_SizingStretchProp))
340 { 487 {
341 ImGui::TableSetupColumn("Pool", ImGuiTableColumnFlags_WidthStretch); 488 ImGui::TableSetupColumn("Pool", ImGuiTableColumnFlags_WidthStretch);
342 ImGui::TableSetupColumn("Fee", ImGuiTableColumnFlags_WidthFixed, 70); 489 ImGui::TableSetupColumn("Fee", ImGuiTableColumnFlags_WidthFixed, 60.0f);
343 ImGui::TableSetupColumn("Stats", ImGuiTableColumnFlags_WidthFixed, 160); 490 ImGui::TableSetupColumn("Stats", ImGuiTableColumnFlags_WidthFixed, 150.0f);
344 ImGui::TableSetupColumn("Dashboard", ImGuiTableColumnFlags_WidthStretch); 491 ImGui::TableSetupColumn("Dashboard", ImGuiTableColumnFlags_WidthStretch);
345 ImGui::TableSetupColumn("Use", ImGuiTableColumnFlags_WidthFixed, 60); 492 ImGui::TableSetupColumn("Use", ImGuiTableColumnFlags_WidthFixed, 50.0f);
346 ImGui::TableHeadersRow(); 493 ImGui::TableHeadersRow();
347 for (size_t i = 0; i < pools.size(); i++) { 494 for (size_t i = 0; i < pools.size(); i++) {
348 const BtfPoolAnnouncement& ann = pools[i]; 495 const BtfPoolAnnouncement& ann = pools[i];
@@ -353,15 +500,19 @@ static void DrawMainWindow()
353 ImGui::TableSetColumnIndex(1); 500 ImGui::TableSetColumnIndex(1);
354 ImGui::Text("%.2f%%", ann.feePercent); 501 ImGui::Text("%.2f%%", ann.feePercent);
355 ImGui::TableSetColumnIndex(2); 502 ImGui::TableSetColumnIndex(2);
356 ImGui::Text("%d miners, %d blocks, %s old", ann.connectedMiners, ann.blocksFound, FmtAge(ann.createdAt).c_str()); 503 ImGui::Text("%d miners %d blocks %.2f H/s %s",
504 ann.connectedMiners, ann.blocksFound, ann.hashRate,
505 FmtAge(ann.createdAt).c_str());
357 ImGui::TableSetColumnIndex(3); 506 ImGui::TableSetColumnIndex(3);
358 if (!ann.dashboardUrl.empty()) 507 if (!ann.dashboardUrl.empty())
359 ImGui::TextWrapped("%s", ann.dashboardUrl.c_str()); 508 ImGui::TextWrapped("%s", ann.dashboardUrl.c_str());
360 else 509 else
361 ImGui::TextDisabled("-"); 510 ImGui::TextDisabled("-");
362 ImGui::TableSetColumnIndex(4); 511 ImGui::TableSetColumnIndex(4);
363 if (ImGui::SmallButton((std::string("Use##") + ann.btfAddress).c_str())) { 512 std::string btnId = "Use##" + ann.btfAddress;
364 strncpy(g_participantPool, ann.btfAddress.c_str(), sizeof(g_participantPool)-1); 513 if (ImGui::SmallButton(btnId.c_str())) {
514 strncpy(g_participantPool, ann.btfAddress.c_str(),
515 sizeof(g_participantPool)-1);
365 strParticipantPool = ann.btfAddress; 516 strParticipantPool = ann.btfAddress;
366 CWalletDB().WriteSetting("strParticipantPool", strParticipantPool); 517 CWalletDB().WriteSetting("strParticipantPool", strParticipantPool);
367 g_needRefresh = true; 518 g_needRefresh = true;
@@ -375,55 +526,60 @@ static void DrawMainWindow()
375 if (nMineMode == MINE_OPERATOR) { 526 if (nMineMode == MINE_OPERATOR) {
376 ImGui::Spacing(); 527 ImGui::Spacing();
377 ImGui::SeparatorText("Operator Overview"); 528 ImGui::SeparatorText("Operator Overview");
378 ImGui::Text("Pool .btf address:"); 529
379 std::string opAddr = BtfLocalAddress(); 530 std::string opAddr = BtfLocalAddress();
531 ImGui::TextDisabled("Pool .btf address:");
532 ImGui::SameLine();
380 if (opAddr.empty()) 533 if (opAddr.empty())
381 ImGui::TextDisabled("Identity not ready yet"); 534 ImGui::TextDisabled("identity not ready");
382 else 535 else
383 ImGui::TextWrapped("%s", opAddr.c_str()); 536 ImGui::TextWrapped("%s", opAddr.c_str());
384 537
538 // Pending payouts
385 std::vector<PendingPayoutView> owed; 539 std::vector<PendingPayoutView> owed;
386 GetPendingPayouts(owed); 540 GetPendingPayouts(owed);
387 ImGui::Spacing(); 541 ImGui::Spacing();
388 ImGui::SeparatorText("Pending Pool Payouts (you owe miners)"); 542 ImGui::SeparatorText("Pending Payouts (owed to miners)");
389 if (owed.empty()) { 543 if (owed.empty()) {
390 ImGui::TextDisabled("No pending pool payouts"); 544 ImGui::TextDisabled("No pending payouts");
391 } else if (ImGui::BeginTable("owedpayouts", 4, 545 } else if (ImGui::BeginTable("owedpayouts", 4,
392 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) 546 ImGuiTableFlags_Borders |
547 ImGuiTableFlags_RowBg |
548 ImGuiTableFlags_SizingStretchProp))
393 { 549 {
394 ImGui::TableSetupColumn("Matures At", ImGuiTableColumnFlags_WidthFixed, 100); 550 ImGui::TableSetupColumn("Matures At", ImGuiTableColumnFlags_WidthFixed, 90.0f);
395 ImGui::TableSetupColumn("Blocks Left", ImGuiTableColumnFlags_WidthFixed, 90); 551 ImGui::TableSetupColumn("Blocks left", ImGuiTableColumnFlags_WidthFixed, 80.0f);
396 ImGui::TableSetupColumn("Recipients", ImGuiTableColumnFlags_WidthFixed, 90); 552 ImGui::TableSetupColumn("Recipients", ImGuiTableColumnFlags_WidthFixed, 80.0f);
397 ImGui::TableSetupColumn("Total", ImGuiTableColumnFlags_WidthStretch); 553 ImGui::TableSetupColumn("Total", ImGuiTableColumnFlags_WidthStretch);
398 ImGui::TableHeadersRow(); 554 ImGui::TableHeadersRow();
399 for (const PendingPayoutView& row : owed) { 555 for (const PendingPayoutView& row : owed) {
400 int remaining = std::max(0, row.matureAtHeight - nBestHeight); 556 int left = std::max(0, row.matureAtHeight - nBestHeight);
401 ImGui::TableNextRow(); 557 ImGui::TableNextRow();
402 ImGui::TableSetColumnIndex(0); 558 ImGui::TableSetColumnIndex(0); ImGui::Text("%d", row.matureAtHeight);
403 ImGui::Text("%d", row.matureAtHeight); 559 ImGui::TableSetColumnIndex(1); ImGui::Text("%d", left);
404 ImGui::TableSetColumnIndex(1); 560 ImGui::TableSetColumnIndex(2); ImGui::Text("%d", row.recipients);
405 ImGui::Text("%d", remaining); 561 ImGui::TableSetColumnIndex(3); ImGui::Text("%s BTF", FmtMoney(row.totalAmount).c_str());
406 ImGui::TableSetColumnIndex(2);
407 ImGui::Text("%d", row.recipients);
408 ImGui::TableSetColumnIndex(3);
409 ImGui::Text("%s BTF", FmtMoney(row.totalAmount).c_str());
410 } 562 }
411 ImGui::EndTable(); 563 ImGui::EndTable();
412 } 564 }
413 565
566 // Workers
414 std::vector<PoolWorkerStatView> workers; 567 std::vector<PoolWorkerStatView> workers;
415 GetPoolWorkerStats(workers); 568 GetPoolWorkerStats(workers);
416 ImGui::Spacing(); 569 ImGui::Spacing();
417 ImGui::SeparatorText("Current Round Workers"); 570 ImGui::SeparatorText("Current Round Workers");
418 if (workers.empty()) { 571 if (workers.empty()) {
419 ImGui::TextDisabled("No active workers in this round"); 572 ImGui::TextDisabled("No active workers this round");
420 } else if (ImGui::BeginTable("workers", 4, 573 } else if (ImGui::BeginTable("workers", 5,
421 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) 574 ImGuiTableFlags_Borders |
575 ImGuiTableFlags_RowBg |
576 ImGuiTableFlags_SizingStretchProp))
422 { 577 {
423 ImGui::TableSetupColumn("Payout Addr", ImGuiTableColumnFlags_WidthStretch); 578 ImGui::TableSetupColumn("Payout Address", ImGuiTableColumnFlags_WidthStretch);
424 ImGui::TableSetupColumn("Worker", ImGuiTableColumnFlags_WidthStretch); 579 ImGui::TableSetupColumn("Worker", ImGuiTableColumnFlags_WidthStretch);
425 ImGui::TableSetupColumn("Round Shares", ImGuiTableColumnFlags_WidthFixed, 90); 580 ImGui::TableSetupColumn("Hashrate", ImGuiTableColumnFlags_WidthFixed, 90.0f);
426 ImGui::TableSetupColumn("Last Seen", ImGuiTableColumnFlags_WidthFixed, 100); 581 ImGui::TableSetupColumn("Round shares", ImGuiTableColumnFlags_WidthFixed, 90.0f);
582 ImGui::TableSetupColumn("Last seen", ImGuiTableColumnFlags_WidthFixed, 90.0f);
427 ImGui::TableHeadersRow(); 583 ImGui::TableHeadersRow();
428 for (const PoolWorkerStatView& row : workers) { 584 for (const PoolWorkerStatView& row : workers) {
429 ImGui::TableNextRow(); 585 ImGui::TableNextRow();
@@ -432,53 +588,19 @@ static void DrawMainWindow()
432 ImGui::TableSetColumnIndex(1); 588 ImGui::TableSetColumnIndex(1);
433 ImGui::TextDisabled("%s", row.worker.empty() ? "worker" : row.worker.c_str()); 589 ImGui::TextDisabled("%s", row.worker.empty() ? "worker" : row.worker.c_str());
434 ImGui::TableSetColumnIndex(2); 590 ImGui::TableSetColumnIndex(2);
435 ImGui::Text("%llu", (unsigned long long)row.roundShares); 591 ImGui::Text("%.2f H/s", row.hashRate);
436 ImGui::TableSetColumnIndex(3); 592 ImGui::TableSetColumnIndex(3);
437 ImGui::TextDisabled("%s", FmtAge(row.lastSeen).c_str()); 593 ImGui::Text("%llu", (unsigned long long)row.roundShares);
594 ImGui::TableSetColumnIndex(4);
595 ImGui::TextDisabled("%s ago", FmtAge(row.lastSeen).c_str());
438 } 596 }
439 ImGui::EndTable(); 597 ImGui::EndTable();
440 } 598 }
441 } 599 }
442 600
443 // ---- Status bar ---- 601 ImGui::EndChild(); // ##scroll
444 {
445 ImGui::Spacing();
446 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,2));
447
448 int peers = (int)vNodes.size();
449 if (nMineMode == MINE_PARTICIPANT) {
450 uint64 sent = 0, accepted = 0;
451 double hashRate = 0.0;
452 std::string mineStatus = GetParticipantMiningStatus();
453 GetParticipantMiningStats(sent, accepted, hashRate);
454 if (sent > 0 || accepted > 0 || hashRate > 0.0)
455 ImGui::TextDisabled("Peers %d height %d shares sent %llu accepted %llu hash rate %.2f H/s",
456 peers,
457 nBestHeight,
458 (unsigned long long)sent,
459 (unsigned long long)accepted,
460 hashRate);
461 else if (!mineStatus.empty())
462 ImGui::TextDisabled("Peers %d height %d %s", peers, nBestHeight, mineStatus.c_str());
463 else
464 ImGui::TextDisabled("Peers %d height %d asking pool for work", peers, nBestHeight);
465 } else if (nMineMode == MINE_OPERATOR) {
466 int miners = 0, blocksFound = 0;
467 uint64 roundShares = 0;
468 GetPoolOperatorStats(miners, blocksFound, roundShares);
469 if (miners > 0 || blocksFound > 0 || roundShares > 0)
470 ImGui::TextDisabled("Peers %d height %d authorized miners %d round shares %llu blocks found %d",
471 peers, nBestHeight, miners, (unsigned long long)roundShares, blocksFound);
472 else
473 ImGui::TextDisabled("Peers %d height %d waiting for miners", peers, nBestHeight);
474 } else {
475 ImGui::TextDisabled("Peers %d height %d", peers, nBestHeight);
476 }
477 ImGui::PopStyleVar();
478 }
479
480 ImGui::SetWindowFontScale(1.0f); 602 ImGui::SetWindowFontScale(1.0f);
481 ImGui::End(); 603 ImGui::End(); // ##main
482} 604}
483 605
484// --------------------------------------------------------------------------- 606// ---------------------------------------------------------------------------
@@ -487,42 +609,53 @@ static void DrawMainWindow()
487static void DrawSendDialog() 609static void DrawSendDialog()
488{ 610{
489 if (!g_showSend) return; 611 if (!g_showSend) return;
490 ImGui::SetNextWindowSize(ImVec2(500, 180), ImGuiCond_Always); 612 ImGui::SetNextWindowSize(ImVec2(500.0f, 180.0f), ImGuiCond_Always);
491 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(.5f,.5f)); 613 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
614 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
492 if (ImGui::Begin("Send Coins", &g_showSend, 615 if (ImGui::Begin("Send Coins", &g_showSend,
493 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) 616 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
494 { 617 {
495 ImGui::Text("Recipient address:"); 618 ImGui::Text("Recipient address:");
496 ImGui::SetNextItemWidth(-1); 619 ImGui::SetNextItemWidth(-1.0f);
497 ImGui::InputText("##sa", g_sendAddr, sizeof(g_sendAddr)); 620 ImGui::InputText("##sa", g_sendAddr, sizeof(g_sendAddr));
498 621
499 ImGui::Text("Amount (BTF):"); 622 ImGui::Text("Amount (BTF):");
500 ImGui::SetNextItemWidth(180); 623 ImGui::SetNextItemWidth(180.0f);
501 ImGui::InputText("##sm", g_sendAmount, sizeof(g_sendAmount)); 624 ImGui::InputText("##sm", g_sendAmount, sizeof(g_sendAmount));
502 625
503 if (!g_sendStatus.empty()) { 626 if (!g_sendStatus.empty()) {
504 ImGui::Spacing(); 627 ImGui::Spacing();
505 ImGui::TextColored(ImVec4(1,.4f,.4f,1), "%s", g_sendStatus.c_str()); 628 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
629 "%s", g_sendStatus.c_str());
506 } 630 }
507 631
508 ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); 632 ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing();
509 if (ImGui::Button("Send", ImVec2(90,0))) { 633 if (ImGui::Button("Send", ImVec2(90.0f, 0.0f))) {
510 uint160 h; int64 nv = 0; 634 uint160 h; int64 nv = 0;
511 if (!AddressToHash160(g_sendAddr, h)) g_sendStatus = "Invalid address."; 635 if (!AddressToHash160(g_sendAddr, h))
512 else if (!ParseMoney(g_sendAmount,nv)||nv<=0) g_sendStatus = "Invalid amount."; 636 g_sendStatus = "Invalid address.";
637 else if (!ParseMoney(g_sendAmount, nv) || nv <= 0)
638 g_sendStatus = "Invalid amount.";
513 else { 639 else {
514 CScript s; s << OP_DUP << OP_HASH160 << h << OP_EQUALVERIFY << OP_CHECKSIG; 640 CScript sc;
641 sc << OP_DUP << OP_HASH160 << h << OP_EQUALVERIFY << OP_CHECKSIG;
515 CWalletTx wtx; 642 CWalletTx wtx;
516 if (SendMoney(s, nv, wtx)) { 643 if (SendMoney(sc, nv, wtx)) {
517 g_showSend=false; g_sendStatus=""; 644 g_showSend = false;
518 memset(g_sendAddr,0,sizeof(g_sendAddr)); 645 g_sendStatus = "";
519 memset(g_sendAmount,0,sizeof(g_sendAmount)); 646 memset(g_sendAddr, 0, sizeof(g_sendAddr));
647 memset(g_sendAmount, 0, sizeof(g_sendAmount));
520 g_needRefresh = true; 648 g_needRefresh = true;
521 } else g_sendStatus = "Transaction failed."; 649 } else {
650 g_sendStatus = "Transaction failed.";
651 }
522 } 652 }
523 } 653 }
524 ImGui::SameLine(); 654 ImGui::SameLine();
525 if (ImGui::Button("Cancel", ImVec2(90,0))) { g_showSend=false; g_sendStatus=""; } 655 if (ImGui::Button("Cancel", ImVec2(90.0f, 0.0f))) {
656 g_showSend = false;
657 g_sendStatus = "";
658 }
526 } 659 }
527 ImGui::End(); 660 ImGui::End();
528} 661}
@@ -534,7 +667,6 @@ static void DrawOptionsDialog()
534{ 667{
535 if (!g_showOptions) return; 668 if (!g_showOptions) return;
536 669
537 // Always sync from current globals when dialog opens
538 static bool wasOpen = false; 670 static bool wasOpen = false;
539 if (!wasOpen) { 671 if (!wasOpen) {
540 g_mineRadio = nMineMode; 672 g_mineRadio = nMineMode;
@@ -544,42 +676,46 @@ static void DrawOptionsDialog()
544 strncpy(g_poolDash, strPoolDashboardUrl.c_str(), sizeof(g_poolDash)-1); 676 strncpy(g_poolDash, strPoolDashboardUrl.c_str(), sizeof(g_poolDash)-1);
545 } 677 }
546 wasOpen = true; 678 wasOpen = true;
547 ImGui::SetNextWindowSize(ImVec2(500, 310), ImGuiCond_Always); 679
548 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(.5f,.5f)); 680 ImGui::SetNextWindowSize(ImVec2(500.0f, 335.0f), ImGuiCond_Always);
681 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
682 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
549 if (ImGui::Begin("Options", &g_showOptions, 683 if (ImGui::Begin("Options", &g_showOptions,
550 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) 684 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
551 { 685 {
552 // Transaction fee 686 // Transaction fee
553 ImGui::SeparatorText("Transaction Fee"); 687 ImGui::SeparatorText("Transaction Fee");
554 static char feeStr[32] = {}; 688 static char feeStr[32] = {};
555 if (feeStr[0]=='\0') snprintf(feeStr,sizeof(feeStr),"%s",FmtMoney(nTransactionFee).c_str()); 689 if (feeStr[0] == '\0')
556 ImGui::SetNextItemWidth(180); 690 snprintf(feeStr, sizeof(feeStr), "%s", FmtMoney(nTransactionFee).c_str());
691 ImGui::SetNextItemWidth(180.0f);
557 ImGui::InputText("BTF per transaction##fee", feeStr, sizeof(feeStr)); 692 ImGui::InputText("BTF per transaction##fee", feeStr, sizeof(feeStr));
558 693
559 // Mining 694 // Mining mode
560 ImGui::SeparatorText("Mining Mode"); 695 ImGui::SeparatorText("Mining Mode");
561 ImGui::RadioButton("Solo — rewards go to your wallet", &g_mineRadio, MINE_SOLO); 696 ImGui::RadioButton("Relay -- just run the node, don't mine", &g_mineRadio, MINE_RELAY);
562 ImGui::RadioButton("Operator — run a pool for other miners", &g_mineRadio, MINE_OPERATOR); 697 ImGui::RadioButton("Solo -- rewards go to your wallet", &g_mineRadio, MINE_SOLO);
563 ImGui::RadioButton("Participant — mine to someone's pool", &g_mineRadio, MINE_PARTICIPANT); 698 ImGui::RadioButton("Operator -- run a pool for other miners", &g_mineRadio, MINE_OPERATOR);
699 ImGui::RadioButton("Participant -- mine to someone's pool", &g_mineRadio, MINE_PARTICIPANT);
564 700
565 if (g_mineRadio == MINE_OPERATOR) { 701 if (g_mineRadio == MINE_OPERATOR) {
566 ImGui::Spacing(); 702 ImGui::Spacing();
567 ImGui::SeparatorText("Pool Announcement"); 703 ImGui::SeparatorText("Pool Announcement");
568 ImGui::Text("Pool name:"); 704 ImGui::Text("Pool name:");
569 ImGui::SetNextItemWidth(-1); 705 ImGui::SetNextItemWidth(-1.0f);
570 ImGui::InputText("##poolname", g_poolName, sizeof(g_poolName)); 706 ImGui::InputText("##poolname", g_poolName, sizeof(g_poolName));
571 ImGui::Text("Fee %%:"); 707 ImGui::Text("Fee %%:");
572 ImGui::SetNextItemWidth(120); 708 ImGui::SetNextItemWidth(120.0f);
573 ImGui::InputText("##poolfee", g_poolFee, sizeof(g_poolFee)); 709 ImGui::InputText("##poolfee", g_poolFee, sizeof(g_poolFee));
574 ImGui::Text("Dashboard URL:"); 710 ImGui::Text("Dashboard URL:");
575 ImGui::SetNextItemWidth(-1); 711 ImGui::SetNextItemWidth(-1.0f);
576 ImGui::InputText("##pooldash", g_poolDash, sizeof(g_poolDash)); 712 ImGui::InputText("##pooldash", g_poolDash, sizeof(g_poolDash));
577 } 713 }
578 714
579 if (g_mineRadio == MINE_PARTICIPANT) { 715 if (g_mineRadio == MINE_PARTICIPANT) {
580 ImGui::Spacing(); 716 ImGui::Spacing();
581 ImGui::SeparatorText("Pool Selection"); 717 ImGui::SeparatorText("Pool Selection");
582 ImGui::TextDisabled("Pick from live discovered pools. Manual entry is disabled here."); 718 ImGui::TextDisabled("Pick from live discovered pools.");
583 719
584 std::vector<BtfPoolAnnouncement> pools; 720 std::vector<BtfPoolAnnouncement> pools;
585 BtfGetPoolAnnouncements(pools); 721 BtfGetPoolAnnouncements(pools);
@@ -591,46 +727,49 @@ static void DrawOptionsDialog()
591 break; 727 break;
592 } 728 }
593 } 729 }
594 if (ImGui::BeginCombo("Discovered pools", current.c_str())) { 730 if (ImGui::BeginCombo("Discovered pools##combo", current.c_str())) {
595 for (size_t i = 0; i < pools.size(); i++) { 731 for (size_t i = 0; i < pools.size(); i++) {
596 const BtfPoolAnnouncement& ann = pools[i]; 732 const BtfPoolAnnouncement& ann = pools[i];
597 std::string label = PoolLabel(ann); 733 bool selected = (ann.btfAddress == g_participantPool);
598 bool selected = ann.btfAddress == g_participantPool; 734 if (ImGui::Selectable(PoolLabel(ann).c_str(), selected))
599 if (ImGui::Selectable(label.c_str(), selected)) { 735 strncpy(g_participantPool, ann.btfAddress.c_str(),
600 strncpy(g_participantPool, ann.btfAddress.c_str(), sizeof(g_participantPool)-1); 736 sizeof(g_participantPool)-1);
601 }
602 } 737 }
603 ImGui::EndCombo(); 738 ImGui::EndCombo();
604 } 739 }
605 } else { 740 } else {
606 ImGui::TextDisabled("No live pools discovered yet"); 741 ImGui::TextDisabled("No live pools discovered yet.");
607 } 742 }
608 } 743 }
609 744
610 ImGui::Spacing();
611 const char* genLabel = fGenerateBitcoins ? "Stop Mining" : "Start Mining";
612 if (ImGui::Button(genLabel, ImVec2(130,0))) {
613 fGenerateBitcoins = fGenerateBitcoins ? 0 : 1;
614 if (fGenerateBitcoins) _beginthread(ThreadBitcoinMiner, 0, NULL);
615 CWalletDB().WriteSetting("fGenerateBitcoins", fGenerateBitcoins);
616 }
617
618 ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); 745 ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing();
619 if (ImGui::Button("OK", ImVec2(90,0))) { 746 if (ImGui::Button("OK", ImVec2(90.0f, 0.0f))) {
620 int64 fee=0; 747 int64 fee = 0;
621 if (ParseMoney(feeStr,fee)) { nTransactionFee=fee; CWalletDB().WriteSetting("nTransactionFee",nTransactionFee); } 748 if (ParseMoney(feeStr, fee)) {
622 749 nTransactionFee = fee;
623 bool changed = (g_mineRadio != nMineMode); 750 CWalletDB().WriteSetting("nTransactionFee", nTransactionFee);
624 nMineMode = g_mineRadio; 751 }
625 strParticipantPool = g_participantPool; 752
626 strPoolName = g_poolName[0] ? g_poolName : "Bitflash Pool"; 753 bool poolChanged =
627 strPoolDashboardUrl = g_poolDash; 754 nMineMode != g_mineRadio ||
628 dPoolFeePercent = atof(g_poolFee); 755 strPoolName != (g_poolName[0] ? g_poolName : "Bitflash Pool") ||
629 CWalletDB().WriteSetting("nMineMode", nMineMode); 756 strPoolDashboardUrl != g_poolDash ||
630 CWalletDB().WriteSetting("strParticipantPool", strParticipantPool); 757 dPoolFeePercent != atof(g_poolFee);
631 CWalletDB().WriteSetting("strPoolName", strPoolName); 758
632 CWalletDB().WriteSetting("strPoolDashboardUrl", strPoolDashboardUrl); 759 nMineMode = g_mineRadio;
633 CWalletDB().WriteSetting("dPoolFeePercent", dPoolFeePercent); 760 strParticipantPool = g_participantPool;
761 strPoolName = g_poolName[0] ? g_poolName : "Bitflash Pool";
762 strPoolDashboardUrl = g_poolDash;
763 dPoolFeePercent = atof(g_poolFee);
764
765 if (nMineMode == MINE_OPERATOR && poolChanged) gAnnounceNow = true;
766
767 if (nMineMode == MINE_RELAY) {
768 fGenerateBitcoins = 0;
769 } else {
770 fGenerateBitcoins = 1;
771 if (!vfThreadRunning[3]) _beginthread(ThreadBitcoinMiner, 0, NULL);
772 }
634 773
635 if (nMineMode == MINE_OPERATOR) { 774 if (nMineMode == MINE_OPERATOR) {
636 if (!gPoolServerRunning) { 775 if (!gPoolServerRunning) {
@@ -645,7 +784,7 @@ static void DrawOptionsDialog()
645 g_showOptions = false; 784 g_showOptions = false;
646 } 785 }
647 ImGui::SameLine(); 786 ImGui::SameLine();
648 if (ImGui::Button("Cancel", ImVec2(90,0))) g_showOptions = false; 787 if (ImGui::Button("Cancel", ImVec2(90.0f, 0.0f))) g_showOptions = false;
649 } 788 }
650 if (!g_showOptions) wasOpen = false; 789 if (!g_showOptions) wasOpen = false;
651 ImGui::End(); 790 ImGui::End();
@@ -657,18 +796,21 @@ static void DrawOptionsDialog()
657static void DrawAboutDialog() 796static void DrawAboutDialog()
658{ 797{
659 if (!g_showAbout) return; 798 if (!g_showAbout) return;
660 ImGui::SetNextWindowSize(ImVec2(380, 170), ImGuiCond_Always); 799 ImGui::SetNextWindowSize(ImVec2(380.0f, 170.0f), ImGuiCond_Always);
661 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(.5f,.5f)); 800 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
801 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
662 if (ImGui::Begin("About Bitflash", &g_showAbout, 802 if (ImGui::Begin("About Bitflash", &g_showAbout,
663 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) 803 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
664 { 804 {
665 ImGui::TextColored(ImVec4(1,.6f,0,1), "Bitflash BTF v1.1.0"); 805 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Bitflash BTF v1.1.0");
666 ImGui::Spacing(); 806 ImGui::Spacing();
667 ImGui::TextWrapped("CPU-only cryptocurrency. RandomX proof of work. Anonymous .btf addressing over Nostr. No premine."); 807 ImGui::TextWrapped(
808 "CPU-only cryptocurrency. RandomX proof of work. "
809 "Anonymous .btf addressing over Nostr. No premine.");
668 ImGui::Spacing(); 810 ImGui::Spacing();
669 ImGui::TextDisabled("Based on Bitcoin 0.1.0 (Satoshi Nakamoto, 2009)"); 811 ImGui::TextDisabled("Based on Bitcoin 0.1.0 (Satoshi Nakamoto, 2009)");
670 ImGui::Spacing(); 812 ImGui::Spacing();
671 if (ImGui::Button("OK", ImVec2(90,0))) g_showAbout = false; 813 if (ImGui::Button("OK", ImVec2(90.0f, 0.0f))) g_showAbout = false;
672 } 814 }
673 ImGui::End(); 815 ImGui::End();
674} 816}
@@ -705,34 +847,33 @@ int RunGUI(int argc, char* argv[])
705 ImGuiIO& io = ImGui::GetIO(); 847 ImGuiIO& io = ImGui::GetIO();
706 io.IniFilename = nullptr; 848 io.IniFilename = nullptr;
707 849
708 // Load Roboto at 16px — clean, readable on all platforms
709 io.Fonts->AddFontFromMemoryCompressedTTF( 850 io.Fonts->AddFontFromMemoryCompressedTTF(
710 RobotoMedium_compressed_data, RobotoMedium_compressed_size, 16.0f); 851 RobotoMedium_compressed_data, RobotoMedium_compressed_size, 14.0f);
711 852
712 ImGui::StyleColorsDark(); 853 ImGui::StyleColorsDark();
713 ImGuiStyle& s = ImGui::GetStyle(); 854 ImGuiStyle& st = ImGui::GetStyle();
714 s.WindowRounding = s.FrameRounding = s.PopupRounding = 4.0f; 855 st.WindowRounding = st.FrameRounding = st.PopupRounding = 4.0f;
715 s.ItemSpacing = ImVec2(10, 7); 856 st.ItemSpacing = ImVec2(10.0f, 7.0f);
716 s.FramePadding = ImVec2(8, 5); 857 st.FramePadding = ImVec2(8.0f, 5.0f);
717 s.WindowPadding = ImVec2(12, 12); 858 st.WindowPadding = ImVec2(12.0f, 12.0f);
718 // Orange accent 859 // Orange accent
719 s.Colors[ImGuiCol_TitleBgActive] = ImVec4(.80f,.45f,.00f,1); 860 st.Colors[ImGuiCol_TitleBgActive] = ImVec4(.80f, .45f, .00f, 1.0f);
720 s.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f,.60f,.00f,1); 861 st.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, .60f, .00f, 1.0f);
721 s.Colors[ImGuiCol_SliderGrab] = ImVec4(.80f,.45f,.00f,1); 862 st.Colors[ImGuiCol_SliderGrab] = ImVec4(.80f, .45f, .00f, 1.0f);
722 s.Colors[ImGuiCol_Button] = ImVec4(.25f,.25f,.25f,1); 863 st.Colors[ImGuiCol_Button] = ImVec4(.25f, .25f, .25f, 1.0f);
723 s.Colors[ImGuiCol_ButtonHovered] = ImVec4(.80f,.45f,.00f,1); 864 st.Colors[ImGuiCol_ButtonHovered] = ImVec4(.80f, .45f, .00f, 1.0f);
724 s.Colors[ImGuiCol_ButtonActive] = ImVec4(.60f,.30f,.00f,1); 865 st.Colors[ImGuiCol_ButtonActive] = ImVec4(.60f, .30f, .00f, 1.0f);
725 s.Colors[ImGuiCol_Header] = ImVec4(.80f,.45f,.00f,.40f); 866 st.Colors[ImGuiCol_Header] = ImVec4(.80f, .45f, .00f, .40f);
726 s.Colors[ImGuiCol_HeaderHovered] = ImVec4(.80f,.45f,.20f,.80f); 867 st.Colors[ImGuiCol_HeaderHovered] = ImVec4(.80f, .45f, .20f, .80f);
727 s.Colors[ImGuiCol_Tab] = ImVec4(.20f,.20f,.20f,1); 868 st.Colors[ImGuiCol_Tab] = ImVec4(.20f, .20f, .20f, 1.0f);
728 s.Colors[ImGuiCol_TabHovered] = ImVec4(.80f,.45f,.00f,1); 869 st.Colors[ImGuiCol_TabHovered] = ImVec4(.80f, .45f, .00f, 1.0f);
729 s.Colors[ImGuiCol_TabActive] = ImVec4(.80f,.45f,.00f,1); 870 st.Colors[ImGuiCol_TabActive] = ImVec4(.80f, .45f, .00f, 1.0f);
730 s.Colors[ImGuiCol_SeparatorHovered]=ImVec4(.80f,.45f,.00f,1); 871 st.Colors[ImGuiCol_SeparatorHovered]= ImVec4(.80f, .45f, .00f, 1.0f);
731 s.Colors[ImGuiCol_FrameBg] = ImVec4(.14f,.14f,.14f,1); 872 st.Colors[ImGuiCol_FrameBg] = ImVec4(.14f, .14f, .14f, 1.0f);
732 s.Colors[ImGuiCol_FrameBgHovered] = ImVec4(.20f,.20f,.20f,1); 873 st.Colors[ImGuiCol_FrameBgHovered] = ImVec4(.20f, .20f, .20f, 1.0f);
733 s.Colors[ImGuiCol_TableHeaderBg] = ImVec4(.18f,.18f,.18f,1); 874 st.Colors[ImGuiCol_TableHeaderBg] = ImVec4(.18f, .18f, .18f, 1.0f);
734 s.Colors[ImGuiCol_TableRowBg] = ImVec4(.11f,.11f,.11f,1); 875 st.Colors[ImGuiCol_TableRowBg] = ImVec4(.11f, .11f, .11f, 1.0f);
735 s.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(.14f,.14f,.14f,1); 876 st.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(.14f, .14f, .14f, 1.0f);
736 877
737 ImGui_ImplGlfw_InitForOpenGL(window, true); 878 ImGui_ImplGlfw_InitForOpenGL(window, true);
738 ImGui_ImplOpenGL3_Init("#version 330"); 879 ImGui_ImplOpenGL3_Init("#version 330");
@@ -744,22 +885,28 @@ int RunGUI(int argc, char* argv[])
744 while (!glfwWindowShouldClose(window) && !fShutdown) 885 while (!glfwWindowShouldClose(window) && !fShutdown)
745 { 886 {
746 glfwPollEvents(); 887 glfwPollEvents();
747 if (g_needRefresh || (frame % 120 == 0)) { RefreshWallet(); g_needRefresh = false; } 888 if (g_needRefresh || (frame % 120 == 0)) {
889 RefreshWallet();
890 g_needRefresh = false;
891 }
748 frame++; 892 frame++;
749 893
750 ImGui_ImplOpenGL3_NewFrame(); 894 ImGui_ImplOpenGL3_NewFrame();
751 ImGui_ImplGlfw_NewFrame(); 895 ImGui_ImplGlfw_NewFrame();
752 ImGui::NewFrame(); 896 ImGui::NewFrame();
753 897
898 // Draw order matters: status bar last so it is always on top.
754 DrawMainWindow(); 899 DrawMainWindow();
755 DrawSendDialog(); 900 DrawSendDialog();
756 DrawOptionsDialog(); 901 DrawOptionsDialog();
757 DrawAboutDialog(); 902 DrawAboutDialog();
903 DrawStatusBar();
758 904
759 ImGui::Render(); 905 ImGui::Render();
760 int w, h; glfwGetFramebufferSize(window, &w, &h); 906 int w, h;
907 glfwGetFramebufferSize(window, &w, &h);
761 glViewport(0, 0, w, h); 908 glViewport(0, 0, w, h);
762 glClearColor(.08f,.08f,.08f,1); 909 glClearColor(.08f, .08f, .08f, 1.0f);
763 glClear(GL_COLOR_BUFFER_BIT); 910 glClear(GL_COLOR_BUFFER_BIT);
764 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 911 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
765 glfwSwapBuffers(window); 912 glfwSwapBuffers(window);