Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
372 lines
13 KiB
C++
372 lines
13 KiB
C++
// 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 <string>
|
|
#include <vector>
|
|
#include <functional>
|
|
|
|
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<MinedBlock>& blocks) { m_minedBlocks = blocks; }
|
|
|
|
void setOnStartMining(std::function<void(int threads)> callback) {
|
|
m_onStartMining = callback;
|
|
}
|
|
void setOnStopMining(std::function<void()> callback) {
|
|
m_onStopMining = callback;
|
|
}
|
|
void setOnSetAddress(std::function<void(const std::string&)> callback) {
|
|
m_onSetAddress = callback;
|
|
}
|
|
|
|
void render();
|
|
|
|
private:
|
|
void renderMiningControls();
|
|
void renderStatsCards();
|
|
void renderMinedBlocksList();
|
|
|
|
MiningStats m_stats;
|
|
std::vector<MinedBlock> m_minedBlocks;
|
|
|
|
std::function<void(int)> m_onStartMining;
|
|
std::function<void()> m_onStopMining;
|
|
std::function<void(const std::string&)> 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
|