// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include "../material/material.h" #include "../../embedded/IconsMaterialDesign.h" #include "imgui.h" #include #include #include namespace dragonx { namespace ui { namespace screens { using namespace material; // ============================================================================ // Mining Screen // ============================================================================ // Mining controls and statistics display /** * @brief Mining statistics */ struct MiningStats { bool isMining = false; int threads = 0; int maxThreads = 8; double hashrate = 0.0; // Hashes per second double networkHashrate = 0.0; // Network hashrate int blocksFound = 0; double totalMined = 0.0; std::string miningAddress; int currentHeight = 0; double networkDifficulty = 0.0; std::string estimatedTimeToBlock; }; /** * @brief Recent mined block */ struct MinedBlock { int height; double reward; std::string time; std::string txid; }; /** * @brief Mining screen with controls and stats */ class MiningScreen { public: void setStats(const MiningStats& stats) { m_stats = stats; } void setMinedBlocks(const std::vector& blocks) { m_minedBlocks = blocks; } void setOnStartMining(std::function callback) { m_onStartMining = callback; } void setOnStopMining(std::function callback) { m_onStopMining = callback; } void setOnSetAddress(std::function callback) { m_onSetAddress = callback; } void render(); private: void renderMiningControls(); void renderStatsCards(); void renderMinedBlocksList(); MiningStats m_stats; std::vector m_minedBlocks; std::function m_onStartMining; std::function m_onStopMining; std::function m_onSetAddress; int m_selectedThreads = 4; }; // ============================================================================ // Implementation // ============================================================================ inline void MiningScreen::render() { // Title ImGui::BeginGroup(); { Typography::instance().text(TypeStyle::H5, "Mining"); ImGui::SameLine(); // Mining status indicator if (m_stats.isMining) { ChipSpec chipSpec; chipSpec.variant = ChipVariant::Filled; chipSpec.color = colors::Green500; Chip(ICON_MD_HARDWARE " Mining Active", chipSpec); } } ImGui::EndGroup(); ImGui::Dummy(ImVec2(0, spacing::dp(2))); // Mining controls card renderMiningControls(); ImGui::Dummy(ImVec2(0, spacing::dp(2))); // Stats cards row renderStatsCards(); ImGui::Dummy(ImVec2(0, spacing::dp(2))); // Mined blocks history renderMinedBlocksList(); } inline void MiningScreen::renderMiningControls() { CardSpec cardSpec; cardSpec.elevation = 2; cardSpec.padding = spacing::dp(3); ImGui::PushID("mining_controls"); if (BeginCard(cardSpec)) { Typography::instance().textColored(TypeStyle::Overline, OnSurfaceMedium(), "MINING CONTROLS"); ImGui::Dummy(ImVec2(0, spacing::dp(2))); // Thread selector ImGui::BeginGroup(); { Typography::instance().text(TypeStyle::Body1, "Mining Threads:"); ImGui::SameLine(); ImGui::SetNextItemWidth(150.0f); ImGui::SliderInt("##threads", &m_selectedThreads, 1, m_stats.maxThreads); ImGui::SameLine(); char threadInfo[64]; snprintf(threadInfo, sizeof(threadInfo), "(%d available)", m_stats.maxThreads); Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), threadInfo); } ImGui::EndGroup(); ImGui::Dummy(ImVec2(0, spacing::dp(2))); // Mining address display if (!m_stats.miningAddress.empty()) { Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), "Mining to:"); std::string displayAddr = m_stats.miningAddress; if (displayAddr.length() > 50) { displayAddr = displayAddr.substr(0, 25) + "..." + displayAddr.substr(displayAddr.length() - 20); } Typography::instance().text(TypeStyle::Body2, displayAddr.c_str()); } ImGui::Dummy(ImVec2(0, spacing::dp(2))); // Start/Stop buttons if (m_stats.isMining) { // Stop button (red) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(colors::Red700.x, colors::Red700.y, colors::Red700.z, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(colors::Red500.x, colors::Red500.y, colors::Red500.z, 1.0f)); if (ImGui::Button("⏹ STOP MINING", ImVec2(150, 40))) { if (m_onStopMining) m_onStopMining(); } ImGui::PopStyleColor(2); ImGui::SameLine(); // Live hashrate display char hashrateStr[64]; if (m_stats.hashrate > 1000000) { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f MH/s", m_stats.hashrate / 1000000.0); } else if (m_stats.hashrate > 1000) { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f KH/s", m_stats.hashrate / 1000.0); } else { snprintf(hashrateStr, sizeof(hashrateStr), "%.0f H/s", m_stats.hashrate); } Typography::instance().textColored(TypeStyle::H6, Primary(), hashrateStr); } else { // Start button (green) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(colors::Green700.x, colors::Green700.y, colors::Green700.z, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(colors::Green500.x, colors::Green500.y, colors::Green500.z, 1.0f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1)); if (ImGui::Button(ICON_MD_PLAY_ARROW " START MINING", ImVec2(150, 40))) { if (m_onStartMining) m_onStartMining(m_selectedThreads); } ImGui::PopStyleColor(3); } EndCard(); } ImGui::PopID(); } inline void MiningScreen::renderStatsCards() { float availWidth = ImGui::GetContentRegionAvail().x; float cardWidth = (availWidth - spacing::dp(2) * 2) / 3.0f; // Hashrate card ImGui::BeginGroup(); { CardSpec cardSpec; cardSpec.elevation = 1; cardSpec.padding = spacing::dp(2); cardSpec.width = cardWidth; ImGui::PushID("hashrate_card"); if (BeginCard(cardSpec)) { Typography::instance().textColored(TypeStyle::Overline, OnSurfaceMedium(), "YOUR HASHRATE"); ImGui::Dummy(ImVec2(0, spacing::dp(1))); char hashrateStr[64]; if (m_stats.hashrate > 1000000) { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f", m_stats.hashrate / 1000000.0); Typography::instance().text(TypeStyle::H4, hashrateStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "MH/s"); } else if (m_stats.hashrate > 1000) { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f", m_stats.hashrate / 1000.0); Typography::instance().text(TypeStyle::H4, hashrateStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "KH/s"); } else { snprintf(hashrateStr, sizeof(hashrateStr), "%.0f", m_stats.hashrate); Typography::instance().text(TypeStyle::H4, hashrateStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "H/s"); } EndCard(); } ImGui::PopID(); } ImGui::EndGroup(); ImGui::SameLine(0, spacing::dp(2)); // Network hashrate card ImGui::BeginGroup(); { CardSpec cardSpec; cardSpec.elevation = 1; cardSpec.padding = spacing::dp(2); cardSpec.width = cardWidth; ImGui::PushID("network_card"); if (BeginCard(cardSpec)) { Typography::instance().textColored(TypeStyle::Overline, OnSurfaceMedium(), "NETWORK HASHRATE"); ImGui::Dummy(ImVec2(0, spacing::dp(1))); char hashrateStr[64]; if (m_stats.networkHashrate > 1000000000) { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f", m_stats.networkHashrate / 1000000000.0); Typography::instance().text(TypeStyle::H4, hashrateStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "GH/s"); } else if (m_stats.networkHashrate > 1000000) { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f", m_stats.networkHashrate / 1000000.0); Typography::instance().text(TypeStyle::H4, hashrateStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "MH/s"); } else { snprintf(hashrateStr, sizeof(hashrateStr), "%.2f", m_stats.networkHashrate / 1000.0); Typography::instance().text(TypeStyle::H4, hashrateStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "KH/s"); } EndCard(); } ImGui::PopID(); } ImGui::EndGroup(); ImGui::SameLine(0, spacing::dp(2)); // Total mined card ImGui::BeginGroup(); { CardSpec cardSpec; cardSpec.elevation = 1; cardSpec.padding = spacing::dp(2); cardSpec.width = cardWidth; ImGui::PushID("mined_card"); if (BeginCard(cardSpec)) { Typography::instance().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL MINED"); ImGui::Dummy(ImVec2(0, spacing::dp(1))); char minedStr[64]; snprintf(minedStr, sizeof(minedStr), "%.4f", m_stats.totalMined); Typography::instance().text(TypeStyle::H4, minedStr); ImGui::SameLine(); Typography::instance().textColored(TypeStyle::Body1, OnSurfaceMedium(), "DRGX"); ImGui::Dummy(ImVec2(0, spacing::dp(0.5f))); char blocksStr[32]; snprintf(blocksStr, sizeof(blocksStr), "%d blocks found", m_stats.blocksFound); Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), blocksStr); EndCard(); } ImGui::PopID(); } ImGui::EndGroup(); } inline void MiningScreen::renderMinedBlocksList() { CardSpec cardSpec; cardSpec.elevation = 1; cardSpec.padding = spacing::dp(2); ImGui::PushID("mined_blocks"); if (BeginCard(cardSpec)) { Typography::instance().textColored(TypeStyle::Overline, OnSurfaceMedium(), "BLOCKS YOU MINED"); ImGui::Dummy(ImVec2(0, spacing::dp(1))); if (m_minedBlocks.empty()) { Typography::instance().textColored(TypeStyle::Body2, OnSurfaceMedium(), "No blocks mined yet. Keep mining!"); } else { BeginList("mined_list", false); for (size_t i = 0; i < m_minedBlocks.size() && i < 10; i++) { const auto& block = m_minedBlocks[i]; ListItemSpec itemSpec; itemSpec.leadingIcon = "🏆"; char primaryStr[64]; snprintf(primaryStr, sizeof(primaryStr), "Block #%d", block.height); itemSpec.primaryText = primaryStr; char secondaryStr[64]; snprintf(secondaryStr, sizeof(secondaryStr), "+%.4f DRGX • %s", block.reward, block.time.c_str()); itemSpec.secondaryText = secondaryStr; itemSpec.dividerBelow = (i < m_minedBlocks.size() - 1 && i < 9); ListItem(itemSpec); } EndList(); } EndCard(); } ImGui::PopID(); } } // namespace screens } // namespace ui } // namespace dragonx