ObsidianDragon - DragonX ImGui Wallet

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)
This commit is contained in:
2026-02-26 02:31:52 -06:00
commit 3aee55b49c
306 changed files with 177789 additions and 0 deletions

View File

@@ -0,0 +1,560 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../material/material.h"
#include "../../config/version.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include <string>
#include <vector>
#include <functional>
#include <cstring>
namespace dragonx {
namespace ui {
namespace screens {
using namespace material;
// ============================================================================
// Settings Screen
// ============================================================================
// Application settings with Material list items
/**
* @brief Setting category
*/
enum class SettingsCategory {
Wallet,
Display,
Network,
Privacy,
Advanced
};
/**
* @brief Settings data
*/
struct SettingsData {
// Wallet
bool autoShield = true;
double defaultFee = DRAGONX_DEFAULT_FEE;
// Display
std::string theme = "dark";
std::string language = "en";
std::string fiatCurrency = "USD";
bool showBalanceInFiat = true;
bool hideEmptyAddresses = true;
// Network
std::string proxyAddress;
int proxyPort = 9050;
bool useTor = false;
int maxConnections = 125;
// Privacy
bool rememberAddresses = true;
bool autoLock = true;
int autoLockTimeout = 5; // minutes
// Advanced
int confirmations = 10;
std::string dataDir;
bool debugMode = false;
};
/**
* @brief Settings screen with Material list
*/
class SettingsScreen {
public:
void setSettings(const SettingsData& settings) { m_settings = settings; }
const SettingsData& getSettings() const { return m_settings; }
void setOnSettingsChanged(std::function<void(const SettingsData&)> callback) {
m_onSettingsChanged = callback;
}
void setOnBackupWallet(std::function<void()> callback) {
m_onBackupWallet = callback;
}
void setOnExportKeys(std::function<void()> callback) {
m_onExportKeys = callback;
}
void setOnRescan(std::function<void()> callback) {
m_onRescan = callback;
}
void render();
private:
void renderWalletSettings();
void renderDisplaySettings();
void renderNetworkSettings();
void renderPrivacySettings();
void renderAdvancedSettings();
void renderAboutSection();
void notifySettingsChanged();
SettingsData m_settings;
std::function<void(const SettingsData&)> m_onSettingsChanged;
std::function<void()> m_onBackupWallet;
std::function<void()> m_onExportKeys;
std::function<void()> m_onRescan;
SettingsCategory m_selectedCategory = SettingsCategory::Wallet;
};
// ============================================================================
// Implementation
// ============================================================================
inline void SettingsScreen::render() {
// Title
Typography::instance().text(TypeStyle::H5, "Settings");
ImGui::Dummy(ImVec2(0, spacing::dp(2)));
// Category tabs
TabSpec tabSpec;
tabSpec.variant = TabVariant::Scrollable;
if (BeginTabs("settings_tabs", tabSpec)) {
if (Tab(ICON_MD_ACCOUNT_BALANCE_WALLET " Wallet", m_selectedCategory == SettingsCategory::Wallet)) {
m_selectedCategory = SettingsCategory::Wallet;
}
if (Tab(ICON_MD_PALETTE " Display", m_selectedCategory == SettingsCategory::Display)) {
m_selectedCategory = SettingsCategory::Display;
}
if (Tab(ICON_MD_PUBLIC " Network", m_selectedCategory == SettingsCategory::Network)) {
m_selectedCategory = SettingsCategory::Network;
}
if (Tab(ICON_MD_LOCK " Privacy", m_selectedCategory == SettingsCategory::Privacy)) {
m_selectedCategory = SettingsCategory::Privacy;
}
if (Tab(ICON_MD_SETTINGS " Advanced", m_selectedCategory == SettingsCategory::Advanced)) {
m_selectedCategory = SettingsCategory::Advanced;
}
EndTabs();
}
ImGui::Dummy(ImVec2(0, spacing::dp(2)));
// Settings content
switch (m_selectedCategory) {
case SettingsCategory::Wallet:
renderWalletSettings();
break;
case SettingsCategory::Display:
renderDisplaySettings();
break;
case SettingsCategory::Network:
renderNetworkSettings();
break;
case SettingsCategory::Privacy:
renderPrivacySettings();
break;
case SettingsCategory::Advanced:
renderAdvancedSettings();
break;
}
ImGui::Dummy(ImVec2(0, spacing::dp(3)));
// About section (always visible)
renderAboutSection();
}
inline void SettingsScreen::renderWalletSettings() {
CardSpec cardSpec;
cardSpec.elevation = 1;
cardSpec.padding = 0;
if (BeginCard(cardSpec)) {
BeginList("wallet_settings", false);
// Auto-shield toggle
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Auto-Shield Transparent Funds";
itemSpec.secondaryText = "Automatically move transparent balance to shielded";
itemSpec.dividerBelow = true;
ImGui::PushID("auto_shield");
ListItem(itemSpec);
// Toggle positioned at end
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.autoShield)) {
notifySettingsChanged();
}
ImGui::PopID();
}
// Default fee
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Default Transaction Fee";
char feeStr[32];
snprintf(feeStr, sizeof(feeStr), "%.8f DRGX", m_settings.defaultFee);
itemSpec.secondaryText = feeStr;
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Backup wallet
{
ListItemSpec itemSpec;
itemSpec.leadingIcon = ICON_MD_SAVE;
itemSpec.primaryText = "Backup Wallet";
itemSpec.secondaryText = "Export wallet backup file";
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
if (ListItem(itemSpec)) {
if (m_onBackupWallet) m_onBackupWallet();
}
}
// Export keys
{
ListItemSpec itemSpec;
itemSpec.leadingIcon = ICON_MD_KEY;
itemSpec.primaryText = "Export Private Keys";
itemSpec.secondaryText = "Export viewing and spending keys";
itemSpec.trailingIcon = ">";
if (ListItem(itemSpec)) {
if (m_onExportKeys) m_onExportKeys();
}
}
EndList();
EndCard();
}
}
inline void SettingsScreen::renderDisplaySettings() {
CardSpec cardSpec;
cardSpec.elevation = 1;
cardSpec.padding = 0;
if (BeginCard(cardSpec)) {
BeginList("display_settings", false);
// Theme selection
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Theme";
itemSpec.secondaryText = m_settings.theme == "dark" ? "Dark" : "Light";
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Language
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Language";
itemSpec.secondaryText = "English"; // Would map from code
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Fiat currency
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Fiat Currency";
itemSpec.secondaryText = m_settings.fiatCurrency.c_str();
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Show balance in fiat toggle
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Show Balance in Fiat";
itemSpec.secondaryText = "Display fiat equivalent on home screen";
itemSpec.dividerBelow = true;
ImGui::PushID("show_fiat");
ListItem(itemSpec);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.showBalanceInFiat)) {
notifySettingsChanged();
}
ImGui::PopID();
}
// Hide empty addresses
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Hide Empty Addresses";
itemSpec.secondaryText = "Don't show addresses with zero balance";
ImGui::PushID("hide_empty");
ListItem(itemSpec);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.hideEmptyAddresses)) {
notifySettingsChanged();
}
ImGui::PopID();
}
EndList();
EndCard();
}
}
inline void SettingsScreen::renderNetworkSettings() {
CardSpec cardSpec;
cardSpec.elevation = 1;
cardSpec.padding = 0;
if (BeginCard(cardSpec)) {
BeginList("network_settings", false);
// Use Tor
{
ListItemSpec itemSpec;
itemSpec.leadingIcon = "🧅";
itemSpec.primaryText = "Use Tor Network";
itemSpec.secondaryText = "Route connections through Tor for privacy";
itemSpec.dividerBelow = true;
ImGui::PushID("use_tor");
ListItem(itemSpec);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.useTor)) {
notifySettingsChanged();
}
ImGui::PopID();
}
// Proxy settings (shown if Tor enabled)
if (m_settings.useTor) {
ListItemSpec itemSpec;
itemSpec.primaryText = "Proxy Address";
char proxyStr[128];
snprintf(proxyStr, sizeof(proxyStr), "%s:%d",
m_settings.proxyAddress.empty() ? "127.0.0.1" : m_settings.proxyAddress.c_str(),
m_settings.proxyPort);
itemSpec.secondaryText = proxyStr;
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Max connections
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Maximum Connections";
char connStr[32];
snprintf(connStr, sizeof(connStr), "%d peers", m_settings.maxConnections);
itemSpec.secondaryText = connStr;
itemSpec.trailingIcon = ">";
ListItem(itemSpec);
}
EndList();
EndCard();
}
}
inline void SettingsScreen::renderPrivacySettings() {
CardSpec cardSpec;
cardSpec.elevation = 1;
cardSpec.padding = 0;
if (BeginCard(cardSpec)) {
BeginList("privacy_settings", false);
// Remember addresses
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Remember Addresses";
itemSpec.secondaryText = "Save recently used addresses";
itemSpec.dividerBelow = true;
ImGui::PushID("remember_addr");
ListItem(itemSpec);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.rememberAddresses)) {
notifySettingsChanged();
}
ImGui::PopID();
}
// Auto-lock
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Auto-Lock Wallet";
itemSpec.secondaryText = "Lock wallet after inactivity";
itemSpec.dividerBelow = true;
ImGui::PushID("auto_lock");
ListItem(itemSpec);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.autoLock)) {
notifySettingsChanged();
}
ImGui::PopID();
}
// Lock timeout (if auto-lock enabled)
if (m_settings.autoLock) {
ListItemSpec itemSpec;
itemSpec.primaryText = "Lock Timeout";
char timeoutStr[32];
snprintf(timeoutStr, sizeof(timeoutStr), "%d minutes", m_settings.autoLockTimeout);
itemSpec.secondaryText = timeoutStr;
itemSpec.trailingIcon = ">";
ListItem(itemSpec);
}
EndList();
EndCard();
}
}
inline void SettingsScreen::renderAdvancedSettings() {
CardSpec cardSpec;
cardSpec.elevation = 1;
cardSpec.padding = 0;
if (BeginCard(cardSpec)) {
BeginList("advanced_settings", false);
// Required confirmations
{
ListItemSpec itemSpec;
itemSpec.primaryText = "Required Confirmations";
char confStr[32];
snprintf(confStr, sizeof(confStr), "%d blocks", m_settings.confirmations);
itemSpec.secondaryText = confStr;
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Data directory
{
ListItemSpec itemSpec;
itemSpec.leadingIcon = "📁";
itemSpec.primaryText = "Data Directory";
itemSpec.secondaryText = m_settings.dataDir.empty() ?
"(default)" : m_settings.dataDir.c_str();
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
ListItem(itemSpec);
}
// Rescan blockchain
{
ListItemSpec itemSpec;
itemSpec.leadingIcon = ICON_MD_SYNC;
itemSpec.primaryText = "Rescan Blockchain";
itemSpec.secondaryText = "Rebuild transaction history from blockchain";
itemSpec.trailingIcon = ">";
itemSpec.dividerBelow = true;
if (ListItem(itemSpec)) {
if (m_onRescan) m_onRescan();
}
}
// Debug mode
{
ListItemSpec itemSpec;
itemSpec.leadingIcon = "🐛";
itemSpec.primaryText = "Debug Mode";
itemSpec.secondaryText = "Enable verbose logging";
ImGui::PushID("debug_mode");
ListItem(itemSpec);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
if (Switch("##toggle", m_settings.debugMode)) {
notifySettingsChanged();
}
ImGui::PopID();
}
EndList();
EndCard();
}
}
inline void SettingsScreen::renderAboutSection() {
CardSpec cardSpec;
cardSpec.elevation = 0;
cardSpec.padding = spacing::dp(3);
cardSpec.outlined = true;
if (BeginCard(cardSpec)) {
float availWidth = ImGui::GetContentRegionAvail().x;
// App name and version centered
const char* appName = "ObsidianDragon";
ImVec2 nameSize = ImGui::CalcTextSize(appName);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - nameSize.x) * 0.5f);
Typography::instance().text(TypeStyle::H6, appName);
const char* version = "Version 1.0.0-imgui";
ImVec2 versionSize = ImGui::CalcTextSize(version);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - versionSize.x) * 0.5f);
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), version);
ImGui::Dummy(ImVec2(0, spacing::dp(1)));
const char* copyright = "© 2024-2026 The Hush Developers";
ImVec2 copySize = ImGui::CalcTextSize(copyright);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - copySize.x) * 0.5f);
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), copyright);
const char* license = "Released under GPLv3";
ImVec2 licenseSize = ImGui::CalcTextSize(license);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - licenseSize.x) * 0.5f);
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), license);
EndCard();
}
}
inline void SettingsScreen::notifySettingsChanged() {
if (m_onSettingsChanged) {
m_onSettingsChanged(m_settings);
}
}
} // namespace screens
} // namespace ui
} // namespace dragonx