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

375
src/ui/schema/ui_schema.h Normal file
View File

@@ -0,0 +1,375 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "color_var_resolver.h"
#include "element_styles.h"
#include "imgui.h"
#include <string>
#include <unordered_map>
#include <any>
#include <filesystem>
namespace dragonx {
namespace ui {
namespace schema {
/**
* @brief Responsive breakpoint state
*/
enum class Breakpoint {
Compact, // Small window (e.g., < 500px wide)
Normal, // Default
Expanded // Large window (e.g., > 900px wide)
};
/**
* @brief Unified UI schema singleton
*
* Loads theme palette, globals, and per-section element styles from a single
* TOML file. Provides merged lookups (section → globals → fallback) and
* poll-based hot-reload.
*
* Usage:
* auto& UI = UISchema::instance();
* UI.loadFromFile("res/themes/ui.toml");
*
* // In main loop:
* UI.pollForChanges();
* UI.applyIfDirty();
*
* // Lookups:
* auto btn = UI.button("dialogs.about", "close-button");
* auto win = UI.window("dialogs.about");
* ImU32 col = UI.resolveColor("var(--primary)");
*/
class UISchema {
public:
/**
* @brief Get the singleton instance
*/
static UISchema& instance();
/**
* @brief Load a unified skin file
* @param path Path to TOML skin file (e.g., "res/themes/ui.toml")
* @return true if loaded successfully
*/
bool loadFromFile(const std::string& path);
/**
* @brief Load from a TOML string (e.g., embedded data fallback)
* @param tomlStr Raw TOML text
* @param label Human-readable label for log messages
* @return true if parsed successfully
*/
bool loadFromString(const std::string& tomlStr, const std::string& label = "embedded");
/**
* @brief Merge a theme overlay on top of the currently loaded base.
*
* Only replaces visual properties (theme palette/elevation/images and
* backdrop section). Layout values from the base are preserved.
*
* @param path Path to the overlay TOML (e.g., dark.toml, light.toml)
* @return true if overlay was parsed and merged successfully
*/
bool mergeOverlayFromFile(const std::string& path);
/**
* @brief Check if a schema file has been loaded successfully
*/
bool isLoaded() const { return loaded_; }
/**
* @brief Get the path of the currently loaded file
*/
const std::string& currentPath() const { return currentPath_; }
/**
* @brief Get the base layout file path (ui.toml)
*/
const std::string& basePath() const { return basePath_; }
/**
* @brief Check if the base theme was loaded from an embedded string.
* When true, reloadBase() uses the stored embedded data instead of a file.
*/
bool hasEmbeddedBase() const { return !embeddedTomlStr_.empty(); }
/**
* @brief Reload the base theme (ui.toml) from file or embedded data.
* Used by SkinManager before merging an overlay to ensure layout values
* are always restored from the authoritative base, even on platforms
* where ui.toml is embedded (e.g., single-file Windows distribution).
* @return true if base was reloaded successfully
*/
bool reloadBase();
/**
* @brief Get the active overlay file path (empty if no overlay)
*/
const std::string& overlayPath() const { return overlayPath_; }
// ====================================================================
// Hot-reload
// ====================================================================
/**
* @brief Poll for file changes (call from main loop)
*
* Checks file modification time every pollInterval_ seconds.
* Sets dirty flag if file has changed.
*/
void pollForChanges();
/**
* @brief Check if the schema needs re-applying
*/
bool isDirty() const { return dirty_; }
/**
* @brief Re-parse and apply if file has changed
*/
void applyIfDirty();
/**
* @brief Check and consume the fonts-changed flag.
* Returns true once after a hot-reload that changed font sizes.
*/
bool consumeFontsChanged() {
bool v = fonts_changed_;
fonts_changed_ = false;
return v;
}
/**
* @brief Re-apply Material Design colors from schema palette to ImGui
*
* Builds a ColorTheme from the current palette and applies to ImGui style.
* Called automatically during hot-reload.
*/
void reapplyColorsToImGui();
/**
* @brief Set the poll interval in seconds (default 0.5)
*/
void setPollInterval(float seconds) { pollInterval_ = seconds; }
// ====================================================================
// Color resolution
// ====================================================================
/**
* @brief Get the color variable resolver (for direct access)
*/
const ColorVarResolver& colors() const { return colorResolver_; }
/**
* @brief Resolve a color string to ImU32
* @param ref Color string: "var(--primary)", "#hex", "rgba(...)", etc.
* @param fallback Value if resolution fails
*/
ImU32 resolveColor(const std::string& ref, ImU32 fallback = IM_COL32(0,0,0,0)) const;
// ====================================================================
// Font resolution
// ====================================================================
/**
* @brief Resolve a font name string to ImFont*
* @param fontName e.g., "button", "button-sm", "button-lg", "h4", "body1"
* @return ImFont* pointer, or nullptr if name is empty/unknown
*/
ImFont* resolveFont(const std::string& fontName) const;
// ====================================================================
// Responsive
// ====================================================================
/**
* @brief Get current breakpoint based on window/viewport size
*/
Breakpoint currentBreakpoint() const;
/**
* @brief Get the breakpoint config
*/
const BreakpointConfig& breakpoints() const { return breakpoints_; }
// ====================================================================
// Element lookups — merged (section → globals → C++ default)
// ====================================================================
/**
* @brief Look up a button style
* @param section Dot-separated section path: "dialogs.about", "tabs.send"
* @param name Element name within the section: "close-button"
* @return Merged ButtonStyle (responsive overrides already applied)
*/
ButtonStyle button(const std::string& section, const std::string& name) const;
/**
* @brief Look up an input style
*/
InputStyle input(const std::string& section, const std::string& name) const;
/**
* @brief Look up a label style
*/
LabelStyle label(const std::string& section, const std::string& name) const;
/**
* @brief Look up a table style
*/
TableStyle table(const std::string& section, const std::string& name) const;
/**
* @brief Look up a checkbox style
*/
CheckboxStyle checkbox(const std::string& section, const std::string& name) const;
/**
* @brief Look up a combo style
*/
ComboStyle combo(const std::string& section, const std::string& name) const;
/**
* @brief Look up a slider style
*/
SliderStyle slider(const std::string& section, const std::string& name) const;
/**
* @brief Look up a window/dialog style
* @param section Section path: "dialogs.about"
* @param name Defaults to "window" — the window block within that section
*/
WindowStyle window(const std::string& section, const std::string& name = "window") const;
/**
* @brief Look up a separator style
*/
SeparatorStyle separator(const std::string& section, const std::string& name) const;
/**
* @brief Look up a DrawList custom element style
*/
DrawElementStyle drawElement(const std::string& section, const std::string& name) const;
/**
* @brief Find a raw stored element by section and name.
* Returns an opaque pointer to the stored data (toml::table* at runtime),
* or nullptr if not found. Consumer must cast via
* static_cast<const toml::table*>(ptr) after including <toml++/toml.hpp>.
*/
const void* findElement(const std::string& section, const std::string& name) const;
// ====================================================================
// Global defaults access
// ====================================================================
const ButtonStyle& defaultButton() const { return globalButton_; }
const InputStyle& defaultInput() const { return globalInput_; }
const LabelStyle& defaultLabel() const { return globalLabel_; }
const TableStyle& defaultTable() const { return globalTable_; }
const CheckboxStyle& defaultCheckbox() const { return globalCheckbox_; }
const ComboStyle& defaultCombo() const { return globalCombo_; }
const SliderStyle& defaultSlider() const { return globalSlider_; }
const WindowStyle& defaultWindow() const { return globalWindow_; }
const SeparatorStyle& defaultSeparator() const { return globalSeparator_; }
// ====================================================================
// Theme metadata
// ====================================================================
const std::string& themeName() const { return themeName_; }
bool isDarkTheme() const { return darkTheme_; }
/**
* @brief Monotonically increasing generation counter.
* Incremented on every loadFromFile / mergeOverlay / applyIfDirty.
* Downstream caches compare against this to invalidate.
*/
uint32_t generation() const { return generation_; }
/**
* @brief Get theme-specified background image path (empty = use default)
*/
const std::string& backgroundImagePath() const { return backgroundImagePath_; }
/**
* @brief Get theme-specified logo image path (empty = use default)
*/
const std::string& logoImagePath() const { return logoImagePath_; }
private:
UISchema() = default;
~UISchema() = default;
UISchema(const UISchema&) = delete;
UISchema& operator=(const UISchema&) = delete;
// Parse top-level sections from a TOML table (passed as opaque void*)
void parseTheme(const void* dataObj);
void parseGlobals(const void* dataObj);
void parseSections(const void* dataObj, const std::string& prefix);
void parseFlatSection(const void* dataObj, const std::string& prefix);
void parseBreakpoints(const void* dataObj);
// ====================================================================
// State
// ====================================================================
bool loaded_ = false;
std::string currentPath_; ///< Path currently loaded (base or overlay for hot-reload)
std::string basePath_; ///< Path to the base layout file (ui.toml)
std::string overlayPath_; ///< Path to active overlay file (empty if base-only)
std::string embeddedTomlStr_; ///< Raw TOML string from loadFromString (for reload)
// Hot-reload
float pollInterval_ = 0.5f;
double lastPollTime_ = 0;
std::filesystem::file_time_type lastModTime_;
std::filesystem::file_time_type baseModTime_; ///< Track base file changes when overlay active
bool dirty_ = false;
bool fonts_changed_ = false;
// Theme
std::string themeName_;
bool darkTheme_ = true;
std::string backgroundImagePath_;
std::string logoImagePath_;
ColorVarResolver colorResolver_;
uint32_t generation_ = 0; ///< bumped on every load/merge/reload
// Breakpoints
BreakpointConfig breakpoints_;
// Global defaults
ButtonStyle globalButton_;
InputStyle globalInput_;
LabelStyle globalLabel_;
TableStyle globalTable_;
CheckboxStyle globalCheckbox_;
ComboStyle globalCombo_;
SliderStyle globalSlider_;
WindowStyle globalWindow_;
SeparatorStyle globalSeparator_;
// Per-section element storage
// Key: "section.elementName" → stored TOML table (accessed via std::any_cast)
// This avoids storing every possible style struct up front —
// we only parse what's actually looked up.
struct StoredElement {
std::any data; // holds toml::table at runtime
};
std::unordered_map<std::string, StoredElement> elements_;
};
// Convenience alias
inline UISchema& UI() { return UISchema::instance(); }
} // namespace schema
} // namespace ui
} // namespace dragonx