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:
375
src/ui/schema/ui_schema.h
Normal file
375
src/ui/schema/ui_schema.h
Normal 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
|
||||
Reference in New Issue
Block a user