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:
848
src/ui/pages/settings_page.cpp.bak
Normal file
848
src/ui/pages/settings_page.cpp.bak
Normal file
@@ -0,0 +1,848 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "settings_page.h"
|
||||
#include "../../app.h"
|
||||
#include "../../version.h"
|
||||
#include "../../config/settings.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../theme.h"
|
||||
#include "../layout.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../schema/skin_manager.h"
|
||||
#include "../notifications.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../windows/validate_address_dialog.h"
|
||||
#include "../windows/address_book_dialog.h"
|
||||
#include "../windows/shield_dialog.h"
|
||||
#include "../windows/request_payment_dialog.h"
|
||||
#include "../windows/block_info_dialog.h"
|
||||
#include "../windows/export_all_keys_dialog.h"
|
||||
#include "../windows/export_transactions_dialog.h"
|
||||
#include "imgui.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
using namespace material;
|
||||
|
||||
// ============================================================================
|
||||
// Settings state — loaded from config::Settings on first render
|
||||
// ============================================================================
|
||||
static bool sp_initialized = false;
|
||||
static int sp_language_index = 0;
|
||||
static bool sp_save_ztxs = true;
|
||||
static bool sp_allow_custom_fees = false;
|
||||
static bool sp_auto_shield = false;
|
||||
static bool sp_fetch_prices = true;
|
||||
static bool sp_use_tor = false;
|
||||
static char sp_rpc_host[128] = DRAGONX_DEFAULT_RPC_HOST;
|
||||
static char sp_rpc_port[16] = DRAGONX_DEFAULT_RPC_PORT;
|
||||
static char sp_rpc_user[64] = "";
|
||||
static char sp_rpc_password[64] = "";
|
||||
static char sp_tx_explorer[256] = "https://explorer.dragonx.is/tx/";
|
||||
static char sp_addr_explorer[256] = "https://explorer.dragonx.is/address/";
|
||||
|
||||
// Acrylic settings
|
||||
static bool sp_acrylic_enabled = true;
|
||||
static int sp_acrylic_quality = 2;
|
||||
static float sp_blur_multiplier = 1.0f;
|
||||
static bool sp_reduced_transparency = false;
|
||||
|
||||
static void loadSettingsPageState(config::Settings* settings) {
|
||||
if (!settings) return;
|
||||
|
||||
sp_save_ztxs = settings->getSaveZtxs();
|
||||
sp_allow_custom_fees = settings->getAllowCustomFees();
|
||||
sp_auto_shield = settings->getAutoShield();
|
||||
sp_fetch_prices = settings->getFetchPrices();
|
||||
sp_use_tor = settings->getUseTor();
|
||||
|
||||
strncpy(sp_tx_explorer, settings->getTxExplorerUrl().c_str(), sizeof(sp_tx_explorer) - 1);
|
||||
strncpy(sp_addr_explorer, settings->getAddressExplorerUrl().c_str(), sizeof(sp_addr_explorer) - 1);
|
||||
|
||||
auto& i18n = util::I18n::instance();
|
||||
const auto& languages = i18n.getAvailableLanguages();
|
||||
std::string current_lang = settings->getLanguage();
|
||||
if (current_lang.empty()) current_lang = "en";
|
||||
|
||||
sp_language_index = 0;
|
||||
int idx = 0;
|
||||
for (const auto& lang : languages) {
|
||||
if (lang.first == current_lang) {
|
||||
sp_language_index = idx;
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
sp_acrylic_enabled = effects::ImGuiAcrylic::IsEnabled();
|
||||
sp_acrylic_quality = static_cast<int>(effects::ImGuiAcrylic::GetQuality());
|
||||
sp_blur_multiplier = effects::ImGuiAcrylic::GetBlurMultiplier();
|
||||
sp_reduced_transparency = effects::ImGuiAcrylic::GetReducedTransparency();
|
||||
|
||||
sp_initialized = true;
|
||||
}
|
||||
|
||||
static void saveSettingsPageState(config::Settings* settings) {
|
||||
if (!settings) return;
|
||||
|
||||
settings->setTheme(settings->getSkinId());
|
||||
settings->setSaveZtxs(sp_save_ztxs);
|
||||
settings->setAllowCustomFees(sp_allow_custom_fees);
|
||||
settings->setAutoShield(sp_auto_shield);
|
||||
settings->setFetchPrices(sp_fetch_prices);
|
||||
settings->setUseTor(sp_use_tor);
|
||||
settings->setTxExplorerUrl(sp_tx_explorer);
|
||||
settings->setAddressExplorerUrl(sp_addr_explorer);
|
||||
|
||||
auto& i18n = util::I18n::instance();
|
||||
const auto& languages = i18n.getAvailableLanguages();
|
||||
auto it = languages.begin();
|
||||
std::advance(it, sp_language_index);
|
||||
if (it != languages.end()) {
|
||||
settings->setLanguage(it->first);
|
||||
}
|
||||
|
||||
settings->save();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Settings Page Renderer
|
||||
// ============================================================================
|
||||
|
||||
void RenderSettingsPage(App* app) {
|
||||
// Load settings state on first render
|
||||
if (!sp_initialized && app->settings()) {
|
||||
loadSettingsPageState(app->settings());
|
||||
}
|
||||
|
||||
auto& S = schema::UI();
|
||||
|
||||
// Responsive layout — matches other tabs
|
||||
ImVec2 contentAvail = ImGui::GetContentRegionAvail();
|
||||
float availWidth = contentAvail.x;
|
||||
float hs = Layout::hScale(availWidth);
|
||||
float vs = Layout::vScale(contentAvail.y);
|
||||
float pad = Layout::cardInnerPadding();
|
||||
float gap = Layout::cardGap();
|
||||
float glassRound = Layout::glassRounding();
|
||||
(void)vs;
|
||||
|
||||
char buf[256];
|
||||
|
||||
// Label column position — adaptive to width
|
||||
float labelW = std::max(100.0f, 120.0f * hs);
|
||||
// Input field width — fill remaining space in card
|
||||
float inputW = std::max(180.0f, availWidth - labelW - pad * 3);
|
||||
|
||||
// Scrollable content area — NoBackground matches other tabs
|
||||
ImGui::BeginChild("##SettingsPageScroll", ImVec2(0, 0), false,
|
||||
ImGuiWindowFlags_NoBackground);
|
||||
|
||||
// Get draw list AFTER BeginChild so we draw on the child window's list
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
GlassPanelSpec glassSpec;
|
||||
glassSpec.rounding = glassRound;
|
||||
ImFont* ovFont = Type().overline();
|
||||
ImFont* capFont = Type().caption();
|
||||
ImFont* body2 = Type().body2();
|
||||
ImFont* sub1 = Type().subtitle1();
|
||||
|
||||
// ====================================================================
|
||||
// GENERAL — Appearance & Preferences card
|
||||
// ====================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "APPEARANCE");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Measure content height for card
|
||||
// We'll use ImGui cursor-based layout inside the card
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
|
||||
// Use a child window inside the glass panel for layout
|
||||
// First draw the glass panel, then place content
|
||||
// We need to estimate height — use a generous estimate and clip
|
||||
float rowH = body2->LegacySize + Layout::spacingSm();
|
||||
float sectionGap = Layout::spacingMd();
|
||||
float cardH = pad // top pad
|
||||
+ rowH // Theme
|
||||
+ rowH // Language
|
||||
+ sectionGap
|
||||
+ rowH * 5 // Visual effects (acrylic + quality + blur + reduce + gap)
|
||||
+ pad; // bottom pad
|
||||
if (!sp_acrylic_enabled) cardH -= rowH * 2;
|
||||
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
// --- Theme row ---
|
||||
{
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Theme");
|
||||
ImGui::SameLine(labelW);
|
||||
|
||||
auto& skinMgr = schema::SkinManager::instance();
|
||||
const auto& skins = skinMgr.available();
|
||||
|
||||
std::string active_preview = "DragonX";
|
||||
bool active_is_custom = false;
|
||||
for (const auto& skin : skins) {
|
||||
if (skin.id == skinMgr.activeSkinId()) {
|
||||
active_preview = skin.name;
|
||||
active_is_custom = !skin.bundled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float refreshBtnW = 80.0f;
|
||||
ImGui::SetNextItemWidth(inputW - refreshBtnW - Layout::spacingSm());
|
||||
if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
|
||||
ImGui::TextDisabled("Built-in");
|
||||
ImGui::Separator();
|
||||
for (size_t i = 0; i < skins.size(); i++) {
|
||||
const auto& skin = skins[i];
|
||||
if (!skin.bundled) continue;
|
||||
bool is_selected = (skin.id == skinMgr.activeSkinId());
|
||||
if (ImGui::Selectable(skin.name.c_str(), is_selected)) {
|
||||
skinMgr.setActiveSkin(skin.id);
|
||||
if (app->settings()) {
|
||||
app->settings()->setSkinId(skin.id);
|
||||
app->settings()->save();
|
||||
}
|
||||
}
|
||||
if (is_selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
bool has_custom = false;
|
||||
for (const auto& skin : skins) {
|
||||
if (!skin.bundled) { has_custom = true; break; }
|
||||
}
|
||||
if (has_custom) {
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("Custom");
|
||||
ImGui::Separator();
|
||||
for (size_t i = 0; i < skins.size(); i++) {
|
||||
const auto& skin = skins[i];
|
||||
if (skin.bundled) continue;
|
||||
bool is_selected = (skin.id == skinMgr.activeSkinId());
|
||||
if (!skin.valid) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::BeginDisabled(true);
|
||||
std::string lbl = skin.name + " (invalid)";
|
||||
ImGui::Selectable(lbl.c_str(), false);
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("%s", skin.validationError.c_str());
|
||||
} else {
|
||||
std::string lbl = skin.name;
|
||||
if (!skin.author.empty()) lbl += " (" + skin.author + ")";
|
||||
if (ImGui::Selectable(lbl.c_str(), is_selected)) {
|
||||
skinMgr.setActiveSkin(skin.id);
|
||||
if (app->settings()) {
|
||||
app->settings()->setSkinId(skin.id);
|
||||
app->settings()->save();
|
||||
}
|
||||
}
|
||||
if (is_selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (active_is_custom) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "*");
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Custom theme active");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton("Refresh", ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
|
||||
schema::SkinManager::instance().refresh();
|
||||
Notifications::instance().info("Theme list refreshed");
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Scan for new themes.\nPlace theme folders in:\n%s",
|
||||
schema::SkinManager::getUserSkinsDirectory().c_str());
|
||||
}
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// --- Language row ---
|
||||
{
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Language");
|
||||
ImGui::SameLine(labelW);
|
||||
|
||||
auto& i18n = util::I18n::instance();
|
||||
const auto& languages = i18n.getAvailableLanguages();
|
||||
std::vector<const char*> lang_names;
|
||||
lang_names.reserve(languages.size());
|
||||
for (const auto& lang : languages) {
|
||||
lang_names.push_back(lang.second.c_str());
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(inputW);
|
||||
if (ImGui::Combo("##Language", &sp_language_index, lang_names.data(),
|
||||
static_cast<int>(lang_names.size()))) {
|
||||
auto it = languages.begin();
|
||||
std::advance(it, sp_language_index);
|
||||
i18n.loadLanguage(it->first);
|
||||
}
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// --- Visual Effects subsection ---
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImGui::GetCursorScreenPos(), OnSurfaceMedium(), "VISUAL EFFECTS");
|
||||
ImGui::Dummy(ImVec2(0, ovFont->LegacySize + Layout::spacingXs()));
|
||||
|
||||
{
|
||||
// Two-column: left = acrylic toggle + reduce toggle, right = quality + blur
|
||||
float colW = (availWidth - pad * 2 - Layout::spacingLg()) * 0.5f;
|
||||
|
||||
if (ImGui::Checkbox("Acrylic effects", &sp_acrylic_enabled)) {
|
||||
effects::ImGuiAcrylic::SetEnabled(sp_acrylic_enabled);
|
||||
}
|
||||
|
||||
if (sp_acrylic_enabled) {
|
||||
ImGui::SameLine(labelW + colW + Layout::spacingLg());
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Quality");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
const char* quality_levels[] = { "Off", "Low", "Medium", "High" };
|
||||
ImGui::SetNextItemWidth(std::max(100.0f, colW - 80.0f));
|
||||
if (ImGui::Combo("##AcrylicQuality", &sp_acrylic_quality, quality_levels,
|
||||
IM_ARRAYSIZE(quality_levels))) {
|
||||
effects::ImGuiAcrylic::SetQuality(
|
||||
static_cast<effects::AcrylicQuality>(sp_acrylic_quality));
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Reduce transparency", &sp_reduced_transparency)) {
|
||||
effects::ImGuiAcrylic::SetReducedTransparency(sp_reduced_transparency);
|
||||
}
|
||||
|
||||
if (sp_acrylic_enabled) {
|
||||
ImGui::SameLine(labelW + colW + Layout::spacingLg());
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Blur");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(std::max(100.0f, colW - 80.0f));
|
||||
if (ImGui::SliderFloat("##BlurAmount", &sp_blur_multiplier, 0.5f, 2.0f, "%.1fx")) {
|
||||
effects::ImGuiAcrylic::SetBlurMultiplier(sp_blur_multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate actual card bottom from cursor
|
||||
ImVec2 cardEnd = ImGui::GetCursorScreenPos();
|
||||
float actualH = (cardEnd.y - cardMin.y) + pad;
|
||||
if (actualH != cardH) {
|
||||
// Redraw glass panel with correct height
|
||||
cardMax.y = cardMin.y + actualH;
|
||||
}
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// PRIVACY & OPTIONS — Two cards side by side
|
||||
// ====================================================================
|
||||
{
|
||||
float colW = (availWidth - gap) * 0.5f;
|
||||
ImVec2 rowOrigin = ImGui::GetCursorScreenPos();
|
||||
|
||||
// --- Privacy card (left) ---
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "PRIVACY");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float cardH = pad + (body2->LegacySize + Layout::spacingSm()) * 3 + pad;
|
||||
ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
ImGui::Checkbox("Save shielded tx history", &sp_save_ztxs);
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
ImGui::Checkbox("Auto-shield transparent funds", &sp_auto_shield);
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
ImGui::Checkbox("Use Tor for connections", &sp_use_tor);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(colW, 0));
|
||||
}
|
||||
|
||||
// --- Options card (right) ---
|
||||
{
|
||||
float rightX = rowOrigin.x + colW + gap;
|
||||
// Position cursor at the same Y as privacy label
|
||||
ImGui::SetCursorScreenPos(ImVec2(rightX, rowOrigin.y));
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "OPTIONS");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float cardH = pad + (body2->LegacySize + Layout::spacingSm()) * 3 + pad;
|
||||
ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
ImGui::Checkbox("Allow custom transaction fees", &sp_allow_custom_fees);
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
ImGui::Checkbox("Fetch price data from CoinGecko", &sp_fetch_prices);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(colW, 0));
|
||||
}
|
||||
|
||||
// Advance past the side-by-side row
|
||||
// Find the maximum bottom
|
||||
float rowBottom = ImGui::GetCursorScreenPos().y;
|
||||
ImGui::SetCursorScreenPos(ImVec2(rowOrigin.x, rowBottom));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// EXPLORER URLS + SAVE — card
|
||||
// ====================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "BLOCK EXPLORER & SETTINGS");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float rowH = body2->LegacySize + Layout::spacingSm();
|
||||
float cardH = pad + rowH * 2 + Layout::spacingSm()
|
||||
+ body2->LegacySize + Layout::spacingMd() // save/reset row
|
||||
+ pad;
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
// Transaction URL
|
||||
{
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Transaction URL");
|
||||
ImGui::SameLine(labelW);
|
||||
ImGui::SetNextItemWidth(inputW);
|
||||
ImGui::InputText("##TxExplorer", sp_tx_explorer, sizeof(sp_tx_explorer));
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
// Address URL
|
||||
{
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Address URL");
|
||||
ImGui::SameLine(labelW);
|
||||
ImGui::SetNextItemWidth(inputW);
|
||||
ImGui::InputText("##AddrExplorer", sp_addr_explorer, sizeof(sp_addr_explorer));
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Save / Reset — right-aligned
|
||||
{
|
||||
float saveBtnW = 120.0f;
|
||||
float resetBtnW = 140.0f;
|
||||
float btnGap = Layout::spacingSm();
|
||||
|
||||
if (TactileButton("Save Settings", ImVec2(saveBtnW, 0), S.resolveFont("button"))) {
|
||||
saveSettingsPageState(app->settings());
|
||||
Notifications::instance().success("Settings saved");
|
||||
}
|
||||
ImGui::SameLine(0, btnGap);
|
||||
if (TactileButton("Reset to Defaults", ImVec2(resetBtnW, 0), S.resolveFont("button"))) {
|
||||
if (app->settings()) {
|
||||
loadSettingsPageState(app->settings());
|
||||
Notifications::instance().info("Settings reloaded from disk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// KEYS & BACKUP — card with two rows
|
||||
// ====================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "KEYS & BACKUP");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm();
|
||||
float cardH = pad + btnRowH * 2 + Layout::spacingSm() + pad;
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
// Keys row — spread buttons across width
|
||||
{
|
||||
float btnW = (availWidth - pad * 2 - Layout::spacingSm() * 2) / 3.0f;
|
||||
if (TactileButton("Import Private Key...", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
app->showImportKeyDialog();
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Export Private Key...", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
app->showExportKeyDialog();
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Export All Keys...", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
ExportAllKeysDialog::show();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Backup row
|
||||
{
|
||||
float btnW = (availWidth - pad * 2 - Layout::spacingSm()) / 2.0f;
|
||||
if (TactileButton("Backup wallet.dat...", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
app->showBackupDialog();
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Export Transactions CSV...", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
ExportTransactionsDialog::show();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// WALLET — Two cards side by side: Tools | Maintenance
|
||||
// ====================================================================
|
||||
{
|
||||
float colW = (availWidth - gap) * 0.5f;
|
||||
ImVec2 rowOrigin = ImGui::GetCursorScreenPos();
|
||||
float btnH = std::max(28.0f, 34.0f * vs);
|
||||
|
||||
// --- Wallet Tools card (left) ---
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "WALLET TOOLS");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float cardH = pad + (btnH + Layout::spacingSm()) * 3 + pad;
|
||||
ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
float innerBtnW = colW - pad * 2;
|
||||
if (TactileButton("Address Book...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) {
|
||||
AddressBookDialog::show();
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
if (TactileButton("Validate Address...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) {
|
||||
ValidateAddressDialog::show();
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
if (TactileButton("Request Payment...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) {
|
||||
RequestPaymentDialog::show();
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(colW, 0));
|
||||
}
|
||||
|
||||
// --- Shielding & Maintenance card (right) ---
|
||||
{
|
||||
float rightX = rowOrigin.x + colW + gap;
|
||||
ImGui::SetCursorScreenPos(ImVec2(rightX, rowOrigin.y));
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "SHIELDING & MAINTENANCE");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float cardH = pad + (btnH + Layout::spacingSm()) * 3 + pad;
|
||||
ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
float innerBtnW = colW - pad * 2;
|
||||
if (TactileButton("Shield Mining Rewards...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) {
|
||||
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
if (TactileButton("Merge to Address...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) {
|
||||
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
ImGui::BeginDisabled(!app->isConnected());
|
||||
if (TactileButton("Rescan Blockchain", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) {
|
||||
if (app->rpc() && app->rpc()->isConnected()) {
|
||||
app->rpc()->rescanBlockchain(0, [](bool success, const nlohmann::json&) {
|
||||
if (success)
|
||||
Notifications::instance().success("Blockchain rescan started");
|
||||
else
|
||||
Notifications::instance().error("Failed to start rescan");
|
||||
});
|
||||
} else {
|
||||
Notifications::instance().warning("Not connected to daemon");
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(colW, 0));
|
||||
}
|
||||
|
||||
// Advance past sidebar row
|
||||
float rowBottom = ImGui::GetCursorScreenPos().y;
|
||||
ImGui::SetCursorScreenPos(ImVec2(rowOrigin.x, rowBottom));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// WALLET INFO — Small card with file path + clear history
|
||||
// ====================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "WALLET INFO");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float rowH = body2->LegacySize + Layout::spacingSm();
|
||||
float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm();
|
||||
float cardH = pad + rowH * 2 + btnRowH + pad;
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
std::string wallet_path = util::Platform::getDragonXDataDir() + "wallet.dat";
|
||||
uint64_t wallet_size = util::Platform::getFileSize(wallet_path);
|
||||
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Location");
|
||||
ImGui::SameLine(labelW);
|
||||
ImGui::TextUnformatted(wallet_path.c_str());
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("File size");
|
||||
ImGui::SameLine(labelW);
|
||||
if (wallet_size > 0) {
|
||||
std::string size_str = util::Platform::formatFileSize(wallet_size);
|
||||
ImGui::TextUnformatted(size_str.c_str());
|
||||
} else {
|
||||
ImGui::TextDisabled("Not found");
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
if (TactileButton("Clear Z-Transaction History", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file))
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
else
|
||||
Notifications::instance().info("No history file found");
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// NODE / RPC — card with two-column inputs
|
||||
// ====================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "NODE / RPC CONNECTION");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float rowH = body2->LegacySize + Layout::spacingSm();
|
||||
float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm();
|
||||
float cardH = pad + rowH * 2 + Layout::spacingSm() + rowH * 2 + Layout::spacingSm()
|
||||
+ capFont->LegacySize + Layout::spacingSm()
|
||||
+ btnRowH + pad;
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
// Two-column: Host+Port on one line, User+Pass on next
|
||||
float halfInput = (availWidth - pad * 2 - labelW * 2 - Layout::spacingLg()) * 0.5f;
|
||||
float rpcLabelW = std::max(70.0f, 85.0f * hs);
|
||||
|
||||
ImGui::PushFont(body2);
|
||||
|
||||
// Row 1: Host + Port
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Host");
|
||||
ImGui::SameLine(rpcLabelW);
|
||||
ImGui::SetNextItemWidth(halfInput + labelW - rpcLabelW);
|
||||
ImGui::InputText("##RPCHost", sp_rpc_host, sizeof(sp_rpc_host));
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Port");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(std::max(60.0f, halfInput * 0.4f));
|
||||
ImGui::InputText("##RPCPort", sp_rpc_port, sizeof(sp_rpc_port));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Row 2: Username + Password
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Username");
|
||||
ImGui::SameLine(rpcLabelW);
|
||||
ImGui::SetNextItemWidth(halfInput + labelW - rpcLabelW);
|
||||
ImGui::InputText("##RPCUser", sp_rpc_user, sizeof(sp_rpc_user));
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Password");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(halfInput);
|
||||
ImGui::InputText("##RPCPassword", sp_rpc_password, sizeof(sp_rpc_password),
|
||||
ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
"Connection settings are usually auto-detected from DRAGONX.conf");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
if (TactileButton("Test Connection", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (app->rpc() && app->rpc()->isConnected()) {
|
||||
app->rpc()->getInfo([](const nlohmann::json& result, const std::string& error) {
|
||||
(void)result;
|
||||
if (error.empty())
|
||||
Notifications::instance().success("RPC connection OK");
|
||||
else
|
||||
Notifications::instance().error("RPC error: " + error);
|
||||
});
|
||||
} else {
|
||||
Notifications::instance().warning("Not connected to daemon");
|
||||
}
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Block Info...", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
BlockInfoDialog::show(app->getBlockHeight());
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// ABOUT — card
|
||||
// ====================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "ABOUT");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float rowH = body2->LegacySize + Layout::spacingXs();
|
||||
float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm();
|
||||
float cardH = pad + sub1->LegacySize + rowH * 2 + Layout::spacingSm()
|
||||
+ body2->LegacySize * 2 + Layout::spacingSm()
|
||||
+ capFont->LegacySize * 2 + Layout::spacingMd()
|
||||
+ btnRowH + pad;
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad));
|
||||
|
||||
// App name + version on same line
|
||||
ImGui::PushFont(sub1);
|
||||
ImGui::TextUnformatted(DRAGONX_APP_NAME);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
ImGui::PushFont(body2);
|
||||
snprintf(buf, sizeof(buf), "v%s", DRAGONX_VERSION);
|
||||
ImGui::TextUnformatted(buf);
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
snprintf(buf, sizeof(buf), "ImGui %s", IMGUI_VERSION);
|
||||
ImGui::TextColored(ImVec4(1,1,1,0.4f), "%s", buf);
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::PushTextWrapPos(cardMax.x - pad);
|
||||
ImGui::TextUnformatted(
|
||||
"A shielded cryptocurrency wallet for DragonX (DRGX), "
|
||||
"built with Dear ImGui for a lightweight, portable experience.");
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
ImGui::PushFont(capFont);
|
||||
ImGui::TextColored(ImVec4(1,1,1,0.5f), "Copyright 2024-2026 The Hush Developers | GPLv3 License");
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
// Buttons — spread across width
|
||||
{
|
||||
float btnW = (availWidth - pad * 2 - Layout::spacingSm() * 2) / 3.0f;
|
||||
if (TactileButton("Website", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
util::Platform::openUrl("https://dragonx.is");
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Report Bug", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
util::Platform::openUrl("https://git.hush.is/hush/SilentDragonX/issues");
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Block Explorer", ImVec2(btnW, 0), S.resolveFont("button"))) {
|
||||
util::Platform::openUrl("https://explorer.dragonx.is");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
ImGui::EndChild(); // ##SettingsPageScroll
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user