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

2357
src/app.cpp Normal file

File diff suppressed because it is too large Load Diff

526
src/app.h Normal file
View File

@@ -0,0 +1,526 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <memory>
#include <string>
#include <functional>
#include <thread>
#include <atomic>
#include <chrono>
#include "data/wallet_state.h"
#include "ui/sidebar.h"
#include "ui/windows/console_tab.h"
#include "imgui.h"
// Forward declarations
namespace dragonx {
namespace rpc {
class RPCClient;
class RPCWorker;
struct ConnectionConfig;
}
namespace config { class Settings; }
namespace daemon { class EmbeddedDaemon; class XmrigManager; }
namespace util { class Bootstrap; class SecureVault; }
}
namespace dragonx {
/**
* @brief First-run wizard states
*/
enum class WizardPhase {
None, // No wizard — normal operation
BootstrapOffer, // Step 1: offer bootstrap download
BootstrapInProgress,// downloading / extracting
BootstrapFailed, // error with retry/skip option
Appearance, // Step 2: visual effects / performance options
EncryptOffer, // Step 3: offer wallet encryption
EncryptInProgress, // encrypting + daemon restarting
PinSetup, // Step 4: optional PIN setup (after encryption)
Done // wizard complete, launch normally
};
/**
* @brief Encrypt-wallet dialog phases (settings page flow)
*/
enum class EncryptDialogPhase {
PassphraseEntry, // Enter & confirm passphrase
Encrypting, // In-progress animation
PinSetup, // Offer PIN after successful encryption
Done // Finished — close dialog
};
/**
* @brief Main application class
*
* Manages application state, RPC connection, and coordinates UI rendering.
*/
class App {
public:
App();
~App();
// Non-copyable
App(const App&) = delete;
App& operator=(const App&) = delete;
/**
* @brief Initialize the application
* @return true if initialization succeeded
*/
bool init();
/**
* @brief Update application state (called every frame)
*/
void update();
/**
* @brief Render the application UI (called every frame)
*/
void render();
/**
* @brief Clean shutdown
*/
void shutdown();
/**
* @brief Check if app should exit (render loop can stop)
* Enforces minimum 1-second display of shutdown screen so user sees feedback.
*/
bool shouldQuit() const {
if (!quit_requested_ || !shutdown_complete_) return false;
auto elapsed = std::chrono::steady_clock::now() - shutdown_start_time_;
return elapsed >= std::chrono::seconds(1);
}
/**
* @brief Request application exit — begins async shutdown
*/
void requestQuit();
/**
* @brief Begin graceful shutdown (called on window close)
* Starts daemon stop on a background thread while UI keeps rendering.
*/
void beginShutdown();
/**
* @brief Whether we are in the shutdown phase
*/
bool isShuttingDown() const { return shutting_down_; }
/**
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
*/
void renderShutdownScreen();
/**
* @brief Render loading overlay in content area while daemon is starting/syncing
* @param contentH Height of the content area child window
*/
void renderLoadingOverlay(float contentH);
// Accessors for subsystems
rpc::RPCClient* rpc() { return rpc_.get(); }
rpc::RPCWorker* worker() { return worker_.get(); }
config::Settings* settings() { return settings_.get(); }
WalletState& state() { return state_; }
const WalletState& state() const { return state_; }
const WalletState& getWalletState() const { return state_; }
// Connection state (convenience wrappers)
bool isConnected() const { return state_.connected; }
int getBlockHeight() const { return state_.sync.blocks; }
const std::string& getConnectionStatus() const { return connection_status_; }
const std::string& getDaemonStatus() const { return daemon_status_; }
// Balance info (convenience wrappers)
double getShieldedBalance() const { return state_.shielded_balance; }
double getTransparentBalance() const { return state_.transparent_balance; }
double getTotalBalance() const { return state_.total_balance; }
double getUnconfirmedBalance() const { return state_.unconfirmed_balance; }
// Addresses
const std::vector<AddressInfo>& getZAddresses() const { return state_.z_addresses; }
const std::vector<AddressInfo>& getTAddresses() const { return state_.t_addresses; }
// Transactions
const std::vector<TransactionInfo>& getTransactions() const { return state_.transactions; }
// Mining
const MiningInfo& getMiningInfo() const { return state_.mining; }
void startMining(int threads);
void stopMining();
bool isMiningToggleInProgress() const { return mining_toggle_in_progress_.load(std::memory_order_relaxed); }
// Pool mining (xmrig)
void startPoolMining(int threads);
void stopPoolMining();
// Peers
const std::vector<PeerInfo>& getPeers() const { return state_.peers; }
const std::vector<BannedPeer>& getBannedPeers() const { return state_.bannedPeers; }
void banPeer(const std::string& ip, int duration_seconds = 86400);
void unbanPeer(const std::string& ip);
void clearBans();
// Address operations
void createNewZAddress(std::function<void(const std::string&)> callback = nullptr);
void createNewTAddress(std::function<void(const std::string&)> callback = nullptr);
// Hide/unhide addresses from the address list (persisted in settings)
void hideAddress(const std::string& addr);
void unhideAddress(const std::string& addr);
bool isAddressHidden(const std::string& addr) const;
int getHiddenAddressCount() const;
void favoriteAddress(const std::string& addr);
void unfavoriteAddress(const std::string& addr);
bool isAddressFavorite(const std::string& addr) const;
// Key export/import
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
void exportAllKeys(std::function<void(const std::string&)> callback);
void importPrivateKey(const std::string& key, std::function<void(bool, const std::string&)> callback);
// Wallet backup
void backupWallet(const std::string& destination, std::function<void(bool, const std::string&)> callback);
// Transaction operations
void sendTransaction(const std::string& from, const std::string& to,
double amount, double fee, const std::string& memo,
std::function<void(bool success, const std::string& result)> callback);
// Force refresh
void refreshNow();
void refreshMiningInfo();
void refreshPeerInfo();
void refreshMarketData();
// UI navigation
void setCurrentPage(ui::NavPage page) { current_page_ = page; }
ui::NavPage getCurrentPage() const { return current_page_; }
// Dialog triggers (used by settings page to open modal dialogs)
void showImportKeyDialog() { show_import_key_ = true; }
void showExportKeyDialog() { show_export_key_ = true; }
void showBackupDialog() { show_backup_ = true; }
void showAboutDialog() { show_about_ = true; }
// Legacy tab compat — maps int to NavPage
void setCurrentTab(int tab);
int getCurrentTab() const { return static_cast<int>(current_page_); }
// Payment URI handling
void handlePaymentURI(const std::string& uri);
bool hasPendingPayment() const { return pending_payment_valid_; }
void clearPendingPayment() { pending_payment_valid_ = false; }
std::string getPendingToAddress() const { return pending_to_address_; }
double getPendingAmount() const { return pending_amount_; }
std::string getPendingMemo() const { return pending_memo_; }
std::string getPendingLabel() const { return pending_label_; }
// Embedded daemon control
bool startEmbeddedDaemon();
void stopEmbeddedDaemon();
bool isEmbeddedDaemonRunning() const;
bool isUsingEmbeddedDaemon() const { return use_embedded_daemon_; }
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
// Get daemon memory usage in MB (uses embedded daemon handle if available,
// falls back to platform-level process scan for external daemons)
double getDaemonMemoryUsageMB() const;
// Diagnostic string describing daemon memory detection path (for debugging)
const std::string& getDaemonMemDiag() const { return daemon_mem_diag_; }
// Background gradient overlay texture
void setGradientTexture(ImTextureID tex) { gradient_tex_ = tex; }
ImTextureID getGradientTexture() const { return gradient_tex_; }
// Logo texture accessor (wallet branding icon)
ImTextureID getLogoTexture() const { return logo_tex_; }
// Coin logo texture accessor (DragonX currency icon for balance tab)
ImTextureID getCoinLogoTexture() const { return coin_logo_tex_; }
/**
* @brief Reload theme images (background gradient + logo) from new paths
* @param bgPath Path to background image override (empty = use default)
* @param logoPath Path to logo image override (empty = use default)
*/
void reloadThemeImages(const std::string& bgPath, const std::string& logoPath);
// Wizard / first-run
WizardPhase getWizardPhase() const { return wizard_phase_; }
bool isFirstRun() const;
/**
* @brief Stop daemon and re-run the setup wizard
* Called from Settings. Daemon restarts automatically when wizard completes.
*/
void restartWizard();
/**
* @brief Restart the embedded daemon (e.g. after changing debug categories)
* Shows "Restarting daemon..." in the loading overlay while the daemon cycles.
*/
void restartDaemon();
// Wallet encryption helpers
void encryptWalletWithPassphrase(const std::string& passphrase);
void unlockWallet(const std::string& passphrase, int timeout);
void lockWallet();
void changePassphrase(const std::string& oldPass, const std::string& newPass);
// Dialog triggers for encryption (from settings page)
void showEncryptDialog() {
show_encrypt_dialog_ = true;
encrypt_dialog_phase_ = EncryptDialogPhase::PassphraseEntry;
encrypt_status_.clear();
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
memset(encrypt_confirm_buf_, 0, sizeof(encrypt_confirm_buf_));
memset(enc_dlg_pin_buf_, 0, sizeof(enc_dlg_pin_buf_));
memset(enc_dlg_pin_confirm_buf_, 0, sizeof(enc_dlg_pin_confirm_buf_));
enc_dlg_saved_passphrase_.clear();
enc_dlg_pin_status_.clear();
}
void showChangePassphraseDialog() { show_change_passphrase_ = true; }
void showDecryptDialog() {
show_decrypt_dialog_ = true;
decrypt_phase_ = 0; // passphrase entry
decrypt_status_.clear();
decrypt_in_progress_ = false;
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
}
// Dialog triggers for PIN (from settings page)
void showPinSetupDialog() { show_pin_setup_ = true; pin_status_.clear(); }
void showPinChangeDialog() { show_pin_change_ = true; pin_status_.clear(); }
void showPinRemoveDialog() { show_pin_remove_ = true; pin_status_.clear(); }
bool hasPinVault() const;
/// @brief Check if RPC worker has queued results waiting to be processed
bool hasPendingRPCResults() const;
private:
// Subsystems
std::unique_ptr<rpc::RPCClient> rpc_;
std::unique_ptr<rpc::RPCWorker> worker_;
std::unique_ptr<config::Settings> settings_;
std::unique_ptr<daemon::EmbeddedDaemon> embedded_daemon_;
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
// Wallet state
WalletState state_;
// Shutdown state
std::atomic<bool> shutting_down_{false};
std::atomic<bool> shutdown_complete_{false};
std::atomic<bool> refresh_in_progress_{false};
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
std::string shutdown_status_;
std::thread shutdown_thread_;
float shutdown_timer_ = 0.0f;
std::chrono::steady_clock::time_point shutdown_start_time_;
// Daemon restart (e.g. after changing debug log categories)
std::atomic<bool> daemon_restarting_{false};
std::thread daemon_restart_thread_;
// UI State
bool quit_requested_ = false;
bool show_demo_window_ = false;
bool show_settings_ = false;
bool show_about_ = false;
bool show_import_key_ = false;
bool show_export_key_ = false;
bool show_backup_ = false;
bool show_address_book_ = false;
// Embedded daemon state
bool use_embedded_daemon_ = true;
std::string daemon_status_;
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
// Export/Import state
std::string export_result_;
char import_key_input_[512] = {0};
std::string export_address_;
std::string import_status_;
bool import_success_ = false;
std::string backup_status_;
bool backup_success_ = false;
// Connection
std::string connection_status_ = "Disconnected";
bool connection_in_progress_ = false;
float loading_timer_ = 0.0f; // spinner animation for loading overlay
// Current page (sidebar navigation)
ui::NavPage current_page_ = ui::NavPage::Overview;
ui::NavPage prev_page_ = ui::NavPage::Overview;
float page_alpha_ = 1.0f; // 0→1 fade on page switch
bool sidebar_collapsed_ = false; // true = icon-only mode
bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse
float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized)
float prev_dpi_scale_ = 0.0f; // detect DPI changes to snap sidebar width
// Background gradient overlay
ImTextureID gradient_tex_ = 0;
// Logo texture (reload on skin change / dark↔light switch)
ImTextureID logo_tex_ = 0;
int logo_w_ = 0;
int logo_h_ = 0;
bool logo_loaded_ = false;
bool logo_is_dark_variant_ = true; // tracks which variant is currently loaded
// Coin logo texture (DragonX currency icon, separate from wallet branding)
ImTextureID coin_logo_tex_ = 0;
int coin_logo_w_ = 0;
int coin_logo_h_ = 0;
bool coin_logo_loaded_ = false;
// Console tab
ui::ConsoleTab console_tab_;
// Pending payment from URI
bool pending_payment_valid_ = false;
std::string pending_to_address_;
double pending_amount_ = 0.0;
std::string pending_memo_;
std::string pending_label_;
// Timers (in seconds since last update)
float refresh_timer_ = 0.0f;
float price_timer_ = 0.0f;
float fast_refresh_timer_ = 0.0f; // For mining stats
// Refresh intervals (seconds)
static constexpr float REFRESH_INTERVAL = 5.0f;
static constexpr float PRICE_INTERVAL = 60.0f;
static constexpr float FAST_REFRESH_INTERVAL = 1.0f;
// Mining refresh guard (prevents worker queue pileup)
std::atomic<bool> mining_refresh_in_progress_{false};
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
// Mining toggle guard (prevents concurrent setgenerate calls)
std::atomic<bool> mining_toggle_in_progress_{false};
// Auto-shield guard (prevents concurrent auto-shield operations)
std::atomic<bool> auto_shield_pending_{false};
// P4: Incremental transaction cache
int last_tx_block_height_ = -1; // block height at last full tx fetch
// First-run wizard state
WizardPhase wizard_phase_ = WizardPhase::None;
std::unique_ptr<util::Bootstrap> bootstrap_;
std::string wizard_pending_passphrase_; // held until daemon connects
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
// Deferred encryption (wizard background task)
std::string deferred_encrypt_passphrase_;
std::string deferred_encrypt_pin_;
bool deferred_encrypt_pending_ = false;
// Wizard: stopping an external daemon before bootstrap
bool wizard_stopping_external_ = false;
std::string wizard_stop_status_;
std::thread wizard_stop_thread_;
// PIN vault
std::unique_ptr<util::SecureVault> vault_;
// Lock screen state
bool lock_screen_was_visible_ = false; // tracks lock→unlock transitions for auto-focus
bool lock_use_pin_ = true; // true = PIN input, false = passphrase input
char lock_pin_buf_[16] = {};
char lock_passphrase_buf_[256] = {};
std::string lock_error_msg_;
float lock_error_timer_ = 0.0f;
int lock_attempts_ = 0;
float lock_lockout_timer_ = 0.0f;
bool lock_unlock_in_progress_ = false;
// Encrypt wallet dialog state
bool show_encrypt_dialog_ = false;
bool show_change_passphrase_ = false;
EncryptDialogPhase encrypt_dialog_phase_ = EncryptDialogPhase::PassphraseEntry;
char encrypt_pass_buf_[256] = {};
char encrypt_confirm_buf_[256] = {};
char change_old_pass_buf_[256] = {};
char change_new_pass_buf_[256] = {};
char change_confirm_buf_[256] = {};
std::string encrypt_status_;
bool encrypt_in_progress_ = false;
std::string enc_dlg_saved_passphrase_; // held for PIN setup after encrypt
char enc_dlg_pin_buf_[16] = {};
char enc_dlg_pin_confirm_buf_[16] = {};
std::string enc_dlg_pin_status_;
// PIN setup dialog state (settings page)
bool show_pin_setup_ = false;
bool show_pin_change_ = false;
bool show_pin_remove_ = false;
char pin_buf_[16] = {};
char pin_confirm_buf_[16] = {};
char pin_old_buf_[16] = {};
char pin_passphrase_buf_[256] = {};
std::string pin_status_;
bool pin_in_progress_ = false;
// Decrypt wallet dialog state
bool show_decrypt_dialog_ = false;
int decrypt_phase_ = 0; // 0=passphrase, 1=working, 2=done, 3=error
char decrypt_pass_buf_[256] = {};
std::string decrypt_status_;
bool decrypt_in_progress_ = false;
// Wizard PIN setup state
char wizard_pin_buf_[16] = {};
char wizard_pin_confirm_buf_[16] = {};
std::string wizard_pin_status_;
// Auto-lock on idle
std::chrono::steady_clock::time_point last_interaction_ = std::chrono::steady_clock::now();
// Private methods - rendering
void renderStatusBar();
void renderAboutDialog();
void renderImportKeyDialog();
void renderExportKeyDialog();
void renderBackupDialog();
void renderFirstRunWizard();
void renderLockScreen();
void renderEncryptWalletDialog();
void renderDecryptWalletDialog();
void renderPinDialogs();
void renderAntivirusHelpDialog();
void processDeferredEncryption();
// Private methods - connection
void tryConnect();
void onConnected();
void onDisconnected(const std::string& reason);
// Private methods - data refresh
void refreshData();
void refreshBalance();
void refreshAddresses();
void refreshTransactions();
void refreshPrice();
void refreshWalletEncryptionState();
void checkAutoLock();
};
} // namespace dragonx

1216
src/app_network.cpp Normal file

File diff suppressed because it is too large Load Diff

1487
src/app_security.cpp Normal file

File diff suppressed because it is too large Load Diff

1274
src/app_wizard.cpp Normal file

File diff suppressed because it is too large Load Diff

242
src/config/settings.cpp Normal file
View File

@@ -0,0 +1,242 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "settings.h"
#include "version.h"
#include <nlohmann/json.hpp>
#include <fstream>
#include <filesystem>
#include "../util/logger.h"
#ifdef _WIN32
#include <shlobj.h>
#else
#include <pwd.h>
#include <unistd.h>
#endif
namespace fs = std::filesystem;
using json = nlohmann::json;
namespace dragonx {
namespace config {
Settings::Settings() = default;
Settings::~Settings() = default;
std::string Settings::getDefaultPath()
{
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
std::string dir = std::string(path) + "\\ObsidianDragon";
fs::create_directories(dir);
return dir + "\\settings.json";
}
return "settings.json";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw->pw_dir;
}
std::string dir = std::string(home) + "/Library/Application Support/ObsidianDragon";
fs::create_directories(dir);
return dir + "/settings.json";
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw->pw_dir;
}
std::string dir = std::string(home) + "/.config/ObsidianDragon";
fs::create_directories(dir);
return dir + "/settings.json";
#endif
}
bool Settings::load()
{
return load(getDefaultPath());
}
bool Settings::load(const std::string& path)
{
settings_path_ = path;
std::ifstream file(path);
if (!file.is_open()) {
return false;
}
try {
json j;
file >> j;
if (j.contains("theme")) theme_ = j["theme"].get<std::string>();
if (j.contains("save_ztxs")) save_ztxs_ = j["save_ztxs"].get<bool>();
if (j.contains("auto_shield")) auto_shield_ = j["auto_shield"].get<bool>();
if (j.contains("use_tor")) use_tor_ = j["use_tor"].get<bool>();
if (j.contains("allow_custom_fees")) allow_custom_fees_ = j["allow_custom_fees"].get<bool>();
if (j.contains("default_fee")) default_fee_ = j["default_fee"].get<double>();
if (j.contains("fetch_prices")) fetch_prices_ = j["fetch_prices"].get<bool>();
if (j.contains("tx_explorer_url")) tx_explorer_url_ = j["tx_explorer_url"].get<std::string>();
if (j.contains("address_explorer_url")) address_explorer_url_ = j["address_explorer_url"].get<std::string>();
if (j.contains("language")) language_ = j["language"].get<std::string>();
if (j.contains("skin_id")) skin_id_ = j["skin_id"].get<std::string>();
if (j.contains("acrylic_enabled")) acrylic_enabled_ = j["acrylic_enabled"].get<bool>();
if (j.contains("acrylic_quality")) acrylic_quality_ = j["acrylic_quality"].get<int>();
if (j.contains("blur_multiplier")) blur_multiplier_ = j["blur_multiplier"].get<float>();
if (j.contains("noise_opacity")) noise_opacity_ = j["noise_opacity"].get<float>();
if (j.contains("gradient_background")) gradient_background_ = j["gradient_background"].get<bool>();
// Migrate legacy reduced_transparency bool -> ui_opacity float
if (j.contains("ui_opacity")) {
ui_opacity_ = j["ui_opacity"].get<float>();
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
ui_opacity_ = 1.0f; // legacy: reduced = fully opaque
}
if (j.contains("window_opacity")) window_opacity_ = j["window_opacity"].get<float>();
if (j.contains("balance_layout")) {
if (j["balance_layout"].is_string())
balance_layout_ = j["balance_layout"].get<std::string>();
else if (j["balance_layout"].is_number_integer()) {
// Legacy migration: convert old int index to string ID
static const char* legacyIds[] = {
"classic","donut","consolidated","dashboard",
"vertical-stack","shield","timeline","two-row","minimal"
};
int idx = j["balance_layout"].get<int>();
if (idx >= 0 && idx < 9) balance_layout_ = legacyIds[idx];
}
}
if (j.contains("scanline_enabled")) scanline_enabled_ = j["scanline_enabled"].get<bool>();
if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) {
hidden_addresses_.clear();
for (const auto& a : j["hidden_addresses"])
if (a.is_string()) hidden_addresses_.insert(a.get<std::string>());
}
if (j.contains("favorite_addresses") && j["favorite_addresses"].is_array()) {
favorite_addresses_.clear();
for (const auto& a : j["favorite_addresses"])
if (a.is_string()) favorite_addresses_.insert(a.get<std::string>());
}
if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get<bool>();
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get<bool>();
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
debug_categories_.clear();
for (const auto& c : j["debug_categories"])
if (c.is_string()) debug_categories_.insert(c.get<std::string>());
}
if (j.contains("theme_effects_enabled")) theme_effects_enabled_ = j["theme_effects_enabled"].get<bool>();
if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get<bool>();
if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get<std::string>();
if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get<std::string>();
if (j.contains("pool_url")) pool_url_ = j["pool_url"].get<std::string>();
if (j.contains("pool_algo")) pool_algo_ = j["pool_algo"].get<std::string>();
if (j.contains("pool_worker")) pool_worker_ = j["pool_worker"].get<std::string>();
if (j.contains("pool_threads")) pool_threads_ = j["pool_threads"].get<int>();
if (j.contains("pool_tls")) pool_tls_ = j["pool_tls"].get<bool>();
if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get<bool>();
if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get<bool>();
if (j.contains("window_width") && j["window_width"].is_number_integer())
window_width_ = j["window_width"].get<int>();
if (j.contains("window_height") && j["window_height"].is_number_integer())
window_height_ = j["window_height"].get<int>();
return true;
} catch (const std::exception& e) {
DEBUG_LOGF("Failed to parse settings: %s\n", e.what());
return false;
}
}
bool Settings::save()
{
if (settings_path_.empty()) {
settings_path_ = getDefaultPath();
}
return save(settings_path_);
}
bool Settings::save(const std::string& path)
{
json j;
j["theme"] = theme_;
j["save_ztxs"] = save_ztxs_;
j["auto_shield"] = auto_shield_;
j["use_tor"] = use_tor_;
j["allow_custom_fees"] = allow_custom_fees_;
j["default_fee"] = default_fee_;
j["fetch_prices"] = fetch_prices_;
j["tx_explorer_url"] = tx_explorer_url_;
j["address_explorer_url"] = address_explorer_url_;
j["language"] = language_;
j["skin_id"] = skin_id_;
j["acrylic_enabled"] = acrylic_enabled_;
j["acrylic_quality"] = acrylic_quality_;
j["blur_multiplier"] = blur_multiplier_;
j["noise_opacity"] = noise_opacity_;
j["gradient_background"] = gradient_background_;
j["ui_opacity"] = ui_opacity_;
j["window_opacity"] = window_opacity_;
j["balance_layout"] = balance_layout_; // saved as string ID
j["scanline_enabled"] = scanline_enabled_;
j["hidden_addresses"] = json::array();
for (const auto& addr : hidden_addresses_)
j["hidden_addresses"].push_back(addr);
j["favorite_addresses"] = json::array();
for (const auto& addr : favorite_addresses_)
j["favorite_addresses"].push_back(addr);
j["wizard_completed"] = wizard_completed_;
j["auto_lock_timeout"] = auto_lock_timeout_;
j["unlock_duration"] = unlock_duration_;
j["pin_enabled"] = pin_enabled_;
j["keep_daemon_running"] = keep_daemon_running_;
j["stop_external_daemon"] = stop_external_daemon_;
j["debug_categories"] = json::array();
for (const auto& cat : debug_categories_)
j["debug_categories"].push_back(cat);
j["theme_effects_enabled"] = theme_effects_enabled_;
j["low_spec_mode"] = low_spec_mode_;
j["selected_exchange"] = selected_exchange_;
j["selected_pair"] = selected_pair_;
j["pool_url"] = pool_url_;
j["pool_algo"] = pool_algo_;
j["pool_worker"] = pool_worker_;
j["pool_threads"] = pool_threads_;
j["pool_tls"] = pool_tls_;
j["pool_hugepages"] = pool_hugepages_;
j["pool_mode"] = pool_mode_;
if (window_width_ > 0 && window_height_ > 0) {
j["window_width"] = window_width_;
j["window_height"] = window_height_;
}
try {
// Ensure directory exists
fs::path p(path);
fs::create_directories(p.parent_path());
std::ofstream file(path);
if (!file.is_open()) {
return false;
}
file << j.dump(4);
return true;
} catch (const std::exception& e) {
DEBUG_LOGF("Failed to save settings: %s\n", e.what());
return false;
}
}
} // namespace config
} // namespace dragonx

263
src/config/settings.h Normal file
View File

@@ -0,0 +1,263 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <set>
namespace dragonx {
namespace config {
/**
* @brief Application settings manager
*
* Handles loading and saving of user preferences.
*/
class Settings {
public:
Settings();
~Settings();
/**
* @brief Load settings from default location
* @return true if loaded successfully
*/
bool load();
/**
* @brief Load settings from specific path
* @param path Path to settings file
* @return true if loaded successfully
*/
bool load(const std::string& path);
/**
* @brief Save settings to default location
* @return true if saved successfully
*/
bool save();
/**
* @brief Save settings to specific path
* @param path Path to settings file
* @return true if saved successfully
*/
bool save(const std::string& path);
/**
* @brief Get default settings file path
*/
static std::string getDefaultPath();
// Theme
std::string getTheme() const { return theme_; }
void setTheme(const std::string& theme) { theme_ = theme; }
// Unified skin
std::string getSkinId() const { return skin_id_; }
void setSkinId(const std::string& id) { skin_id_ = id; }
// Privacy
bool getSaveZtxs() const { return save_ztxs_; }
void setSaveZtxs(bool save) { save_ztxs_ = save; }
bool getAutoShield() const { return auto_shield_; }
void setAutoShield(bool shield) { auto_shield_ = shield; }
bool getUseTor() const { return use_tor_; }
void setUseTor(bool tor) { use_tor_ = tor; }
// Fees
bool getAllowCustomFees() const { return allow_custom_fees_; }
void setAllowCustomFees(bool allow) { allow_custom_fees_ = allow; }
double getDefaultFee() const { return default_fee_; }
void setDefaultFee(double fee) { default_fee_ = fee; }
// Price
bool getFetchPrices() const { return fetch_prices_; }
void setFetchPrices(bool fetch) { fetch_prices_ = fetch; }
// Explorer URLs
std::string getTxExplorerUrl() const { return tx_explorer_url_; }
void setTxExplorerUrl(const std::string& url) { tx_explorer_url_ = url; }
std::string getAddressExplorerUrl() const { return address_explorer_url_; }
void setAddressExplorerUrl(const std::string& url) { address_explorer_url_ = url; }
// Language
std::string getLanguage() const { return language_; }
void setLanguage(const std::string& lang) { language_ = lang; }
// Visual effects
bool getAcrylicEnabled() const { return acrylic_enabled_; }
void setAcrylicEnabled(bool v) { acrylic_enabled_ = v; }
int getAcrylicQuality() const { return acrylic_quality_; }
void setAcrylicQuality(int v) { acrylic_quality_ = v; }
float getBlurMultiplier() const { return blur_multiplier_; }
void setBlurMultiplier(float v) { blur_multiplier_ = v; }
bool getReducedTransparency() const { return ui_opacity_ >= 0.99f; }
void setReducedTransparency(bool v) { if (v) ui_opacity_ = 1.0f; }
float getUIOpacity() const { return ui_opacity_; }
void setUIOpacity(float v) { ui_opacity_ = std::max(0.3f, std::min(1.0f, v)); }
float getWindowOpacity() const { return window_opacity_; }
void setWindowOpacity(float v) { window_opacity_ = std::max(0.3f, std::min(1.0f, v)); }
float getNoiseOpacity() const { return noise_opacity_; }
void setNoiseOpacity(float v) { noise_opacity_ = v; }
// Gradient background mode (use gradient variant of theme background)
bool getGradientBackground() const { return gradient_background_; }
void setGradientBackground(bool v) { gradient_background_ = v; }
// Balance layout (string ID, e.g. "classic", "donut")
std::string getBalanceLayout() const { return balance_layout_; }
void setBalanceLayout(const std::string& v) { balance_layout_ = v; }
// Console scanline effect
bool getScanlineEnabled() const { return scanline_enabled_; }
void setScanlineEnabled(bool v) { scanline_enabled_ = v; }
// Hidden addresses (addresses hidden from the UI by the user)
const std::set<std::string>& getHiddenAddresses() const { return hidden_addresses_; }
bool isAddressHidden(const std::string& addr) const { return hidden_addresses_.count(addr) > 0; }
void hideAddress(const std::string& addr) { hidden_addresses_.insert(addr); }
void unhideAddress(const std::string& addr) { hidden_addresses_.erase(addr); }
int getHiddenAddressCount() const { return (int)hidden_addresses_.size(); }
// Favorite addresses (pinned to top of address list)
const std::set<std::string>& getFavoriteAddresses() const { return favorite_addresses_; }
bool isAddressFavorite(const std::string& addr) const { return favorite_addresses_.count(addr) > 0; }
void favoriteAddress(const std::string& addr) { favorite_addresses_.insert(addr); }
void unfavoriteAddress(const std::string& addr) { favorite_addresses_.erase(addr); }
int getFavoriteAddressCount() const { return (int)favorite_addresses_.size(); }
// First-run wizard
bool getWizardCompleted() const { return wizard_completed_; }
void setWizardCompleted(bool v) { wizard_completed_ = v; }
// Security — auto-lock timeout (seconds; 0 = disabled)
int getAutoLockTimeout() const { return auto_lock_timeout_; }
void setAutoLockTimeout(int seconds) { auto_lock_timeout_ = seconds; }
// Security — unlock duration (seconds) for walletpassphrase
int getUnlockDuration() const { return unlock_duration_; }
void setUnlockDuration(int seconds) { unlock_duration_ = seconds; }
// Security — PIN unlock enabled
bool getPinEnabled() const { return pin_enabled_; }
void setPinEnabled(bool v) { pin_enabled_ = v; }
// Daemon — keep running in background when closing the app
bool getKeepDaemonRunning() const { return keep_daemon_running_; }
void setKeepDaemonRunning(bool v) { keep_daemon_running_ = v; }
// Daemon — stop externally-started daemons on exit (default: false)
bool getStopExternalDaemon() const { return stop_external_daemon_; }
void setStopExternalDaemon(bool v) { stop_external_daemon_ = v; }
// Daemon — debug logging categories
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
bool hasDebugCategory(const std::string& cat) const { return debug_categories_.count(cat) > 0; }
void toggleDebugCategory(const std::string& cat) {
if (debug_categories_.count(cat)) debug_categories_.erase(cat);
else debug_categories_.insert(cat);
}
// Visual effects — animated theme effects (hue cycling, shimmer, glow, etc.)
bool getThemeEffectsEnabled() const { return theme_effects_enabled_; }
void setThemeEffectsEnabled(bool v) { theme_effects_enabled_ = v; }
// Low-spec mode — disables heavy visual effects for better performance
bool getLowSpecMode() const { return low_spec_mode_; }
void setLowSpecMode(bool v) { low_spec_mode_ = v; }
// Market — last selected exchange + pair
std::string getSelectedExchange() const { return selected_exchange_; }
void setSelectedExchange(const std::string& v) { selected_exchange_ = v; }
std::string getSelectedPair() const { return selected_pair_; }
void setSelectedPair(const std::string& v) { selected_pair_ = v; }
// Pool mining
std::string getPoolUrl() const { return pool_url_; }
void setPoolUrl(const std::string& v) { pool_url_ = v; }
std::string getPoolAlgo() const { return pool_algo_; }
void setPoolAlgo(const std::string& v) { pool_algo_ = v; }
std::string getPoolWorker() const { return pool_worker_; }
void setPoolWorker(const std::string& v) { pool_worker_ = v; }
int getPoolThreads() const { return pool_threads_; }
void setPoolThreads(int v) { pool_threads_ = v; }
bool getPoolTls() const { return pool_tls_; }
void setPoolTls(bool v) { pool_tls_ = v; }
bool getPoolHugepages() const { return pool_hugepages_; }
void setPoolHugepages(bool v) { pool_hugepages_ = v; }
bool getPoolMode() const { return pool_mode_; }
void setPoolMode(bool v) { pool_mode_ = v; }
// Window size persistence (logical pixels at 1x scale)
int getWindowWidth() const { return window_width_; }
int getWindowHeight() const { return window_height_; }
void setWindowSize(int w, int h) { window_width_ = w; window_height_ = h; }
private:
std::string settings_path_;
// Settings values
std::string theme_ = "dragonx";
std::string skin_id_ = "dragonx";
bool save_ztxs_ = true;
bool auto_shield_ = true;
bool use_tor_ = false;
bool allow_custom_fees_ = false;
double default_fee_ = 0.0001;
bool fetch_prices_ = true;
std::string tx_explorer_url_ = "https://explorer.dragonx.is/tx/";
std::string address_explorer_url_ = "https://explorer.dragonx.is/address/";
std::string language_ = "en";
bool acrylic_enabled_ = true;
int acrylic_quality_ = 2;
float blur_multiplier_ = 0.10f;
float noise_opacity_ = 0.5f;
bool gradient_background_ = false;
float ui_opacity_ = 0.50f; // Card/sidebar opacity (0.31.0, 1.0 = opaque)
float window_opacity_ = 0.75f; // Background alpha (0.31.0, <1 = desktop visible)
std::string balance_layout_ = "classic";
bool scanline_enabled_ = true;
std::set<std::string> hidden_addresses_;
std::set<std::string> favorite_addresses_;
bool wizard_completed_ = false;
int auto_lock_timeout_ = 900; // 15 minutes
int unlock_duration_ = 600; // 10 minutes
bool pin_enabled_ = false;
bool keep_daemon_running_ = false;
bool stop_external_daemon_ = false;
std::set<std::string> debug_categories_;
bool theme_effects_enabled_ = true;
bool low_spec_mode_ = false;
std::string selected_exchange_ = "TradeOgre";
std::string selected_pair_ = "DRGX/BTC";
// Pool mining
std::string pool_url_ = "pool.dragonx.is";
std::string pool_algo_ = "rx/hush";
std::string pool_worker_ = "x";
int pool_threads_ = 0;
bool pool_tls_ = false;
bool pool_hugepages_ = true;
bool pool_mode_ = false; // false=solo, true=pool
// Window size (logical pixels at 1x scale; 0 = use default 1200×775)
int window_width_ = 0;
int window_height_ = 0;
};
} // namespace config
} // namespace dragonx

28
src/config/version.h Normal file
View File

@@ -0,0 +1,28 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#define DRAGONX_VERSION "1.0.0"
#define DRAGONX_VERSION_MAJOR 1
#define DRAGONX_VERSION_MINOR 0
#define DRAGONX_VERSION_PATCH 0
#define DRAGONX_APP_NAME "ObsidianDragon"
#define DRAGONX_ORG_NAME "Hush"
// Default RPC settings
#define DRAGONX_DEFAULT_RPC_HOST "127.0.0.1"
#define DRAGONX_DEFAULT_RPC_PORT "21769"
// Coin parameters
#define DRAGONX_TICKER "DRGX"
#define DRAGONX_COIN_NAME "DragonX"
#define DRAGONX_URI_SCHEME "drgx"
#define DRAGONX_ZATOSHI_PER_COIN 100000000
#define DRAGONX_DEFAULT_FEE 0.0001
// Config file names
#define DRAGONX_CONF_FILENAME "DRAGONX.conf"
#define DRAGONX_WALLET_FILENAME "wallet.dat"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,195 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <functional>
#include <memory>
#include <atomic>
#include <thread>
#include <mutex>
#include <set>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#endif
namespace dragonx {
namespace daemon {
/**
* @brief Manages the embedded dragonxd process
*
* Handles starting, stopping, and monitoring the dragonxd daemon process.
* Ports functionality from Qt version's connection.cpp.
*/
class EmbeddedDaemon {
public:
enum class State {
Stopped,
Starting,
Running,
Stopping,
Error
};
using StateCallback = std::function<void(State, const std::string&)>;
EmbeddedDaemon();
~EmbeddedDaemon();
// Non-copyable
EmbeddedDaemon(const EmbeddedDaemon&) = delete;
EmbeddedDaemon& operator=(const EmbeddedDaemon&) = delete;
/**
* @brief Start the embedded dragonxd daemon
* @param binary_path Path to dragonxd binary (auto-detect if empty)
* @return true if process started successfully
*/
bool start(const std::string& binary_path = "");
/**
* @brief Stop the running daemon
* @param wait_ms Maximum milliseconds to wait for graceful shutdown
*/
void stop(int wait_ms = 5000);
/**
* @brief Check if daemon process is running
*/
bool isRunning() const;
/**
* @brief Get current daemon state
*/
State getState() const { return state_; }
/**
* @brief Get daemon process memory usage (RSS) in MB
* @return Memory usage in MB, or 0 if unavailable
*/
double getMemoryUsageMB() const;
/**
* @brief Get last error message
*/
const std::string& getLastError() const { return last_error_; }
/**
* @brief Get dragonxd process output (thread-safe copy)
*/
std::string getOutput() const {
std::lock_guard<std::mutex> lk(output_mutex_);
return process_output_;
}
/**
* @brief Get new output since a given offset (thread-safe).
* Returns the new text and updates offset to the current size.
*/
std::string getOutputSince(size_t& offset) const {
std::lock_guard<std::mutex> lk(output_mutex_);
if (offset >= process_output_.size()) {
offset = process_output_.size();
return {};
}
std::string result = process_output_.substr(offset);
offset = process_output_.size();
return result;
}
/**
* @brief Get current output size (thread-safe, no copy)
*/
size_t getOutputSize() const {
std::lock_guard<std::mutex> lk(output_mutex_);
return process_output_.size();
}
/**
* @brief Get last N lines of daemon output (thread-safe snapshot)
*/
std::vector<std::string> getRecentLines(int maxLines = 8) const;
/**
* @brief Whether start() detected an existing daemon on the RPC port.
* When true the wallet should connect to it instead of showing an error.
*/
bool externalDaemonDetected() const { return external_daemon_detected_; }
/**
* @brief Set callback for state changes
*/
void setStateCallback(StateCallback cb) { state_callback_ = cb; }
/**
* @brief Find dragonxd binary in standard locations
* @return Path to binary, or empty if not found
*/
static std::string findDaemonBinary();
/**
* @brief Check whether anything is listening on the default RPC port.
* Useful for detecting an externally-started daemon.
*/
static bool isRpcPortInUse();
/**
* @brief Get the chain parameters for dragonxd
* @return Command line arguments for dragonx chain
*/
static std::vector<std::string> getChainParams();
/**
* @brief Set debug logging categories (appended as -debug= flags on next start)
*/
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
/** Get number of consecutive daemon crashes (resets on successful start or manual reset) */
int getCrashCount() const { return crash_count_.load(); }
/** Reset crash counter (call on successful connection or manual restart) */
void resetCrashCount() { crash_count_ = 0; }
private:
void setState(State s, const std::string& message = "");
void monitorProcess();
void drainOutput(); // read any pending stdout into process_output_
void appendOutput(const char* data, size_t len); // append + trim (caller holds output_mutex_)
bool startProcess(const std::string& binary_path, const std::vector<std::string>& args);
std::atomic<State> state_{State::Stopped};
std::atomic<bool> external_daemon_detected_{false};
std::string last_error_;
mutable std::mutex output_mutex_; // protects process_output_
std::string process_output_;
StateCallback state_callback_;
// Process handle
#ifdef _WIN32
HANDLE process_handle_ = nullptr;
HANDLE stdout_read_ = nullptr; // pipe handle (Linux-style pipe, unused when tailing debug.log)
std::string debug_log_path_; // path to daemon's debug.log for output tailing
size_t debug_log_offset_ = 0; // read offset into debug.log
std::string launch_cmd_; // full command line used to launch daemon (for diagnostics)
std::string launch_binary_; // binary path used (for diagnostics)
std::string launch_workdir_; // working directory used (for diagnostics)
#else
pid_t process_pid_ = 0;
int stdout_fd_ = -1;
#endif
std::thread monitor_thread_;
std::atomic<bool> should_stop_{false};
std::set<std::string> debug_categories_;
std::atomic<int> crash_count_{0}; // consecutive crash counter
};
} // namespace daemon
} // namespace dragonx

View File

@@ -0,0 +1,715 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "xmrig_manager.h"
#include "../resources/embedded_resources.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <filesystem>
#include <random>
#include <sstream>
#include <algorithm>
#include <chrono>
#include <nlohmann/json.hpp>
#include <curl/curl.h>
#include "../util/logger.h"
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <shlobj.h>
#include <psapi.h>
#else
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <errno.h>
#endif
namespace fs = std::filesystem;
using json = nlohmann::json;
namespace dragonx {
namespace daemon {
// ============================================================================
// Helpers
// ============================================================================
static std::string randomHexToken(int bytes = 16) {
static const char hex[] = "0123456789abcdef";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(0, 15);
std::string out;
out.reserve(bytes * 2);
for (int i = 0; i < bytes * 2; ++i)
out.push_back(hex[dist(gen)]);
return out;
}
static int randomPort() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(18000, 18999);
return dist(gen);
}
static std::string getConfigDir() {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
return std::string(path) + "\\ObsidianDragon";
}
return ".";
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw ? pw->pw_dir : "/tmp";
}
return std::string(home) + "/.config/ObsidianDragon";
#endif
}
// libcurl write callback
static size_t curlWriteCb(void* ptr, size_t sz, size_t n, void* userdata) {
auto* s = static_cast<std::string*>(userdata);
s->append(static_cast<char*>(ptr), sz * n);
return sz * n;
}
// ============================================================================
// Lifecycle
// ============================================================================
XmrigManager::XmrigManager() = default;
XmrigManager::~XmrigManager() {
if (isRunning()) {
stop(3000);
}
}
// ============================================================================
// Binary discovery
// ============================================================================
std::string XmrigManager::findXmrigBinary() {
// Use the embedded_resources system (same pattern as daemon)
std::string path = resources::getXmrigPath();
if (!path.empty() && fs::exists(path)) {
return path;
}
// Fallback: system PATH
#ifdef _WIN32
FILE* f = _popen("where xmrig.exe 2>nul", "r");
#else
FILE* f = popen("which xmrig 2>/dev/null", "r");
#endif
if (f) {
char line[512];
if (fgets(line, sizeof(line), f)) {
std::string s(line);
while (!s.empty() && (s.back() == '\n' || s.back() == '\r'))
s.pop_back();
if (!s.empty() && fs::exists(s)) {
#ifdef _WIN32
_pclose(f);
#else
pclose(f);
#endif
return s;
}
}
#ifdef _WIN32
_pclose(f);
#else
pclose(f);
#endif
}
return {};
}
// ============================================================================
// Config generation
// ============================================================================
bool XmrigManager::generateConfig(const Config& cfg, const std::string& outPath) {
api_port_ = randomPort();
api_token_ = randomHexToken(16);
int hw = (int)std::thread::hardware_concurrency();
if (hw < 1) hw = 1;
// Use explicit thread count (not just a hint)
threads_ = (cfg.threads > 0) ? cfg.threads : std::max(1, hw / 2);
if (threads_ > hw) threads_ = hw;
json j;
j["autosave"] = false;
j["background"] = false;
j["colors"] = false;
j["http"] = {
{"enabled", true},
{"host", "127.0.0.1"},
{"port", api_port_},
{"access-token", api_token_},
{"restricted", true}
};
j["randomx"] = {
{"init", -1},
{"mode", "auto"},
{"1gb-pages", false},
{"numa", true},
{"scratchpad_prefetch_mode", 1}
};
j["cpu"] = {
{"enabled", true},
{"huge-pages", cfg.hugepages},
{"max-threads-hint", 100}, // Use 100% of allotted threads
{"priority", 0}, // Idle priority (lowest) - prevents UI lag
{"yield", true} // Yield to other processes
};
j["pools"] = json::array({
{
{"algo", cfg.algo},
{"url", cfg.pool_url},
{"user", cfg.wallet_address},
{"pass", cfg.worker_name},
{"keepalive", true},
{"tls", cfg.tls}
}
});
j["donate-level"] = 0;
j["print-time"] = 10;
j["retries"] = 5;
j["retry-pause"] = 5;
try {
fs::create_directories(fs::path(outPath).parent_path());
std::ofstream ofs(outPath);
if (!ofs.is_open()) {
last_error_ = "Cannot write xmrig config: " + outPath;
DEBUG_LOGF("[ERROR] XmrigManager: %s\n", last_error_.c_str());
return false;
}
ofs << j.dump(4);
ofs.close();
#ifndef _WIN32
// 0600 permissions — only owner can read/write
chmod(outPath.c_str(), 0600);
#endif
return true;
} catch (const std::exception& e) {
last_error_ = std::string("Config write error: ") + e.what();
DEBUG_LOGF("[ERROR] XmrigManager: %s\n", last_error_.c_str());
return false;
}
}
// ============================================================================
// start / stop
// ============================================================================
bool XmrigManager::start(const Config& cfg) {
if (state_ == State::Running || state_ == State::Starting) {
last_error_ = "Already running";
DEBUG_LOGF("[WARN] XmrigManager: %s\n", last_error_.c_str());
return false;
}
state_ = State::Starting;
should_stop_ = false;
last_error_.clear();
{
std::lock_guard<std::mutex> lk(output_mutex_);
process_output_.clear();
}
stats_ = PoolStats{};
// Find binary
std::string binary = findXmrigBinary();
if (binary.empty()) {
last_error_ = "xmrig binary not found";
state_ = State::Error;
DEBUG_LOGF("[ERROR] XmrigManager: xmrig binary not found\n");
return false;
}
DEBUG_LOGF("[INFO] XmrigManager: found binary at %s\n", binary.c_str());
// Generate config
std::string cfgDir = getConfigDir();
#ifdef _WIN32
std::string cfgPath = cfgDir + "\\xmrig-pool.json";
#else
std::string cfgPath = cfgDir + "/xmrig-pool.json";
#endif
if (!generateConfig(cfg, cfgPath)) {
state_ = State::Error;
DEBUG_LOGF("[ERROR] XmrigManager: failed to generate config\n");
return false;
}
DEBUG_LOGF("[INFO] XmrigManager: config written to %s (API port %d, threads %d)\n",
cfgPath.c_str(), api_port_, threads_);
// Spawn process
if (!startProcess(binary, cfgPath, threads_)) {
state_ = State::Error;
return false;
}
// Start monitor thread
monitor_thread_ = std::thread(&XmrigManager::monitorProcess, this);
state_ = State::Running;
DEBUG_LOGF("[INFO] XmrigManager: started\n");
return true;
}
void XmrigManager::stop(int wait_ms) {
if (state_ == State::Stopped || state_ == State::Stopping)
return;
state_ = State::Stopping;
should_stop_ = true;
#ifdef _WIN32
if (process_handle_) {
// Try graceful termination first
TerminateProcess(process_handle_, 0);
WaitForSingleObject(process_handle_, wait_ms);
CloseHandle(process_handle_);
process_handle_ = nullptr;
}
if (stdout_read_) {
CloseHandle(stdout_read_);
stdout_read_ = nullptr;
}
#else
if (process_pid_ > 0) {
// Send SIGTERM to process group
kill(-process_pid_, SIGTERM);
// Poll-wait with drain
auto deadline = std::chrono::steady_clock::now()
+ std::chrono::milliseconds(wait_ms);
while (std::chrono::steady_clock::now() < deadline) {
drainOutput();
int status = 0;
pid_t ret = waitpid(process_pid_, &status, WNOHANG);
if (ret == process_pid_ || ret < 0) break;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
// If still alive, SIGKILL
if (kill(process_pid_, 0) == 0) {
kill(-process_pid_, SIGKILL);
waitpid(process_pid_, nullptr, 0);
}
process_pid_ = 0;
}
if (stdout_fd_ >= 0) {
close(stdout_fd_);
stdout_fd_ = -1;
}
#endif
if (monitor_thread_.joinable())
monitor_thread_.join();
state_ = State::Stopped;
DEBUG_LOGF("[INFO] XmrigManager: stopped\n");
}
// ============================================================================
// Process spawning — platform-specific
// ============================================================================
#ifdef _WIN32
bool XmrigManager::startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads) {
SECURITY_ATTRIBUTES sa{};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
HANDLE hRead = nullptr, hWrite = nullptr;
if (!CreatePipe(&hRead, &hWrite, &sa, 0)) {
last_error_ = "CreatePipe failed";
DEBUG_LOGF("[ERROR] XmrigManager: %s\n", last_error_.c_str());
return false;
}
SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0);
// Use explicit --threads to enforce exact thread count (not just a hint)
std::string cmdLine = "\"" + xmrigPath + "\" --config=\"" + cfgPath + "\" --threads=" + std::to_string(threads);
STARTUPINFOA si{};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.hStdOutput = hWrite;
si.hStdError = hWrite;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi{};
BOOL ok = CreateProcessA(
nullptr, const_cast<char*>(cmdLine.c_str()),
nullptr, nullptr, TRUE,
CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP | IDLE_PRIORITY_CLASS,
nullptr, nullptr, &si, &pi
);
CloseHandle(hWrite);
if (!ok) {
CloseHandle(hRead);
DWORD err = GetLastError();
char errBuf[256];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, err, 0, errBuf, sizeof(errBuf), NULL);
last_error_ = "CreateProcess failed for xmrig (error " + std::to_string(err) + "): " + errBuf;
DEBUG_LOGF("[ERROR] XmrigManager: %s\nCommand: %s\n", last_error_.c_str(), cmdLine.c_str());
return false;
}
process_handle_ = pi.hProcess;
stdout_read_ = hRead;
CloseHandle(pi.hThread);
return true;
}
bool XmrigManager::isRunning() const {
if (!process_handle_) return false;
DWORD exit_code;
GetExitCodeProcess(process_handle_, &exit_code);
return exit_code == STILL_ACTIVE;
}
double XmrigManager::getMemoryUsageMB() const {
if (!process_handle_) return 0.0;
PROCESS_MEMORY_COUNTERS pmc;
ZeroMemory(&pmc, sizeof(pmc));
pmc.cb = sizeof(pmc);
if (GetProcessMemoryInfo(process_handle_, &pmc, sizeof(pmc))) {
return static_cast<double>(pmc.WorkingSetSize) / (1024.0 * 1024.0);
}
return 0.0;
}
void XmrigManager::drainOutput() {
if (!stdout_read_) return;
char buf[4096];
DWORD avail = 0;
while (PeekNamedPipe(stdout_read_, nullptr, 0, nullptr, &avail, nullptr) && avail > 0) {
DWORD nread = 0;
DWORD toRead = std::min(avail, (DWORD)sizeof(buf));
if (ReadFile(stdout_read_, buf, toRead, &nread, nullptr) && nread > 0) {
std::lock_guard<std::mutex> lk(output_mutex_);
appendOutput(buf, nread);
} else {
break;
}
}
}
#else // ---- POSIX ----
bool XmrigManager::startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads) {
int pipefd[2];
if (pipe(pipefd) != 0) {
last_error_ = "pipe() failed";
DEBUG_LOGF("[ERROR] XmrigManager: %s\n", last_error_.c_str());
return false;
}
pid_t pid = fork();
if (pid < 0) {
last_error_ = "fork() failed";
DEBUG_LOGF("[ERROR] XmrigManager: %s\n", last_error_.c_str());
close(pipefd[0]);
close(pipefd[1]);
return false;
}
if (pid == 0) {
// Child
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
// New process group so we can kill the whole group
setpgid(0, 0);
// Lowest priority to reduce UI lag (nice value 19 = minimum priority)
if (nice(19) == -1 && errno != 0) { /* ignore failure */ }
std::string cfgArg = "--config=" + cfgPath;
std::string threadsArg = "--threads=" + std::to_string(threads);
const char* argv[] = { xmrigPath.c_str(), cfgArg.c_str(), threadsArg.c_str(), nullptr };
execv(xmrigPath.c_str(), const_cast<char* const*>(argv));
_exit(127);
}
// Parent
close(pipefd[1]);
process_pid_ = pid;
stdout_fd_ = pipefd[0];
// Non-blocking reads
int flags = fcntl(stdout_fd_, F_GETFL, 0);
fcntl(stdout_fd_, F_SETFL, flags | O_NONBLOCK);
return true;
}
bool XmrigManager::isRunning() const {
// Use state_ instead of waitpid() to avoid races with moitorProcess
// which also calls waitpid. state_ is atomic and always correct.
State s = state_.load(std::memory_order_relaxed);
return (s == State::Running || s == State::Starting);
}
double XmrigManager::getMemoryUsageMB() const {
if (process_pid_ <= 0) return 0.0;
char path[64];
snprintf(path, sizeof(path), "/proc/%d/statm", process_pid_);
FILE* fp = fopen(path, "r");
if (!fp) return 0.0;
long dummy = 0, pages = 0;
// statm: size resident shared text lib data dt
// We want resident (2nd field)
if (fscanf(fp, "%ld %ld", &dummy, &pages) != 2) pages = 0;
fclose(fp);
long pageSize = sysconf(_SC_PAGESIZE);
return static_cast<double>(pages * pageSize) / (1024.0 * 1024.0);
}
void XmrigManager::drainOutput() {
if (stdout_fd_ < 0) return;
char buf[4096];
while (true) {
ssize_t n = read(stdout_fd_, buf, sizeof(buf));
if (n > 0) {
std::lock_guard<std::mutex> lk(output_mutex_);
appendOutput(buf, (size_t)n);
} else {
break;
}
}
}
#endif // platform
// ============================================================================
// Output management
// ============================================================================
void XmrigManager::appendOutput(const char* data, size_t len) {
// Caller must hold output_mutex_
static constexpr size_t MAX_OUTPUT = 1024 * 1024; // 1 MB cap
process_output_.append(data, len);
if (process_output_.size() > MAX_OUTPUT) {
// Trim from the front at a newline boundary
size_t cut = process_output_.size() - MAX_OUTPUT;
auto pos = process_output_.find('\n', cut);
if (pos != std::string::npos)
process_output_.erase(0, pos + 1);
else
process_output_.erase(0, cut);
}
}
std::vector<std::string> XmrigManager::getRecentLines(int maxLines) const {
std::lock_guard<std::mutex> lk(output_mutex_);
std::vector<std::string> lines;
if (process_output_.empty()) return lines;
// Walk backwards collecting lines
size_t end = process_output_.size();
while ((int)lines.size() < maxLines && end > 0) {
size_t nl = process_output_.rfind('\n', end - 1);
if (nl == std::string::npos) {
lines.push_back(process_output_.substr(0, end));
break;
}
if (nl + 1 < end)
lines.push_back(process_output_.substr(nl + 1, end - nl - 1));
end = nl;
}
std::reverse(lines.begin(), lines.end());
// Remove empty trailing line
while (!lines.empty() && lines.back().empty())
lines.pop_back();
return lines;
}
// ============================================================================
// Monitor thread
// ============================================================================
void XmrigManager::monitorProcess() {
// Wait a few seconds for xmrig HTTP API to start up before first poll
for (int i = 0; i < 30 && !should_stop_; i++) {
drainOutput();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
int poll_counter = 0;
while (!should_stop_) {
drainOutput();
// Check if the child process is still alive (monitor thread only)
#ifdef _WIN32
if (process_handle_) {
DWORD exitCode = 0;
if (GetExitCodeProcess(process_handle_, &exitCode) && exitCode != STILL_ACTIVE) {
DEBUG_LOGF("[ERROR] XmrigManager: process exited (code %lu)\n", exitCode);
state_ = State::Error;
last_error_ = "xmrig process exited unexpectedly";
break;
}
}
#else
if (process_pid_ > 0) {
int status = 0;
pid_t ret = waitpid(process_pid_, &status, WNOHANG);
if (ret == process_pid_ || ret < 0) {
DEBUG_LOGF("[ERROR] XmrigManager: process exited (waitpid=%d)\n", ret);
state_ = State::Error;
last_error_ = "xmrig process exited unexpectedly";
break;
}
}
#endif
// Poll HTTP stats every ~2 seconds (20 * 100ms)
if (++poll_counter >= 20) {
poll_counter = 0;
fetchStatsHttp();
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
drainOutput(); // Final drain
}
// ============================================================================
// Stats polling via HTTP API
// ============================================================================
void XmrigManager::pollStats() {
// No-op on UI thread — stats are fetched by the monitor thread.
// Just drain stdout so log lines stay fresh.
drainOutput();
}
void XmrigManager::fetchStatsHttp() {
if (state_ != State::Running) return;
// Drain stdout while we're at it
drainOutput();
// Build URL
char url[256];
snprintf(url, sizeof(url), "http://127.0.0.1:%d/2/summary", api_port_);
std::string responseData;
CURL* curl = curl_easy_init();
if (!curl) {
DEBUG_LOGF("[WARN] XmrigManager::pollStats: curl_easy_init failed\n");
return;
}
// Set up auth header
std::string authHeader = "Authorization: Bearer " + api_token_;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, authHeader.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 2000L); // 2s timeout — generous for loaded system
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 1000L); // 1s connect timeout
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); // Avoid signal issues in threads
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
static int s_fail_count = 0;
if (++s_fail_count <= 5 || s_fail_count % 30 == 0) {
DEBUG_LOGF("[WARN] XmrigManager::pollStats: curl failed (%d): %s url=%s\n",
s_fail_count, curl_easy_strerror(res), url);
}
return;
}
try {
json resp = json::parse(responseData);
std::lock_guard<std::mutex> lk(stats_mutex_);
if (resp.contains("hashrate") && resp["hashrate"].contains("total")) {
auto& total = resp["hashrate"]["total"];
if (total.is_array() && total.size() >= 3) {
stats_.hashrate_10s = total[0].is_null() ? 0.0 : total[0].get<double>();
stats_.hashrate_60s = total[1].is_null() ? 0.0 : total[1].get<double>();
stats_.hashrate_15m = total[2].is_null() ? 0.0 : total[2].get<double>();
}
}
if (resp.contains("connection")) {
auto& conn = resp["connection"];
stats_.accepted = conn.value("accepted", (int64_t)0);
stats_.rejected = conn.value("rejected", (int64_t)0);
stats_.uptime_sec = conn.value("uptime", (int64_t)0);
stats_.pool_diff = conn.value("diff", 0.0);
stats_.pool_url = conn.value("pool", std::string{});
stats_.algo = conn.value("algo", std::string{});
stats_.connected = (stats_.uptime_sec > 0);
}
// Parse memory usage from "resources" section
if (resp.contains("resources") && resp["resources"].contains("memory")) {
auto& mem = resp["resources"]["memory"];
stats_.memory_free = mem.value("free", (int64_t)0);
stats_.memory_total = mem.value("total", (int64_t)0);
stats_.memory_used = mem.value("resident_set_memory", (int64_t)0);
}
// Parse active thread count from hashrate.threads array
if (resp.contains("hashrate") && resp["hashrate"].contains("threads")) {
auto& threads = resp["hashrate"]["threads"];
if (threads.is_array()) {
stats_.threads_active = static_cast<int>(threads.size());
}
} else if (resp.contains("cpu") && resp["cpu"].contains("threads")) {
// Fallback: get from cpu section
stats_.threads_active = resp["cpu"].value("threads", 0);
}
} catch (...) {
// Malformed JSON — ignore, retry next poll
}
}
} // namespace daemon
} // namespace dragonx

169
src/daemon/xmrig_manager.h Normal file
View File

@@ -0,0 +1,169 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#include <vector>
#include <cstdint>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <sys/types.h>
#endif
namespace dragonx {
namespace daemon {
/**
* @brief Manages the xmrig pool mining process
*
* Handles starting/stopping xmrig for pool mining, polling stats via its
* HTTP API, and capturing stdout for the log panel. Modelled on
* EmbeddedDaemon (same fork/pipe/monitor pattern, same #ifdef split).
*/
class XmrigManager {
public:
enum class State { Stopped, Starting, Running, Stopping, Error };
/// Live stats polled from xmrig HTTP API (/2/summary)
struct PoolStats {
double hashrate_10s = 0;
double hashrate_60s = 0;
double hashrate_15m = 0;
int64_t accepted = 0;
int64_t rejected = 0;
int64_t uptime_sec = 0;
double pool_diff = 0;
std::string pool_url;
std::string algo;
bool connected = false;
// Memory usage
int64_t memory_free = 0; // bytes
int64_t memory_total = 0; // bytes
int64_t memory_used = 0; // bytes (resident set size)
int threads_active = 0; // actual mining threads
};
/// User-facing config (maps 1:1 to UI fields / Settings)
struct Config {
std::string pool_url = "pool.dragonx.is";
std::string wallet_address;
std::string worker_name = "x";
std::string algo = "rx/hush";
int threads = 0; // 0 = xmrig auto
bool tls = false;
bool hugepages = true;
};
XmrigManager();
~XmrigManager();
// Non-copyable
XmrigManager(const XmrigManager&) = delete;
XmrigManager& operator=(const XmrigManager&) = delete;
/**
* @brief Start xmrig with the given config.
* Generates a JSON config file, finds the binary, and spawns the process.
*/
bool start(const Config& cfg);
/**
* @brief Stop xmrig gracefully (SIGTERM → wait → SIGKILL).
*/
void stop(int wait_ms = 5000);
bool isRunning() const;
State getState() const { return state_.load(std::memory_order_relaxed); }
const PoolStats& getStats() const { return stats_; }
const std::string& getLastError() const { return last_error_; }
/**
* @brief Get last N lines of xmrig stdout (thread-safe snapshot).
*/
std::vector<std::string> getRecentLines(int maxLines = 30) const;
/**
* @brief Get new output since a given offset (thread-safe).
* Returns the new text and updates offset to the current size.
*/
std::string getOutputSince(size_t& offset) const {
std::lock_guard<std::mutex> lk(output_mutex_);
if (offset >= process_output_.size()) {
offset = process_output_.size();
return {};
}
std::string result = process_output_.substr(offset);
offset = process_output_.size();
return result;
}
/**
* @brief Get current output size (thread-safe, no copy)
*/
size_t getOutputSize() const {
std::lock_guard<std::mutex> lk(output_mutex_);
return process_output_.size();
}
/**
* @brief Poll the xmrig HTTP API for live stats.
* Lightweight — reads cached stats updated by the monitor thread.
* Called from App::update() every ~2 s while running.
*/
void pollStats();
/**
* @brief Get xmrig process memory usage in MB (from OS, not API).
*/
double getMemoryUsageMB() const;
/**
* @brief Find xmrig binary in standard locations.
*/
static std::string findXmrigBinary();
private:
bool generateConfig(const Config& cfg, const std::string& outPath);
bool startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads);
void monitorProcess();
void drainOutput();
void appendOutput(const char* data, size_t len);
void fetchStatsHttp(); // Blocking HTTP call — runs on monitor thread only
std::atomic<State> state_{State::Stopped};
std::string last_error_;
mutable std::mutex output_mutex_;
std::string process_output_;
// xmrig HTTP API credentials (random per session)
int api_port_ = 0;
std::string api_token_;
int threads_ = 0; // Thread count for mining
PoolStats stats_;
mutable std::mutex stats_mutex_;
// Process handles
#ifdef _WIN32
HANDLE process_handle_ = nullptr;
HANDLE stdout_read_ = nullptr;
#else
pid_t process_pid_ = 0;
int stdout_fd_ = -1;
#endif
std::thread monitor_thread_;
std::atomic<bool> should_stop_{false};
};
} // namespace daemon
} // namespace dragonx

184
src/data/address_book.cpp Normal file
View File

@@ -0,0 +1,184 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "address_book.h"
#include <nlohmann/json.hpp>
#include <fstream>
#include <filesystem>
#include "../util/logger.h"
#ifdef _WIN32
#include <shlobj.h>
#else
#include <pwd.h>
#include <unistd.h>
#endif
namespace fs = std::filesystem;
using json = nlohmann::json;
namespace dragonx {
namespace data {
AddressBook::AddressBook() = default;
AddressBook::~AddressBook() = default;
std::string AddressBook::getDefaultPath()
{
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
std::string dir = std::string(path) + "\\ObsidianDragon";
fs::create_directories(dir);
return dir + "\\addressbook.json";
}
return "addressbook.json";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw->pw_dir;
}
std::string dir = std::string(home) + "/Library/Application Support/ObsidianDragon";
fs::create_directories(dir);
return dir + "/addressbook.json";
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw->pw_dir;
}
std::string dir = std::string(home) + "/.config/ObsidianDragon";
fs::create_directories(dir);
return dir + "/addressbook.json";
#endif
}
bool AddressBook::load()
{
file_path_ = getDefaultPath();
std::ifstream file(file_path_);
if (!file.is_open()) {
// No file yet - that's OK
return true;
}
try {
json j;
file >> j;
entries_.clear();
if (j.contains("entries") && j["entries"].is_array()) {
for (const auto& entry : j["entries"]) {
AddressBookEntry e;
e.label = entry.value("label", "");
e.address = entry.value("address", "");
e.notes = entry.value("notes", "");
if (!e.address.empty()) {
entries_.push_back(e);
}
}
}
DEBUG_LOGF("Address book loaded: %zu entries\n", entries_.size());
return true;
} catch (const std::exception& e) {
DEBUG_LOGF("Error loading address book: %s\n", e.what());
return false;
}
}
bool AddressBook::save()
{
if (file_path_.empty()) {
file_path_ = getDefaultPath();
}
try {
json j;
j["entries"] = json::array();
for (const auto& entry : entries_) {
json e;
e["label"] = entry.label;
e["address"] = entry.address;
e["notes"] = entry.notes;
j["entries"].push_back(e);
}
// Ensure directory exists
fs::path p(file_path_);
fs::create_directories(p.parent_path());
std::ofstream file(file_path_);
if (!file.is_open()) {
DEBUG_LOGF("Could not open address book for writing: %s\n", file_path_.c_str());
return false;
}
file << j.dump(2);
DEBUG_LOGF("Address book saved: %zu entries\n", entries_.size());
return true;
} catch (const std::exception& e) {
DEBUG_LOGF("Error saving address book: %s\n", e.what());
return false;
}
}
bool AddressBook::addEntry(const AddressBookEntry& entry)
{
// Check for duplicate address
if (findByAddress(entry.address) >= 0) {
return false;
}
entries_.push_back(entry);
return save();
}
bool AddressBook::updateEntry(size_t index, const AddressBookEntry& entry)
{
if (index >= entries_.size()) {
return false;
}
// Check for duplicate address (excluding current entry)
int existing = findByAddress(entry.address);
if (existing >= 0 && static_cast<size_t>(existing) != index) {
return false;
}
entries_[index] = entry;
return save();
}
bool AddressBook::removeEntry(size_t index)
{
if (index >= entries_.size()) {
return false;
}
entries_.erase(entries_.begin() + index);
return save();
}
int AddressBook::findByAddress(const std::string& address) const
{
for (size_t i = 0; i < entries_.size(); i++) {
if (entries_[i].address == address) {
return static_cast<int>(i);
}
}
return -1;
}
} // namespace data
} // namespace dragonx

103
src/data/address_book.h Normal file
View File

@@ -0,0 +1,103 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <vector>
namespace dragonx {
namespace data {
/**
* @brief A single address book entry
*/
struct AddressBookEntry {
std::string label;
std::string address;
std::string notes;
AddressBookEntry() = default;
AddressBookEntry(const std::string& l, const std::string& a, const std::string& n = "")
: label(l), address(a), notes(n) {}
};
/**
* @brief Address book manager
*
* Stores labeled addresses for easy access when sending.
*/
class AddressBook {
public:
AddressBook();
~AddressBook();
/**
* @brief Load address book from disk
* @return true if loaded successfully
*/
bool load();
/**
* @brief Save address book to disk
* @return true if saved successfully
*/
bool save();
/**
* @brief Get default address book file path
*/
static std::string getDefaultPath();
/**
* @brief Add a new entry
* @param entry The entry to add
* @return true if added (false if duplicate address)
*/
bool addEntry(const AddressBookEntry& entry);
/**
* @brief Update an existing entry
* @param index Index of entry to update
* @param entry New entry data
* @return true if updated
*/
bool updateEntry(size_t index, const AddressBookEntry& entry);
/**
* @brief Remove an entry
* @param index Index of entry to remove
* @return true if removed
*/
bool removeEntry(size_t index);
/**
* @brief Find entry by address
* @param address Address to search for
* @return Index or -1 if not found
*/
int findByAddress(const std::string& address) const;
/**
* @brief Get all entries
*/
const std::vector<AddressBookEntry>& entries() const { return entries_; }
/**
* @brief Get entry count
*/
size_t size() const { return entries_.size(); }
/**
* @brief Check if empty
*/
bool empty() const { return entries_.empty(); }
private:
std::vector<AddressBookEntry> entries_;
std::string file_path_;
};
} // namespace data
} // namespace dragonx

View File

@@ -0,0 +1,34 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "exchange_info.h"
namespace dragonx {
namespace data {
const std::vector<ExchangeInfo>& getExchangeRegistry()
{
static const std::vector<ExchangeInfo> registry = {
{
"TradeOgre",
"https://tradeogre.com",
{
{"DRGX", "BTC", "DRGX/BTC", "https://tradeogre.com/exchange/DRGX-BTC"},
{"DRGX", "LTC", "DRGX/LTC", "https://tradeogre.com/exchange/DRGX-LTC"},
{"DRGX", "USDT", "DRGX/USDT", "https://tradeogre.com/exchange/DRGX-USDT"},
}
},
{
"Exbitron",
"https://www.exbitron.com",
{
{"DRGX", "USDT", "DRGX/USDT", "https://www.exbitron.com/trading/drgxusdt"},
}
},
};
return registry;
}
} // namespace data
} // namespace dragonx

38
src/data/exchange_info.h Normal file
View File

@@ -0,0 +1,38 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <vector>
namespace dragonx {
namespace data {
/**
* @brief A single trading pair on an exchange (e.g. DRGX/BTC)
*/
struct ExchangePair {
std::string base; ///< e.g. "DRGX"
std::string quote; ///< e.g. "BTC"
std::string displayName; ///< e.g. "DRGX/BTC"
std::string tradeUrl; ///< Link to the exchange pair page
};
/**
* @brief Metadata for a supported exchange
*/
struct ExchangeInfo {
std::string name; ///< e.g. "TradeOgre"
std::string baseUrl; ///< e.g. "https://tradeogre.com"
std::vector<ExchangePair> pairs;
};
/**
* @brief Returns the static registry of supported exchanges + pairs
*/
const std::vector<ExchangeInfo>& getExchangeRegistry();
} // namespace data
} // namespace dragonx

57
src/data/wallet_state.cpp Normal file
View File

@@ -0,0 +1,57 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "wallet_state.h"
#include <ctime>
#include <sstream>
#include <iomanip>
namespace dragonx {
std::string TransactionInfo::getTimeString() const
{
if (timestamp == 0) return "Unknown";
std::time_t t = static_cast<std::time_t>(timestamp);
std::tm* tm = std::localtime(&t);
std::stringstream ss;
ss << std::put_time(tm, "%Y-%m-%d %H:%M");
return ss.str();
}
std::string TransactionInfo::getTypeDisplay() const
{
if (type == "send") return "Sent";
if (type == "receive") return "Received";
if (type == "mined" || type == "generate" || type == "immature") return "Mined";
return type;
}
std::string PeerInfo::getConnectionTime() const
{
if (conntime == 0) return "Unknown";
int64_t now = std::time(nullptr);
int64_t diff = now - conntime;
if (diff < 60) return std::to_string(diff) + "s";
if (diff < 3600) return std::to_string(diff / 60) + "m";
if (diff < 86400) return std::to_string(diff / 3600) + "h";
return std::to_string(diff / 86400) + "d";
}
std::string BannedPeer::getBannedUntilString() const
{
if (banned_until == 0) return "Never";
std::time_t t = static_cast<std::time_t>(banned_until);
std::tm* tm = std::localtime(&t);
std::stringstream ss;
ss << std::put_time(tm, "%Y-%m-%d %H:%M");
return ss.str();
}
} // namespace dragonx

267
src/data/wallet_state.h Normal file
View File

@@ -0,0 +1,267 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <chrono>
namespace dragonx {
/**
* @brief Represents an address with its balance
*/
struct AddressInfo {
std::string address;
double balance = 0.0;
std::string type; // "shielded" or "transparent"
// For display
std::string label;
// Derived
bool isZAddr() const { return !address.empty() && address[0] == 'z'; }
bool isShielded() const { return type == "shielded"; }
};
/**
* @brief Represents a wallet transaction
*/
struct TransactionInfo {
std::string txid;
std::string type; // "send", "receive", "mined"
double amount = 0.0;
int64_t timestamp = 0; // Unix timestamp
int confirmations = 0;
std::string address; // destination (send) or source (receive)
std::string from_address; // source address for sends
std::string memo;
// Computed fields
std::string getTimeString() const;
std::string getTypeDisplay() const;
bool isConfirmed() const { return confirmations >= 1; }
bool isMature() const { return confirmations >= 100; }
};
/**
* @brief Represents a connected peer
*/
struct PeerInfo {
int id = 0;
std::string addr;
std::string subver;
std::string services;
int version = 0;
int64_t conntime = 0;
int banscore = 0;
double pingtime = 0.0;
int64_t bytessent = 0;
int64_t bytesrecv = 0;
int startingheight = 0;
int synced_headers = 0;
int synced_blocks = 0;
bool inbound = false;
// TLS info
std::string tls_cipher;
bool tls_verified = false;
std::string getConnectionTime() const;
};
/**
* @brief Represents a banned peer
*/
struct BannedPeer {
std::string address;
std::string subnet;
int64_t banned_until = 0;
std::string getBannedUntilString() const;
};
/**
* @brief Mining statistics
*/
struct MiningInfo {
bool generate = false;
int genproclimit = 0; // -1 means max CPUs
double localHashrate = 0.0; // Local hashrate (H/s) from getlocalsolps RPC (RandomX)
double networkHashrate = 0.0; // Network hashrate (H/s)
int blocks = 0;
double difficulty = 0.0;
std::string chain;
double daemon_memory_mb = 0.0; // Daemon process RSS in MB
// History for chart
std::vector<double> hashrate_history; // Last N samples
static constexpr int MAX_HISTORY = 300; // 5 minutes at 1s intervals
};
/**
* @brief Blockchain synchronization info
*/
struct SyncInfo {
int blocks = 0;
int headers = 0;
double verification_progress = 0.0;
bool syncing = false;
std::string best_blockhash;
bool isSynced() const { return !syncing && blocks > 0 && blocks >= headers - 2; }
};
/**
* @brief Market/price information
*/
struct MarketInfo {
double price_usd = 0.0;
double price_btc = 0.0;
double volume_24h = 0.0;
double change_24h = 0.0;
double market_cap = 0.0;
std::string last_updated;
// Price history for chart
std::vector<double> price_history;
static constexpr int MAX_HISTORY = 24; // 24 hours
};
/**
* @brief Pool mining state (from xmrig HTTP API)
*/
struct PoolMiningState {
bool pool_mode = false; // UI toggle: solo vs pool
bool xmrig_running = false;
std::string pool_url;
std::string algo;
double hashrate_10s = 0;
double hashrate_60s = 0;
double hashrate_15m = 0;
int64_t accepted = 0;
int64_t rejected = 0;
int64_t uptime_sec = 0;
double pool_diff = 0;
bool connected = false;
// Memory/thread usage (bytes for memory)
int64_t memory_used = 0;
int threads_active = 0;
// Hashrate history for chart (mirrors MiningInfo::hashrate_history)
std::vector<double> hashrate_history;
static constexpr int MAX_HISTORY = 60; // 5 minutes at ~5s intervals
// Recent log lines for the log panel
std::vector<std::string> log_lines;
};
/**
* @brief Complete wallet state - all data fetched from daemon
*/
struct WalletState {
// Connection
bool connected = false;
int daemon_version = 0;
std::string daemon_subversion;
int protocol_version = 0;
int p2p_port = 0;
int longestchain = 0;
int notarized = 0;
// Sync status
SyncInfo sync;
// Balances (named to match UI usage)
double privateBalance = 0.0; // shielded balance
double transparentBalance = 0.0;
double totalBalance = 0.0;
double unconfirmedBalance = 0.0;
// Aliases for backward compatibility
double& shielded_balance = privateBalance;
double& transparent_balance = transparentBalance;
double& total_balance = totalBalance;
double& unconfirmed_balance = unconfirmedBalance;
// Addresses - combined list for UI convenience
std::vector<AddressInfo> addresses;
// Also keep separate lists for legacy code
std::vector<AddressInfo> z_addresses;
std::vector<AddressInfo> t_addresses;
// Transactions
std::vector<TransactionInfo> transactions;
// Peers
std::vector<PeerInfo> peers;
std::vector<BannedPeer> bannedPeers;
// Aliases for banned_peers
std::vector<BannedPeer>& banned_peers = bannedPeers;
// Mining
MiningInfo mining;
// Pool mining (xmrig)
PoolMiningState pool_mining;
// Market
MarketInfo market;
// Wallet encryption state (populated from getwalletinfo)
bool encrypted = false; // true if wallet has ever been encrypted
bool locked = false; // true if encrypted && unlocked_until <= now
int64_t unlocked_until = 0; // 0 = locked, >0 = unix timestamp when auto-lock fires
bool encryption_state_known = false; // true once first getwalletinfo response processed
bool isEncrypted() const { return encrypted; }
bool isLocked() const { return encrypted && locked; }
bool isUnlocked() const { return encrypted && !locked; }
// Timestamps for refresh logic
int64_t last_balance_update = 0;
int64_t last_tx_update = 0;
int64_t last_peer_update = 0;
int64_t last_mining_update = 0;
// Helper methods
int getAddressCount() const { return addresses.size(); }
double getBalanceUSD() const { return totalBalance * market.price_usd; }
void clear() {
connected = false;
privateBalance = transparentBalance = totalBalance = 0.0;
encrypted = false;
locked = false;
unlocked_until = 0;
encryption_state_known = false;
addresses.clear();
z_addresses.clear();
t_addresses.clear();
transactions.clear();
peers.clear();
bannedPeers.clear();
}
// Rebuild combined addresses list from z/t lists
void rebuildAddressList() {
addresses.clear();
addresses.reserve(z_addresses.size() + t_addresses.size());
for (const auto& addr : z_addresses) {
addresses.push_back(addr);
}
for (const auto& addr : t_addresses) {
addresses.push_back(addr);
}
}
};
} // namespace dragonx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
// DragonX Wallet - INCBIN font embedding (CMake-generated)
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// This file is generated by CMake's configure_file() from
// src/embedded/embedded_fonts.cpp.in — do NOT edit the generated copy.
// The absolute paths below are resolved at CMake configure time.
#include "incbin.h"
INCBIN(ubuntu_regular, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-R.ttf");
INCBIN(ubuntu_light, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Light.ttf");
INCBIN(ubuntu_medium, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Medium.ttf");
INCBIN(material_icons, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialIcons-Regular.ttf");

View File

@@ -0,0 +1,29 @@
// DragonX Wallet - Embedded font data declarations (INCBIN)
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// The actual data is emitted in generated/embedded_fonts.cpp via the
// assembler .incbin directive, so the compiler never parses 122K lines
// of hex literals.
#pragma once
#include <cstdint>
// These symbols are defined in generated/embedded_fonts.cpp (via INCBIN).
// INCBIN creates: g_<name>_data[] (const unsigned char)
// g_<name>_size (const unsigned int)
// g_<name>_end (const unsigned char*)
extern "C" {
extern const unsigned char g_ubuntu_regular_data[];
extern const unsigned int g_ubuntu_regular_size;
extern const unsigned char g_ubuntu_light_data[];
extern const unsigned int g_ubuntu_light_size;
extern const unsigned char g_ubuntu_medium_data[];
extern const unsigned int g_ubuntu_medium_size;
extern const unsigned char g_material_icons_data[];
extern const unsigned int g_material_icons_size;
}

382
src/embedded/lang_de.h Normal file
View File

@@ -0,0 +1,382 @@
unsigned char res_lang_de_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x4b, 0x6f, 0x6e, 0x74, 0x6f, 0x73,
0x74, 0x61, 0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x65, 0x6e, 0x64,
0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65,
0x63, 0x65, 0x69, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6d, 0x70,
0x66, 0x61, 0x6e, 0x67, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x6b, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20,
0x22, 0x4d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x4b, 0x6e, 0x6f, 0x74, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x4d, 0x61, 0x72, 0x6b, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x45, 0x69, 0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x6e, 0x67,
0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xc3, 0x9c,
0x62, 0x65, 0x72, 0x73, 0x69, 0x63, 0x68, 0x74, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x73, 0x63, 0x68, 0xc3, 0xbc, 0x74,
0x7a, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20,
0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x74, 0x61,
0x6c, 0x22, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x73, 0x61, 0x6d, 0x74, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x6e,
0x62, 0x65, 0x73, 0x74, 0xc3, 0xa4, 0x74, 0x69, 0x67, 0x74, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x79, 0x6f, 0x75, 0x72, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x49, 0x68, 0x72, 0x65, 0x20, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x7a, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x5a, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x2d, 0x41,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x69, 0x6e, 0x65,
0x20, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x20, 0x67, 0x65,
0x66, 0x75, 0x6e, 0x64, 0x65, 0x6e, 0x2e, 0x20, 0x45, 0x72, 0x73, 0x74,
0x65, 0x6c, 0x6c, 0x65, 0x6e, 0x20, 0x53, 0x69, 0x65, 0x20, 0x65, 0x69,
0x6e, 0x65, 0x20, 0x6d, 0x69, 0x74, 0x20, 0x64, 0x65, 0x6e, 0x20, 0x53,
0x63, 0x68, 0x61, 0x6c, 0x74, 0x66, 0x6c, 0xc3, 0xa4, 0x63, 0x68, 0x65,
0x6e, 0x20, 0x6f, 0x62, 0x65, 0x6e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x65, 0x75, 0x65,
0x20, 0x5a, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x65, 0x75, 0x65, 0x20, 0x54, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x79, 0x70, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x6f,
0x6c, 0x6c, 0x73, 0x74, 0xc3, 0xa4, 0x6e, 0x64, 0x69, 0x67, 0x65, 0x20,
0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20, 0x6b, 0x6f, 0x70, 0x69,
0x65, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x65, 0x6e, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x68,
0x69, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x56, 0x6f, 0x6e, 0x20, 0x64, 0x69, 0x65, 0x73, 0x65, 0x72,
0x20, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20, 0x73, 0x65, 0x6e,
0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65,
0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x6e, 0x20, 0x53, 0x63, 0x68, 0x6c, 0xc3, 0xbc,
0x73, 0x73, 0x65, 0x6c, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x69,
0x65, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x69,
0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6e,
0x73, 0x69, 0x63, 0x68, 0x74, 0x73, 0x73, 0x63, 0x68, 0x6c, 0xc3, 0xbc,
0x73, 0x73, 0x65, 0x6c, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x69,
0x65, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x68, 0x6f, 0x77, 0x5f, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x51, 0x52, 0x2d, 0x43, 0x6f, 0x64, 0x65, 0x20,
0x61, 0x6e, 0x7a, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x69, 0x63,
0x68, 0x74, 0x20, 0x6d, 0x69, 0x74, 0x20, 0x44, 0x61, 0x65, 0x6d, 0x6f,
0x6e, 0x20, 0x76, 0x65, 0x72, 0x62, 0x75, 0x6e, 0x64, 0x65, 0x6e, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70,
0x61, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x5a,
0x61, 0x68, 0x6c, 0x65, 0x6e, 0x20, 0x76, 0x6f, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x6f,
0x22, 0x3a, 0x20, 0x22, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x20, 0x61,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f,
0x75, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x65, 0x74, 0x72, 0x61,
0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d,
0x6f, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x6d, 0x6f, 0x20, 0x28, 0x6f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2c, 0x20, 0x76, 0x65, 0x72,
0x73, 0x63, 0x68, 0x6c, 0xc3, 0xbc, 0x73, 0x73, 0x65, 0x6c, 0x74, 0x29,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65,
0x72, 0x5f, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e,
0x65, 0x72, 0x2d, 0x47, 0x65, 0x62, 0xc3, 0xbc, 0x68, 0x72, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x47, 0x65, 0x62, 0xc3, 0xbc, 0x68, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x6b, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x73, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x22, 0x3a, 0x20, 0x22, 0x4c,
0xc3, 0xb6, 0x73, 0x63, 0x68, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x20, 0x61, 0x75, 0x73, 0x77, 0xc3, 0xa4, 0x68,
0x6c, 0x65, 0x6e, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x70, 0x61, 0x73, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x69, 0x6e, 0x66, 0xc3, 0xbc, 0x67, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x78, 0x22, 0x3a, 0x20, 0x22, 0x4d,
0x61, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x76,
0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x56,
0x65, 0x72, 0x66, 0xc3, 0xbc, 0x67, 0x62, 0x61, 0x72, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x55, 0x6e, 0x67, 0xc3, 0xbc, 0x6c, 0x74, 0x69, 0x67, 0x65, 0x73, 0x20,
0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f,
0x5f, 0x7a, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x48,
0x69, 0x6e, 0x77, 0x65, 0x69, 0x73, 0x3a, 0x20, 0x4d, 0x65, 0x6d, 0x6f,
0x73, 0x20, 0x73, 0x69, 0x6e, 0x64, 0x20, 0x6e, 0x75, 0x72, 0x20, 0x62,
0x65, 0x69, 0x6d, 0x20, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x20, 0x61,
0x6e, 0x20, 0x67, 0x65, 0x73, 0x63, 0x68, 0xc3, 0xbc, 0x74, 0x7a, 0x74,
0x65, 0x20, 0x28, 0x7a, 0x29, 0x20, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x6e, 0x20, 0x76, 0x65, 0x72, 0x66, 0xc3, 0xbc, 0x67, 0x62, 0x61,
0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x68, 0x61,
0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x5a,
0x65, 0x69, 0x63, 0x68, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x6f,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x22,
0x3a, 0x20, 0x22, 0x41, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x6b, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x77, 0x69, 0x72, 0x64, 0x20, 0x67, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x65,
0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x20, 0x62, 0x65, 0x73, 0x74,
0xc3, 0xa4, 0x74, 0x69, 0x67, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x6b, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x62, 0x65, 0x73, 0x74, 0xc3, 0xa4, 0x74, 0x69, 0x67, 0x65,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6e,
0x64, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x65, 0x73, 0x74, 0xc3, 0xa4, 0x74,
0x69, 0x67, 0x65, 0x6e, 0x20, 0x26, 0x20, 0x53, 0x65, 0x6e, 0x64, 0x65,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x6e,
0x63, 0x65, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x62, 0x62, 0x72, 0x65,
0x63, 0x68, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x49, 0x68, 0x72, 0x65, 0x20, 0x45, 0x6d, 0x70, 0x66, 0x61, 0x6e, 0x67,
0x73, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f, 0x73,
0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x65, 0x75, 0x65, 0x20, 0x7a, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x20, 0x28, 0x67, 0x65, 0x73, 0x63, 0x68, 0xc3, 0xbc, 0x74, 0x7a,
0x74, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x65, 0x75, 0x65, 0x20,
0x74, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20, 0x28, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x29, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x64, 0x65, 0x74, 0x61,
0x69, 0x6c, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76,
0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e, 0x5f, 0x65, 0x78, 0x70, 0x6c, 0x6f,
0x72, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d, 0x20, 0x45, 0x78,
0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x73, 0x65, 0x68,
0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x71, 0x72,
0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x51, 0x52, 0x2d,
0x43, 0x6f, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x5a, 0x61, 0x68, 0x6c, 0x75,
0x6e, 0x67, 0x20, 0x61, 0x6e, 0x66, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x6e,
0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x61, 0x74, 0x75, 0x6d, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x42,
0x65, 0x73, 0x74, 0xc3, 0xa4, 0x74, 0x69, 0x67, 0x75, 0x6e, 0x67, 0x65,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x65,
0x73, 0x74, 0xc3, 0xa4, 0x74, 0x69, 0x67, 0x74, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0x41, 0x75, 0x73, 0x73, 0x74, 0x65, 0x68, 0x65, 0x6e,
0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e,
0x74, 0x22, 0x3a, 0x20, 0x22, 0x67, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x65,
0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63,
0x65, 0x69, 0x76, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x65, 0x6d, 0x70,
0x66, 0x61, 0x6e, 0x67, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x67,
0x65, 0x73, 0x63, 0x68, 0xc3, 0xbc, 0x72, 0x66, 0x74, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67,
0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, 0x3a, 0x20, 0x22,
0x4d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x53, 0x74, 0x65, 0x75, 0x65,
0x72, 0x75, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x73,
0x74, 0x61, 0x72, 0x74, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20,
0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68,
0x72, 0x65, 0x61, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x2d, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73,
0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x53,
0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x6b, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x4c, 0x6f, 0x6b, 0x61, 0x6c, 0x65, 0x20, 0x48, 0x61, 0x73, 0x68, 0x72,
0x61, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72,
0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x65, 0x74, 0x7a, 0x77,
0x65, 0x72, 0x6b, 0x2d, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x66, 0x66,
0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x63,
0x68, 0x77, 0x69, 0x65, 0x72, 0x69, 0x67, 0x6b, 0x65, 0x69, 0x74, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x73, 0x74, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x22, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x73, 0x63, 0x68, 0x2e, 0x20, 0x5a,
0x65, 0x69, 0x74, 0x20, 0x62, 0x69, 0x73, 0x20, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x66, 0x66, 0x22, 0x3a, 0x20, 0x22, 0x4d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x74, 0x20, 0x41, 0x55,
0x53, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69,
0x6e, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x74, 0x20, 0x41, 0x4e, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x62, 0x75, 0x6e, 0x64, 0x65, 0x6e,
0x65, 0x20, 0x4b, 0x6e, 0x6f, 0x74, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70,
0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x73, 0x70,
0x65, 0x72, 0x72, 0x74, 0x65, 0x20, 0x4b, 0x6e, 0x6f, 0x74, 0x65, 0x6e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x70, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x50,
0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x22, 0x3a, 0x20, 0x22, 0x48, 0xc3, 0xb6, 0x68, 0x65, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20,
0x22, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x62, 0x61, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x70, 0x65, 0x72,
0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75,
0x6e, 0x62, 0x61, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x74, 0x73,
0x70, 0x65, 0x72, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x6c, 0x5f,
0x62, 0x61, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6c, 0x6c, 0x65,
0x20, 0x53, 0x70, 0x65, 0x72, 0x72, 0x65, 0x6e, 0x20, 0x61, 0x75, 0x66,
0x68, 0x65, 0x62, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x72,
0x74, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x72, 0x65, 0x69, 0x73, 0x64, 0x69,
0x61, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72,
0x69, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6b, 0x74, 0x75, 0x65,
0x6c, 0x6c, 0x65, 0x72, 0x20, 0x50, 0x72, 0x65, 0x69, 0x73, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x32, 0x34, 0x68, 0x2d,
0xc3, 0x84, 0x6e, 0x64, 0x65, 0x72, 0x75, 0x6e, 0x67, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x76, 0x6f, 0x6c,
0x75, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x32, 0x34, 0x68, 0x2d, 0x56,
0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x61, 0x70,
0x22, 0x3a, 0x20, 0x22, 0x4d, 0x61, 0x72, 0x6b, 0x74, 0x6b, 0x61, 0x70,
0x69, 0x74, 0x61, 0x6c, 0x69, 0x73, 0x69, 0x65, 0x72, 0x75, 0x6e, 0x67,
0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6e,
0x65, 0x72, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6c, 0x6c, 0x67,
0x65, 0x6d, 0x65, 0x69, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x20, 0x22,
0x41, 0x6e, 0x7a, 0x65, 0x69, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a,
0x20, 0x22, 0x4e, 0x65, 0x74, 0x7a, 0x77, 0x65, 0x72, 0x6b, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x70, 0x72, 0x61, 0x63, 0x68, 0x65,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x72, 0x61, 0x67,
0x6f, 0x6e, 0x78, 0x5f, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x3a, 0x20,
0x22, 0x44, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x58, 0x20, 0x28, 0x47, 0x72,
0xc3, 0xbc, 0x6e, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x64, 0x61, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x75, 0x6e, 0x6b,
0x65, 0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x48, 0x65, 0x6c, 0x6c, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x66, 0x65, 0x65, 0x73,
0x22, 0x3a, 0x20, 0x22, 0x42, 0x65, 0x6e, 0x75, 0x74, 0x7a, 0x65, 0x72,
0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x65, 0x72, 0x74, 0x65, 0x20, 0x47,
0x65, 0x62, 0xc3, 0xbc, 0x68, 0x72, 0x65, 0x6e, 0x20, 0x65, 0x72, 0x6c,
0x61, 0x75, 0x62, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x75, 0x73, 0x65, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65,
0x64, 0x5f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22,
0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x65, 0x72, 0x74, 0x65, 0x6e,
0x20, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64, 0x20, 0x76, 0x65,
0x72, 0x77, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x53,
0x70, 0x65, 0x69, 0x63, 0x68, 0x65, 0x72, 0x6e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x53, 0x63, 0x68, 0x6c, 0x69, 0x65, 0xc3, 0x9f, 0x65, 0x6e, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x44, 0x61, 0x74, 0x65, 0x69, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x3a, 0x20,
0x22, 0x42, 0x65, 0x61, 0x72, 0x62, 0x65, 0x69, 0x74, 0x65, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x22,
0x3a, 0x20, 0x22, 0x41, 0x6e, 0x73, 0x69, 0x63, 0x68, 0x74, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x70, 0x22, 0x3a,
0x20, 0x22, 0x48, 0x69, 0x6c, 0x66, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x6e, 0x20, 0x53, 0x63,
0x68, 0x6c, 0xc3, 0xbc, 0x73, 0x73, 0x65, 0x6c, 0x20, 0x69, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x69, 0x65, 0x72, 0x65, 0x6e, 0x2e, 0x2e, 0x2e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x75,
0x70, 0x5f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x69, 0x63, 0x68, 0x65,
0x72, 0x6e, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x65, 0x78, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x65, 0x65,
0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x61, 0x62, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e,
0x78, 0x22, 0x3a, 0x20, 0x22, 0xc3, 0x9c, 0x62, 0x65, 0x72, 0x20, 0x4f,
0x62, 0x73, 0x69, 0x64, 0x69, 0x61, 0x6e, 0x44, 0x72, 0x61, 0x67, 0x6f,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x66,
0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f, 0x77, 0x22, 0x3a, 0x20, 0x22,
0x4a, 0x65, 0x74, 0x7a, 0x74, 0x20, 0x61, 0x6b, 0x74, 0x75, 0x61, 0x6c,
0x69, 0x73, 0x69, 0x65, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xc3, 0x9c, 0x62, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x65, 0x72, 0x65, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72,
0x74, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x69,
0x65, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6c, 0x69, 0x70,
0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6e, 0x20,
0x5a, 0x77, 0x69, 0x73, 0x63, 0x68, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x61,
0x67, 0x65, 0x20, 0x6b, 0x6f, 0x70, 0x69, 0x65, 0x72, 0x65, 0x6e, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72,
0x62, 0x75, 0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x74, 0x72, 0x65, 0x6e,
0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0x56, 0x65, 0x72, 0x62, 0x69, 0x6e, 0x64, 0x65, 0x2e, 0x2e, 0x2e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x79, 0x6e, 0x63, 0x69,
0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x79, 0x6e, 0x63, 0x68, 0x72,
0x6f, 0x6e, 0x69, 0x73, 0x69, 0x65, 0x72, 0x65, 0x2e, 0x2e, 0x2e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x22, 0x3a, 0x20, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x69, 0x6e, 0x65,
0x20, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x20, 0x76, 0x65,
0x72, 0x66, 0xc3, 0xbc, 0x67, 0x62, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a,
0x20, 0x22, 0x46, 0x65, 0x68, 0x6c, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x72, 0x66, 0x6f, 0x6c, 0x67, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0x57, 0x61, 0x72, 0x6e, 0x75, 0x6e, 0x67, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61,
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x65, 0x74,
0x72, 0x61, 0x67, 0x20, 0xc3, 0xbc, 0x62, 0x65, 0x72, 0x73, 0x74, 0x65,
0x69, 0x67, 0x74, 0x20, 0x4b, 0x6f, 0x6e, 0x74, 0x6f, 0x73, 0x74, 0x61,
0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65,
0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x6b, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x72, 0x66, 0x6f, 0x6c, 0x67,
0x72, 0x65, 0x69, 0x63, 0x68, 0x20, 0x67, 0x65, 0x73, 0x65, 0x6e, 0x64,
0x65, 0x74, 0x22, 0x0a, 0x7d, 0x0a
};
unsigned int res_lang_de_json_len = 4542;

386
src/embedded/lang_es.h Normal file
View File

@@ -0,0 +1,386 @@
unsigned char res_lang_es_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x61, 0x6c, 0x64, 0x6f, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69, 0x61, 0x72, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x63, 0x69, 0x62, 0x69, 0x72, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69,
0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0xc3,
0xad, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65,
0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x64, 0x6f, 0x73,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b,
0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x72, 0x63, 0x61, 0x64,
0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x52,
0x65, 0x73, 0x75, 0x6d, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a,
0x20, 0x22, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x67, 0x69, 0x64, 0x6f, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22,
0x3a, 0x20, 0x22, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x69, 0x6e, 0x20, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x79, 0x6f, 0x75, 0x72, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x75,
0x73, 0x20, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x65,
0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x7a, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x2d,
0x5a, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x2d,
0x54, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x4e, 0x6f, 0x20, 0x73, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x61, 0x72, 0x6f, 0x6e, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63,
0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x2e, 0x20, 0x43, 0x72, 0x65, 0x61,
0x20, 0x75, 0x6e, 0x61, 0x20, 0x75, 0x73, 0x61, 0x6e, 0x64, 0x6f, 0x20,
0x6c, 0x6f, 0x73, 0x20, 0x62, 0x6f, 0x74, 0x6f, 0x6e, 0x65, 0x73, 0x20,
0x64, 0x65, 0x20, 0x61, 0x72, 0x72, 0x69, 0x62, 0x61, 0x2e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x75, 0x65, 0x76, 0x61, 0x20, 0x44, 0x69, 0x72, 0x2d, 0x5a, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x75, 0x65, 0x76, 0x61, 0x20, 0x44, 0x69, 0x72, 0x2d, 0x54, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x54, 0x69, 0x70, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x43,
0x6f, 0x70, 0x69, 0x61, 0x72, 0x20, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63,
0x69, 0xc3, 0xb3, 0x6e, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e,
0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x68, 0x69, 0x73, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x6e, 0x76, 0x69, 0x61, 0x72, 0x20, 0x44, 0x65, 0x73, 0x64, 0x65, 0x20,
0x45, 0x73, 0x74, 0x61, 0x20, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69,
0xc3, 0xb3, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65,
0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x61, 0x72, 0x20, 0x43, 0x6c, 0x61, 0x76, 0x65, 0x20,
0x50, 0x72, 0x69, 0x76, 0x61, 0x64, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x76, 0x69,
0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x72, 0x20, 0x43, 0x6c,
0x61, 0x76, 0x65, 0x20, 0x64, 0x65, 0x20, 0x56, 0x69, 0x73, 0x74, 0x61,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68, 0x6f, 0x77,
0x5f, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x4d, 0x6f, 0x73, 0x74, 0x72, 0x61, 0x72, 0x20, 0x43, 0xc3, 0xb3, 0x64,
0x69, 0x67, 0x6f, 0x20, 0x51, 0x52, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x20, 0x63, 0x6f,
0x6e, 0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x20, 0x61, 0x6c, 0x20, 0x64,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x79,
0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x67,
0x61, 0x72, 0x20, 0x44, 0x65, 0x73, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69, 0x61, 0x72, 0x20, 0x41, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e,
0x74, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x61, 0x6e, 0x74, 0x69, 0x64, 0x61,
0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d,
0x6f, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x6d, 0x6f, 0x20, 0x28, 0x6f,
0x70, 0x63, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2c, 0x20, 0x65, 0x6e, 0x63,
0x72, 0x69, 0x70, 0x74, 0x61, 0x64, 0x6f, 0x29, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x66, 0x65,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6d, 0x69, 0x73, 0x69, 0xc3,
0xb3, 0x6e, 0x20, 0x64, 0x65, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x6f,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6d, 0x69, 0x73, 0x69, 0xc3, 0xb3, 0x6e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64,
0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69, 0x61, 0x72, 0x20, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72,
0x22, 0x3a, 0x20, 0x22, 0x4c, 0x69, 0x6d, 0x70, 0x69, 0x61, 0x72, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x61, 0x72,
0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61,
0x73, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x65, 0x67, 0x61, 0x72,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x78, 0x22,
0x3a, 0x20, 0x22, 0x4d, 0xc3, 0xa1, 0x78, 0x69, 0x6d, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x73, 0x70, 0x6f,
0x6e, 0x69, 0x62, 0x6c, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x6f, 0x72, 0x6d,
0x61, 0x74, 0x6f, 0x20, 0x64, 0x65, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63,
0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x20, 0x69, 0x6e, 0x76, 0xc3, 0xa1, 0x6c,
0x69, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x65, 0x6d, 0x6f, 0x5f, 0x7a, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x22, 0x3a,
0x20, 0x22, 0x4e, 0x6f, 0x74, 0x61, 0x3a, 0x20, 0x4c, 0x6f, 0x73, 0x20,
0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x20, 0x73, 0x6f, 0x6c, 0x6f, 0x20, 0x65,
0x73, 0x74, 0xc3, 0xa1, 0x6e, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6f, 0x6e,
0x69, 0x62, 0x6c, 0x65, 0x73, 0x20, 0x61, 0x6c, 0x20, 0x65, 0x6e, 0x76,
0x69, 0x61, 0x72, 0x20, 0x61, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x63,
0x69, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x67,
0x69, 0x64, 0x61, 0x73, 0x20, 0x28, 0x7a, 0x29, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65,
0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x63, 0x61, 0x72, 0x61, 0x63, 0x74,
0x65, 0x72, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65, 0x73, 0x64,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x22,
0x3a, 0x20, 0x22, 0x41, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x6e, 0x76, 0x69, 0x61, 0x6e, 0x64, 0x6f, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x63, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f,
0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x72, 0x6d, 0x61, 0x72, 0x20, 0x45, 0x6e, 0x76, 0xc3, 0xad, 0x6f,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x72, 0x6d, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x72, 0x6d, 0x61, 0x72, 0x20, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x61, 0x6e, 0x64,
0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x61, 0x72, 0x20, 0x79, 0x20, 0x45, 0x6e, 0x76,
0x69, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x61, 0x6e,
0x63, 0x65, 0x6c, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x75, 0x73, 0x20, 0x44, 0x69, 0x72,
0x65, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x64, 0x65, 0x20,
0x52, 0x65, 0x63, 0x65, 0x70, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f,
0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22,
0x4e, 0x75, 0x65, 0x76, 0x61, 0x20, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63,
0x69, 0xc3, 0xb3, 0x6e, 0x2d, 0x7a, 0x20, 0x28, 0x50, 0x72, 0x6f, 0x74,
0x65, 0x67, 0x69, 0x64, 0x61, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x75, 0x65, 0x76, 0x61, 0x20, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69,
0xc3, 0xb3, 0x6e, 0x2d, 0x74, 0x20, 0x28, 0x54, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x29, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f,
0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x44,
0x65, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x73, 0x20, 0x64, 0x65, 0x20, 0x44,
0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0xc3, 0xb3, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e,
0x5f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20,
0x22, 0x56, 0x65, 0x72, 0x20, 0x65, 0x6e, 0x20, 0x45, 0x78, 0x70, 0x6c,
0x6f, 0x72, 0x61, 0x64, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x43, 0xc3, 0xb3, 0x64, 0x69, 0x67, 0x6f, 0x20, 0x51, 0x52, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x3a,
0x20, 0x22, 0x53, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x61, 0x72, 0x20,
0x50, 0x61, 0x67, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x46, 0x65, 0x63, 0x68, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x73, 0x74, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x72, 0x6d, 0x61, 0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x61, 0x64, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x50,
0x65, 0x6e, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x65, 0x6e, 0x76, 0x69, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x22,
0x3a, 0x20, 0x22, 0x72, 0x65, 0x63, 0x69, 0x62, 0x69, 0x64, 0x6f, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x61, 0x64, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x20, 0x64, 0x65, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0xc3, 0xad, 0x61,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x72,
0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0x49, 0x6e, 0x69, 0x63, 0x69, 0x61, 0x72, 0x20, 0x4d, 0x69, 0x6e, 0x65,
0x72, 0xc3, 0xad, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0x44, 0x65, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x20, 0x4d,
0x69, 0x6e, 0x65, 0x72, 0xc3, 0xad, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68,
0x72, 0x65, 0x61, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x48, 0x69, 0x6c,
0x6f, 0x73, 0x20, 0x64, 0x65, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0xc3,
0xad, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69,
0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74,
0x69, 0x63, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x73, 0x74, 0x61, 0x64,
0xc3, 0xad, 0x73, 0x74, 0x69, 0x63, 0x61, 0x73, 0x20, 0x64, 0x65, 0x20,
0x4d, 0x69, 0x6e, 0x65, 0x72, 0xc3, 0xad, 0x61, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x61,
0x73, 0x61, 0x20, 0x48, 0x61, 0x73, 0x68, 0x20, 0x4c, 0x6f, 0x63, 0x61,
0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x61, 0x73, 0x61, 0x20, 0x48, 0x61,
0x73, 0x68, 0x20, 0x64, 0x65, 0x20, 0x52, 0x65, 0x64, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75,
0x6c, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x66, 0x69, 0x63,
0x75, 0x6c, 0x74, 0x61, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f,
0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x69,
0x65, 0x6d, 0x70, 0x6f, 0x20, 0x45, 0x73, 0x74, 0x2e, 0x20, 0x61, 0x6c,
0x20, 0x42, 0x6c, 0x6f, 0x71, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x66,
0x66, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0xc3, 0xad,
0x61, 0x20, 0x41, 0x50, 0x41, 0x47, 0x41, 0x44, 0x41, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f,
0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0xc3,
0xad, 0x61, 0x20, 0x45, 0x4e, 0x43, 0x45, 0x4e, 0x44, 0x49, 0x44, 0x41,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x70,
0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x64, 0x6f,
0x73, 0x20, 0x43, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x73,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e,
0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x4e, 0x6f, 0x64, 0x6f, 0x73, 0x20, 0x42, 0x6c, 0x6f, 0x71, 0x75, 0x65,
0x61, 0x64, 0x6f, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x44, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0xc3, 0xb3, 0x6e,
0x20, 0x49, 0x50, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x65,
0x72, 0x73, 0x69, 0xc3, 0xb3, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x41, 0x6c, 0x74, 0x75, 0x72, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x69,
0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x6c, 0x6f, 0x71, 0x75, 0x65, 0x61,
0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x62,
0x61, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65, 0x73, 0x62, 0x6c, 0x6f,
0x71, 0x75, 0x65, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x62,
0x61, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4c, 0x69, 0x6d, 0x70, 0x69,
0x61, 0x72, 0x20, 0x54, 0x6f, 0x64, 0x6f, 0x73, 0x20, 0x6c, 0x6f, 0x73,
0x20, 0x42, 0x6c, 0x6f, 0x71, 0x75, 0x65, 0x6f, 0x73, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72,
0x69, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x74, 0x22, 0x3a, 0x20,
0x22, 0x47, 0x72, 0xc3, 0xa1, 0x66, 0x69, 0x63, 0x6f, 0x20, 0x64, 0x65,
0x20, 0x50, 0x72, 0x65, 0x63, 0x69, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70,
0x72, 0x69, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x72, 0x65, 0x63,
0x69, 0x6f, 0x20, 0x41, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x61, 0x6d, 0x62, 0x69,
0x6f, 0x20, 0x32, 0x34, 0x68, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x32, 0x34, 0x68, 0x5f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x6e, 0x20, 0x32,
0x34, 0x68, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61,
0x72, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x61, 0x70, 0x22, 0x3a, 0x20, 0x22,
0x43, 0x61, 0x70, 0x2e, 0x20, 0x64, 0x65, 0x20, 0x4d, 0x65, 0x72, 0x63,
0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x22,
0x3a, 0x20, 0x22, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
0x79, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x6e, 0x74, 0x61, 0x6c, 0x6c,
0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x64, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x54, 0x65, 0x6d, 0x61, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x49, 0x64, 0x69, 0x6f, 0x6d, 0x61, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e,
0x78, 0x5f, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44,
0x72, 0x61, 0x67, 0x6f, 0x6e, 0x58, 0x20, 0x28, 0x56, 0x65, 0x72, 0x64,
0x65, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61,
0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x4f, 0x73, 0x63, 0x75, 0x72, 0x6f,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6c, 0x61, 0x72, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f,
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x69, 0x72, 0x20,
0x63, 0x6f, 0x6d, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x70,
0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x64, 0x61,
0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x73, 0x65,
0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x64, 0x61,
0x65, 0x6d, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x73, 0x61, 0x72,
0x20, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64, 0x20, 0x69, 0x6e,
0x74, 0x65, 0x67, 0x72, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x47,
0x75, 0x61, 0x72, 0x64, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x43,
0x65, 0x72, 0x72, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x6f, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x3a, 0x20,
0x22, 0x45, 0x64, 0x69, 0x74, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x22, 0x3a, 0x20, 0x22, 0x56,
0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65,
0x6c, 0x70, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x79, 0x75, 0x64, 0x61, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
0x79, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61,
0x72, 0x20, 0x43, 0x6c, 0x61, 0x76, 0x65, 0x20, 0x50, 0x72, 0x69, 0x76,
0x61, 0x64, 0x61, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x73, 0x70, 0x61,
0x6c, 0x64, 0x61, 0x72, 0x20, 0x43, 0x61, 0x72, 0x74, 0x65, 0x72, 0x61,
0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65,
0x78, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x61, 0x6c, 0x69, 0x72,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75,
0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x22, 0x3a, 0x20,
0x22, 0x41, 0x63, 0x65, 0x72, 0x63, 0x61, 0x20, 0x64, 0x65, 0x20, 0x44,
0x72, 0x61, 0x67, 0x6f, 0x6e, 0x58, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f,
0x77, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x69,
0x7a, 0x61, 0x72, 0x20, 0x41, 0x68, 0x6f, 0x72, 0x61, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62,
0x6f, 0x75, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x63, 0x65, 0x72, 0x63,
0x61, 0x20, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f,
0x63, 0x6c, 0x69, 0x70, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x43, 0x6f, 0x70, 0x69, 0x61, 0x72, 0x20, 0x61, 0x6c, 0x20, 0x50,
0x6f, 0x72, 0x74, 0x61, 0x70, 0x61, 0x70, 0x65, 0x6c, 0x65, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x43, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65,
0x73, 0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x65,
0x63, 0x74, 0x61, 0x6e, 0x64, 0x6f, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0x53, 0x69, 0x6e, 0x63, 0x72, 0x6f, 0x6e, 0x69,
0x7a, 0x61, 0x6e, 0x64, 0x6f, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20,
0x22, 0x42, 0x6c, 0x6f, 0x71, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x20, 0x68, 0x61, 0x79, 0x20,
0x64, 0x69, 0x72, 0x65, 0x63, 0x63, 0x69, 0x6f, 0x6e, 0x65, 0x73, 0x20,
0x64, 0x69, 0x73, 0x70, 0x6f, 0x6e, 0x69, 0x62, 0x6c, 0x65, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x72, 0x72,
0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75,
0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xc3, 0x89, 0x78,
0x69, 0x74, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77,
0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x64,
0x76, 0x65, 0x72, 0x74, 0x65, 0x6e, 0x63, 0x69, 0x61, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x4c, 0x61, 0x20, 0x63, 0x61,
0x6e, 0x74, 0x69, 0x64, 0x61, 0x64, 0x20, 0x65, 0x78, 0x63, 0x65, 0x64,
0x65, 0x20, 0x65, 0x6c, 0x20, 0x73, 0x61, 0x6c, 0x64, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a,
0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x63, 0x69, 0xc3,
0xb3, 0x6e, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x61, 0x64, 0x61, 0x20, 0x65,
0x78, 0x69, 0x74, 0x6f, 0x73, 0x61, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x22,
0x0a, 0x7d, 0x0a
};
unsigned int res_lang_es_json_len = 4587;

388
src/embedded/lang_fr.h Normal file
View File

@@ -0,0 +1,388 @@
unsigned char res_lang_fr_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x6f, 0x6c, 0x64, 0x65, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x65, 0x72, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x63, 0x65, 0x76, 0x6f, 0x69,
0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x61, 0x67,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x65,
0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0xc5, 0x93, 0x75, 0x64, 0x73,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b,
0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x61, 0x72, 0x63, 0x68, 0xc3,
0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x72,
0x61, 0x6d, 0xc3, 0xa8, 0x74, 0x72, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
0x22, 0x3a, 0x20, 0x22, 0x52, 0xc3, 0xa9, 0x73, 0x75, 0x6d, 0xc3, 0xa9,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68, 0x69, 0x65,
0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x72, 0x6f, 0x74,
0xc3, 0xa9, 0x67, 0xc3, 0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
0x22, 0x3a, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x6f, 0x74, 0x61,
0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22,
0x4e, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0xc3,
0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x79, 0x6f, 0x75,
0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x56, 0x6f, 0x73, 0x20, 0x61, 0x64, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x7a,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x2d, 0x5a,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x41,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x2d, 0x54, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x75, 0x63,
0x75, 0x6e, 0x65, 0x20, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20,
0x74, 0x72, 0x6f, 0x75, 0x76, 0xc3, 0xa9, 0x65, 0x2e, 0x20, 0x43, 0x72,
0xc3, 0xa9, 0x65, 0x7a, 0x2d, 0x65, 0x6e, 0x20, 0x75, 0x6e, 0x65, 0x20,
0x61, 0x76, 0x65, 0x63, 0x20, 0x6c, 0x65, 0x73, 0x20, 0x62, 0x6f, 0x75,
0x74, 0x6f, 0x6e, 0x73, 0x20, 0x63, 0x69, 0x2d, 0x64, 0x65, 0x73, 0x73,
0x75, 0x73, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e,
0x65, 0x77, 0x5f, 0x7a, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x75, 0x76, 0x65, 0x6c, 0x6c, 0x65,
0x20, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x2d, 0x5a, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x6f, 0x75, 0x76, 0x65, 0x6c, 0x6c, 0x65, 0x20, 0x61, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x2d, 0x54, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x79, 0x70,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x70, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x43, 0x6f, 0x70, 0x69, 0x65, 0x72, 0x20, 0x6c, 0x27,
0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70,
0x6c, 0xc3, 0xa8, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74,
0x68, 0x69, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x65, 0x72, 0x20, 0x64,
0x65, 0x70, 0x75, 0x69, 0x73, 0x20, 0x63, 0x65, 0x74, 0x74, 0x65, 0x20,
0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x20, 0x6c, 0x61,
0x20, 0x63, 0x6c, 0xc3, 0xa9, 0x20, 0x70, 0x72, 0x69, 0x76, 0xc3, 0xa9,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f,
0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6f, 0x72,
0x74, 0x65, 0x72, 0x20, 0x6c, 0x61, 0x20, 0x63, 0x6c, 0xc3, 0xa9, 0x20,
0x64, 0x65, 0x20, 0x76, 0x69, 0x73, 0x75, 0x61, 0x6c, 0x69, 0x73, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x68, 0x6f, 0x77, 0x5f, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x41, 0x66, 0x66, 0x69, 0x63, 0x68, 0x65, 0x72,
0x20, 0x6c, 0x65, 0x20, 0x51, 0x52, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x74, 0x5f, 0x63,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22,
0x4e, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0xc3,
0xa9, 0x20, 0x61, 0x75, 0x20, 0x64, 0xc3, 0xa9, 0x6d, 0x6f, 0x6e, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70,
0x61, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x50,
0x61, 0x79, 0x65, 0x72, 0x20, 0x64, 0x65, 0x70, 0x75, 0x69, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f,
0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x65,
0x72, 0x20, 0xc3, 0xa0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x6f,
0x6e, 0x74, 0x61, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0xc3, 0xa9,
0x6d, 0x6f, 0x20, 0x28, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x6e, 0x65,
0x6c, 0x2c, 0x20, 0x63, 0x68, 0x69, 0x66, 0x66, 0x72, 0xc3, 0xa9, 0x29,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65,
0x72, 0x5f, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x72, 0x61,
0x69, 0x73, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x69, 0x6e, 0x65, 0x75, 0x72,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x46, 0x72, 0x61, 0x69, 0x73, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x6e, 0x76, 0x6f, 0x79, 0x65, 0x72, 0x20, 0x6c, 0x61, 0x20, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x66, 0x66, 0x61, 0x63, 0x65, 0x72, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x53, 0xc3, 0xa9, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x6e, 0x65,
0x72, 0x20, 0x75, 0x6e, 0x65, 0x20, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x70, 0x61, 0x73, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x61, 0x78, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x61, 0x78, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62,
0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x6e,
0x69, 0x62, 0x6c, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x6f, 0x72, 0x6d, 0x61,
0x74, 0x20, 0x64, 0x27, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20,
0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x7a, 0x5f, 0x6f,
0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x74, 0x65, 0x20,
0x3a, 0x20, 0x6c, 0x65, 0x73, 0x20, 0x6d, 0xc3, 0xa9, 0x6d, 0x6f, 0x73,
0x20, 0x6e, 0x65, 0x20, 0x73, 0x6f, 0x6e, 0x74, 0x20, 0x64, 0x69, 0x73,
0x70, 0x6f, 0x6e, 0x69, 0x62, 0x6c, 0x65, 0x73, 0x20, 0x71, 0x75, 0x27,
0x65, 0x6e, 0x20, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x61, 0x6e, 0x74, 0x20,
0x76, 0x65, 0x72, 0x73, 0x20, 0x64, 0x65, 0x73, 0x20, 0x61, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x74, 0xc3, 0xa9,
0x67, 0xc3, 0xa9, 0x65, 0x73, 0x20, 0x28, 0x7a, 0x29, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74,
0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x63, 0x61, 0x72, 0x61, 0x63,
0x74, 0xc3, 0xa8, 0x72, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x22, 0x3a,
0x20, 0x22, 0xc3, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x6e, 0x76, 0x6f, 0x69, 0x20, 0x64, 0x65, 0x20, 0x6c, 0x61, 0x20, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x72, 0x20, 0x6c, 0x27, 0x65, 0x6e,
0x76, 0x6f, 0x69, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x72, 0x20, 0x6c, 0x61, 0x20, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a,
0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x72, 0x20,
0x65, 0x74, 0x20, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x65, 0x72, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c,
0x22, 0x3a, 0x20, 0x22, 0x41, 0x6e, 0x6e, 0x75, 0x6c, 0x65, 0x72, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65,
0x69, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x6f, 0x73, 0x20, 0x61,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x64, 0x65, 0x20, 0x72,
0xc3, 0xa9, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f, 0x73,
0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x6f, 0x75, 0x76, 0x65, 0x6c, 0x6c, 0x65, 0x20, 0x61, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x2d, 0x7a, 0x20, 0x28, 0x70, 0x72, 0x6f, 0x74, 0xc3,
0xa9, 0x67, 0xc3, 0xa9, 0x65, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x6f, 0x75, 0x76, 0x65, 0x6c, 0x6c, 0x65, 0x20, 0x61, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x2d, 0x74, 0x20, 0x28, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x29, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f,
0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x44,
0xc3, 0xa9, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x64, 0x65, 0x20, 0x6c,
0x27, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e, 0x5f,
0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x22,
0x56, 0x6f, 0x69, 0x72, 0x20, 0x73, 0x75, 0x72, 0x20, 0x6c, 0x27, 0x65,
0x78, 0x70, 0x6c, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x75, 0x72, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x51, 0x52, 0x20, 0x43, 0x6f, 0x64, 0x65,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22,
0x3a, 0x20, 0x22, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x20,
0x75, 0x6e, 0x20, 0x70, 0x61, 0x69, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x44, 0x61, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x75, 0x74, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x72, 0x6d, 0xc3, 0xa9, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x6e, 0x20, 0x61, 0x74, 0x74, 0x65, 0x6e, 0x74, 0x65, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a,
0x20, 0x22, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0xc3, 0xa9, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x64, 0x22, 0x3a, 0x20, 0x22, 0x72, 0x65, 0xc3, 0xa7, 0x75, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x22,
0x3a, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0xc3, 0xa9, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x43,
0x6f, 0x6e, 0x74, 0x72, 0xc3, 0xb4, 0x6c, 0x65, 0x20, 0x64, 0x75, 0x20,
0x6d, 0x69, 0x6e, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x69,
0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x44, 0xc3, 0xa9, 0x6d, 0x61, 0x72,
0x72, 0x65, 0x72, 0x20, 0x6c, 0x65, 0x20, 0x6d, 0x69, 0x6e, 0x61, 0x67,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f,
0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0x41, 0x72, 0x72, 0xc3, 0xaa, 0x74, 0x65, 0x72, 0x20, 0x6c, 0x65, 0x20,
0x6d, 0x69, 0x6e, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68, 0x72,
0x65, 0x61, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x68, 0x72, 0x65,
0x61, 0x64, 0x73, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x69, 0x6e, 0x61, 0x67,
0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69,
0x63, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73,
0x74, 0x69, 0x71, 0x75, 0x65, 0x73, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x69,
0x6e, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61,
0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61,
0x74, 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x20, 0x64, 0x75, 0x20,
0x72, 0xc3, 0xa9, 0x73, 0x65, 0x61, 0x75, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74,
0x79, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75,
0x6c, 0x74, 0xc3, 0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x65, 0x6d,
0x70, 0x73, 0x20, 0x65, 0x73, 0x74, 0x2e, 0x20, 0x61, 0x76, 0x61, 0x6e,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x66, 0x66,
0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x44,
0xc3, 0x89, 0x53, 0x41, 0x43, 0x54, 0x49, 0x56, 0xc3, 0x89, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67,
0x5f, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x61, 0x67,
0x65, 0x20, 0x41, 0x43, 0x54, 0x49, 0x56, 0xc3, 0x89, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x4e, 0xc5, 0x93, 0x75, 0x64, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0xc3, 0xa9, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65,
0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0xc5, 0x93, 0x75, 0x64, 0x73,
0x20, 0x62, 0x61, 0x6e, 0x6e, 0x69, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x20, 0x49, 0x50, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x48,
0x61, 0x75, 0x74, 0x65, 0x75, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x69,
0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x6e, 0x6e, 0x69, 0x72, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x62, 0x61, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0x44, 0xc3, 0xa9, 0x62, 0x61, 0x6e, 0x6e, 0x69,
0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65,
0x61, 0x72, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x62, 0x61, 0x6e, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x75,
0x73, 0x20, 0x6c, 0x65, 0x73, 0x20, 0x62, 0x61, 0x6e, 0x6e, 0x69, 0x73,
0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x68,
0x61, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x47, 0x72, 0x61, 0x70, 0x68,
0x69, 0x71, 0x75, 0x65, 0x20, 0x64, 0x65, 0x73, 0x20, 0x70, 0x72, 0x69,
0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72,
0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x50, 0x72, 0x69, 0x78, 0x20, 0x61, 0x63, 0x74, 0x75, 0x65,
0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x56,
0x61, 0x72, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x34, 0x68,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f,
0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x6f,
0x6c, 0x75, 0x6d, 0x65, 0x20, 0x32, 0x34, 0x68, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x63,
0x61, 0x70, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x61, 0x70, 0x69, 0x74, 0x61,
0x6c, 0x69, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
0x22, 0x3a, 0x20, 0x22, 0x47, 0xc3, 0xa9, 0x6e, 0xc3, 0xa9, 0x72, 0x61,
0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73,
0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x66, 0x66, 0x69,
0x63, 0x68, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22,
0x52, 0xc3, 0xa9, 0x73, 0x65, 0x61, 0x75, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x54, 0x68, 0xc3, 0xa8, 0x6d, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x5f,
0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x72, 0x61,
0x67, 0x6f, 0x6e, 0x58, 0x20, 0x28, 0x56, 0x65, 0x72, 0x74, 0x29, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x72, 0x6b, 0x22,
0x3a, 0x20, 0x22, 0x53, 0x6f, 0x6d, 0x62, 0x72, 0x65, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a,
0x20, 0x22, 0x43, 0x6c, 0x61, 0x69, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x41, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x73, 0x65, 0x72, 0x20, 0x6c, 0x65,
0x73, 0x20, 0x66, 0x72, 0x61, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x73,
0x6f, 0x6e, 0x6e, 0x61, 0x6c, 0x69, 0x73, 0xc3, 0xa9, 0x73, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x73, 0x65, 0x5f, 0x65, 0x6d,
0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x73, 0x65,
0x72, 0x20, 0x6c, 0x65, 0x20, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78,
0x64, 0x20, 0x69, 0x6e, 0x74, 0xc3, 0xa9, 0x67, 0x72, 0xc3, 0xa9, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c,
0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x65, 0x72, 0x6d, 0x65,
0x72, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69,
0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x69, 0x63, 0x68, 0x69, 0x65,
0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x69,
0x74, 0x22, 0x3a, 0x20, 0x22, 0xc3, 0x89, 0x64, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65,
0x77, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x66, 0x66, 0x69, 0x63, 0x68, 0x61,
0x67, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65,
0x6c, 0x70, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x69, 0x64, 0x65, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72,
0x20, 0x75, 0x6e, 0x65, 0x20, 0x63, 0x6c, 0xc3, 0xa9, 0x20, 0x70, 0x72,
0x69, 0x76, 0xc3, 0xa9, 0x65, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x61, 0x75,
0x76, 0x65, 0x67, 0x61, 0x72, 0x64, 0x65, 0x72, 0x20, 0x6c, 0x65, 0x20,
0x70, 0x6f, 0x72, 0x74, 0x65, 0x66, 0x65, 0x75, 0x69, 0x6c, 0x6c, 0x65,
0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65,
0x78, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x51, 0x75, 0x69, 0x74, 0x74,
0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62,
0x6f, 0x75, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x22,
0x3a, 0x20, 0x22, 0xc3, 0x80, 0x20, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73,
0x20, 0x64, 0x27, 0x4f, 0x62, 0x73, 0x69, 0x64, 0x69, 0x61, 0x6e, 0x44,
0x72, 0x61, 0x67, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f, 0x77,
0x22, 0x3a, 0x20, 0x22, 0x41, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x69, 0x73,
0x65, 0x72, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e,
0x74, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62,
0x6f, 0x75, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xc3, 0x80, 0x20, 0x70, 0x72,
0x6f, 0x70, 0x6f, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f,
0x63, 0x6c, 0x69, 0x70, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x43, 0x6f, 0x70, 0x69, 0x65, 0x72, 0x20, 0x64, 0x61, 0x6e, 0x73,
0x20, 0x6c, 0x65, 0x20, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x2d, 0x70,
0x61, 0x70, 0x69, 0x65, 0x72, 0x73, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0xc3,
0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x44, 0xc3, 0xa9, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0xc3,
0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x43,
0x6f, 0x6e, 0x6e, 0x65, 0x78, 0x69, 0x6f, 0x6e, 0x2e, 0x2e, 0x2e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x79, 0x6e, 0x63, 0x69,
0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x79, 0x6e, 0x63, 0x68, 0x72,
0x6f, 0x6e, 0x69, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x2e, 0x2e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x75, 0x63, 0x75, 0x6e,
0x65, 0x20, 0x61, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x20, 0x64, 0x69,
0x73, 0x70, 0x6f, 0x6e, 0x69, 0x62, 0x6c, 0x65, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a,
0x20, 0x22, 0x45, 0x72, 0x72, 0x65, 0x75, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x53, 0x75, 0x63, 0x63, 0xc3, 0xa8, 0x73, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73,
0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x63,
0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x4c, 0x65, 0x20, 0x6d, 0x6f, 0x6e, 0x74, 0x61,
0x6e, 0x74, 0x20, 0x64, 0xc3, 0xa9, 0x70, 0x61, 0x73, 0x73, 0x65, 0x20,
0x6c, 0x65, 0x20, 0x73, 0x6f, 0x6c, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x65, 0x6e, 0x76, 0x6f, 0x79, 0xc3, 0xa9, 0x65, 0x20, 0x61, 0x76, 0x65,
0x63, 0x20, 0x73, 0x75, 0x63, 0x63, 0xc3, 0xa8, 0x73, 0x22, 0x0a, 0x7d,
0x0a
};
unsigned int res_lang_fr_json_len = 4609;

416
src/embedded/lang_ja.h Normal file
View File

@@ -0,0 +1,416 @@
unsigned char res_lang_ja_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xae, 0x8b, 0xe9, 0xab, 0x98,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0x97, 0xe5, 0x8f, 0x96, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe5,
0x8f, 0x96, 0xe5, 0xbc, 0x95, 0xe5, 0xb1, 0xa5, 0xe6, 0xad, 0xb4, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x9e, 0xe3, 0x82, 0xa4, 0xe3,
0x83, 0x8b, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20,
0x22, 0xe3, 0x83, 0x8e, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x89, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74,
0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x9e, 0xe3, 0x83, 0xbc, 0xe3, 0x82,
0xb1, 0xe3, 0x83, 0x83, 0xe3, 0x83, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xe8, 0xa8, 0xad, 0xe5, 0xae, 0x9a, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72,
0x79, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xa6, 0x82, 0xe8, 0xa6, 0x81, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68, 0x69, 0x65, 0x6c,
0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xb7, 0xe3, 0x83,
0xbc, 0xe3, 0x83, 0xab, 0xe3, 0x83, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x88, 0xe3, 0x83, 0xa9,
0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb9, 0xe3, 0x83, 0x91, 0xe3, 0x83, 0xac,
0xe3, 0x83, 0xb3, 0xe3, 0x83, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe5,
0x90, 0x88, 0xe8, 0xa8, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xe6, 0x9c, 0xaa, 0xe7, 0xa2, 0xba, 0xe8, 0xaa,
0x8d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x79, 0x6f, 0x75,
0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac,
0xe3, 0x82, 0xb9, 0xe4, 0xb8, 0x80, 0xe8, 0xa6, 0xa7, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x7a, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x5a, 0x2d, 0xe3, 0x82,
0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x2d, 0xe3,
0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3,
0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0xe3,
0x81, 0x8c, 0xe8, 0xa6, 0x8b, 0xe3, 0x81, 0xa4, 0xe3, 0x81, 0x8b, 0xe3,
0x82, 0x8a, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0x9b, 0xe3, 0x82, 0x93, 0xe3,
0x80, 0x82, 0xe4, 0xb8, 0x8a, 0xe3, 0x81, 0xae, 0xe3, 0x83, 0x9c, 0xe3,
0x82, 0xbf, 0xe3, 0x83, 0xb3, 0xe3, 0x81, 0xa7, 0xe4, 0xbd, 0x9c, 0xe6,
0x88, 0x90, 0xe3, 0x81, 0x97, 0xe3, 0x81, 0xa6, 0xe3, 0x81, 0x8f, 0xe3,
0x81, 0xa0, 0xe3, 0x81, 0x95, 0xe3, 0x81, 0x84, 0xe3, 0x80, 0x82, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xe6, 0x96, 0xb0, 0xe8, 0xa6, 0x8f, 0x20, 0x5a, 0x2d, 0xe3, 0x82, 0xa2,
0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x96,
0xb0, 0xe8, 0xa6, 0x8f, 0x20, 0x54, 0x2d, 0xe3, 0x82, 0xa2, 0xe3, 0x83,
0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe3,
0x82, 0xbf, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x97, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac,
0xe3, 0x82, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x70, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac,
0xe3, 0x82, 0xb9, 0xe3, 0x82, 0x92, 0xe3, 0x82, 0xb3, 0xe3, 0x83, 0x94,
0xe3, 0x83, 0xbc, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6e, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x68, 0x69,
0x73, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20,
0x22, 0xe3, 0x81, 0x93, 0xe3, 0x81, 0xae, 0xe3, 0x82, 0xa2, 0xe3, 0x83,
0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0xe3, 0x81, 0x8b, 0xe3, 0x82,
0x89, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0xe7, 0xa7, 0x98, 0xe5, 0xaf, 0x86, 0xe9, 0x8d, 0xb5, 0xe3, 0x82,
0x92, 0xe3, 0x82, 0xa8, 0xe3, 0x82, 0xaf, 0xe3, 0x82, 0xb9, 0xe3, 0x83,
0x9d, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x76, 0x69,
0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0xe9, 0x96, 0xb2, 0xe8, 0xa6, 0xa7, 0xe9, 0x8d, 0xb5, 0xe3, 0x82,
0x92, 0xe3, 0x82, 0xa8, 0xe3, 0x82, 0xaf, 0xe3, 0x82, 0xb9, 0xe3, 0x83,
0x9d, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x71, 0x72, 0x5f, 0x63,
0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x51, 0x52, 0xe3, 0x82, 0xb3,
0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x89, 0xe3, 0x82, 0x92, 0xe8, 0xa1, 0xa8,
0xe7, 0xa4, 0xba, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e,
0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x87, 0xe3, 0x83, 0xbc, 0xe3, 0x83,
0xa2, 0xe3, 0x83, 0xb3, 0xe3, 0x81, 0xab, 0xe6, 0x9c, 0xaa, 0xe6, 0x8e,
0xa5, 0xe7, 0xb6, 0x9a, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6d,
0x22, 0x3a, 0x20, 0x22, 0xe6, 0x94, 0xaf, 0xe6, 0x89, 0x95, 0xe5, 0x85,
0x83, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e,
0x64, 0x5f, 0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81, 0xe9,
0x87, 0x91, 0xe5, 0x85, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe9,
0x87, 0x91, 0xe9, 0xa1, 0x8d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0xa1,
0xe3, 0x83, 0xa2, 0xef, 0xbc, 0x88, 0xe4, 0xbb, 0xbb, 0xe6, 0x84, 0x8f,
0xe3, 0x80, 0x81, 0xe6, 0x9a, 0x97, 0xe5, 0x8f, 0xb7, 0xe5, 0x8c, 0x96,
0xef, 0xbc, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22,
0xe3, 0x83, 0x9e, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x8a, 0xe3, 0x83, 0xbc,
0xe6, 0x89, 0x8b, 0xe6, 0x95, 0xb0, 0xe6, 0x96, 0x99, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22,
0xe6, 0x89, 0x8b, 0xe6, 0x95, 0xb0, 0xe6, 0x96, 0x99, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20,
0x22, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91, 0xe3, 0x81, 0x99, 0xe3, 0x82,
0x8b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65,
0x61, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xaf, 0xe3, 0x83, 0xaa,
0xe3, 0x82, 0xa2, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3,
0x83, 0xac, 0xe3, 0x82, 0xb9, 0xe3, 0x82, 0x92, 0xe9, 0x81, 0xb8, 0xe6,
0x8a, 0x9e, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x70, 0x61, 0x73, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe8, 0xb2,
0xbc, 0xe3, 0x82, 0x8a, 0xe4, 0xbb, 0x98, 0xe3, 0x81, 0x91, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x78, 0x22, 0x3a, 0x20,
0x22, 0xe6, 0x9c, 0x80, 0xe5, 0xa4, 0xa7, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0x88, 0xa9, 0xe7, 0x94, 0xa8, 0xe5, 0x8f,
0xaf, 0xe8, 0x83, 0xbd, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0x84, 0xa1, 0xe5, 0x8a,
0xb9, 0xe3, 0x81, 0xaa, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83,
0xac, 0xe3, 0x82, 0xb9, 0xe5, 0xbd, 0xa2, 0xe5, 0xbc, 0x8f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x7a,
0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xb3, 0xa8,
0xef, 0xbc, 0x9a, 0xe3, 0x83, 0xa1, 0xe3, 0x83, 0xa2, 0xe3, 0x81, 0xaf,
0xe3, 0x82, 0xb7, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0xab, 0xe3, 0x83, 0x89,
0xef, 0xbc, 0x88, 0x7a, 0xef, 0xbc, 0x89, 0xe3, 0x82, 0xa2, 0xe3, 0x83,
0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0xe3, 0x81, 0xb8, 0xe3, 0x81,
0xae, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91, 0xe6, 0x99, 0x82, 0xe3, 0x81,
0xae, 0xe3, 0x81, 0xbf, 0xe5, 0x88, 0xa9, 0xe7, 0x94, 0xa8, 0xe5, 0x8f,
0xaf, 0xe8, 0x83, 0xbd, 0xe3, 0x81, 0xa7, 0xe3, 0x81, 0x99, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63,
0x74, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x96, 0x87, 0xe5,
0xad, 0x97, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72,
0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91,
0xe5, 0x85, 0x83, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x6f, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91, 0xe5,
0x85, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81,
0xe9, 0x87, 0x91, 0xe4, 0xb8, 0xad, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x73, 0x65,
0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91,
0xe7, 0xa2, 0xba, 0xe8, 0xaa, 0x8d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20,
0x22, 0xe5, 0x8f, 0x96, 0xe5, 0xbc, 0x95, 0xe7, 0xa2, 0xba, 0xe8, 0xaa,
0x8d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6e,
0x64, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xa2, 0xba, 0xe8, 0xaa, 0x8d, 0xe3,
0x81, 0x97, 0xe3, 0x81, 0xa6, 0xe9, 0x80, 0x81, 0xe9, 0x87, 0x91, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x6e, 0x63, 0x65,
0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xad, 0xe3, 0x83, 0xa3, 0xe3,
0x83, 0xb3, 0xe3, 0x82, 0xbb, 0xe3, 0x83, 0xab, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x69,
0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0x97, 0xe5, 0x8f, 0x96, 0xe3, 0x82,
0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f,
0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22,
0xe6, 0x96, 0xb0, 0xe8, 0xa6, 0x8f, 0x20, 0x7a, 0x2d, 0xe3, 0x82, 0xa2,
0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0xef, 0xbc, 0x88,
0xe3, 0x82, 0xb7, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0xab, 0xe3, 0x83, 0x89,
0xef, 0xbc, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e,
0x65, 0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61,
0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x96, 0xb0, 0xe8,
0xa6, 0x8f, 0x20, 0x74, 0x2d, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3,
0x83, 0xac, 0xe3, 0x82, 0xb9, 0xef, 0xbc, 0x88, 0xe3, 0x83, 0x88, 0xe3,
0x83, 0xa9, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb9, 0xe3, 0x83, 0x91, 0xe3,
0x83, 0xac, 0xe3, 0x83, 0xb3, 0xe3, 0x83, 0x88, 0xef, 0xbc, 0x89, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3,
0x82, 0xb9, 0xe8, 0xa9, 0xb3, 0xe7, 0xb4, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e, 0x5f,
0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x22,
0xe3, 0x82, 0xa8, 0xe3, 0x82, 0xaf, 0xe3, 0x82, 0xb9, 0xe3, 0x83, 0x97,
0xe3, 0x83, 0xad, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0xa9, 0xe3, 0x83, 0xbc,
0xe3, 0x81, 0xa7, 0xe8, 0xa1, 0xa8, 0xe7, 0xa4, 0xba, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x51, 0x52, 0xe3, 0x82, 0xb3, 0xe3, 0x83, 0xbc,
0xe3, 0x83, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x94, 0xaf, 0xe6, 0x89, 0x95,
0xe3, 0x81, 0x84, 0xe3, 0x82, 0x92, 0xe8, 0xa6, 0x81, 0xe6, 0xb1, 0x82,
0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x97, 0xa5, 0xe4, 0xbb, 0x98, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xb9, 0xe3, 0x83, 0x86, 0xe3,
0x83, 0xbc, 0xe3, 0x82, 0xbf, 0xe3, 0x82, 0xb9, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xa2, 0xba,
0xe8, 0xaa, 0x8d, 0xe6, 0x95, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22,
0x3a, 0x20, 0x22, 0xe7, 0xa2, 0xba, 0xe8, 0xaa, 0x8d, 0xe6, 0xb8, 0x88,
0xe3, 0x81, 0xbf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xbf,
0x9d, 0xe7, 0x95, 0x99, 0xe4, 0xb8, 0xad, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe9,
0x80, 0x81, 0xe9, 0x87, 0x91, 0xe6, 0xb8, 0x88, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0x97, 0xe5, 0x8f, 0x96, 0xe6, 0xb8,
0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8e, 0xa1, 0xe6, 0x8e, 0x98,
0xe6, 0xb8, 0x88, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x9e, 0xe3, 0x82, 0xa4,
0xe3, 0x83, 0x8b, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb0, 0xe5, 0x88, 0xb6,
0xe5, 0xbe, 0xa1, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0xe3, 0x83, 0x9e, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x8b,
0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb0, 0xe9, 0x96, 0x8b, 0xe5, 0xa7, 0x8b,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f, 0x70,
0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe3,
0x83, 0x9e, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x8b, 0xe3, 0x83, 0xb3, 0xe3,
0x82, 0xb0, 0xe5, 0x81, 0x9c, 0xe6, 0xad, 0xa2, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74,
0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83,
0x9e, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x8b, 0xe3, 0x83, 0xb3, 0xe3, 0x82,
0xb0, 0xe3, 0x82, 0xb9, 0xe3, 0x83, 0xac, 0xe3, 0x83, 0x83, 0xe3, 0x83,
0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69,
0x63, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x9e, 0xe3, 0x82, 0xa4,
0xe3, 0x83, 0x8b, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb0, 0xe7, 0xb5, 0xb1,
0xe8, 0xa8, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0xad, 0xe3, 0x83, 0xbc, 0xe3,
0x82, 0xab, 0xe3, 0x83, 0xab, 0xe3, 0x83, 0x8f, 0xe3, 0x83, 0x83, 0xe3,
0x82, 0xb7, 0xe3, 0x83, 0xa5, 0xe3, 0x83, 0xac, 0xe3, 0x83, 0xbc, 0xe3,
0x83, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61,
0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x8d, 0xe3, 0x83, 0x83,
0xe3, 0x83, 0x88, 0xe3, 0x83, 0xaf, 0xe3, 0x83, 0xbc, 0xe3, 0x82, 0xaf,
0xe3, 0x83, 0x8f, 0xe3, 0x83, 0x83, 0xe3, 0x82, 0xb7, 0xe3, 0x83, 0xa5,
0xe3, 0x83, 0xac, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x88, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75,
0x6c, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x9b, 0xa3, 0xe6, 0x98,
0x93, 0xe5, 0xba, 0xa6, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8e, 0xa8,
0xe5, 0xae, 0x9a, 0xe3, 0x83, 0x96, 0xe3, 0x83, 0xad, 0xe3, 0x83, 0x83,
0xe3, 0x82, 0xaf, 0xe7, 0x99, 0xba, 0xe8, 0xa6, 0x8b, 0xe6, 0x99, 0x82,
0xe9, 0x96, 0x93, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x66, 0x66, 0x22, 0x3a, 0x20,
0x22, 0xe3, 0x83, 0x9e, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x8b, 0xe3, 0x83,
0xb3, 0xe3, 0x82, 0xb0, 0xe5, 0x81, 0x9c, 0xe6, 0xad, 0xa2, 0xe4, 0xb8,
0xad, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83,
0x9e, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0x8b, 0xe3, 0x83, 0xb3, 0xe3, 0x82,
0xb0, 0xe7, 0xa8, 0xbc, 0xe5, 0x83, 0x8d, 0xe4, 0xb8, 0xad, 0x22, 0x2c,
0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xe6, 0x8e, 0xa5, 0xe7, 0xb6, 0x9a, 0xe4, 0xb8, 0xad, 0xe3,
0x81, 0xae, 0xe3, 0x83, 0x8e, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x89, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e, 0x65,
0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3,
0x83, 0x96, 0xe3, 0x83, 0xad, 0xe3, 0x83, 0x83, 0xe3, 0x82, 0xaf, 0xe6,
0xb8, 0x88, 0xe3, 0x81, 0xbf, 0xe3, 0x83, 0x8e, 0xe3, 0x83, 0xbc, 0xe3,
0x83, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x70,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x49, 0x50, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89, 0xe3, 0x83, 0xac, 0xe3,
0x82, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x90,
0xe3, 0x83, 0xbc, 0xe3, 0x82, 0xb8, 0xe3, 0x83, 0xa7, 0xe3, 0x83, 0xb3,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x69, 0x67,
0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x96, 0xe3, 0x83, 0xad,
0xe3, 0x83, 0x83, 0xe3, 0x82, 0xaf, 0xe9, 0xab, 0x98, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20,
0x22, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x62, 0x61, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x96, 0xe3,
0x83, 0xad, 0xe3, 0x83, 0x83, 0xe3, 0x82, 0xaf, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x62, 0x61, 0x6e, 0x22, 0x3a, 0x20,
0x22, 0xe3, 0x83, 0x96, 0xe3, 0x83, 0xad, 0xe3, 0x83, 0x83, 0xe3, 0x82,
0xaf, 0xe8, 0xa7, 0xa3, 0xe9, 0x99, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x6c,
0x5f, 0x62, 0x61, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x81, 0x99,
0xe3, 0x81, 0xb9, 0xe3, 0x81, 0xa6, 0xe3, 0x81, 0xae, 0xe3, 0x83, 0x96,
0xe3, 0x83, 0xad, 0xe3, 0x83, 0x83, 0xe3, 0x82, 0xaf, 0xe3, 0x82, 0x92,
0xe8, 0xa7, 0xa3, 0xe9, 0x99, 0xa4, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61,
0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xbe, 0xa1, 0xe6, 0xa0, 0xbc,
0xe3, 0x83, 0x81, 0xe3, 0x83, 0xa3, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x88,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22, 0x3a, 0x20,
0x22, 0xe7, 0x8f, 0xbe, 0xe5, 0x9c, 0xa8, 0xe4, 0xbe, 0xa1, 0xe6, 0xa0,
0xbc, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x32,
0x34, 0xe6, 0x99, 0x82, 0xe9, 0x96, 0x93, 0xe5, 0xa4, 0x89, 0xe5, 0x8b,
0x95, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68,
0x5f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x32,
0x34, 0xe6, 0x99, 0x82, 0xe9, 0x96, 0x93, 0xe5, 0x8f, 0x96, 0xe5, 0xbc,
0x95, 0xe9, 0x87, 0x8f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x61, 0x70, 0x22, 0x3a,
0x20, 0x22, 0xe6, 0x99, 0x82, 0xe4, 0xbe, 0xa1, 0xe7, 0xb7, 0x8f, 0xe9,
0xa1, 0x8d, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67,
0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xb8,
0x80, 0xe8, 0x88, 0xac, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe8,
0xa1, 0xa8, 0xe7, 0xa4, 0xba, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22,
0xe3, 0x83, 0x8d, 0xe3, 0x83, 0x83, 0xe3, 0x83, 0x88, 0xe3, 0x83, 0xaf,
0xe3, 0x83, 0xbc, 0xe3, 0x82, 0xaf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe3,
0x83, 0x86, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x9e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
0x22, 0x3a, 0x20, 0x22, 0xe8, 0xa8, 0x80, 0xe8, 0xaa, 0x9e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e,
0x78, 0x5f, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44,
0x72, 0x61, 0x67, 0x6f, 0x6e, 0x58, 0xef, 0xbc, 0x88, 0xe3, 0x82, 0xb0,
0xe3, 0x83, 0xaa, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0xb3, 0xef, 0xbc, 0x89,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x72, 0x6b,
0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x80, 0xe3, 0x83, 0xbc, 0xe3, 0x82,
0xaf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x67,
0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0xa9, 0xe3, 0x82, 0xa4,
0xe3, 0x83, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61,
0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f,
0x66, 0x65, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xab, 0xe3,
0x82, 0xb9, 0xe3, 0x82, 0xbf, 0xe3, 0x83, 0xa0, 0xe6, 0x89, 0x8b, 0xe6,
0x95, 0xb0, 0xe6, 0x96, 0x99, 0xe3, 0x82, 0x92, 0xe8, 0xa8, 0xb1, 0xe5,
0x8f, 0xaf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x73,
0x65, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x64,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x86, 0x85,
0xe8, 0x94, 0xb5, 0x20, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64,
0x20, 0xe3, 0x82, 0x92, 0xe4, 0xbd, 0xbf, 0xe7, 0x94, 0xa8, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22, 0x3a,
0x20, 0x22, 0xe4, 0xbf, 0x9d, 0xe5, 0xad, 0x98, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20,
0x22, 0xe9, 0x96, 0x89, 0xe3, 0x81, 0x98, 0xe3, 0x82, 0x8b, 0x22, 0x2c,
0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22,
0x3a, 0x20, 0x22, 0xe3, 0x83, 0x95, 0xe3, 0x82, 0xa1, 0xe3, 0x82, 0xa4,
0xe3, 0x83, 0xab, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65,
0x64, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xb7, 0xa8, 0xe9, 0x9b,
0x86, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65,
0x77, 0x22, 0x3a, 0x20, 0x22, 0xe8, 0xa1, 0xa8, 0xe7, 0xa4, 0xba, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x70, 0x22,
0x3a, 0x20, 0x22, 0xe3, 0x83, 0x98, 0xe3, 0x83, 0xab, 0xe3, 0x83, 0x97,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b,
0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xa7, 0x98, 0xe5, 0xaf, 0x86,
0xe9, 0x8d, 0xb5, 0xe3, 0x82, 0x92, 0xe3, 0x82, 0xa4, 0xe3, 0x83, 0xb3,
0xe3, 0x83, 0x9d, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x88, 0x2e, 0x2e, 0x2e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x63, 0x6b,
0x75, 0x70, 0x5f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xe3, 0x82, 0xa6, 0xe3, 0x82, 0xa9, 0xe3, 0x83, 0xac, 0xe3, 0x83,
0x83, 0xe3, 0x83, 0x88, 0xe3, 0x82, 0x92, 0xe3, 0x83, 0x90, 0xe3, 0x83,
0x83, 0xe3, 0x82, 0xaf, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x83, 0xe3, 0x83,
0x97, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x65, 0x78, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xb5, 0x82, 0xe4,
0xba, 0x86, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62,
0x6f, 0x75, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x22,
0x3a, 0x20, 0x22, 0x4f, 0x62, 0x73, 0x69, 0x64, 0x69, 0x61, 0x6e, 0x44,
0x72, 0x61, 0x67, 0x6f, 0x6e, 0xe3, 0x81, 0xab, 0xe3, 0x81, 0xa4, 0xe3,
0x81, 0x84, 0xe3, 0x81, 0xa6, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f, 0x77,
0x22, 0x3a, 0x20, 0x22, 0xe4, 0xbb, 0x8a, 0xe3, 0x81, 0x99, 0xe3, 0x81,
0x90, 0xe6, 0x9b, 0xb4, 0xe6, 0x96, 0xb0, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xe3, 0x81, 0x93, 0xe3, 0x81, 0xae, 0xe3, 0x82, 0xa2, 0xe3, 0x83,
0x97, 0xe3, 0x83, 0xaa, 0xe3, 0x81, 0xab, 0xe3, 0x81, 0xa4, 0xe3, 0x81,
0x84, 0xe3, 0x81, 0xa6, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82,
0xa4, 0xe3, 0x83, 0xb3, 0xe3, 0x83, 0x9d, 0xe3, 0x83, 0xbc, 0xe3, 0x83,
0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xa8, 0xe3, 0x82,
0xaf, 0xe3, 0x82, 0xb9, 0xe3, 0x83, 0x9d, 0xe3, 0x83, 0xbc, 0xe3, 0x83,
0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70,
0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x62, 0x6f, 0x61,
0x72, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xaf, 0xe3, 0x83, 0xaa,
0xe3, 0x83, 0x83, 0xe3, 0x83, 0x97, 0xe3, 0x83, 0x9c, 0xe3, 0x83, 0xbc,
0xe3, 0x83, 0x89, 0xe3, 0x81, 0xab, 0xe3, 0x82, 0xb3, 0xe3, 0x83, 0x94,
0xe3, 0x83, 0xbc, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20,
0x22, 0xe6, 0x8e, 0xa5, 0xe7, 0xb6, 0x9a, 0xe6, 0xb8, 0x88, 0xe3, 0x81,
0xbf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20,
0x22, 0xe5, 0x88, 0x87, 0xe6, 0x96, 0xad, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8e, 0xa5, 0xe7, 0xb6, 0x9a, 0xe4,
0xb8, 0xad, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0xe5, 0x90, 0x8c, 0xe6, 0x9c, 0x9f, 0xe4, 0xb8, 0xad, 0x2e, 0x2e, 0x2e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x83, 0x96, 0xe3, 0x83, 0xad, 0xe3,
0x83, 0x83, 0xe3, 0x82, 0xaf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22,
0x3a, 0x20, 0x22, 0xe5, 0x88, 0xa9, 0xe7, 0x94, 0xa8, 0xe5, 0x8f, 0xaf,
0xe8, 0x83, 0xbd, 0xe3, 0x81, 0xaa, 0xe3, 0x82, 0xa2, 0xe3, 0x83, 0x89,
0xe3, 0x83, 0xac, 0xe3, 0x82, 0xb9, 0xe3, 0x81, 0x8c, 0xe3, 0x81, 0x82,
0xe3, 0x82, 0x8a, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0x9b, 0xe3, 0x82, 0x93,
0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xe3, 0x82, 0xa8, 0xe3, 0x83, 0xa9,
0xe3, 0x83, 0xbc, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x88,
0x90, 0xe5, 0x8a, 0x9f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe8,
0xad, 0xa6, 0xe5, 0x91, 0x8a, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65,
0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22,
0x3a, 0x20, 0x22, 0xe9, 0x87, 0x91, 0xe9, 0xa1, 0x8d, 0xe3, 0x81, 0x8c,
0xe6, 0xae, 0x8b, 0xe9, 0xab, 0x98, 0xe3, 0x82, 0x92, 0xe8, 0xb6, 0x85,
0xe3, 0x81, 0x88, 0xe3, 0x81, 0xa6, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0xbe,
0xe3, 0x81, 0x99, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x81, 0xe9, 0x87,
0x91, 0xe3, 0x81, 0x8c, 0xe5, 0xae, 0x8c, 0xe4, 0xba, 0x86, 0xe3, 0x81,
0x97, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0x97, 0xe3, 0x81, 0x9f, 0x22, 0x0a,
0x7d, 0x0a
};
unsigned int res_lang_ja_json_len = 4946;

379
src/embedded/lang_ko.h Normal file
View File

@@ -0,0 +1,379 @@
unsigned char res_lang_ko_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x9e, 0x94, 0xec, 0x95, 0xa1,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xb4, 0xea, 0xb8,
0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63,
0x65, 0x69, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb0, 0x9b, 0xea,
0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xea, 0xb1, 0xb0, 0xeb, 0x9e, 0x98, 0x20, 0xeb, 0x82, 0xb4,
0xec, 0x97, 0xad, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84,
0xea, 0xb5, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70,
0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x85, 0xb8, 0xeb,
0x93, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61,
0x72, 0x6b, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x8b, 0x9c, 0xec,
0x9e, 0xa5, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65,
0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x84,
0xa4, 0xec, 0xa0, 0x95, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22,
0xec, 0x9a, 0x94, 0xec, 0x95, 0xbd, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a,
0x20, 0x22, 0xec, 0xb0, 0xa8, 0xed, 0x8f, 0x90, 0xeb, 0x90, 0xa8, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xed, 0x88,
0xac, 0xeb, 0xaa, 0x85, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xed, 0x95, 0xa9,
0xea, 0xb3, 0x84, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75,
0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a,
0x20, 0x22, 0xeb, 0xaf, 0xb8, 0xed, 0x99, 0x95, 0xec, 0x9d, 0xb8, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x79, 0x6f, 0x75, 0x72, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20,
0x22, 0xeb, 0x82, 0xb4, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x7a, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x5a, 0x2d,
0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x2d, 0xec, 0xa3, 0xbc, 0xec, 0x86,
0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20,
0x22, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0xeb, 0xa5, 0xbc, 0x20, 0xec,
0xb0, 0xbe, 0xec, 0x9d, 0x84, 0x20, 0xec, 0x88, 0x98, 0x20, 0xec, 0x97,
0x86, 0xec, 0x8a, 0xb5, 0xeb, 0x8b, 0x88, 0xeb, 0x8b, 0xa4, 0x2e, 0x20,
0xec, 0x9c, 0x84, 0xec, 0x9d, 0x98, 0x20, 0xeb, 0xb2, 0x84, 0xed, 0x8a,
0xbc, 0xec, 0x9d, 0x84, 0x20, 0xec, 0x82, 0xac, 0xec, 0x9a, 0xa9, 0xed,
0x95, 0x98, 0xec, 0x97, 0xac, 0x20, 0xec, 0x83, 0x9d, 0xec, 0x84, 0xb1,
0xed, 0x95, 0x98, 0xec, 0x84, 0xb8, 0xec, 0x9a, 0x94, 0x2e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec,
0x83, 0x88, 0x20, 0x5a, 0x2d, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xec, 0x83, 0x88, 0x20, 0x54, 0x2d, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65,
0x22, 0x3a, 0x20, 0x22, 0xec, 0x9c, 0xa0, 0xed, 0x98, 0x95, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec,
0xa0, 0x84, 0xec, 0xb2, 0xb4, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c,
0x20, 0xeb, 0xb3, 0xb5, 0xec, 0x82, 0xac, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d,
0x5f, 0x74, 0x68, 0x69, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x9d, 0xb4, 0x20, 0xec, 0xa3, 0xbc,
0xec, 0x86, 0x8c, 0xec, 0x97, 0x90, 0xec, 0x84, 0x9c, 0x20, 0xeb, 0xb3,
0xb4, 0xeb, 0x82, 0xb4, 0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0xea, 0xb0, 0x9c, 0xec, 0x9d, 0xb8, 0xed, 0x82, 0xa4, 0x20, 0xeb,
0x82, 0xb4, 0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xb4, 0xea, 0xb8, 0xb0, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72,
0x74, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65,
0x79, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa1, 0xb0, 0xed, 0x9a, 0x8c, 0xed,
0x82, 0xa4, 0x20, 0xeb, 0x82, 0xb4, 0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xb4,
0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x68, 0x6f, 0x77, 0x5f, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x51, 0x52, 0x20, 0xec, 0xbd, 0x94, 0xeb, 0x93, 0x9c,
0x20, 0xed, 0x91, 0x9c, 0xec, 0x8b, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x8d, 0xb0, 0xeb,
0xaa, 0xac, 0xec, 0x97, 0x90, 0x20, 0xec, 0x97, 0xb0, 0xea, 0xb2, 0xb0,
0xeb, 0x90, 0x98, 0xec, 0xa7, 0x80, 0x20, 0xec, 0x95, 0x8a, 0xec, 0x9d,
0x8c, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x70, 0x61, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20,
0x22, 0xec, 0xb6, 0x9c, 0xea, 0xb8, 0x88, 0x20, 0xec, 0xa3, 0xbc, 0xec,
0x86, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65,
0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb0, 0x9b,
0xeb, 0x8a, 0x94, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x22, 0x3a, 0x20, 0x22, 0xea, 0xb8, 0x88, 0xec, 0x95, 0xa1, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x3a,
0x20, 0x22, 0xeb, 0xa9, 0x94, 0xeb, 0xaa, 0xa8, 0x20, 0x28, 0xec, 0x84,
0xa0, 0xed, 0x83, 0x9d, 0xec, 0x82, 0xac, 0xed, 0x95, 0xad, 0x2c, 0x20,
0xec, 0x95, 0x94, 0xed, 0x98, 0xb8, 0xed, 0x99, 0x94, 0xeb, 0x90, 0xa8,
0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1,
0x84, 0xea, 0xb5, 0xb4, 0xec, 0x9e, 0x90, 0x20, 0xec, 0x88, 0x98, 0xec,
0x88, 0x98, 0xeb, 0xa3, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x88, 0x98, 0xec,
0x88, 0x98, 0xeb, 0xa3, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xea, 0xb1, 0xb0,
0xeb, 0x9e, 0x98, 0x20, 0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xb4, 0xea, 0xb8,
0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65,
0x61, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa7, 0x80, 0xec, 0x9a, 0xb0,
0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x20,
0xec, 0x84, 0xa0, 0xed, 0x83, 0x9d, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x73, 0x74, 0x65, 0x22, 0x3a,
0x20, 0x22, 0xeb, 0xb6, 0x99, 0xec, 0x97, 0xac, 0xeb, 0x84, 0xa3, 0xea,
0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61,
0x78, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb5, 0x9c, 0xeb, 0x8c, 0x80, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c,
0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x82, 0xac, 0xec,
0x9a, 0xa9, 0x20, 0xea, 0xb0, 0x80, 0xeb, 0x8a, 0xa5, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xec, 0x9e, 0x98, 0xeb, 0xaa, 0xbb, 0xeb, 0x90, 0x9c, 0x20, 0xec, 0xa3,
0xbc, 0xec, 0x86, 0x8c, 0x20, 0xed, 0x98, 0x95, 0xec, 0x8b, 0x9d, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x5f,
0x7a, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb0,
0xb8, 0xea, 0xb3, 0xa0, 0x3a, 0x20, 0xeb, 0xa9, 0x94, 0xeb, 0xaa, 0xa8,
0xeb, 0x8a, 0x94, 0x20, 0xec, 0xb0, 0xa8, 0xed, 0x8f, 0x90, 0x28, 0x7a,
0x29, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0xeb, 0xa1, 0x9c, 0x20,
0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xbc, 0x20, 0xeb, 0x95, 0x8c, 0xeb, 0xa7,
0x8c, 0x20, 0xec, 0x82, 0xac, 0xec, 0x9a, 0xa9, 0xed, 0x95, 0xa0, 0x20,
0xec, 0x88, 0x98, 0x20, 0xec, 0x9e, 0x88, 0xec, 0x8a, 0xb5, 0xeb, 0x8b,
0x88, 0xeb, 0x8b, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xea, 0xb8, 0x80, 0xec, 0x9e, 0x90, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22,
0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xb8, 0x20, 0xec, 0x82, 0xac, 0xeb, 0x9e,
0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x22,
0x3a, 0x20, 0x22, 0xeb, 0xb0, 0x9b, 0xeb, 0x8a, 0x94, 0x20, 0xec, 0x82,
0xac, 0xeb, 0x9e, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xea,
0xb1, 0xb0, 0xeb, 0x9e, 0x98, 0x20, 0xec, 0xa0, 0x84, 0xec, 0x86, 0xa1,
0x20, 0xec, 0xa4, 0x91, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x73, 0x65, 0x6e, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb3, 0xb4, 0xeb, 0x82, 0xb4, 0xea, 0xb8,
0xb0, 0x20, 0xed, 0x99, 0x95, 0xec, 0x9d, 0xb8, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f,
0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22,
0x3a, 0x20, 0x22, 0xea, 0xb1, 0xb0, 0xeb, 0x9e, 0x98, 0x20, 0xed, 0x99,
0x95, 0xec, 0x9d, 0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x61, 0x6e, 0x64, 0x5f,
0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xed, 0x99, 0x95, 0xec,
0x9d, 0xb8, 0x20, 0xeb, 0xb0, 0x8f, 0x20, 0xeb, 0xb3, 0xb4, 0xeb, 0x82,
0xb4, 0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb7,
0xa8, 0xec, 0x86, 0x8c, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xec, 0x88, 0x98, 0xec, 0x8b, 0xa0, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86,
0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77,
0x5f, 0x7a, 0x5f, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22,
0x3a, 0x20, 0x22, 0xec, 0x83, 0x88, 0x20, 0x7a, 0x2d, 0xec, 0xa3, 0xbc,
0xec, 0x86, 0x8c, 0x20, 0x28, 0xec, 0xb0, 0xa8, 0xed, 0x8f, 0x90, 0x29,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f,
0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e,
0x74, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x83, 0x88, 0x20, 0x74, 0x2d, 0xec,
0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x20, 0x28, 0xed, 0x88, 0xac, 0xeb, 0xaa,
0x85, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x20,
0xec, 0x83, 0x81, 0xec, 0x84, 0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e, 0x5f, 0x65, 0x78,
0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xed, 0x83,
0x90, 0xec, 0x83, 0x89, 0xea, 0xb8, 0xb0, 0xec, 0x97, 0x90, 0xec, 0x84,
0x9c, 0x20, 0xeb, 0xb3, 0xb4, 0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x51, 0x52, 0x20, 0xec, 0xbd, 0x94, 0xeb, 0x93, 0x9c,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22,
0x3a, 0x20, 0x22, 0xea, 0xb2, 0xb0, 0xec, 0xa0, 0x9c, 0x20, 0xec, 0x9a,
0x94, 0xec, 0xb2, 0xad, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x64, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x82, 0xa0,
0xec, 0xa7, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x83, 0x81,
0xed, 0x83, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xed, 0x99, 0x95, 0xec, 0x9d, 0xb8, 0x20, 0xec,
0x88, 0x98, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xed,
0x99, 0x95, 0xec, 0x9d, 0xb8, 0xeb, 0x90, 0xa8, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0xeb, 0x8c, 0x80, 0xea, 0xb8, 0xb0, 0x20, 0xec, 0xa4,
0x91, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e,
0x74, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb3, 0xb4, 0xeb, 0x83, 0x84, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69,
0x76, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb0, 0x9b, 0xec, 0x9d,
0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5, 0xb4,
0xeb, 0x90, 0xa8, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5, 0xb4,
0x20, 0xec, 0xa0, 0x9c, 0xec, 0x96, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5,
0xb4, 0x20, 0xec, 0x8b, 0x9c, 0xec, 0x9e, 0x91, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5,
0xb4, 0x20, 0xec, 0xa4, 0x91, 0xec, 0xa7, 0x80, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74,
0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1,
0x84, 0xea, 0xb5, 0xb4, 0x20, 0xec, 0x8a, 0xa4, 0xeb, 0xa0, 0x88, 0xeb,
0x93, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69,
0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74,
0x69, 0x63, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5,
0xb4, 0x20, 0xed, 0x86, 0xb5, 0xea, 0xb3, 0x84, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xa1,
0x9c, 0xec, 0xbb, 0xac, 0x20, 0xed, 0x95, 0xb4, 0xec, 0x8b, 0x9c, 0xeb,
0xa0, 0x88, 0xec, 0x9d, 0xb4, 0xed, 0x8a, 0xb8, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22,
0xeb, 0x84, 0xa4, 0xed, 0x8a, 0xb8, 0xec, 0x9b, 0x8c, 0xed, 0x81, 0xac,
0x20, 0xed, 0x95, 0xb4, 0xec, 0x8b, 0x9c, 0xeb, 0xa0, 0x88, 0xec, 0x9d,
0xb4, 0xed, 0x8a, 0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x22, 0x3a,
0x20, 0x22, 0xeb, 0x82, 0x9c, 0xec, 0x9d, 0xb4, 0xeb, 0x8f, 0x84, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x73, 0x74, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x22, 0x3a, 0x20, 0x22, 0xec, 0x98, 0x88, 0xec, 0x83, 0x81, 0x20, 0xeb,
0xb8, 0x94, 0xeb, 0xa1, 0x9d, 0x20, 0xeb, 0xb0, 0x9c, 0xea, 0xb2, 0xac,
0x20, 0xec, 0x8b, 0x9c, 0xea, 0xb0, 0x84, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x66,
0x66, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5, 0xb4, 0x20,
0xea, 0xba, 0xbc, 0xec, 0xa7, 0x90, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x22,
0x3a, 0x20, 0x22, 0xec, 0xb1, 0x84, 0xea, 0xb5, 0xb4, 0x20, 0xec, 0xbc,
0x9c, 0xec, 0xa7, 0x90, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x70,
0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x97, 0xb0, 0xea,
0xb2, 0xb0, 0xeb, 0x90, 0x9c, 0x20, 0xeb, 0x85, 0xb8, 0xeb, 0x93, 0x9c,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e,
0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xec, 0xb0, 0xa8, 0xeb, 0x8b, 0xa8, 0xeb, 0x90, 0x9c, 0x20, 0xeb, 0x85,
0xb8, 0xeb, 0x93, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x49, 0x50, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86, 0x8c, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb2, 0x84, 0xec, 0xa0, 0x84,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x69, 0x67,
0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb8, 0x94, 0xeb, 0xa1, 0x9d,
0x20, 0xeb, 0x86, 0x92, 0xec, 0x9d, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xed,
0x95, 0x91, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xb0, 0xa8, 0xeb, 0x8b, 0xa8, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x62, 0x61, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0xec, 0xb0, 0xa8, 0xeb, 0x8b, 0xa8, 0x20, 0xed,
0x95, 0xb4, 0xec, 0xa0, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x62,
0x61, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xaa, 0xa8, 0xeb, 0x93,
0xa0, 0x20, 0xec, 0xb0, 0xa8, 0xeb, 0x8b, 0xa8, 0x20, 0xed, 0x95, 0xb4,
0xec, 0xa0, 0x9c, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x74, 0x22,
0x3a, 0x20, 0x22, 0xea, 0xb0, 0x80, 0xea, 0xb2, 0xa9, 0x20, 0xec, 0xb0,
0xa8, 0xed, 0x8a, 0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xed, 0x98, 0x84, 0xec, 0x9e, 0xac, 0x20,
0xea, 0xb0, 0x80, 0xea, 0xb2, 0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x32, 0x34, 0xec, 0x8b, 0x9c, 0xea, 0xb0, 0x84,
0x20, 0xeb, 0xb3, 0x80, 0xeb, 0x8f, 0x99, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x76, 0x6f, 0x6c, 0x75, 0x6d,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x32, 0x34, 0xec, 0x8b, 0x9c, 0xea, 0xb0,
0x84, 0x20, 0xea, 0xb1, 0xb0, 0xeb, 0x9e, 0x98, 0xeb, 0x9f, 0x89, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65,
0x74, 0x5f, 0x63, 0x61, 0x70, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x8b, 0x9c,
0xea, 0xb0, 0x80, 0xec, 0xb4, 0x9d, 0xec, 0x95, 0xa1, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x6c, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x9d, 0xbc, 0xeb, 0xb0, 0x98, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c,
0x61, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xed, 0x99, 0x94, 0xeb, 0xa9, 0xb4,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x84, 0xa4, 0xed, 0x8a,
0xb8, 0xec, 0x9b, 0x8c, 0xed, 0x81, 0xac, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22,
0xed, 0x85, 0x8c, 0xeb, 0xa7, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x3a,
0x20, 0x22, 0xec, 0x96, 0xb8, 0xec, 0x96, 0xb4, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x5f,
0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x72, 0x61,
0x67, 0x6f, 0x6e, 0x58, 0x20, 0x28, 0xeb, 0x85, 0xb9, 0xec, 0x83, 0x89,
0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x72,
0x6b, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x8b, 0xa4, 0xed, 0x81, 0xac, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x67, 0x68, 0x74,
0x22, 0x3a, 0x20, 0x22, 0xeb, 0x9d, 0xbc, 0xec, 0x9d, 0xb4, 0xed, 0x8a,
0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6c, 0x6c,
0x6f, 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x66, 0x65,
0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x82, 0xac, 0xec, 0x9a, 0xa9,
0xec, 0x9e, 0x90, 0x20, 0xec, 0x88, 0x98, 0xec, 0x88, 0x98, 0xeb, 0xa3,
0x8c, 0x20, 0xed, 0x97, 0x88, 0xec, 0x9a, 0xa9, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x75, 0x73, 0x65, 0x5f, 0x65, 0x6d, 0x62, 0x65,
0x64, 0x64, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x22,
0x3a, 0x20, 0x22, 0xeb, 0x82, 0xb4, 0xec, 0x9e, 0xa5, 0x20, 0x64, 0x72,
0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64, 0x20, 0xec, 0x82, 0xac, 0xec, 0x9a,
0xa9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x61, 0x76,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa0, 0x80, 0xec, 0x9e, 0xa5, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65,
0x22, 0x3a, 0x20, 0x22, 0xeb, 0x8b, 0xab, 0xea, 0xb8, 0xb0, 0x22, 0x2c,
0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22,
0x3a, 0x20, 0x22, 0xed, 0x8c, 0x8c, 0xec, 0x9d, 0xbc, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xed, 0x8e, 0xb8, 0xec, 0xa7, 0x91, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x22, 0x3a, 0x20, 0x22, 0xeb,
0xb3, 0xb4, 0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x68, 0x65, 0x6c, 0x70, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x8f, 0x84,
0xec, 0x9b, 0x80, 0xeb, 0xa7, 0x90, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22,
0xea, 0xb0, 0x9c, 0xec, 0x9d, 0xb8, 0xed, 0x82, 0xa4, 0x20, 0xea, 0xb0,
0x80, 0xec, 0xa0, 0xb8, 0xec, 0x98, 0xa4, 0xea, 0xb8, 0xb0, 0x2e, 0x2e,
0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x63,
0x6b, 0x75, 0x70, 0x5f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x22, 0x3a,
0x20, 0x22, 0xec, 0xa7, 0x80, 0xea, 0xb0, 0x91, 0x20, 0xeb, 0xb0, 0xb1,
0xec, 0x97, 0x85, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x78, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xec, 0xa2,
0x85, 0xeb, 0xa3, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x61, 0x62, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e,
0x78, 0x22, 0x3a, 0x20, 0x22, 0x4f, 0x62, 0x73, 0x69, 0x64, 0x69, 0x61,
0x6e, 0x44, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x20, 0xec, 0xa0, 0x95, 0xeb,
0xb3, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f, 0x77, 0x22, 0x3a, 0x20,
0x22, 0xec, 0xa7, 0x80, 0xea, 0xb8, 0x88, 0x20, 0xec, 0x83, 0x88, 0xeb,
0xa1, 0x9c, 0xea, 0xb3, 0xa0, 0xec, 0xb9, 0xa8, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x22, 0x3a,
0x20, 0x22, 0xec, 0xa0, 0x95, 0xeb, 0xb3, 0xb4, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a,
0x20, 0x22, 0xea, 0xb0, 0x80, 0xec, 0xa0, 0xb8, 0xec, 0x98, 0xa4, 0xea,
0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78,
0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0x82, 0xb4, 0xeb,
0xb3, 0xb4, 0xeb, 0x82, 0xb4, 0xea, 0xb8, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f,
0x63, 0x6c, 0x69, 0x70, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20,
0x22, 0xed, 0x81, 0xb4, 0xeb, 0xa6, 0xbd, 0xeb, 0xb3, 0xb4, 0xeb, 0x93,
0x9c, 0xec, 0x97, 0x90, 0x20, 0xeb, 0xb3, 0xb5, 0xec, 0x82, 0xac, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x97, 0xb0,
0xea, 0xb2, 0xb0, 0xeb, 0x90, 0xa8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x97, 0xb0, 0xea, 0xb2, 0xb0,
0x20, 0xeb, 0x81, 0x8a, 0xea, 0xb9, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0xec, 0x97, 0xb0, 0xea, 0xb2, 0xb0, 0x20,
0xec, 0xa4, 0x91, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20,
0x22, 0xeb, 0x8f, 0x99, 0xea, 0xb8, 0xb0, 0xed, 0x99, 0x94, 0x20, 0xec,
0xa4, 0x91, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xeb, 0xb8,
0x94, 0xeb, 0xa1, 0x9d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a,
0x20, 0x22, 0xec, 0x82, 0xac, 0xec, 0x9a, 0xa9, 0x20, 0xea, 0xb0, 0x80,
0xeb, 0x8a, 0xa5, 0xed, 0x95, 0x9c, 0x20, 0xec, 0xa3, 0xbc, 0xec, 0x86,
0x8c, 0x20, 0xec, 0x97, 0x86, 0xec, 0x9d, 0x8c, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a,
0x20, 0x22, 0xec, 0x98, 0xa4, 0xeb, 0xa5, 0x98, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xec, 0x84, 0xb1, 0xea, 0xb3, 0xb5, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0xea, 0xb2, 0xbd, 0xea, 0xb3, 0xa0, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xea, 0xb8, 0x88, 0xec,
0x95, 0xa1, 0xec, 0x9d, 0xb4, 0x20, 0xec, 0x9e, 0x94, 0xec, 0x95, 0xa1,
0xec, 0x9d, 0x84, 0x20, 0xec, 0xb4, 0x88, 0xea, 0xb3, 0xbc, 0xed, 0x95,
0xa9, 0xeb, 0x8b, 0x88, 0xeb, 0x8b, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xea,
0xb1, 0xb0, 0xeb, 0x9e, 0x98, 0xea, 0xb0, 0x80, 0x20, 0xec, 0x84, 0xb1,
0xea, 0xb3, 0xb5, 0xec, 0xa0, 0x81, 0xec, 0x9c, 0xbc, 0xeb, 0xa1, 0x9c,
0x20, 0xec, 0xa0, 0x84, 0xec, 0x86, 0xa1, 0xeb, 0x90, 0x98, 0xec, 0x97,
0x88, 0xec, 0x8a, 0xb5, 0xeb, 0x8b, 0x88, 0xeb, 0x8b, 0xa4, 0x22, 0x0a,
0x7d, 0x0a
};
unsigned int res_lang_ko_json_len = 4502;

382
src/embedded/lang_pt.h Normal file
View File

@@ -0,0 +1,382 @@
unsigned char res_lang_pt_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x61, 0x6c, 0x64, 0x6f, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x22,
0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69, 0x61, 0x72, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x63, 0x65, 0x62, 0x65, 0x72, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0xc3, 0xa7, 0xc3, 0xb5, 0x65, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x61, 0xc3,
0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0xc3, 0xb3,
0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72,
0x6b, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x72, 0x63, 0x61,
0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65,
0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0xc3, 0xa7, 0xc3, 0xb5, 0x65,
0x73, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75,
0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x73,
0x75, 0x6d, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x50,
0x72, 0x6f, 0x74, 0x65, 0x67, 0x69, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22,
0x54, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0x4e, 0xc3, 0xa3, 0x6f, 0x20, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x79, 0x6f, 0x75, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x65, 0x75,
0x73, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x73,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x7a, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x73, 0x2d, 0x5a, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e,
0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x73, 0x2d, 0x54, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x65,
0x6e, 0x68, 0x75, 0x6d, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3,
0xa7, 0x6f, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x64,
0x6f, 0x2e, 0x20, 0x43, 0x72, 0x69, 0x65, 0x20, 0x75, 0x6d, 0x20, 0x75,
0x73, 0x61, 0x6e, 0x64, 0x6f, 0x20, 0x6f, 0x73, 0x20, 0x62, 0x6f, 0x74,
0xc3, 0xb5, 0x65, 0x73, 0x20, 0x61, 0x63, 0x69, 0x6d, 0x61, 0x2e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x4e, 0x6f, 0x76, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3,
0xa7, 0x6f, 0x2d, 0x5a, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f, 0x76, 0x6f, 0x20, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x2d, 0x54, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x54, 0x69, 0x70, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x70,
0x69, 0x61, 0x72, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7,
0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x66,
0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x68, 0x69, 0x73, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69,
0x61, 0x72, 0x20, 0x64, 0x65, 0x73, 0x74, 0x65, 0x20, 0x65, 0x6e, 0x64,
0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x72, 0x20, 0x63, 0x68, 0x61,
0x76, 0x65, 0x20, 0x70, 0x72, 0x69, 0x76, 0x61, 0x64, 0x61, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74,
0x5f, 0x76, 0x69, 0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79,
0x22, 0x3a, 0x20, 0x22, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x72,
0x20, 0x63, 0x68, 0x61, 0x76, 0x65, 0x20, 0x64, 0x65, 0x20, 0x76, 0x69,
0x73, 0x75, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68, 0x6f, 0x77,
0x5f, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x4d, 0x6f, 0x73, 0x74, 0x72, 0x61, 0x72, 0x20, 0x63, 0xc3, 0xb3, 0x64,
0x69, 0x67, 0x6f, 0x20, 0x51, 0x52, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0xc3, 0xa3, 0x6f, 0x20,
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x20, 0x61, 0x6f,
0x20, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x2e, 0x2e, 0x22, 0x2c,
0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x79, 0x5f, 0x66,
0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x67, 0x61, 0x72,
0x20, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e,
0x76, 0x69, 0x61, 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22,
0x3a, 0x20, 0x22, 0x56, 0x61, 0x6c, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x3a, 0x20, 0x22,
0x4d, 0x65, 0x6d, 0x6f, 0x20, 0x28, 0x6f, 0x70, 0x63, 0x69, 0x6f, 0x6e,
0x61, 0x6c, 0x2c, 0x20, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x67, 0x72,
0x61, 0x66, 0x61, 0x64, 0x6f, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x54, 0x61, 0x78, 0x61, 0x20, 0x64, 0x6f, 0x20, 0x6d,
0x69, 0x6e, 0x65, 0x72, 0x61, 0x64, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x54,
0x61, 0x78, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6e, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69, 0x61,
0x72, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0xc3, 0xa7, 0xc3, 0xa3,
0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65,
0x61, 0x72, 0x22, 0x3a, 0x20, 0x22, 0x4c, 0x69, 0x6d, 0x70, 0x61, 0x72,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6c, 0x65,
0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x69, 0x6f, 0x6e, 0x61, 0x72,
0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x2e, 0x2e,
0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x73,
0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6c, 0x61, 0x72, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x78, 0x22, 0x3a,
0x20, 0x22, 0x4d, 0xc3, 0xa1, 0x78, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x6e, 0xc3, 0xad,
0x76, 0x65, 0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69,
0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74,
0x6f, 0x20, 0x64, 0x65, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3,
0xa7, 0x6f, 0x20, 0x69, 0x6e, 0x76, 0xc3, 0xa1, 0x6c, 0x69, 0x64, 0x6f,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f,
0x5f, 0x7a, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0x6f, 0x74, 0x61, 0x3a, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x20, 0x73,
0xc3, 0xb3, 0x20, 0x65, 0x73, 0x74, 0xc3, 0xa3, 0x6f, 0x20, 0x64, 0x69,
0x73, 0x70, 0x6f, 0x6e, 0xc3, 0xad, 0x76, 0x65, 0x69, 0x73, 0x20, 0x61,
0x6f, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x61, 0x72, 0x20, 0x70, 0x61, 0x72,
0x61, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x73,
0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x67, 0x69, 0x64, 0x6f, 0x73, 0x20,
0x28, 0x7a, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x63, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x65, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x22,
0x3a, 0x20, 0x22, 0x44, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x72, 0x61, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x76, 0x69, 0x61, 0x6e,
0x64, 0x6f, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0xc3, 0xa7, 0xc3,
0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a,
0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x72, 0x20,
0x65, 0x6e, 0x76, 0x69, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x72, 0x20, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x72, 0x20, 0x65,
0x20, 0x65, 0x6e, 0x76, 0x69, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x22, 0x3a, 0x20,
0x22, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61, 0x72, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x65, 0x75, 0x73, 0x20, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x73, 0x20, 0x64, 0x65, 0x20,
0x72, 0x65, 0x63, 0x65, 0x62, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x6f, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a,
0x5f, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20,
0x22, 0x4e, 0x6f, 0x76, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65,
0xc3, 0xa7, 0x6f, 0x2d, 0x7a, 0x20, 0x28, 0x70, 0x72, 0x6f, 0x74, 0x65,
0x67, 0x69, 0x64, 0x6f, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0x6f,
0x76, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f,
0x2d, 0x74, 0x20, 0x28, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x65, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x64, 0x65, 0x74,
0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65, 0x74, 0x61,
0x6c, 0x68, 0x65, 0x73, 0x20, 0x64, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x65,
0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e, 0x5f, 0x65, 0x78, 0x70,
0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72,
0x20, 0x6e, 0x6f, 0x20, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x61, 0x64,
0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x71, 0x72,
0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x43, 0xc3, 0xb3,
0x64, 0x69, 0x67, 0x6f, 0x20, 0x51, 0x52, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x6f,
0x6c, 0x69, 0x63, 0x69, 0x74, 0x61, 0x72, 0x20, 0x70, 0x61, 0x67, 0x61,
0x6d, 0x65, 0x6e, 0x74, 0x6f, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x61,
0x74, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0xc3,
0xa7, 0xc3, 0xb5, 0x65, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a,
0x20, 0x22, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x64, 0x61,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x65, 0x6e, 0x64, 0x65,
0x6e, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x65, 0x6e, 0x76, 0x69, 0x61,
0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65,
0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x72, 0x65,
0x63, 0x65, 0x62, 0x69, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x65, 0x72, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x65, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x69,
0x6e, 0x65, 0x72, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6e, 0x69,
0x63, 0x69, 0x61, 0x72, 0x20, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x61, 0xc3,
0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0x50, 0x61, 0x72, 0x61, 0x72, 0x20, 0x6d, 0x69, 0x6e,
0x65, 0x72, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74,
0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x68,
0x72, 0x65, 0x61, 0x64, 0x73, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x69, 0x6e,
0x65, 0x72, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73,
0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x22, 0x3a, 0x20,
0x22, 0x45, 0x73, 0x74, 0x61, 0x74, 0xc3, 0xad, 0x73, 0x74, 0x69, 0x63,
0x61, 0x73, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x61,
0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72,
0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x48, 0x61, 0x73, 0x68, 0x72,
0x61, 0x74, 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x20, 0x64, 0x61,
0x20, 0x72, 0x65, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x22,
0x3a, 0x20, 0x22, 0x44, 0x69, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x64, 0x61,
0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x73,
0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x65, 0x6d, 0x70, 0x6f,
0x20, 0x65, 0x73, 0x74, 0x2e, 0x20, 0x61, 0x74, 0xc3, 0xa9, 0x20, 0x6f,
0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x66, 0x66,
0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x61, 0xc3, 0xa7,
0xc3, 0xa3, 0x6f, 0x20, 0x44, 0x45, 0x53, 0x4c, 0x49, 0x47, 0x41, 0x44,
0x41, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69,
0x6e, 0x65, 0x72, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x20, 0x4c, 0x49,
0x47, 0x41, 0x44, 0x41, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x70,
0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e, 0xc3, 0xb3, 0x73,
0x20, 0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e, 0x65,
0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x4e,
0xc3, 0xb3, 0x73, 0x20, 0x62, 0x61, 0x6e, 0x69, 0x64, 0x6f, 0x73, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x64,
0x65, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x20, 0x49, 0x50, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0xc3, 0xa3, 0x6f, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6c, 0x74, 0x75, 0x72, 0x61, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x61,
0x6e, 0x69, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75,
0x6e, 0x62, 0x61, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65, 0x73, 0x62,
0x61, 0x6e, 0x69, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x62, 0x61,
0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x72, 0x20, 0x74, 0x6f, 0x64, 0x6f, 0x73, 0x20, 0x6f, 0x73, 0x20, 0x62,
0x61, 0x6e, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x6f, 0x73, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f,
0x63, 0x68, 0x61, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x47, 0x72, 0xc3,
0xa1, 0x66, 0x69, 0x63, 0x6f, 0x20, 0x64, 0x65, 0x20, 0x70, 0x72, 0x65,
0xc3, 0xa7, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x50, 0x72, 0x65, 0xc3, 0xa7, 0x6f, 0x20, 0x61,
0x74, 0x75, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x32, 0x34, 0x68, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x56, 0x61, 0x72, 0x69, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f,
0x20, 0x32, 0x34, 0x68, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x32, 0x34, 0x68, 0x5f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x20, 0x32, 0x34, 0x68,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b,
0x65, 0x74, 0x5f, 0x63, 0x61, 0x70, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x61,
0x70, 0x2e, 0x20, 0x64, 0x65, 0x20, 0x6d, 0x65, 0x72, 0x63, 0x61, 0x64,
0x6f, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65,
0x6e, 0x65, 0x72, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x72,
0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69,
0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x78, 0x69,
0x62, 0x69, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a,
0x20, 0x22, 0x52, 0x65, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x54,
0x65, 0x6d, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c,
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x49,
0x64, 0x69, 0x6f, 0x6d, 0x61, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x5f, 0x67, 0x72, 0x65,
0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x72, 0x61, 0x67, 0x6f, 0x6e,
0x58, 0x20, 0x28, 0x56, 0x65, 0x72, 0x64, 0x65, 0x29, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x72, 0x6b, 0x22, 0x3a, 0x20,
0x22, 0x45, 0x73, 0x63, 0x75, 0x72, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x20, 0x22,
0x43, 0x6c, 0x61, 0x72, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x50, 0x65,
0x72, 0x6d, 0x69, 0x74, 0x69, 0x72, 0x20, 0x74, 0x61, 0x78, 0x61, 0x73,
0x20, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61,
0x64, 0x61, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75,
0x73, 0x65, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x73,
0x61, 0x72, 0x20, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64, 0x20,
0x65, 0x6d, 0x62, 0x75, 0x74, 0x69, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x53, 0x61, 0x6c, 0x76, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x46,
0x65, 0x63, 0x68, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x72,
0x71, 0x75, 0x69, 0x76, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x64, 0x69,
0x74, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76,
0x69, 0x65, 0x77, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x78, 0x69, 0x62, 0x69,
0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x6c,
0x70, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6a, 0x75, 0x64, 0x61, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x72,
0x20, 0x63, 0x68, 0x61, 0x76, 0x65, 0x20, 0x70, 0x72, 0x69, 0x76, 0x61,
0x64, 0x61, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x46, 0x61, 0x7a, 0x65, 0x72, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x20, 0x64, 0x61, 0x20, 0x63, 0x61,
0x72, 0x74, 0x65, 0x69, 0x72, 0x61, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x69, 0x74, 0x22, 0x3a, 0x20,
0x22, 0x53, 0x61, 0x69, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f,
0x6e, 0x78, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x6f, 0x62, 0x72, 0x65, 0x20,
0x4f, 0x62, 0x73, 0x69, 0x64, 0x69, 0x61, 0x6e, 0x44, 0x72, 0x61, 0x67,
0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f, 0x77, 0x22, 0x3a, 0x20,
0x22, 0x41, 0x74, 0x75, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x72, 0x20, 0x61,
0x67, 0x6f, 0x72, 0x61, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x6f,
0x62, 0x72, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x78, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63,
0x6c, 0x69, 0x70, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20, 0x22,
0x43, 0x6f, 0x70, 0x69, 0x61, 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x20,
0xc3, 0xa1, 0x72, 0x65, 0x61, 0x20, 0x64, 0x65, 0x20, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x66, 0x65, 0x72, 0xc3, 0xaa, 0x6e, 0x63, 0x69, 0x61, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e,
0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65, 0x73, 0x63, 0x6f, 0x6e,
0x65, 0x63, 0x74, 0x61, 0x64, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x61, 0x6e,
0x64, 0x6f, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22,
0x53, 0x69, 0x6e, 0x63, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x6e, 0x64,
0x6f, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0x42, 0x6c, 0x6f,
0x63, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x61,
0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22,
0x4e, 0x65, 0x6e, 0x68, 0x75, 0x6d, 0x20, 0x65, 0x6e, 0x64, 0x65, 0x72,
0x65, 0xc3, 0xa7, 0x6f, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6f, 0x6e, 0xc3,
0xad, 0x76, 0x65, 0x6c, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x72,
0x72, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75,
0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x75, 0x63,
0x65, 0x73, 0x73, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x41,
0x76, 0x69, 0x73, 0x6f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65,
0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x56, 0x61, 0x6c, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x63, 0x65,
0x64, 0x65, 0x20, 0x6f, 0x20, 0x73, 0x61, 0x6c, 0x64, 0x6f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a,
0x20, 0x22, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0xc3, 0xa7, 0xc3, 0xa3,
0x6f, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x61, 0x64, 0x61, 0x20, 0x63, 0x6f,
0x6d, 0x20, 0x73, 0x75, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x22, 0x0a, 0x7d,
0x0a
};
unsigned int res_lang_pt_json_len = 4537;

502
src/embedded/lang_ru.h Normal file
View File

@@ -0,0 +1,502 @@
unsigned char res_lang_ru_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x91, 0xd0, 0xb0, 0xd0, 0xbb,
0xd0, 0xb0, 0xd0, 0xbd, 0xd1, 0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e,
0xd1, 0x82, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xb8,
0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9f, 0xd0, 0xbe, 0xd0, 0xbb, 0xd1, 0x83, 0xd1, 0x87, 0xd0, 0xb8, 0xd1,
0x82, 0xd1, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0xa2, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0,
0xb7, 0xd0, 0xb0, 0xd0, 0xba, 0xd1, 0x86, 0xd0, 0xb8, 0xd0, 0xb8, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9c, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0,
0xbd, 0xd0, 0xb8, 0xd0, 0xbd, 0xd0, 0xb3, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xd0, 0xa3, 0xd0, 0xb7, 0xd0, 0xbb, 0xd1, 0x8b, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0xa0, 0xd1, 0x8b, 0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xba,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x74, 0x74,
0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xb0,
0xd1, 0x81, 0xd1, 0x82, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb9, 0xd0, 0xba,
0xd0, 0xb8, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1,
0xd0, 0xb2, 0xd0, 0xbe, 0xd0, 0xb4, 0xd0, 0xba, 0xd0, 0xb0, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64,
0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x97, 0xd0, 0xb0, 0xd1, 0x89,
0xd0, 0xb8, 0xd1, 0x89, 0xd1, 0x91, 0xd0, 0xbd, 0xd0, 0xbd, 0xd1, 0x8b,
0xd0, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9f, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb7, 0xd1, 0x80, 0xd0,
0xb0, 0xd1, 0x87, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb9, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x98, 0xd1, 0x82, 0xd0, 0xbe, 0xd0, 0xb3, 0xd0, 0xbe,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9d, 0xd0, 0xb5, 0xd0, 0xbf, 0xd0, 0xbe, 0xd0, 0xb4, 0xd1, 0x82, 0xd0,
0xb2, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb6, 0xd0, 0xb4, 0xd1, 0x91, 0xd0,
0xbd, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb5, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x79, 0x6f, 0x75, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x92, 0xd0,
0xb0, 0xd1, 0x88, 0xd0, 0xb8, 0x20, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80,
0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x7a, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x5a, 0x2d, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1,
0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x2d, 0xd0, 0xb0, 0xd0, 0xb4,
0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x90, 0xd0, 0xb4,
0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x20, 0xd0, 0xbd, 0xd0,
0xb5, 0x20, 0xd0, 0xbd, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xb4, 0xd0, 0xb5,
0xd0, 0xbd, 0xd1, 0x8b, 0x2e, 0x20, 0xd0, 0xa1, 0xd0, 0xbe, 0xd0, 0xb7,
0xd0, 0xb4, 0xd0, 0xb0, 0xd0, 0xb9, 0xd1, 0x82, 0xd0, 0xb5, 0x20, 0xd0,
0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0x20, 0xd1, 0x81,
0x20, 0xd0, 0xbf, 0xd0, 0xbe, 0xd0, 0xbc, 0xd0, 0xbe, 0xd1, 0x89, 0xd1,
0x8c, 0xd1, 0x8e, 0x20, 0xd0, 0xba, 0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xbf,
0xd0, 0xbe, 0xd0, 0xba, 0x20, 0xd0, 0xb2, 0xd1, 0x8b, 0xd1, 0x88, 0xd0,
0xb5, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x77, 0x5f, 0x7a, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xbe, 0xd0, 0xb2, 0xd1, 0x8b, 0xd0,
0xb9, 0x20, 0x5a, 0x2d, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5,
0xd1, 0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x77, 0x5f, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xbe, 0xd0, 0xb2, 0xd1, 0x8b, 0xd0,
0xb9, 0x20, 0x54, 0x2d, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5,
0xd1, 0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79,
0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa2, 0xd0, 0xb8, 0xd0, 0xbf,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x90, 0xd0, 0xb4, 0xd1,
0x80, 0xd0, 0xb5, 0xd1, 0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9a, 0xd0, 0xbe, 0xd0, 0xbf, 0xd0,
0xb8, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb0, 0xd1, 0x82, 0xd1,
0x8c, 0x20, 0xd0, 0xbf, 0xd0, 0xbe, 0xd0, 0xbb, 0xd0, 0xbd, 0xd1, 0x8b,
0xd0, 0xb9, 0x20, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1,
0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e,
0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x68, 0x69, 0x73, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9e, 0xd1, 0x82, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0,
0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd1, 0x81, 0x20, 0xd1, 0x8d, 0xd1,
0x82, 0xd0, 0xbe, 0xd0, 0xb3, 0xd0, 0xbe, 0x20, 0xd0, 0xb0, 0xd0, 0xb4,
0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0xad, 0xd0, 0xba, 0xd1, 0x81, 0xd0, 0xbf, 0xd0, 0xbe,
0xd1, 0x80, 0xd1, 0x82, 0x20, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb8, 0xd0,
0xb2, 0xd0, 0xb0, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xb3, 0xd0,
0xbe, 0x20, 0xd0, 0xba, 0xd0, 0xbb, 0xd1, 0x8e, 0xd1, 0x87, 0xd0, 0xb0,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f,
0x72, 0x74, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x6b,
0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xad, 0xd0, 0xba, 0xd1, 0x81,
0xd0, 0xbf, 0xd0, 0xbe, 0xd1, 0x80, 0xd1, 0x82, 0x20, 0xd0, 0xba, 0xd0,
0xbb, 0xd1, 0x8e, 0xd1, 0x87, 0xd0, 0xb0, 0x20, 0xd0, 0xbf, 0xd1, 0x80,
0xd0, 0xbe, 0xd1, 0x81, 0xd0, 0xbc, 0xd0, 0xbe, 0xd1, 0x82, 0xd1, 0x80,
0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68,
0x6f, 0x77, 0x5f, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xba, 0xd0, 0xb0, 0xd0, 0xb7,
0xd0, 0xb0, 0xd1, 0x82, 0xd1, 0x8c, 0x20, 0x51, 0x52, 0x2d, 0xd0, 0xba,
0xd0, 0xbe, 0xd0, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xb5, 0x20, 0xd0, 0xbf,
0xd0, 0xbe, 0xd0, 0xb4, 0xd0, 0xba, 0xd0, 0xbb, 0xd1, 0x8e, 0xd1, 0x87,
0xd1, 0x91, 0xd0, 0xbd, 0x20, 0xd0, 0xba, 0x20, 0xd0, 0xb4, 0xd0, 0xb5,
0xd0, 0xbc, 0xd0, 0xbe, 0xd0, 0xbd, 0xd1, 0x83, 0x2e, 0x2e, 0x2e, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x79, 0x5f,
0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd0, 0xbf,
0xd0, 0xbb, 0xd0, 0xb0, 0xd1, 0x82, 0xd0, 0xb0, 0x20, 0xd1, 0x81, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f,
0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd1, 0x82, 0xd0, 0xbf,
0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c,
0x20, 0xd0, 0xbd, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0xa1, 0xd1, 0x83, 0xd0, 0xbc, 0xd0, 0xbc, 0xd0, 0xb0, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9f, 0xd1, 0x80, 0xd0, 0xb8, 0xd0, 0xbc, 0xd0, 0xb5, 0xd1,
0x87, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xb5, 0x20, 0x28, 0xd0,
0xbd, 0xd0, 0xb5, 0xd0, 0xbe, 0xd0, 0xb1, 0xd1, 0x8f, 0xd0, 0xb7, 0xd0,
0xb0, 0xd1, 0x82, 0xd0, 0xb5, 0xd0, 0xbb, 0xd1, 0x8c, 0xd0, 0xbd, 0xd0,
0xbe, 0x2c, 0x20, 0xd0, 0xb7, 0xd0, 0xb0, 0xd1, 0x88, 0xd0, 0xb8, 0xd1,
0x84, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0,
0xbe, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69,
0x6e, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9a, 0xd0, 0xbe, 0xd0, 0xbc, 0xd0, 0xb8, 0xd1, 0x81, 0xd1, 0x81, 0xd0,
0xb8, 0xd1, 0x8f, 0x20, 0xd0, 0xbc, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbd,
0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9a, 0xd0,
0xbe, 0xd0, 0xbc, 0xd0, 0xb8, 0xd1, 0x81, 0xd1, 0x81, 0xd0, 0xb8, 0xd1,
0x8f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e,
0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd1, 0x82, 0xd0, 0xbf, 0xd1,
0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x20,
0xd1, 0x82, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0, 0xb7, 0xd0, 0xb0,
0xd0, 0xba, 0xd1, 0x86, 0xd0, 0xb8, 0xd1, 0x8e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9e, 0xd1, 0x87, 0xd0, 0xb8, 0xd1, 0x81, 0xd1, 0x82, 0xd0,
0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x92, 0xd1, 0x8b, 0xd0,
0xb1, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb8, 0xd1, 0x82, 0xd0, 0xb5, 0x20,
0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0x2e, 0x2e,
0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x73,
0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x92, 0xd1, 0x81, 0xd1, 0x82,
0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x78, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9c, 0xd0, 0xb0, 0xd0, 0xba, 0xd1, 0x81, 0x2e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x94, 0xd0, 0xbe, 0xd1,
0x81, 0xd1, 0x82, 0xd1, 0x83, 0xd0, 0xbf, 0xd0, 0xbd, 0xd0, 0xbe, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6e, 0x76, 0x61, 0x6c,
0x69, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xb5, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x80,
0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb9, 0x20, 0xd1, 0x84, 0xd0, 0xbe, 0xd1,
0x80, 0xd0, 0xbc, 0xd0, 0xb0, 0xd1, 0x82, 0x20, 0xd0, 0xb0, 0xd0, 0xb4,
0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x7a, 0x5f, 0x6f,
0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd1, 0x80, 0xd0,
0xb8, 0xd0, 0xbc, 0xd0, 0xb5, 0xd1, 0x87, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0,
0xb8, 0xd1, 0x8f, 0x20, 0xd0, 0xb4, 0xd0, 0xbe, 0xd1, 0x81, 0xd1, 0x82,
0xd1, 0x83, 0xd0, 0xbf, 0xd0, 0xbd, 0xd1, 0x8b, 0x20, 0xd1, 0x82, 0xd0,
0xbe, 0xd0, 0xbb, 0xd1, 0x8c, 0xd0, 0xba, 0xd0, 0xbe, 0x20, 0xd0, 0xbf,
0xd1, 0x80, 0xd0, 0xb8, 0x20, 0xd0, 0xbe, 0xd1, 0x82, 0xd0, 0xbf, 0xd1,
0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xba, 0xd0, 0xb5, 0x20, 0xd0, 0xbd,
0xd0, 0xb0, 0x20, 0xd0, 0xb7, 0xd0, 0xb0, 0xd1, 0x89, 0xd0, 0xb8, 0xd1,
0x89, 0xd1, 0x91, 0xd0, 0xbd, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb5, 0x20,
0x28, 0x7a, 0x29, 0x20, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5,
0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xd1, 0x81, 0xd0, 0xb8, 0xd0, 0xbc, 0xd0, 0xb2, 0xd0, 0xbe,
0xd0, 0xbb, 0xd0, 0xbe, 0xd0, 0xb2, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e,
0xd1, 0x82, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9a, 0xd0, 0xbe, 0xd0, 0xbc, 0xd1, 0x83,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd1, 0x82, 0xd0,
0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xba, 0xd0, 0xb0, 0x20,
0xd1, 0x82, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0, 0xb7, 0xd0, 0xb0,
0xd0, 0xba, 0xd1, 0x86, 0xd0, 0xb8, 0xd0, 0xb8, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f,
0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe,
0xd0, 0xb4, 0xd1, 0x82, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb4,
0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xbe, 0xd1, 0x82, 0xd0,
0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xba, 0xd1, 0x83, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x72, 0x6d, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xb4,
0xd1, 0x82, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb4, 0xd0, 0xb8,
0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd1, 0x82, 0xd1, 0x80, 0xd0, 0xb0, 0xd0,
0xbd, 0xd0, 0xb7, 0xd0, 0xb0, 0xd0, 0xba, 0xd1, 0x86, 0xd0, 0xb8, 0xd1,
0x8e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6e,
0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xb4, 0xd1,
0x82, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb4, 0xd0, 0xb8, 0xd1,
0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xb8, 0x20, 0xd0, 0xbe, 0xd1, 0x82, 0xd0,
0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xb8, 0xd1, 0x82, 0xd1,
0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x6e,
0x63, 0x65, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd1, 0x82, 0xd0,
0xbc, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x69, 0x6e,
0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0x92, 0xd0, 0xb0, 0xd1, 0x88, 0xd0, 0xb8, 0x20,
0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0,
0x20, 0xd0, 0xb4, 0xd0, 0xbb, 0xd1, 0x8f, 0x20, 0xd0, 0xbf, 0xd0, 0xbe,
0xd0, 0xbb, 0xd1, 0x83, 0xd1, 0x87, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb8,
0xd1, 0x8f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x77, 0x5f, 0x7a, 0x5f, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xbe, 0xd0, 0xb2, 0xd1, 0x8b,
0xd0, 0xb9, 0x20, 0x7a, 0x2d, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0,
0xb5, 0xd1, 0x81, 0x20, 0x28, 0xd0, 0xb7, 0xd0, 0xb0, 0xd1, 0x89, 0xd0,
0xb8, 0xd1, 0x89, 0xd1, 0x91, 0xd0, 0xbd, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0,
0xb9, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xbe, 0xd0,
0xb2, 0xd1, 0x8b, 0xd0, 0xb9, 0x20, 0x74, 0x2d, 0xd0, 0xb0, 0xd0, 0xb4,
0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0x20, 0x28, 0xd0, 0xbf, 0xd1, 0x80,
0xd0, 0xbe, 0xd0, 0xb7, 0xd1, 0x80, 0xd0, 0xb0, 0xd1, 0x87, 0xd0, 0xbd,
0xd1, 0x8b, 0xd0, 0xb9, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x64, 0x65, 0x74,
0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x94, 0xd0, 0xb5,
0xd1, 0x82, 0xd0, 0xb0, 0xd0, 0xbb, 0xd0, 0xb8, 0x20, 0xd0, 0xb0, 0xd0,
0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0xd0, 0xb0, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e,
0x5f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9e, 0xd1, 0x82, 0xd0, 0xba, 0xd1, 0x80, 0xd1, 0x8b, 0xd1,
0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xb2, 0x20, 0xd0, 0xbe, 0xd0, 0xb1, 0xd0,
0xbe, 0xd0, 0xb7, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb2, 0xd0, 0xb0, 0xd1,
0x82, 0xd0, 0xb5, 0xd0, 0xbb, 0xd0, 0xb5, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x51, 0x52, 0x2d, 0xd0, 0xba, 0xd0, 0xbe, 0xd0, 0xb4, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x97, 0xd0, 0xb0, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xbe,
0xd1, 0x81, 0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xbf, 0xd0,
0xbb, 0xd0, 0xb0, 0xd1, 0x82, 0xd1, 0x91, 0xd0, 0xb6, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74, 0x65, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x94, 0xd0, 0xb0, 0xd1, 0x82, 0xd0, 0xb0, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd1, 0x82, 0xd0, 0xb0, 0xd1, 0x82,
0xd1, 0x83, 0xd1, 0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xb4, 0xd1,
0x82, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb6, 0xd0, 0xb4, 0xd0,
0xb5, 0xd0, 0xbd, 0xd0, 0xb8, 0xd1, 0x8f, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xb4, 0xd1, 0x82,
0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x80, 0xd0, 0xb6, 0xd0, 0xb4, 0xd0, 0xb5,
0xd0, 0xbd, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9e, 0xd0, 0xb6, 0xd0, 0xb8, 0xd0, 0xb4, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0,
0xb8, 0xd0, 0xb5, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xbe, 0xd1, 0x82, 0xd0,
0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xbb, 0xd0, 0xb5, 0xd0,
0xbd, 0xd0, 0xbe, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72,
0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0xbf, 0xd0, 0xbe, 0xd0, 0xbb, 0xd1, 0x83, 0xd1, 0x87, 0xd0, 0xb5, 0xd0,
0xbd, 0xd0, 0xbe, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xb4, 0xd0, 0xbe,
0xd0, 0xb1, 0xd1, 0x8b, 0xd1, 0x82, 0xd0, 0xbe, 0x22, 0x2c, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0xa3, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xb2, 0xd0, 0xbb, 0xd0,
0xb5, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xb5, 0x20, 0xd0, 0xbc, 0xd0, 0xb0,
0xd0, 0xb9, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xbd, 0xd0, 0xb3, 0xd0, 0xbe,
0xd0, 0xbc, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74,
0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x9d, 0xd0, 0xb0, 0xd1, 0x87, 0xd0, 0xb0, 0xd1, 0x82,
0xd1, 0x8c, 0x20, 0xd0, 0xbc, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbd, 0xd0,
0xb8, 0xd0, 0xbd, 0xd0, 0xb3, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xb0,
0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c,
0x20, 0xd0, 0xbc, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0,
0xbd, 0xd0, 0xb3, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd1, 0x82, 0xd0,
0xbe, 0xd0, 0xba, 0xd0, 0xb8, 0x20, 0xd0, 0xbc, 0xd0, 0xb0, 0xd0, 0xb9,
0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xbd, 0xd0, 0xb3, 0xd0, 0xb0, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67,
0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd1, 0x82, 0xd0, 0xb0, 0xd1, 0x82, 0xd0,
0xb8, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xb8, 0xd0, 0xba, 0xd0, 0xb0, 0x20,
0xd0, 0xbc, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xbd,
0xd0, 0xb3, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61,
0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9b, 0xd0, 0xbe, 0xd0, 0xba,
0xd0, 0xb0, 0xd0, 0xbb, 0xd1, 0x8c, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb9,
0x20, 0xd1, 0x85, 0xd0, 0xb5, 0xd1, 0x88, 0xd1, 0x80, 0xd0, 0xb5, 0xd0,
0xb9, 0xd1, 0x82, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72,
0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa5, 0xd0, 0xb5, 0xd1,
0x88, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb9, 0xd1, 0x82, 0x20, 0xd1, 0x81,
0xd0, 0xb5, 0xd1, 0x82, 0xd0, 0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd0, 0xbb, 0xd0, 0xbe, 0xd0, 0xb6,
0xd0, 0xbd, 0xd0, 0xbe, 0xd1, 0x81, 0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd0, 0xb6, 0xd0, 0xb8, 0xd0, 0xb4, 0xd0,
0xb0, 0xd0, 0xb5, 0xd0, 0xbc, 0xd0, 0xbe, 0xd0, 0xb5, 0x20, 0xd0, 0xb2,
0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xbc, 0xd1, 0x8f, 0x20, 0xd0, 0xb4, 0xd0,
0xbe, 0x20, 0xd0, 0xb1, 0xd0, 0xbb, 0xd0, 0xbe, 0xd0, 0xba, 0xd0, 0xb0,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69,
0x6e, 0x67, 0x5f, 0x6f, 0x66, 0x66, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9c,
0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xbd, 0xd0, 0xb3,
0x20, 0xd0, 0x92, 0xd0, 0xab, 0xd0, 0x9a, 0xd0, 0x9b, 0xd0, 0xae, 0xd0,
0xa7, 0xd0, 0x95, 0xd0, 0x9d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x9c, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbd, 0xd0, 0xb8,
0xd0, 0xbd, 0xd0, 0xb3, 0x20, 0xd0, 0x92, 0xd0, 0x9a, 0xd0, 0x9b, 0xd0,
0xae, 0xd0, 0xa7, 0xd0, 0x81, 0xd0, 0x9d, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9f, 0xd0, 0xbe, 0xd0, 0xb4, 0xd0, 0xba, 0xd0, 0xbb, 0xd1, 0x8e, 0xd1,
0x87, 0xd1, 0x91, 0xd0, 0xbd, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb5, 0x20,
0xd1, 0x83, 0xd0, 0xb7, 0xd0, 0xbb, 0xd1, 0x8b, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70,
0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x97, 0xd0, 0xb0,
0xd0, 0xb1, 0xd0, 0xbb, 0xd0, 0xbe, 0xd0, 0xba, 0xd0, 0xb8, 0xd1, 0x80,
0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0, 0xbd, 0xd1, 0x8b,
0xd0, 0xb5, 0x20, 0xd1, 0x83, 0xd0, 0xb7, 0xd0, 0xbb, 0xd1, 0x8b, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x50, 0x2d,
0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x81, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x92, 0xd0, 0xb5, 0xd1, 0x80, 0xd1,
0x81, 0xd0, 0xb8, 0xd1, 0x8f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x92, 0xd1, 0x8b, 0xd1, 0x81, 0xd0, 0xbe, 0xd1, 0x82, 0xd0, 0xb0, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xb8, 0xd0, 0xbd, 0xd0, 0xb3, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x91, 0xd0, 0xbb, 0xd0, 0xbe, 0xd0, 0xba, 0xd0, 0xb8,
0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb0, 0xd1, 0x82, 0xd1, 0x8c,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x62, 0x61,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa0, 0xd0, 0xb0, 0xd0, 0xb7, 0xd0,
0xb1, 0xd0, 0xbb, 0xd0, 0xbe, 0xd0, 0xba, 0xd0, 0xb8, 0xd1, 0x80, 0xd0,
0xbe, 0xd0, 0xb2, 0xd0, 0xb0, 0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61,
0x6c, 0x6c, 0x5f, 0x62, 0x61, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0xa1, 0xd0, 0xbd, 0xd1, 0x8f, 0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xb2,
0xd1, 0x81, 0xd0, 0xb5, 0x20, 0xd0, 0xb1, 0xd0, 0xbb, 0xd0, 0xbe, 0xd0,
0xba, 0xd0, 0xb8, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xba, 0xd0,
0xb8, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72,
0x69, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x93, 0xd1, 0x80, 0xd0, 0xb0, 0xd1, 0x84, 0xd0, 0xb8, 0xd0,
0xba, 0x20, 0xd1, 0x86, 0xd0, 0xb5, 0xd0, 0xbd, 0xd1, 0x8b, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0xa2, 0xd0, 0xb5, 0xd0, 0xba, 0xd1, 0x83, 0xd1, 0x89, 0xd0, 0xb0, 0xd1,
0x8f, 0x20, 0xd1, 0x86, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb0, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x98, 0xd0, 0xb7,
0xd0, 0xbc, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb8,
0xd0, 0xb5, 0x20, 0xd0, 0xb7, 0xd0, 0xb0, 0x20, 0x32, 0x34, 0xd1, 0x87,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x32, 0x34, 0x68, 0x5f,
0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e,
0xd0, 0xb1, 0xd1, 0x8a, 0xd1, 0x91, 0xd0, 0xbc, 0x20, 0xd0, 0xb7, 0xd0,
0xb0, 0x20, 0x32, 0x34, 0xd1, 0x87, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x61, 0x70,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa0, 0xd1, 0x8b, 0xd0, 0xbd, 0xd0, 0xbe,
0xd1, 0x87, 0xd0, 0xbd, 0xd0, 0xb0, 0xd1, 0x8f, 0x20, 0xd0, 0xba, 0xd0,
0xb0, 0xd0, 0xbf, 0x2e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22,
0xd0, 0x9e, 0xd1, 0x81, 0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xbd,
0xd1, 0x8b, 0xd0, 0xb5, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9e, 0xd1, 0x82, 0xd0, 0xbe, 0xd0, 0xb1, 0xd1, 0x80, 0xd0, 0xb0, 0xd0,
0xb6, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xb5, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd0, 0xb5, 0xd1, 0x82, 0xd1, 0x8c,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa2, 0xd0, 0xb5, 0xd0, 0xbc, 0xd0,
0xb0, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x61, 0x6e,
0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xaf, 0xd0,
0xb7, 0xd1, 0x8b, 0xd0, 0xba, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x5f, 0x67, 0x72, 0x65,
0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x72, 0x61, 0x67, 0x6f, 0x6e,
0x58, 0x20, 0x28, 0xd0, 0xb7, 0xd0, 0xb5, 0xd0, 0xbb, 0xd1, 0x91, 0xd0,
0xbd, 0xd1, 0x8b, 0xd0, 0xb9, 0x29, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x61, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa2,
0xd1, 0x91, 0xd0, 0xbc, 0xd0, 0xbd, 0xd0, 0xb0, 0xd1, 0x8f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0xd0,
0xbb, 0xd0, 0xb0, 0xd1, 0x8f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa0,
0xd0, 0xb0, 0xd0, 0xb7, 0xd1, 0x80, 0xd0, 0xb5, 0xd1, 0x88, 0xd0, 0xb8,
0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xbf, 0xd0, 0xbe, 0xd0, 0xbb, 0xd1,
0x8c, 0xd0, 0xb7, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb0, 0xd1, 0x82, 0xd0,
0xb5, 0xd0, 0xbb, 0xd1, 0x8c, 0xd1, 0x81, 0xd0, 0xba, 0xd0, 0xb8, 0xd0,
0xb5, 0x20, 0xd0, 0xba, 0xd0, 0xbe, 0xd0, 0xbc, 0xd0, 0xb8, 0xd1, 0x81,
0xd1, 0x81, 0xd0, 0xb8, 0xd0, 0xb8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x75, 0x73, 0x65, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64,
0x65, 0x64, 0x5f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x92, 0xd1, 0x81, 0xd1, 0x82, 0xd1, 0x80, 0xd0, 0xbe, 0xd0,
0xb5, 0xd0, 0xbd, 0xd0, 0xbd, 0xd1, 0x8b, 0xd0, 0xb9, 0x20, 0x64, 0x72,
0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1,
0xd0, 0xbe, 0xd1, 0x85, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0, 0xb8,
0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x97, 0xd0,
0xb0, 0xd0, 0xba, 0xd1, 0x80, 0xd1, 0x8b, 0xd1, 0x82, 0xd1, 0x8c, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa4, 0xd0, 0xb0, 0xd0, 0xb9, 0xd0, 0xbb,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa0, 0xd0, 0xb5, 0xd0, 0xb4, 0xd0, 0xb0,
0xd0, 0xba, 0xd1, 0x82, 0xd0, 0xb8, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2,
0xd0, 0xb0, 0xd1, 0x82, 0xd1, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x92,
0xd0, 0xb8, 0xd0, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x68, 0x65, 0x6c, 0x70, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe,
0xd0, 0xbc, 0xd0, 0xbe, 0xd1, 0x89, 0xd1, 0x8c, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0x98, 0xd0, 0xbc, 0xd0, 0xbf, 0xd0, 0xbe, 0xd1, 0x80,
0xd1, 0x82, 0x20, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb8, 0xd0, 0xb2, 0xd0,
0xb0, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xb3, 0xd0, 0xbe, 0x20,
0xd0, 0xba, 0xd0, 0xbb, 0xd1, 0x8e, 0xd1, 0x87, 0xd0, 0xb0, 0x2e, 0x2e,
0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x63,
0x6b, 0x75, 0x70, 0x5f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x22, 0x3a,
0x20, 0x22, 0xd0, 0xa0, 0xd0, 0xb5, 0xd0, 0xb7, 0xd0, 0xb5, 0xd1, 0x80,
0xd0, 0xb2, 0xd0, 0xbd, 0xd0, 0xbe, 0xd0, 0xb5, 0x20, 0xd0, 0xba, 0xd0,
0xbe, 0xd0, 0xbf, 0xd0, 0xb8, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0,
0xb0, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xb5, 0x20, 0xd0, 0xba, 0xd0, 0xbe,
0xd1, 0x88, 0xd0, 0xb5, 0xd0, 0xbb, 0xd1, 0x8c, 0xd0, 0xba, 0xd0, 0xb0,
0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65,
0x78, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x92, 0xd1, 0x8b, 0xd1,
0x85, 0xd0, 0xbe, 0xd0, 0xb4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x67, 0x6f,
0x6e, 0x78, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0x20, 0xd0, 0xbf, 0xd1,
0x80, 0xd0, 0xbe, 0xd0, 0xb3, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbc, 0xd0,
0xbc, 0xd0, 0xb5, 0x20, 0x4f, 0x62, 0x73, 0x69, 0x64, 0x69, 0x61, 0x6e,
0x44, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e, 0x6f,
0x77, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0xd0, 0xb1, 0xd0, 0xbd, 0xd0,
0xbe, 0xd0, 0xb2, 0xd0, 0xb8, 0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd1, 0x81,
0xd0, 0xb5, 0xd0, 0xb9, 0xd1, 0x87, 0xd0, 0xb0, 0xd1, 0x81, 0x22, 0x2c,
0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75, 0x74,
0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9e, 0x20, 0xd0, 0xbf, 0xd1, 0x80, 0xd0,
0xbe, 0xd0, 0xb3, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbc, 0xd0, 0xbc, 0xd0,
0xb5, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x98, 0xd0, 0xbc, 0xd0,
0xbf, 0xd0, 0xbe, 0xd1, 0x80, 0xd1, 0x82, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0xad, 0xd0, 0xba, 0xd1, 0x81, 0xd0, 0xbf, 0xd0, 0xbe, 0xd1,
0x80, 0xd1, 0x82, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x62,
0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9a, 0xd0, 0xbe,
0xd0, 0xbf, 0xd0, 0xb8, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xb2, 0xd0, 0xb0,
0xd1, 0x82, 0xd1, 0x8c, 0x20, 0xd0, 0xb2, 0x20, 0xd0, 0xb1, 0xd1, 0x83,
0xd1, 0x84, 0xd0, 0xb5, 0xd1, 0x80, 0x20, 0xd0, 0xbe, 0xd0, 0xb1, 0xd0,
0xbc, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb0, 0x22, 0x2c, 0x0a, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xb4, 0xd0,
0xba, 0xd0, 0xbb, 0xd1, 0x8e, 0xd1, 0x87, 0xd1, 0x91, 0xd0, 0xbd, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9e, 0xd1, 0x82, 0xd0, 0xba, 0xd0, 0xbb, 0xd1, 0x8e, 0xd1, 0x87, 0xd1,
0x91, 0xd0, 0xbd, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9f, 0xd0, 0xbe, 0xd0, 0xb4, 0xd0, 0xba, 0xd0, 0xbb, 0xd1,
0x8e, 0xd1, 0x87, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xb5, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x79,
0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd0,
0xb8, 0xd0, 0xbd, 0xd1, 0x85, 0xd1, 0x80, 0xd0, 0xbe, 0xd0, 0xbd, 0xd0,
0xb8, 0xd0, 0xb7, 0xd0, 0xb0, 0xd1, 0x86, 0xd0, 0xb8, 0xd1, 0x8f, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0x91, 0xd0, 0xbb, 0xd0,
0xbe, 0xd0, 0xba, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e,
0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f,
0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20,
0x22, 0xd0, 0x9d, 0xd0, 0xb5, 0xd1, 0x82, 0x20, 0xd0, 0xb4, 0xd0, 0xbe,
0xd1, 0x81, 0xd1, 0x82, 0xd1, 0x83, 0xd0, 0xbf, 0xd0, 0xbd, 0xd1, 0x8b,
0xd1, 0x85, 0x20, 0xd0, 0xb0, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd1,
0x81, 0xd0, 0xbe, 0xd0, 0xb2, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xd0,
0x9e, 0xd1, 0x88, 0xd0, 0xb8, 0xd0, 0xb1, 0xd0, 0xba, 0xd0, 0xb0, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x63, 0x63, 0x65,
0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa3, 0xd1, 0x81, 0xd0, 0xbf,
0xd0, 0xb5, 0xd1, 0x88, 0xd0, 0xbd, 0xd0, 0xbe, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0x9f, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb4, 0xd1,
0x83, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb6, 0xd0, 0xb4, 0xd0,
0xb5, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xb5, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78,
0x63, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xd0, 0xa1, 0xd1, 0x83, 0xd0, 0xbc, 0xd0,
0xbc, 0xd0, 0xb0, 0x20, 0xd0, 0xbf, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb2,
0xd1, 0x8b, 0xd1, 0x88, 0xd0, 0xb0, 0xd0, 0xb5, 0xd1, 0x82, 0x20, 0xd0,
0xb1, 0xd0, 0xb0, 0xd0, 0xbb, 0xd0, 0xb0, 0xd0, 0xbd, 0xd1, 0x81, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x22,
0x3a, 0x20, 0x22, 0xd0, 0xa2, 0xd1, 0x80, 0xd0, 0xb0, 0xd0, 0xbd, 0xd0,
0xb7, 0xd0, 0xb0, 0xd0, 0xba, 0xd1, 0x86, 0xd0, 0xb8, 0xd1, 0x8f, 0x20,
0xd1, 0x83, 0xd1, 0x81, 0xd0, 0xbf, 0xd0, 0xb5, 0xd1, 0x88, 0xd0, 0xbd,
0xd0, 0xbe, 0x20, 0xd0, 0xbe, 0xd1, 0x82, 0xd0, 0xbf, 0xd1, 0x80, 0xd0,
0xb0, 0xd0, 0xb2, 0xd0, 0xbb, 0xd0, 0xb5, 0xd0, 0xbd, 0xd0, 0xb0, 0x22,
0x0a, 0x7d, 0x0a
};
unsigned int res_lang_ru_json_len = 5979;

355
src/embedded/lang_zh.h Normal file
View File

@@ -0,0 +1,355 @@
unsigned char res_lang_zh_json[] = {
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xbd, 0x99, 0xe9, 0xa2, 0x9d,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0x91, 0xe9, 0x80, 0x81, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8e, 0xa5, 0xe6, 0x94, 0xb6, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe4,
0xba, 0xa4, 0xe6, 0x98, 0x93, 0xe8, 0xae, 0xb0, 0xe5, 0xbd, 0x95, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8c, 0x96, 0xe7, 0x9f, 0xbf, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x65, 0x72, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xe8, 0x8a, 0x82, 0xe7, 0x82, 0xb9, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb8, 0x82, 0xe5, 0x9c, 0xba, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e,
0x67, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe8, 0xae, 0xbe, 0xe7, 0xbd, 0xae,
0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x6d,
0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xa6, 0x82, 0xe8,
0xa7, 0x88, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x68,
0x69, 0x65, 0x6c, 0x64, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x9a,
0x90, 0xe7, 0xa7, 0x81, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x8f,
0xe6, 0x98, 0x8e, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a,
0x20, 0x22, 0xe6, 0x80, 0xbb, 0xe8, 0xae, 0xa1, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x9c, 0xaa, 0xe7, 0xa1,
0xae, 0xe8, 0xae, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x79, 0x6f, 0x75, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x82, 0xa8, 0xe7, 0x9a, 0x84,
0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x7a, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x5a, 0x2d, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d,
0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x54, 0x2d, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x9c, 0xaa, 0xe6,
0x89, 0xbe, 0xe5, 0x88, 0xb0, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0xe3,
0x80, 0x82, 0xe8, 0xaf, 0xb7, 0xe4, 0xbd, 0xbf, 0xe7, 0x94, 0xa8, 0xe4,
0xb8, 0x8a, 0xe6, 0x96, 0xb9, 0xe6, 0x8c, 0x89, 0xe9, 0x92, 0xae, 0xe5,
0x88, 0x9b, 0xe5, 0xbb, 0xba, 0xe3, 0x80, 0x82, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x7a, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x96, 0xb0,
0xe5, 0xbb, 0xba, 0x20, 0x5a, 0x2d, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f,
0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20,
0x22, 0xe6, 0x96, 0xb0, 0xe5, 0xbb, 0xba, 0x20, 0x54, 0x2d, 0xe5, 0x9c,
0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xb1, 0xbb, 0xe5,
0x9e, 0x8b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x9c, 0xb0,
0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63,
0x6f, 0x70, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xe5, 0xa4, 0x8d, 0xe5, 0x88, 0xb6, 0xe5, 0xae, 0x8c,
0xe6, 0x95, 0xb4, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x66, 0x72,
0x6f, 0x6d, 0x5f, 0x74, 0x68, 0x69, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xbb, 0x8e, 0xe6, 0xad,
0xa4, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0xe5, 0x8f, 0x91, 0xe9, 0x80,
0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f,
0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xaf, 0xbc, 0xe5, 0x87,
0xba, 0xe7, 0xa7, 0x81, 0xe9, 0x92, 0xa5, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x76, 0x69,
0x65, 0x77, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x20,
0x22, 0xe5, 0xaf, 0xbc, 0xe5, 0x87, 0xba, 0xe6, 0x9f, 0xa5, 0xe7, 0x9c,
0x8b, 0xe5, 0xaf, 0x86, 0xe9, 0x92, 0xa5, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x71, 0x72, 0x5f, 0x63,
0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x98, 0xbe, 0xe7, 0xa4,
0xba, 0xe4, 0xba, 0x8c, 0xe7, 0xbb, 0xb4, 0xe7, 0xa0, 0x81, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe6,
0x9c, 0xaa, 0xe8, 0xbf, 0x9e, 0xe6, 0x8e, 0xa5, 0xe5, 0x88, 0xb0, 0xe5,
0xae, 0x88, 0xe6, 0x8a, 0xa4, 0xe8, 0xbf, 0x9b, 0xe7, 0xa8, 0x8b, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70,
0x61, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0xe4,
0xbb, 0x98, 0xe6, 0xac, 0xbe, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f,
0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x94, 0xb6, 0xe6, 0xac, 0xbe,
0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22,
0xe9, 0x87, 0x91, 0xe9, 0xa2, 0x9d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xa4,
0x87, 0xe6, 0xb3, 0xa8, 0xef, 0xbc, 0x88, 0xe5, 0x8f, 0xaf, 0xe9, 0x80,
0x89, 0xef, 0xbc, 0x8c, 0xe5, 0xb7, 0xb2, 0xe5, 0x8a, 0xa0, 0xe5, 0xaf,
0x86, 0xef, 0xbc, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x22, 0x3a, 0x20,
0x22, 0xe7, 0x9f, 0xbf, 0xe5, 0xb7, 0xa5, 0xe6, 0x89, 0x8b, 0xe7, 0xbb,
0xad, 0xe8, 0xb4, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x66, 0x65, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x89, 0x8b, 0xe7, 0xbb,
0xad, 0xe8, 0xb4, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0x91, 0xe9,
0x80, 0x81, 0xe4, 0xba, 0xa4, 0xe6, 0x98, 0x93, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x22, 0x3a, 0x20,
0x22, 0xe6, 0xb8, 0x85, 0xe9, 0x99, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x89,
0xe6, 0x8b, 0xa9, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x2e, 0x2e, 0x2e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x73, 0x74,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xb2, 0x98, 0xe8, 0xb4, 0xb4, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x78, 0x22, 0x3a,
0x20, 0x22, 0xe6, 0x9c, 0x80, 0xe5, 0xa4, 0xa7, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0xaf, 0xe7, 0x94, 0xa8, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6e, 0x76, 0x61, 0x6c,
0x69, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a,
0x20, 0x22, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0xe6, 0xa0, 0xbc, 0xe5,
0xbc, 0x8f, 0xe6, 0x97, 0xa0, 0xe6, 0x95, 0x88, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x7a, 0x5f, 0x6f,
0x6e, 0x6c, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xb3, 0xa8, 0xe6, 0x84,
0x8f, 0xef, 0xbc, 0x9a, 0xe5, 0xa4, 0x87, 0xe6, 0xb3, 0xa8, 0xe4, 0xbb,
0x85, 0xe5, 0x9c, 0xa8, 0xe5, 0x8f, 0x91, 0xe9, 0x80, 0x81, 0xe5, 0x88,
0xb0, 0xe9, 0x9a, 0x90, 0xe7, 0xa7, 0x81, 0xef, 0xbc, 0x88, 0x7a, 0xef,
0xbc, 0x89, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0xe6, 0x97, 0xb6, 0xe5,
0x8f, 0xaf, 0xe7, 0x94, 0xa8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x22,
0x3a, 0x20, 0x22, 0xe5, 0xad, 0x97, 0xe7, 0xac, 0xa6, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x20,
0x22, 0xe5, 0x8f, 0x91, 0xe9, 0x80, 0x81, 0xe6, 0x96, 0xb9, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22,
0xe6, 0x8e, 0xa5, 0xe6, 0x94, 0xb6, 0xe6, 0x96, 0xb9, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0xe6, 0xad, 0xa3, 0xe5, 0x9c, 0xa8, 0xe5, 0x8f,
0x91, 0xe9, 0x80, 0x81, 0xe4, 0xba, 0xa4, 0xe6, 0x98, 0x93, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xa1,
0xae, 0xe8, 0xae, 0xa4, 0xe5, 0x8f, 0x91, 0xe9, 0x80, 0x81, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xa1, 0xae, 0xe8, 0xae, 0xa4, 0xe4,
0xba, 0xa4, 0xe6, 0x98, 0x93, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x61, 0x6e, 0x64,
0x5f, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xa1, 0xae,
0xe8, 0xae, 0xa4, 0xe5, 0xb9, 0xb6, 0xe5, 0x8f, 0x91, 0xe9, 0x80, 0x81,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x6e, 0x63,
0x65, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8f, 0x96, 0xe6, 0xb6, 0x88,
0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x63,
0x65, 0x69, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x82, 0xa8, 0xe7,
0x9a, 0x84, 0xe6, 0x8e, 0xa5, 0xe6, 0x94, 0xb6, 0xe5, 0x9c, 0xb0, 0xe5,
0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65,
0x77, 0x5f, 0x7a, 0x5f, 0x73, 0x68, 0x69, 0x65, 0x6c, 0x64, 0x65, 0x64,
0x22, 0x3a, 0x20, 0x22, 0xe6, 0x96, 0xb0, 0xe5, 0xbb, 0xba, 0x20, 0x7a,
0x2d, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0xef, 0xbc, 0x88, 0xe9, 0x9a,
0x90, 0xe7, 0xa7, 0x81, 0xef, 0xbc, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x5f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22,
0xe6, 0x96, 0xb0, 0xe5, 0xbb, 0xba, 0x20, 0x74, 0x2d, 0xe5, 0x9c, 0xb0,
0xe5, 0x9d, 0x80, 0xef, 0xbc, 0x88, 0xe9, 0x80, 0x8f, 0xe6, 0x98, 0x8e,
0xef, 0xbc, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69,
0x6c, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x9c, 0xb0, 0xe5, 0x9d, 0x80,
0xe8, 0xaf, 0xa6, 0xe6, 0x83, 0x85, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6f, 0x6e, 0x5f, 0x65, 0x78,
0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x9c,
0xa8, 0xe6, 0xb5, 0x8f, 0xe8, 0xa7, 0x88, 0xe5, 0x99, 0xa8, 0xe6, 0x9f,
0xa5, 0xe7, 0x9c, 0x8b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe4,
0xba, 0x8c, 0xe7, 0xbb, 0xb4, 0xe7, 0xa0, 0x81, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f,
0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe8,
0xaf, 0xb7, 0xe6, 0xb1, 0x82, 0xe4, 0xbb, 0x98, 0xe6, 0xac, 0xbe, 0x22,
0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, 0x74, 0x65,
0x22, 0x3a, 0x20, 0x22, 0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0x9f, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xe7, 0x8a, 0xb6, 0xe6, 0x80, 0x81, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe7,
0xa1, 0xae, 0xe8, 0xae, 0xa4, 0xe6, 0x95, 0xb0, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65,
0x64, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb7, 0xb2, 0xe7, 0xa1, 0xae, 0xe8,
0xae, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xbe, 0x85,
0xe7, 0xa1, 0xae, 0xe8, 0xae, 0xa4, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb7,
0xb2, 0xe5, 0x8f, 0x91, 0xe9, 0x80, 0x81, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x22,
0x3a, 0x20, 0x22, 0xe5, 0xb7, 0xb2, 0xe6, 0x8e, 0xa5, 0xe6, 0x94, 0xb6,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x65,
0x64, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb7, 0xb2, 0xe6, 0x8c, 0x96, 0xe5,
0x87, 0xba, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8c, 0x96, 0xe7, 0x9f, 0xbf, 0xe6,
0x8e, 0xa7, 0xe5, 0x88, 0xb6, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xbc, 0x80, 0xe5, 0xa7, 0x8b, 0xe6,
0x8c, 0x96, 0xe7, 0x9f, 0xbf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0x81, 0x9c, 0xe6, 0xad, 0xa2, 0xe6, 0x8c,
0x96, 0xe7, 0x9f, 0xbf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61,
0x64, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8c, 0x96, 0xe7, 0x9f, 0xbf,
0xe7, 0xba, 0xbf, 0xe7, 0xa8, 0x8b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61,
0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6,
0x8c, 0x96, 0xe7, 0x9f, 0xbf, 0xe7, 0xbb, 0x9f, 0xe8, 0xae, 0xa1, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20,
0x22, 0xe6, 0x9c, 0xac, 0xe5, 0x9c, 0xb0, 0xe7, 0xae, 0x97, 0xe5, 0x8a,
0x9b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x85, 0xa8, 0xe7, 0xbd, 0x91, 0xe7,
0xae, 0x97, 0xe5, 0x8a, 0x9b, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x22,
0x3a, 0x20, 0x22, 0xe9, 0x9a, 0xbe, 0xe5, 0xba, 0xa6, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
0x20, 0x22, 0xe9, 0xa2, 0x84, 0xe8, 0xae, 0xa1, 0xe5, 0x87, 0xba, 0xe5,
0x9d, 0x97, 0xe6, 0x97, 0xb6, 0xe9, 0x97, 0xb4, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6f,
0x66, 0x66, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8c, 0x96, 0xe7, 0x9f, 0xbf,
0xe5, 0xb7, 0xb2, 0xe5, 0x85, 0xb3, 0xe9, 0x97, 0xad, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f,
0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x8c, 0x96, 0xe7, 0x9f, 0xbf,
0xe5, 0xb7, 0xb2, 0xe5, 0xbc, 0x80, 0xe5, 0x90, 0xaf, 0x22, 0x2c, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x20,
0x22, 0xe5, 0xb7, 0xb2, 0xe8, 0xbf, 0x9e, 0xe6, 0x8e, 0xa5, 0xe8, 0x8a,
0x82, 0xe7, 0x82, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb7, 0xb2, 0xe5, 0xb0, 0x81, 0xe7, 0xa6,
0x81, 0xe8, 0x8a, 0x82, 0xe7, 0x82, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x3a, 0x20, 0x22, 0x49, 0x50, 0x20, 0xe5, 0x9c, 0xb0, 0xe5,
0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0x89, 0x88,
0xe6, 0x9c, 0xac, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0xab, 0x98,
0xe5, 0xba, 0xa6, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xbb, 0xb6, 0xe8, 0xbf,
0x9f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x61, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb0, 0x81, 0xe7, 0xa6, 0x81, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x6e, 0x62, 0x61, 0x6e, 0x22,
0x3a, 0x20, 0x22, 0xe8, 0xa7, 0xa3, 0xe5, 0xb0, 0x81, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x61,
0x6c, 0x6c, 0x5f, 0x62, 0x61, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x22, 0xe6,
0xb8, 0x85, 0xe9, 0x99, 0xa4, 0xe6, 0x89, 0x80, 0xe6, 0x9c, 0x89, 0xe5,
0xb0, 0x81, 0xe7, 0xa6, 0x81, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x72,
0x74, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xbb, 0xb7, 0xe6, 0xa0, 0xbc, 0xe5,
0x9b, 0xbe, 0xe8, 0xa1, 0xa8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69,
0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xbd, 0x93, 0xe5, 0x89, 0x8d,
0xe4, 0xbb, 0xb7, 0xe6, 0xa0, 0xbc, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x32, 0x34, 0xe5, 0xb0, 0x8f, 0xe6, 0x97, 0xb6,
0xe6, 0xb6, 0xa8, 0xe8, 0xb7, 0x8c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x32, 0x34, 0x68, 0x5f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65,
0x22, 0x3a, 0x20, 0x22, 0x32, 0x34, 0xe5, 0xb0, 0x8f, 0xe6, 0x97, 0xb6,
0xe6, 0x88, 0x90, 0xe4, 0xba, 0xa4, 0xe9, 0x87, 0x8f, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f,
0x63, 0x61, 0x70, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb8, 0x82, 0xe5, 0x80,
0xbc, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65,
0x6e, 0x65, 0x72, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x80, 0x9a,
0xe7, 0x94, 0xa8, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64,
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x98,
0xbe, 0xe7, 0xa4, 0xba, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xe7,
0xbd, 0x91, 0xe7, 0xbb, 0x9c, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe4, 0xb8,
0xbb, 0xe9, 0xa2, 0x98, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x20, 0x22,
0xe8, 0xaf, 0xad, 0xe8, 0xa8, 0x80, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x5f, 0x67, 0x72,
0x65, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x72, 0x61, 0x67, 0x6f,
0x6e, 0x58, 0xef, 0xbc, 0x88, 0xe7, 0xbb, 0xbf, 0xe8, 0x89, 0xb2, 0xef,
0xbc, 0x89, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61,
0x72, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xb7, 0xb1, 0xe8, 0x89, 0xb2,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0xb5, 0x85, 0xe8, 0x89, 0xb2, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x66, 0x65, 0x65, 0x73,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0x85, 0x81, 0xe8, 0xae, 0xb8, 0xe8, 0x87,
0xaa, 0xe5, 0xae, 0x9a, 0xe4, 0xb9, 0x89, 0xe6, 0x89, 0x8b, 0xe7, 0xbb,
0xad, 0xe8, 0xb4, 0xb9, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x75, 0x73, 0x65, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64,
0x5f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0xe4,
0xbd, 0xbf, 0xe7, 0x94, 0xa8, 0xe5, 0x86, 0x85, 0xe7, 0xbd, 0xae, 0x20,
0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x64, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x61, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x22,
0xe4, 0xbf, 0x9d, 0xe5, 0xad, 0x98, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe5,
0x85, 0xb3, 0xe9, 0x97, 0xad, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x96,
0x87, 0xe4, 0xbb, 0xb6, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x65, 0x64, 0x69, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xbc, 0x96, 0xe8,
0xbe, 0x91, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x69,
0x65, 0x77, 0x22, 0x3a, 0x20, 0x22, 0xe6, 0x9f, 0xa5, 0xe7, 0x9c, 0x8b,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x70,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0xb8, 0xae, 0xe5, 0x8a, 0xa9, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
0x22, 0x3a, 0x20, 0x22, 0xe5, 0xaf, 0xbc, 0xe5, 0x85, 0xa5, 0xe7, 0xa7,
0x81, 0xe9, 0x92, 0xa5, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0xa4, 0x87, 0xe4,
0xbb, 0xbd, 0xe9, 0x92, 0xb1, 0xe5, 0x8c, 0x85, 0x2e, 0x2e, 0x2e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x69, 0x74, 0x22,
0x3a, 0x20, 0x22, 0xe9, 0x80, 0x80, 0xe5, 0x87, 0xba, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x5f, 0x64,
0x72, 0x61, 0x67, 0x6f, 0x6e, 0x78, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x85,
0xb3, 0xe4, 0xba, 0x8e, 0x20, 0x4f, 0x62, 0x73, 0x69, 0x64, 0x69, 0x61,
0x6e, 0x44, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x6e,
0x6f, 0x77, 0x22, 0x3a, 0x20, 0x22, 0xe7, 0xab, 0x8b, 0xe5, 0x8d, 0xb3,
0xe5, 0x88, 0xb7, 0xe6, 0x96, 0xb0, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x22, 0x3a, 0x20, 0x22,
0xe5, 0x85, 0xb3, 0xe4, 0xba, 0x8e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22,
0xe5, 0xaf, 0xbc, 0xe5, 0x85, 0xa5, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20, 0x22,
0xe5, 0xaf, 0xbc, 0xe5, 0x87, 0xba, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6c,
0x69, 0x70, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20, 0x22, 0xe5,
0xa4, 0x8d, 0xe5, 0x88, 0xb6, 0xe5, 0x88, 0xb0, 0xe5, 0x89, 0xaa, 0xe8,
0xb4, 0xb4, 0xe6, 0x9d, 0xbf, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22,
0x3a, 0x20, 0x22, 0xe5, 0xb7, 0xb2, 0xe8, 0xbf, 0x9e, 0xe6, 0x8e, 0xa5,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x69, 0x73, 0x63,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x22,
0xe5, 0xb7, 0xb2, 0xe6, 0x96, 0xad, 0xe5, 0xbc, 0x80, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0xe8, 0xbf, 0x9e, 0xe6, 0x8e,
0xa5, 0xe4, 0xb8, 0xad, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a,
0x20, 0x22, 0xe5, 0x90, 0x8c, 0xe6, 0xad, 0xa5, 0xe4, 0xb8, 0xad, 0x2e,
0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x20, 0x22, 0xe5, 0x8c, 0xba, 0xe5, 0x9d,
0x97, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x61, 0x76,
0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0xe6,
0x9a, 0x82, 0xe6, 0x97, 0xa0, 0xe5, 0x8f, 0xaf, 0xe7, 0x94, 0xa8, 0xe5,
0x9c, 0xb0, 0xe5, 0x9d, 0x80, 0x22, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x20, 0x22, 0xe9,
0x94, 0x99, 0xe8, 0xaf, 0xaf, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0xe6, 0x88, 0x90, 0xe5, 0x8a, 0x9f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20,
0x22, 0xe8, 0xad, 0xa6, 0xe5, 0x91, 0x8a, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78,
0x63, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x65, 0x22, 0x3a, 0x20, 0x22, 0xe9, 0x87, 0x91, 0xe9, 0xa2, 0x9d, 0xe8,
0xb6, 0x85, 0xe5, 0x87, 0xba, 0xe4, 0xbd, 0x99, 0xe9, 0xa2, 0x9d, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x22,
0x3a, 0x20, 0x22, 0xe4, 0xba, 0xa4, 0xe6, 0x98, 0x93, 0xe5, 0x8f, 0x91,
0xe9, 0x80, 0x81, 0xe6, 0x88, 0x90, 0xe5, 0x8a, 0x9f, 0x22, 0x0a, 0x7d,
0x0a
};
unsigned int res_lang_zh_json_len = 4213;

65
src/embedded/resources.h Normal file
View File

@@ -0,0 +1,65 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// Embedded Resources Header
// This provides access to resources embedded in the binary
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <unordered_map>
namespace dragonx {
namespace embedded {
// Forward declarations for embedded data (generated at build time)
struct EmbeddedResource {
const unsigned char* data;
size_t size;
};
// Resource registry
class Resources {
public:
static Resources& instance() {
static Resources inst;
return inst;
}
// Get embedded resource by name
// Returns nullptr if not found
const EmbeddedResource* get(const std::string& name) const {
auto it = resources_.find(name);
if (it != resources_.end()) {
return &it->second;
}
return nullptr;
}
// Check if resource exists
bool has(const std::string& name) const {
return resources_.find(name) != resources_.end();
}
// Register a resource (called during static init)
void registerResource(const std::string& name, const unsigned char* data, size_t size) {
resources_[name] = {data, size};
}
private:
Resources() = default;
std::unordered_map<std::string, EmbeddedResource> resources_;
};
// Helper macro for registering resources
#define REGISTER_EMBEDDED_RESOURCE(name, data, size) \
static struct _EmbeddedResourceRegister_##name { \
_EmbeddedResourceRegister_##name() { \
dragonx::embedded::Resources::instance().registerResource(#name, data, size); \
} \
} _embedded_resource_register_##name
} // namespace embedded
} // namespace dragonx

1745
src/main.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "dx11_context.h"
#ifdef _WIN32
#include <SDL3/SDL.h>
#include <dcomp.h>
#include "../util/logger.h"
// Not defined in older MinGW SDK headers
#ifndef WS_EX_NOREDIRECTIONBITMAP
#define WS_EX_NOREDIRECTIONBITMAP 0x00200000L
#endif
namespace dragonx {
namespace platform {
// Helper to get HWND from SDL window
static HWND getHWND(SDL_Window* window) {
if (!window) return nullptr;
SDL_PropertiesID props = SDL_GetWindowProperties(window);
if (props == 0) return nullptr;
return (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
}
bool DX11Context::init(SDL_Window* window)
{
HWND hwnd = getHWND(window);
if (!hwnd) {
DEBUG_LOGF("DX11: Failed to get HWND from SDL window\n");
return false;
}
hwnd_ = hwnd;
// Ensure WS_EX_NOREDIRECTIONBITMAP is set. The main code creates the
// HWND with this style, but if someone passes a different window we try
// to set it here as a fallback. We also call SetWindowPos with
// SWP_FRAMECHANGED so the DWM re-evaluates the extended style.
LONG_PTR exStyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
if (!(exStyle & WS_EX_NOREDIRECTIONBITMAP)) {
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOREDIRECTIONBITMAP);
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
LONG_PTR verify = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
if (verify & WS_EX_NOREDIRECTIONBITMAP)
DEBUG_LOGF("DX11: WS_EX_NOREDIRECTIONBITMAP set via fallback path\n");
else
DEBUG_LOGF("DX11: WARNING - WS_EX_NOREDIRECTIONBITMAP could NOT be set!\n");
} else {
DEBUG_LOGF("DX11: WS_EX_NOREDIRECTIONBITMAP already present (good)\n");
}
fflush(stdout);
// Create D3D11 device and context
D3D_FEATURE_LEVEL featureLevel;
const D3D_FEATURE_LEVEL featureLevelArray[] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0,
};
UINT createDeviceFlags = 0;
#ifdef DRAGONX_DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Need BGRA support for DirectComposition
createDeviceFlags |= D3D11_CREATE_DEVICE_BGRA_SUPPORT;
HRESULT hr = D3D11CreateDevice(
nullptr, // Default adapter
D3D_DRIVER_TYPE_HARDWARE, // Hardware rendering
nullptr, // No software module
createDeviceFlags,
featureLevelArray,
2,
D3D11_SDK_VERSION,
&device_,
&featureLevel,
&deviceContext_
);
if (FAILED(hr)) {
DEBUG_LOGF("DX11: D3D11CreateDevice failed (HRESULT 0x%08lx)\n", hr);
return false;
}
DEBUG_LOGF("DX11: Device created (feature level %d.%d)\n",
(featureLevel >> 12) & 0xF, (featureLevel >> 8) & 0xF);
// Get DXGI device for factory access and DirectComposition
IDXGIDevice* dxgiDevice = nullptr;
hr = device_->QueryInterface(IID_PPV_ARGS(&dxgiDevice));
if (FAILED(hr)) {
DEBUG_LOGF("DX11: QueryInterface IDXGIDevice failed\n");
shutdown();
return false;
}
IDXGIAdapter* dxgiAdapter = nullptr;
hr = dxgiDevice->GetAdapter(&dxgiAdapter);
if (FAILED(hr)) {
dxgiDevice->Release();
DEBUG_LOGF("DX11: GetAdapter failed\n");
shutdown();
return false;
}
IDXGIFactory2* dxgiFactory = nullptr;
hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
dxgiAdapter->Release();
if (FAILED(hr)) {
dxgiDevice->Release();
DEBUG_LOGF("DX11: GetParent IDXGIFactory2 failed\n");
shutdown();
return false;
}
// ---------------------------------------------------------------
// Strategy: CreateSwapChainForComposition + DirectComposition
//
// DXGI_ALPHA_MODE_PREMULTIPLIED only works with composition swap chains.
// CreateSwapChainForHwnd does NOT support premultiplied alpha.
// We must use DirectComposition to bind the composition swap chain
// to the window. This is how Windows Terminal achieves transparency.
// ---------------------------------------------------------------
DXGI_SWAP_CHAIN_DESC1 sd = {};
sd.Width = 0; // Auto-detect from HWND
sd.Height = 0;
sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
sd.Stereo = FALSE;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 2;
sd.Scaling = DXGI_SCALING_STRETCH;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
sd.Flags = 0;
// CreateSwapChainForComposition needs explicit width/height
RECT clientRect;
GetClientRect(hwnd, &clientRect);
sd.Width = clientRect.right - clientRect.left;
sd.Height = clientRect.bottom - clientRect.top;
if (sd.Width == 0) sd.Width = 1400;
if (sd.Height == 0) sd.Height = 900;
hr = dxgiFactory->CreateSwapChainForComposition(
device_,
&sd,
nullptr,
&swapChain_
);
if (SUCCEEDED(hr)) {
DEBUG_LOGF("DX11: Composition swap chain created (%ux%u, PREMULTIPLIED alpha)\n",
sd.Width, sd.Height);
// Create DirectComposition device and bind swap chain to HWND
hr = DCompositionCreateDevice(dxgiDevice, IID_PPV_ARGS(&dcompDevice_));
if (SUCCEEDED(hr)) {
hr = dcompDevice_->CreateTargetForHwnd(hwnd, TRUE, &dcompTarget_);
}
if (SUCCEEDED(hr)) {
hr = dcompDevice_->CreateVisual(&dcompVisual_);
}
if (SUCCEEDED(hr)) {
hr = dcompVisual_->SetContent(swapChain_);
}
if (SUCCEEDED(hr)) {
hr = dcompTarget_->SetRoot(dcompVisual_);
}
if (SUCCEEDED(hr)) {
hr = dcompDevice_->Commit();
}
if (SUCCEEDED(hr)) {
hasAlpha_ = true;
DEBUG_LOGF("DX11: DirectComposition bound to HWND (alpha compositing active)\n");
fflush(stdout);
} else {
DEBUG_LOGF("DX11: DirectComposition setup failed (0x%08lx), falling back\n", hr);
fflush(stderr);
// Clean up the composition objects and swap chain
if (dcompVisual_) { dcompVisual_->Release(); dcompVisual_ = nullptr; }
if (dcompTarget_) { dcompTarget_->Release(); dcompTarget_ = nullptr; }
if (dcompDevice_) { dcompDevice_->Release(); dcompDevice_ = nullptr; }
swapChain_->Release();
swapChain_ = nullptr;
}
} else {
DEBUG_LOGF("DX11: CreateSwapChainForComposition failed (0x%08lx)\n", hr);
}
// Fallback: standard HWND swap chain (no alpha transparency)
if (!swapChain_) {
sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
sd.Width = 0; // Auto-detect
sd.Height = 0;
hr = dxgiFactory->CreateSwapChainForHwnd(
device_,
hwnd,
&sd,
nullptr,
nullptr,
&swapChain_
);
if (SUCCEEDED(hr)) {
DEBUG_LOGF("DX11: HWND swap chain created (opaque, no alpha)\n");
}
}
dxgiFactory->Release();
dxgiDevice->Release();
if (FAILED(hr) || !swapChain_) {
DEBUG_LOGF("DX11: All swap chain creation paths failed\n");
shutdown();
return false;
}
createRenderTarget();
return true;
}
void DX11Context::shutdown()
{
cleanupRenderTarget();
// Release DirectComposition objects
if (dcompVisual_) { dcompVisual_->Release(); dcompVisual_ = nullptr; }
if (dcompTarget_) { dcompTarget_->Release(); dcompTarget_ = nullptr; }
if (dcompDevice_) { dcompDevice_->Release(); dcompDevice_ = nullptr; }
if (swapChain_) {
swapChain_->Release();
swapChain_ = nullptr;
}
if (deviceContext_) {
deviceContext_->Release();
deviceContext_ = nullptr;
}
if (device_) {
device_->Release();
device_ = nullptr;
}
hasAlpha_ = false;
}
void DX11Context::resize(int width, int height)
{
(void)width;
(void)height;
if (!swapChain_ || !deviceContext_) return;
cleanupRenderTarget();
// Unbind the render target from the pipeline — ResizeBuffers requires
// ALL outstanding references to back-buffer resources to be released.
// Without this, ResizeBuffers fails with DXGI_ERROR_INVALID_CALL when
// growing the window (shrinking appears fine because the old larger
// buffer simply gets cropped by DWM).
deviceContext_->OMSetRenderTargets(0, nullptr, nullptr);
deviceContext_->Flush();
// For composition swap chains (CreateSwapChainForComposition),
// passing 0,0 means "keep current size" — NOT auto-detect from HWND.
// We must pass the actual pixel dimensions from GetClientRect.
UINT w = 0, h = 0;
if (hwnd_) {
RECT rc;
GetClientRect(hwnd_, &rc);
w = static_cast<UINT>(rc.right - rc.left);
h = static_cast<UINT>(rc.bottom - rc.top);
}
if (w == 0 || h == 0) return;
HRESULT hr = swapChain_->ResizeBuffers(0, w, h, DXGI_FORMAT_UNKNOWN, 0);
if (FAILED(hr)) {
DEBUG_LOGF("DX11: ResizeBuffers(%u x %u) failed (0x%08lx)\n", w, h, hr);
}
createRenderTarget();
// Commit DirectComposition so it picks up the new buffer size
if (dcompDevice_) {
dcompDevice_->Commit();
}
}
void DX11Context::ensureSize()
{
if (!swapChain_ || !hwnd_) return;
// Query current swap chain buffer dimensions
DXGI_SWAP_CHAIN_DESC1 desc;
if (FAILED(swapChain_->GetDesc1(&desc))) return;
// Query actual HWND client area
RECT rc;
GetClientRect(hwnd_, &rc);
UINT clientW = static_cast<UINT>(rc.right - rc.left);
UINT clientH = static_cast<UINT>(rc.bottom - rc.top);
// Resize only when there's a mismatch
if (clientW > 0 && clientH > 0 &&
(desc.Width != clientW || desc.Height != clientH)) {
resize(static_cast<int>(clientW), static_cast<int>(clientH));
}
}
void DX11Context::clear(float r, float g, float b, float a)
{
if (!deviceContext_ || !renderTargetView_) return;
const float clearColor[4] = { r, g, b, a };
deviceContext_->ClearRenderTargetView(renderTargetView_, clearColor);
}
void DX11Context::present(int vsync)
{
if (!swapChain_) return;
swapChain_->Present(vsync ? 1 : 0, 0);
}
void DX11Context::createRenderTarget()
{
if (!swapChain_ || !device_) return;
ID3D11Texture2D* backBuffer = nullptr;
swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
if (backBuffer) {
device_->CreateRenderTargetView(backBuffer, nullptr, &renderTargetView_);
backBuffer->Release();
}
}
void DX11Context::cleanupRenderTarget()
{
if (renderTargetView_) {
renderTargetView_->Release();
renderTargetView_ = nullptr;
}
}
} // namespace platform
} // namespace dragonx
#endif // _WIN32

108
src/platform/dx11_context.h Normal file
View File

@@ -0,0 +1,108 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// DirectX 11 Rendering Context for Windows
// Uses DXGI swap chain with premultiplied alpha + DirectComposition
// for true per-pixel transparency with DWM Mica/Acrylic backdrops.
#pragma once
#ifdef _WIN32
#include <d3d11.h>
#include <dxgi1_2.h>
#include <cstdio>
// Forward declarations - DirectComposition interfaces
struct IDCompositionDevice;
struct IDCompositionTarget;
struct IDCompositionVisual;
struct SDL_Window;
namespace dragonx {
namespace platform {
class DX11Context {
public:
DX11Context() = default;
~DX11Context() { shutdown(); }
// Non-copyable
DX11Context(const DX11Context&) = delete;
DX11Context& operator=(const DX11Context&) = delete;
/**
* @brief Initialize D3D11 device and DXGI swap chain with alpha support
* @param window SDL window to create the swap chain for
* @return true on success
*/
bool init(SDL_Window* window);
/**
* @brief Shut down and release all D3D11/DXGI resources
*/
void shutdown();
/**
* @brief Resize the swap chain buffers (call on window resize)
* @param width New width (0 = auto-detect from swap chain)
* @param height New height (0 = auto-detect from swap chain)
*/
void resize(int width, int height);
/**
* @brief Ensure swap chain matches the current HWND client size.
* Call once per frame to guarantee the buffers stay in sync.
* No-op if the sizes already match.
*/
void ensureSize();
/**
* @brief Clear the render target with a color
* @param r Red (0-1)
* @param g Green (0-1)
* @param b Blue (0-1)
* @param a Alpha (0-1), 0 = fully transparent for DWM
*/
void clear(float r, float g, float b, float a);
/**
* @brief Present the frame (swap buffers)
* @param vsync 1 = vsync on, 0 = vsync off
*/
void present(int vsync = 1);
/** @brief Whether premultiplied alpha compositing is active */
bool hasAlphaCompositing() const { return hasAlpha_; }
// Accessors
ID3D11Device* device() const { return device_; }
ID3D11DeviceContext* deviceContext() const { return deviceContext_; }
ID3D11RenderTargetView* renderTargetView() const { return renderTargetView_; }
IDXGISwapChain1* swapChain() const { return swapChain_; }
private:
void createRenderTarget();
void cleanupRenderTarget();
// D3D11 core
ID3D11Device* device_ = nullptr;
ID3D11DeviceContext* deviceContext_ = nullptr;
IDXGISwapChain1* swapChain_ = nullptr;
ID3D11RenderTargetView* renderTargetView_ = nullptr;
// DirectComposition (for premultiplied alpha swap chain presentation)
IDCompositionDevice* dcompDevice_ = nullptr;
IDCompositionTarget* dcompTarget_ = nullptr;
IDCompositionVisual* dcompVisual_ = nullptr;
bool hasAlpha_ = false;
HWND hwnd_ = nullptr;
};
} // namespace platform
} // namespace dragonx
#endif // _WIN32

View File

@@ -0,0 +1,182 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "windows_backdrop.h"
#ifdef _WIN32
#include <windows.h>
#include <dwmapi.h>
#include <SDL3/SDL.h>
#include <cstdio>
#include "../util/logger.h"
// Link with dwmapi.lib
#pragma comment(lib, "dwmapi.lib")
// DWM attribute values not in older SDK headers
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
#ifndef DWMWA_SYSTEMBACKDROP_TYPE
#define DWMWA_SYSTEMBACKDROP_TYPE 38
#endif
#ifndef DWMWA_MICA_EFFECT
#define DWMWA_MICA_EFFECT 1029
#endif
// System backdrop types (Windows 11 22H2+)
typedef enum {
DWMSBT_AUTO = 0,
DWMSBT_NONE = 1,
DWMSBT_MAINWINDOW = 2, // Mica
DWMSBT_TRANSIENTWINDOW = 3, // Acrylic
DWMSBT_TABBEDWINDOW = 4 // Mica Alt
} DWM_SYSTEMBACKDROP_TYPE;
namespace dragonx {
namespace platform {
// Helper to get HWND from SDL window
static HWND getHWND(SDL_Window* window) {
if (!window) return nullptr;
SDL_PropertiesID props = SDL_GetWindowProperties(window);
if (props == 0) return nullptr;
return (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
}
WindowsVersionInfo getWindowsVersion() {
WindowsVersionInfo info = {};
// Use RtlGetVersion to get the real version (GetVersionEx is deprecated/lies)
typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (ntdll) {
RtlGetVersionPtr rtlGetVersion = (RtlGetVersionPtr)GetProcAddress(ntdll, "RtlGetVersion");
if (rtlGetVersion) {
RTL_OSVERSIONINFOW osvi = {};
osvi.dwOSVersionInfoSize = sizeof(osvi);
if (rtlGetVersion(&osvi) == 0) {
info.major = osvi.dwMajorVersion;
info.minor = osvi.dwMinorVersion;
info.build = osvi.dwBuildNumber;
}
}
}
info.isWindows11 = (info.major >= 10 && info.build >= 22000);
info.isWindows10 = (info.major >= 10 && info.build >= 10240);
return info;
}
bool isWindowsBackdropAvailable() {
WindowsVersionInfo ver = getWindowsVersion();
// Backdrop effects require at least Windows 10
return ver.isWindows10;
}
bool enableWindowsBackdrop(SDL_Window* window, WindowsBackdrop type) {
HWND hwnd = getHWND(window);
if (!hwnd) {
DEBUG_LOGF("WindowsBackdrop: Failed to get HWND\n");
return false;
}
WindowsVersionInfo ver = getWindowsVersion();
DEBUG_LOGF("WindowsBackdrop: Windows %d.%d.%d detected\n", ver.major, ver.minor, ver.build);
if (!ver.isWindows10) {
DEBUG_LOGF("WindowsBackdrop: Windows 10+ required\n");
return false;
}
// Enable dark mode for title bar (looks better with acrylic)
BOOL useDarkMode = TRUE;
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &useDarkMode, sizeof(useDarkMode));
// Auto-detect best backdrop type
if (type == WindowsBackdrop::Auto) {
if (ver.isWindows11 && ver.build >= 22621) {
// Windows 11 22H2+ - use Mica Alt for dark themed apps
type = WindowsBackdrop::MicaAlt;
} else if (ver.isWindows11) {
// Windows 11 21H2 - use Mica
type = WindowsBackdrop::Mica;
} else {
// Windows 10 - use Acrylic
type = WindowsBackdrop::Acrylic;
}
}
// Extend DWM frame into entire client area - required for all backdrop types
// This lets the DWM composition show through where we render with alpha = 0
MARGINS margins = {-1, -1, -1, -1};
DwmExtendFrameIntoClientArea(hwnd, &margins);
HRESULT hr = S_OK;
if (ver.build >= 22621) {
// Windows 11 22H2+ - use DWMWA_SYSTEMBACKDROP_TYPE
DWM_SYSTEMBACKDROP_TYPE backdrop;
switch (type) {
case WindowsBackdrop::Mica:
backdrop = DWMSBT_MAINWINDOW;
DEBUG_LOGF("WindowsBackdrop: Enabling Mica\n");
break;
case WindowsBackdrop::MicaAlt:
backdrop = DWMSBT_TABBEDWINDOW;
DEBUG_LOGF("WindowsBackdrop: Enabling Mica Alt\n");
break;
case WindowsBackdrop::Acrylic:
backdrop = DWMSBT_TRANSIENTWINDOW;
DEBUG_LOGF("WindowsBackdrop: Enabling Acrylic\n");
break;
default:
backdrop = DWMSBT_NONE;
break;
}
hr = DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdrop, sizeof(backdrop));
}
else if (ver.isWindows11) {
// Windows 11 21H2 - use legacy DWMWA_MICA_EFFECT
BOOL enableMica = (type == WindowsBackdrop::Mica || type == WindowsBackdrop::MicaAlt);
hr = DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &enableMica, sizeof(enableMica));
DEBUG_LOGF("WindowsBackdrop: Enabling Mica (legacy API)\n");
}
else {
// Windows 10 - frame already extended above, basic DWM transparency
// Note: Full acrylic on Win10 requires undocumented APIs
DEBUG_LOGF("WindowsBackdrop: Using extended frame (Win10)\n");
}
if (SUCCEEDED(hr)) {
DEBUG_LOGF("WindowsBackdrop: Successfully enabled\n");
return true;
} else {
DEBUG_LOGF("WindowsBackdrop: DWM call failed with HRESULT 0x%08lx\n", hr);
return false;
}
}
void disableWindowsBackdrop(SDL_Window* window) {
HWND hwnd = getHWND(window);
if (!hwnd) return;
DWM_SYSTEMBACKDROP_TYPE backdrop = DWMSBT_NONE;
DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdrop, sizeof(backdrop));
MARGINS margins = {0, 0, 0, 0};
DwmExtendFrameIntoClientArea(hwnd, &margins);
}
} // namespace platform
} // namespace dragonx
#endif // _WIN32

View File

@@ -0,0 +1,68 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// Windows-specific backdrop blur (Mica/Acrylic) support
// Uses DWM (Desktop Window Manager) APIs available on Windows 10/11
#ifdef _WIN32
#include <SDL3/SDL.h>
namespace dragonx {
namespace platform {
// Backdrop types available on Windows
enum class WindowsBackdrop {
None, // No system backdrop
Mica, // Windows 11 Mica material
MicaAlt, // Windows 11 Mica Alt (darker)
Acrylic, // Windows 10/11 Acrylic (transient)
Auto // Auto-detect best option
};
// Enable Windows system backdrop blur
// Returns true if successfully enabled
bool enableWindowsBackdrop(SDL_Window* window, WindowsBackdrop type = WindowsBackdrop::Auto);
// Disable Windows system backdrop
void disableWindowsBackdrop(SDL_Window* window);
// Check if system backdrop is available
bool isWindowsBackdropAvailable();
// Get Windows version info
struct WindowsVersionInfo {
int major;
int minor;
int build;
bool isWindows11; // Build >= 22000
bool isWindows10; // Build >= 10240
};
WindowsVersionInfo getWindowsVersion();
} // namespace platform
} // namespace dragonx
#else // !_WIN32
// Stub for non-Windows platforms
namespace dragonx {
namespace platform {
enum class WindowsBackdrop { None, Mica, MicaAlt, Acrylic, Auto };
inline bool enableWindowsBackdrop(SDL_Window*, WindowsBackdrop = WindowsBackdrop::Auto) { return false; }
inline void disableWindowsBackdrop(SDL_Window*) {}
inline bool isWindowsBackdropAvailable() { return false; }
struct WindowsVersionInfo { int major = 0; int minor = 0; int build = 0; bool isWindows11 = false; bool isWindows10 = false; };
inline WindowsVersionInfo getWindowsVersion() { return {}; }
} // namespace platform
} // namespace dragonx
#endif // _WIN32

View File

@@ -0,0 +1,564 @@
#include "embedded_resources.h"
#include "../util/platform.h"
#include "../util/logger.h"
#include <fstream>
#include <filesystem>
#include <vector>
#include <cstdio>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
// Include generated resource data if available
#if __has_include("embedded_data.h")
#include "embedded_data.h"
#define HAS_EMBEDDED_RESOURCES 1
#else
#define HAS_EMBEDDED_RESOURCES 0
#endif
namespace dragonx {
namespace resources {
#if HAS_EMBEDDED_RESOURCES
// Resource table - populated by embedded_data.h (INCBIN symbols: g_NAME_data / g_NAME_size)
static const EmbeddedResource s_resources[] = {
{ g_sapling_spend_params_data, g_sapling_spend_params_size, RESOURCE_SAPLING_SPEND },
{ g_sapling_output_params_data, g_sapling_output_params_size, RESOURCE_SAPLING_OUTPUT },
{ g_asmap_dat_data, g_asmap_dat_size, RESOURCE_ASMAP },
#ifdef HAS_EMBEDDED_DAEMON
{ g_hushd_exe_data, g_hushd_exe_size, RESOURCE_HUSHD },
{ g_hush_cli_exe_data, g_hush_cli_exe_size, RESOURCE_HUSH_CLI },
{ g_hush_tx_exe_data, g_hush_tx_exe_size, RESOURCE_HUSH_TX },
{ g_dragonxd_bat_data, g_dragonxd_bat_size, RESOURCE_DRAGONXD_BAT },
{ g_dragonx_cli_bat_data, g_dragonx_cli_bat_size, RESOURCE_DRAGONX_CLI_BAT },
#endif
#ifdef HAS_EMBEDDED_XMRIG
{ g_xmrig_exe_data, g_xmrig_exe_size, RESOURCE_XMRIG },
#endif
#ifdef HAS_EMBEDDED_GRADIENT
{ g_dark_gradient_png_data, g_dark_gradient_png_size, RESOURCE_DARK_GRADIENT },
#endif
#ifdef HAS_EMBEDDED_LOGO
{ g_logo_ObsidianDragon_dark_png_data, g_logo_ObsidianDragon_dark_png_size, RESOURCE_LOGO },
#endif
{ nullptr, 0, nullptr } // Sentinel
};
// Embedded themes table is generated by s_embedded_themes[] in embedded_data.h
#else
static const EmbeddedResource s_resources[] = {
{ nullptr, 0, nullptr } // No embedded resources
};
// No embedded themes on non-Windows builds (themes live on disk)
struct EmbeddedThemeEntry { const uint8_t* data; unsigned int size; const char* filename; };
static const EmbeddedThemeEntry s_embedded_themes[] = {
{ nullptr, 0, nullptr }
};
// No embedded images on non-Windows builds (images live on disk)
struct EmbeddedImageEntry { const uint8_t* data; unsigned int size; const char* filename; };
static const EmbeddedImageEntry s_embedded_images[] = {
{ nullptr, 0, nullptr }
};
#endif
bool hasEmbeddedResources()
{
#if HAS_EMBEDDED_RESOURCES
return true;
#else
return false;
#endif
}
const EmbeddedResource* getEmbeddedResource(const std::string& name)
{
// Search static resource table (params, daemon binaries, etc.)
for (const auto* res = s_resources; res->data != nullptr; ++res) {
if (name == res->filename) {
return res;
}
}
// Search dynamically generated image table (backgrounds + logos)
// These are generated by build.sh from res/img/ contents.
for (const auto* img = s_embedded_images; img->data != nullptr; ++img) {
if (name == img->filename) {
static thread_local EmbeddedResource imageResult;
imageResult = { img->data, img->size, img->filename };
return &imageResult;
}
}
return nullptr;
}
const EmbeddedTheme* getEmbeddedThemes()
{
// Map from generated table to public struct
// s_embedded_themes is generated by build.sh (or empty fallback)
static std::vector<EmbeddedTheme> themes;
static bool init = false;
if (!init) {
for (const auto* t = s_embedded_themes; t->data != nullptr; ++t) {
themes.push_back({ t->data, t->size, t->filename });
}
themes.push_back({ nullptr, 0, nullptr }); // Sentinel
init = true;
}
return themes.data();
}
int extractBundledThemes(const std::string& destDir)
{
namespace fs = std::filesystem;
int count = 0;
const auto* themes = getEmbeddedThemes();
if (!themes || !themes->data) return 0;
// Ensure destination exists
std::error_code ec;
fs::create_directories(destDir, ec);
if (ec) {
DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to create theme dir: %s\n", destDir.c_str());
return 0;
}
for (const auto* t = themes; t->data != nullptr; ++t) {
fs::path dest = fs::path(destDir) / t->filename;
// Always overwrite — bundled themes should match the binary version
std::ofstream f(dest, std::ios::binary);
if (f.is_open()) {
f.write(reinterpret_cast<const char*>(t->data), t->size);
f.close();
DEBUG_LOGF("[INFO] EmbeddedResources: Extracted theme: %s (%zu bytes)\n",
t->filename, t->size);
count++;
} else {
DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to write theme: %s\n", dest.string().c_str());
}
}
return count;
}
std::string getParamsDirectory()
{
#ifdef _WIN32
char appdata[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, appdata))) {
return std::string(appdata) + "\\ZcashParams";
}
return "";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw ? pw->pw_dir : "/tmp";
}
return std::string(home) + "/Library/Application Support/ZcashParams";
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw ? pw->pw_dir : "/tmp";
}
return std::string(home) + "/.zcash-params";
#endif
}
bool needsParamsExtraction()
{
if (!hasEmbeddedResources()) {
return false;
}
// Check daemon directory (hush3/) — the only extraction target
std::string daemonDir = getDaemonDirectory();
std::string spendPath = daemonDir +
#ifdef _WIN32
"\\sapling-spend.params";
#else
"/sapling-spend.params";
#endif
std::string outputPath = daemonDir +
#ifdef _WIN32
"\\sapling-output.params";
#else
"/sapling-output.params";
#endif
// Check if both params exist in daemon directory
return !std::filesystem::exists(spendPath) || !std::filesystem::exists(outputPath);
}
static bool extractResource(const EmbeddedResource* res, const std::string& destPath)
{
if (!res || !res->data || res->size == 0) {
return false;
}
// Create parent directories
std::filesystem::path path(destPath);
if (path.has_parent_path()) {
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
if (ec) {
DEBUG_LOGF("[ERROR] Failed to create directory %s: %s\n",
path.parent_path().string().c_str(), ec.message().c_str());
return false;
}
}
// Write file
std::ofstream file(destPath, std::ios::binary);
if (!file) {
DEBUG_LOGF("[ERROR] Failed to open %s for writing\n", destPath.c_str());
return false;
}
file.write(reinterpret_cast<const char*>(res->data), res->size);
if (!file) {
DEBUG_LOGF("[ERROR] Failed to write %zu bytes to %s\n", res->size, destPath.c_str());
return false;
}
DEBUG_LOGF("[INFO] Extracted %s (%zu bytes)\n", destPath.c_str(), res->size);
return true;
}
bool extractEmbeddedResources()
{
if (!hasEmbeddedResources()) {
DEBUG_LOGF("[ERROR] No embedded resources available\n");
return false;
}
bool success = true;
#ifdef _WIN32
const char pathSep = '\\';
#else
const char pathSep = '/';
#endif
// All files go to <ObsidianDragonDir>/hush3/
std::string daemonDir = getDaemonDirectory();
// Extract Sapling params to daemon directory alongside hushd
const EmbeddedResource* spendRes = getEmbeddedResource(RESOURCE_SAPLING_SPEND);
if (spendRes) {
std::string dest = daemonDir + pathSep + RESOURCE_SAPLING_SPEND;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting sapling-spend.params (%zu MB)...\n", spendRes->size / (1024*1024));
if (!extractResource(spendRes, dest)) {
success = false;
}
}
}
const EmbeddedResource* outputRes = getEmbeddedResource(RESOURCE_SAPLING_OUTPUT);
if (outputRes) {
std::string dest = daemonDir + pathSep + RESOURCE_SAPLING_OUTPUT;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting sapling-output.params (%zu MB)...\n", outputRes->size / (1024*1024));
if (!extractResource(outputRes, dest)) {
success = false;
}
}
}
// Extract asmap.dat to daemon directory
const EmbeddedResource* asmapRes = getEmbeddedResource(RESOURCE_ASMAP);
if (asmapRes) {
std::string dest = daemonDir + pathSep + RESOURCE_ASMAP;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting asmap.dat...\n");
if (!extractResource(asmapRes, dest)) {
success = false;
}
}
}
// Extract daemon binaries — NOT the data directory.
// Running hushd.exe from inside the data directory (where it writes blockchain
// data, debug.log, lock files, etc.) causes crashes on some Windows machines.
#ifdef HAS_EMBEDDED_DAEMON
DEBUG_LOGF("[INFO] Daemon extraction directory: %s\n", daemonDir.c_str());
const EmbeddedResource* hushdRes = getEmbeddedResource(RESOURCE_HUSHD);
if (hushdRes) {
std::string dest = daemonDir + pathSep + RESOURCE_HUSHD;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting hushd.exe (%zu MB)...\n", hushdRes->size / (1024*1024));
if (!extractResource(hushdRes, dest)) {
success = false;
}
}
}
const EmbeddedResource* cliRes = getEmbeddedResource(RESOURCE_HUSH_CLI);
if (cliRes) {
std::string dest = daemonDir + pathSep + RESOURCE_HUSH_CLI;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting hush-cli.exe (%zu MB)...\n", cliRes->size / (1024*1024));
if (!extractResource(cliRes, dest)) {
success = false;
}
}
}
const EmbeddedResource* batRes = getEmbeddedResource(RESOURCE_DRAGONXD_BAT);
if (batRes) {
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONXD_BAT;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting dragonxd.bat...\n");
if (!extractResource(batRes, dest)) {
success = false;
}
}
}
const EmbeddedResource* txRes = getEmbeddedResource(RESOURCE_HUSH_TX);
if (txRes) {
std::string dest = daemonDir + pathSep + RESOURCE_HUSH_TX;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting hush-tx.exe (%zu MB)...\n", txRes->size / (1024*1024));
if (!extractResource(txRes, dest)) {
success = false;
}
}
}
const EmbeddedResource* cliBatRes = getEmbeddedResource(RESOURCE_DRAGONX_CLI_BAT);
if (cliBatRes) {
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_CLI_BAT;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting dragonx-cli.bat...\n");
if (!extractResource(cliBatRes, dest)) {
success = false;
}
}
}
#endif
#ifdef HAS_EMBEDDED_XMRIG
const EmbeddedResource* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
if (xmrigRes) {
std::string dest = daemonDir + pathSep + RESOURCE_XMRIG;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting xmrig.exe (%zu MB)...\n", xmrigRes->size / (1024*1024));
if (!extractResource(xmrigRes, dest)) {
success = false;
}
}
}
#endif
return success;
}
std::string getDaemonDirectory()
{
// Daemon binaries live in %APPDATA%/ObsidianDragon/hush3/ (Windows) or
// ~/.config/ObsidianDragon/hush3/ (Linux) — separate from the blockchain
// data directory to avoid lock-file conflicts.
std::string obsidianDir = util::Platform::getObsidianDragonDir();
#ifdef _WIN32
return obsidianDir + "\\hush3";
#else
return obsidianDir + "/hush3";
#endif
}
bool needsDaemonExtraction()
{
#ifdef HAS_EMBEDDED_DAEMON
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
std::string hushdPath = daemonDir + "\\hushd.exe";
#else
std::string hushdPath = daemonDir + "/hushd";
#endif
return !std::filesystem::exists(hushdPath);
#else
return false;
#endif
}
bool hasDaemonAvailable()
{
#ifdef HAS_EMBEDDED_DAEMON
return true;
#else
// Check if daemon exists alongside the executable
#ifdef _WIN32
return std::filesystem::exists("hushd.exe") || std::filesystem::exists("dragonxd.bat");
#else
return std::filesystem::exists("hushd") || std::filesystem::exists("dragonxd");
#endif
#endif
}
std::string getDaemonPath()
{
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
const char pathSep = '\\';
const char* daemonName = "hushd.exe";
#else
const char pathSep = '/';
const char* daemonName = "hushd";
#endif
DEBUG_LOGF("[DEBUG] getDaemonPath: daemonDir=%s\n", daemonDir.c_str());
#ifdef HAS_EMBEDDED_DAEMON
// Extract if needed
std::string embeddedPath = daemonDir + pathSep + daemonName;
DEBUG_LOGF("[DEBUG] getDaemonPath: checking embedded path %s\n", embeddedPath.c_str());
if (!std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[INFO] getDaemonPath: daemon not found, extracting embedded resources...\n");
extractEmbeddedResources();
}
if (std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[INFO] getDaemonPath: found at %s\n", embeddedPath.c_str());
return embeddedPath;
}
#endif
// Check local directory
if (std::filesystem::exists(daemonName)) {
DEBUG_LOGF("[INFO] getDaemonPath: found in local directory\n");
return daemonName;
}
DEBUG_LOGF("[ERROR] getDaemonPath: daemon binary not found anywhere\n");
return "";
}
bool needsXmrigExtraction()
{
#ifdef HAS_EMBEDDED_XMRIG
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
std::string xmrigPath = daemonDir + "\\xmrig.exe";
#else
std::string xmrigPath = daemonDir + "/xmrig";
#endif
return !std::filesystem::exists(xmrigPath);
#else
return false;
#endif
}
bool hasXmrigAvailable()
{
#ifdef HAS_EMBEDDED_XMRIG
return true;
#else
// Check if xmrig exists alongside the executable
#ifdef _WIN32
return std::filesystem::exists("xmrig.exe");
#else
return std::filesystem::exists("xmrig");
#endif
#endif
}
bool forceExtractXmrig()
{
#ifdef HAS_EMBEDDED_XMRIG
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
std::string dest = daemonDir + "\\" + RESOURCE_XMRIG;
#else
std::string dest = daemonDir + "/" + RESOURCE_XMRIG;
#endif
const EmbeddedResource* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
if (!xmrigRes) {
DEBUG_LOGF("[ERROR] forceExtractXmrig: no embedded xmrig resource\n");
return false;
}
DEBUG_LOGF("[INFO] forceExtractXmrig: extracting xmrig (%zu MB) to %s\n",
xmrigRes->size / (1024*1024), dest.c_str());
if (!extractResource(xmrigRes, dest)) {
DEBUG_LOGF("[ERROR] forceExtractXmrig: extraction failed\n");
return false;
}
#ifndef _WIN32
// Set executable permission on Linux
chmod(dest.c_str(), 0755);
#endif
return true;
#else
return false;
#endif
}
std::string getXmrigPath()
{
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
const char pathSep = '\\';
const char* xmrigName = "xmrig.exe";
#else
const char pathSep = '/';
const char* xmrigName = "xmrig";
#endif
DEBUG_LOGF("[DEBUG] getXmrigPath: daemonDir=%s\n", daemonDir.c_str());
#ifdef HAS_EMBEDDED_XMRIG
// Extract if needed — force re-extract in case Defender deleted it
std::string embeddedPath = daemonDir + pathSep + xmrigName;
DEBUG_LOGF("[DEBUG] getXmrigPath: checking embedded path %s\n", embeddedPath.c_str());
if (!std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[DEBUG] getXmrigPath: not found, force re-extracting xmrig\n");
forceExtractXmrig();
}
if (std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[INFO] getXmrigPath: found at %s\n", embeddedPath.c_str());
return embeddedPath;
}
#endif
// Check local directory
if (std::filesystem::exists(xmrigName)) {
DEBUG_LOGF("[INFO] getXmrigPath: found in local directory\n");
return xmrigName;
}
// Check development paths
#ifdef _WIN32
// Not applicable for cross-compiled Windows builds
#else
// Linux development build — resolve relative to executable directory
// (build/bin/), not CWD which may differ at runtime
std::string exeDir = util::Platform::getExecutableDirectory();
std::vector<std::string> devPaths = {
exeDir + "/../../external/xmrig-HAC/xmrig/build/xmrig", // from build/linux/bin/
exeDir + "/../external/xmrig-HAC/xmrig/build/xmrig", // from build/linux/
"../external/xmrig-HAC/xmrig/build/xmrig", // CWD = build/linux/
"../../external/xmrig-HAC/xmrig/build/xmrig", // CWD = build/linux/bin/
"external/xmrig-HAC/xmrig/build/xmrig", // CWD = project root
};
for (const auto& dp : devPaths) {
if (std::filesystem::exists(dp)) {
std::string resolved = std::filesystem::canonical(dp).string();
DEBUG_LOGF("[INFO] getXmrigPath: found dev binary at %s\n", resolved.c_str());
return resolved;
}
}
#endif
DEBUG_LOGF("[ERROR] getXmrigPath: xmrig binary not found anywhere\n");
return "";
}
} // namespace resources
} // namespace dragonx

View File

@@ -0,0 +1,86 @@
#pragma once
#include <string>
#include <cstdint>
namespace dragonx {
namespace resources {
// Embedded resource data (generated at build time)
struct EmbeddedResource {
const uint8_t* data;
size_t size;
const char* filename;
};
// Check if embedded resources are available
bool hasEmbeddedResources();
// Get embedded resource by name
// Returns nullptr if not found or not embedded
const EmbeddedResource* getEmbeddedResource(const std::string& name);
// Extract all embedded resources to appropriate locations
// Returns true if all resources extracted successfully
bool extractEmbeddedResources();
// Check if params need to be extracted (not already present)
bool needsParamsExtraction();
// Get the params directory path
std::string getParamsDirectory();
// Resource names
constexpr const char* RESOURCE_SAPLING_SPEND = "sapling-spend.params";
constexpr const char* RESOURCE_SAPLING_OUTPUT = "sapling-output.params";
constexpr const char* RESOURCE_ASMAP = "asmap.dat";
constexpr const char* RESOURCE_HUSHD = "hushd.exe";
constexpr const char* RESOURCE_HUSH_CLI = "hush-cli.exe";
constexpr const char* RESOURCE_HUSH_TX = "hush-tx.exe";
constexpr const char* RESOURCE_DRAGONXD_BAT = "dragonxd.bat";
constexpr const char* RESOURCE_DRAGONX_CLI_BAT = "dragonx-cli.bat";
constexpr const char* RESOURCE_XMRIG = "xmrig.exe";
constexpr const char* RESOURCE_DARK_GRADIENT = "dark_gradient.png";
constexpr const char* RESOURCE_LOGO = "logo_ObsidianDragon_dark.png";
// Embedded theme overlay info (for iterating bundled themes)
struct EmbeddedTheme {
const uint8_t* data;
size_t size;
const char* filename; // e.g. "dark.toml"
};
// Get array of embedded overlay themes (nullptr-terminated)
const EmbeddedTheme* getEmbeddedThemes();
// Extract bundled overlay themes to the given directory
// Returns number of themes extracted
int extractBundledThemes(const std::string& destDir);
// Check if daemon needs to be extracted
bool needsDaemonExtraction();
// Get daemon binary directory (<exe_dir>/hush3/)
std::string getDaemonDirectory();
// Check if daemon is available (embedded or in app directory)
bool hasDaemonAvailable();
// Get daemon executable path (extracts if needed)
std::string getDaemonPath();
// Check if xmrig needs to be extracted
bool needsXmrigExtraction();
// Check if xmrig is available (embedded or in app directory)
bool hasXmrigAvailable();
// Force re-extract xmrig binary from embedded resources
// (bypasses exists-check; use when Defender deletes the file)
bool forceExtractXmrig();
// Get xmrig executable path (extracts if needed)
std::string getXmrigPath();
} // namespace resources
} // namespace dragonx

224
src/rpc/connection.cpp Normal file
View File

@@ -0,0 +1,224 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "connection.h"
#include "../config/version.h"
#include "../resources/embedded_resources.h"
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <ctime>
#include <filesystem>
#include "../util/logger.h"
#ifdef _WIN32
#include <shlobj.h>
#else
#include <pwd.h>
#include <unistd.h>
#endif
namespace fs = std::filesystem;
namespace dragonx {
namespace rpc {
Connection::Connection() = default;
Connection::~Connection() = default;
std::string Connection::getDefaultDataDir()
{
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
return std::string(path) + "\\Hush\\DRAGONX";
}
return "";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw->pw_dir;
}
// Match SilentDragonX path: Library/Application Support/Hush/DRAGONX
return std::string(home) + "/Library/Application Support/Hush/DRAGONX";
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw->pw_dir;
}
return std::string(home) + "/.hush/DRAGONX";
#endif
}
std::string Connection::getDefaultConfPath()
{
return getDefaultDataDir() + "/" + DRAGONX_CONF_FILENAME;
}
std::string Connection::getSaplingParamsDir()
{
// Sapling params are now extracted alongside the daemon binaries
// in <ObsidianDragonDir>/hush3/ — no longer in the legacy ZcashParams dir.
return resources::getDaemonDirectory();
}
bool Connection::verifySaplingParams()
{
std::string params_dir = getSaplingParamsDir();
if (params_dir.empty()) {
DEBUG_LOGF("verifySaplingParams: params dir is empty\n");
return false;
}
#ifdef _WIN32
std::string spend_path = params_dir + "\\sapling-spend.params";
std::string output_path = params_dir + "\\sapling-output.params";
#else
std::string spend_path = params_dir + "/sapling-spend.params";
std::string output_path = params_dir + "/sapling-output.params";
#endif
bool spend_exists = fs::exists(spend_path);
bool output_exists = fs::exists(output_path);
DEBUG_LOGF("verifySaplingParams: dir=%s\n", params_dir.c_str());
DEBUG_LOGF(" spend: %s -> %s\n", spend_path.c_str(), spend_exists ? "found" : "MISSING");
DEBUG_LOGF(" output: %s -> %s\n", output_path.c_str(), output_exists ? "found" : "MISSING");
return spend_exists && output_exists;
}
ConnectionConfig Connection::parseConfFile(const std::string& path)
{
ConnectionConfig config;
std::ifstream file(path);
if (!file.is_open()) {
return config;
}
std::string line;
while (std::getline(file, line)) {
// Skip empty lines and comments
if (line.empty() || line[0] == '#') {
continue;
}
// Parse key=value
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) {
continue;
}
std::string key = line.substr(0, eq_pos);
std::string value = line.substr(eq_pos + 1);
// Trim whitespace
while (!key.empty() && (key.back() == ' ' || key.back() == '\t')) {
key.pop_back();
}
while (!value.empty() && (value[0] == ' ' || value[0] == '\t')) {
value.erase(0, 1);
}
// Map to config
if (key == "rpcuser") {
config.rpcuser = value;
} else if (key == "rpcpassword") {
config.rpcpassword = value;
} else if (key == "rpcport") {
config.port = value;
} else if (key == "rpchost" || key == "rpcconnect") {
config.host = value;
} else if (key == "proxy") {
config.proxy = value;
}
}
return config;
}
ConnectionConfig Connection::autoDetectConfig()
{
ConnectionConfig config;
// Ensure data directory exists
std::string data_dir = getDefaultDataDir();
if (!fs::exists(data_dir)) {
DEBUG_LOGF("Creating data directory: %s\n", data_dir.c_str());
fs::create_directories(data_dir);
}
// Try to find DRAGONX.conf
std::string conf_path = getDefaultConfPath();
if (fs::exists(conf_path)) {
config = parseConfFile(conf_path);
config.hush_dir = data_dir;
} else {
// Create a default config file
if (createDefaultConfig(conf_path)) {
config = parseConfFile(conf_path);
config.hush_dir = data_dir;
}
}
// Set defaults for missing values
if (config.host.empty()) {
config.host = DRAGONX_DEFAULT_RPC_HOST;
}
if (config.port.empty()) {
config.port = DRAGONX_DEFAULT_RPC_PORT;
}
return config;
}
bool Connection::createDefaultConfig(const std::string& path)
{
// Generate random rpcuser/rpcpassword
auto generateRandomString = [](int length) -> std::string {
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::string result;
result.reserve(length);
std::srand(static_cast<unsigned>(std::time(nullptr)));
for (int i = 0; i < length; i++) {
result += charset[std::rand() % (sizeof(charset) - 1)];
}
return result;
};
std::string rpcuser = generateRandomString(16);
std::string rpcpassword = generateRandomString(32);
std::ofstream file(path);
if (!file.is_open()) {
DEBUG_LOGF("Failed to create config file: %s\n", path.c_str());
return false;
}
file << "# DragonX configuration file\n";
file << "# Auto-generated by DragonX Wallet\n";
file << "\n";
file << "rpcuser=" << rpcuser << "\n";
file << "rpcpassword=" << rpcpassword << "\n";
file << "rpcport=" << DRAGONX_DEFAULT_RPC_PORT << "\n";
file << "server=1\n";
file << "txindex=1\n";
file << "addnode=195.201.20.230\n";
file << "addnode=195.201.137.219\n";
file.close();
DEBUG_LOGF("Created default config file: %s\n", path.c_str());
return true;
}
} // namespace rpc
} // namespace dragonx

80
src/rpc/connection.h Normal file
View File

@@ -0,0 +1,80 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
namespace dragonx {
namespace rpc {
/**
* @brief Connection configuration
*/
struct ConnectionConfig {
std::string host = "127.0.0.1";
std::string port = "21769";
std::string rpcuser;
std::string rpcpassword;
std::string hush_dir;
std::string proxy; // SOCKS5 proxy for Tor
bool use_embedded = true;
};
/**
* @brief Manages connection to dragonxd
*
* Handles auto-detection of DRAGONX.conf, starting embedded daemon,
* and connection lifecycle.
*/
class Connection {
public:
Connection();
~Connection();
/**
* @brief Auto-detect and load connection config
* @return Config from DRAGONX.conf or defaults
*/
static ConnectionConfig autoDetectConfig();
/**
* @brief Get the default DRAGONX.conf location
*/
static std::string getDefaultConfPath();
/**
* @brief Get the default DragonX data directory
*/
static std::string getDefaultDataDir();
/**
* @brief Parse a DRAGONX.conf file
* @param path Path to conf file
* @return Parsed configuration
*/
static ConnectionConfig parseConfFile(const std::string& path);
/**
* @brief Check if Sapling params exist
*/
static bool verifySaplingParams();
/**
* @brief Get the Sapling params directory
*/
static std::string getSaplingParamsDir();
/**
* @brief Create a default DRAGONX.conf file
* @param path Path to create the file
* @return true if created successfully
*/
static bool createDefaultConfig(const std::string& path);
private:
};
} // namespace rpc
} // namespace dragonx

599
src/rpc/rpc_client.cpp Normal file
View File

@@ -0,0 +1,599 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "rpc_client.h"
#include "../config/version.h"
#include "../util/base64.h"
#include <curl/curl.h>
#include <cstdio>
#include <cstring>
#include "../util/logger.h"
namespace dragonx {
namespace rpc {
// Callback for libcurl to write response data
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
size_t totalSize = size * nmemb;
userp->append((char*)contents, totalSize);
return totalSize;
}
// Private implementation using libcurl
class RPCClient::Impl {
public:
CURL* curl = nullptr;
struct curl_slist* headers = nullptr;
std::string url;
~Impl() {
if (headers) {
curl_slist_free_all(headers);
}
if (curl) {
curl_easy_cleanup(curl);
}
}
};
// Initialize curl globally (once)
static bool initCurl() {
static bool initialized = false;
if (!initialized) {
curl_global_init(CURL_GLOBAL_DEFAULT);
initialized = true;
}
return true;
}
static bool curl_init = initCurl();
RPCClient::RPCClient() : impl_(std::make_unique<Impl>())
{
}
RPCClient::~RPCClient() = default;
bool RPCClient::connect(const std::string& host, const std::string& port,
const std::string& user, const std::string& password)
{
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
host_ = host;
port_ = port;
// Create Basic auth header with proper base64 encoding
std::string credentials = user + ":" + password;
auth_ = util::base64_encode(credentials);
// Build URL - use HTTP for localhost RPC (TLS not always enabled)
impl_->url = "http://" + host + ":" + port + "/";
DEBUG_LOGF("Connecting to dragonxd at %s\n", impl_->url.c_str());
// Initialize curl handle
impl_->curl = curl_easy_init();
if (!impl_->curl) {
DEBUG_LOGF("Failed to initialize curl\n");
return false;
}
// Set up headers - daemon expects text/plain, not application/json
impl_->headers = curl_slist_append(impl_->headers, "Content-Type: text/plain");
std::string auth_header = "Authorization: Basic " + auth_;
impl_->headers = curl_slist_append(impl_->headers, auth_header.c_str());
// Configure curl
curl_easy_setopt(impl_->curl, CURLOPT_URL, impl_->url.c_str());
curl_easy_setopt(impl_->curl, CURLOPT_HTTPHEADER, impl_->headers);
curl_easy_setopt(impl_->curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, 30L);
curl_easy_setopt(impl_->curl, CURLOPT_CONNECTTIMEOUT, 3L);
// Test connection with getinfo
try {
json result = call("getinfo");
if (result.contains("version")) {
connected_ = true;
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
return true;
}
} catch (const std::exception& e) {
DEBUG_LOGF("Connection failed: %s\n", e.what());
}
connected_ = false;
return false;
}
void RPCClient::disconnect()
{
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
connected_ = false;
if (impl_->curl) {
curl_easy_cleanup(impl_->curl);
impl_->curl = nullptr;
}
if (impl_->headers) {
curl_slist_free_all(impl_->headers);
impl_->headers = nullptr;
}
}
json RPCClient::makePayload(const std::string& method, const json& params)
{
return {
{"jsonrpc", "1.0"},
{"id", "ObsidianDragon"},
{"method", method},
{"params", params}
};
}
json RPCClient::call(const std::string& method, const json& params)
{
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
if (!impl_->curl) {
throw std::runtime_error("Not connected");
}
json payload = makePayload(method, params);
std::string body = payload.dump();
std::string response_data;
// Set POST data
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
curl_easy_setopt(impl_->curl, CURLOPT_WRITEDATA, &response_data);
// Perform request
CURLcode res = curl_easy_perform(impl_->curl);
if (res != CURLE_OK) {
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
}
// Check HTTP response code
long http_code = 0;
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code);
// Bitcoin/Hush RPC returns HTTP 500 for application-level errors
// (insufficient funds, bad params, etc.) with a valid JSON body.
// Parse the body first to extract the real error message.
if (http_code != 200) {
try {
json response = json::parse(response_data);
if (response.contains("error") && !response["error"].is_null()) {
std::string err_msg = response["error"]["message"].get<std::string>();
throw std::runtime_error(err_msg);
}
} catch (const json::exception&) {
// Body wasn't valid JSON — fall through to generic HTTP error
}
throw std::runtime_error("RPC error: HTTP " + std::to_string(http_code));
}
json response = json::parse(response_data);
if (response.contains("error") && !response["error"].is_null()) {
std::string err_msg = response["error"]["message"].get<std::string>();
throw std::runtime_error("RPC error: " + err_msg);
}
return response["result"];
}
std::string RPCClient::callRaw(const std::string& method, const json& params)
{
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
if (!impl_->curl) {
throw std::runtime_error("Not connected");
}
json payload = makePayload(method, params);
std::string body = payload.dump();
std::string response_data;
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
curl_easy_setopt(impl_->curl, CURLOPT_WRITEDATA, &response_data);
CURLcode res = curl_easy_perform(impl_->curl);
if (res != CURLE_OK) {
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
}
long http_code = 0;
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 200) {
try {
json response = json::parse(response_data);
if (response.contains("error") && !response["error"].is_null()) {
std::string err_msg = response["error"]["message"].get<std::string>();
throw std::runtime_error(err_msg);
}
} catch (const json::exception&) {}
throw std::runtime_error("RPC error: HTTP " + std::to_string(http_code));
}
// Parse with ordered_json to preserve the daemon's original key order
nlohmann::ordered_json oj = nlohmann::ordered_json::parse(response_data);
if (oj.contains("error") && !oj["error"].is_null()) {
std::string err_msg = oj["error"]["message"].get<std::string>();
throw std::runtime_error("RPC error: " + err_msg);
}
auto& result = oj["result"];
if (result.is_null()) {
return "null";
} else if (result.is_string()) {
// Return the raw string (not JSON-encoded) — caller wraps as needed
return result.get<std::string>();
} else {
return result.dump(4);
}
}
void RPCClient::doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err)
{
try {
json result = call(method, params);
if (cb) cb(result);
} catch (const std::exception& e) {
if (err) {
err(e.what());
} else {
DEBUG_LOGF("RPC error (%s): %s\n", method.c_str(), e.what());
}
}
}
// High-level API implementations
void RPCClient::getInfo(Callback cb, ErrorCallback err)
{
doRPC("getinfo", {}, cb, err);
}
void RPCClient::getBlockchainInfo(Callback cb, ErrorCallback err)
{
doRPC("getblockchaininfo", {}, cb, err);
}
void RPCClient::getMiningInfo(Callback cb, ErrorCallback err)
{
doRPC("getmininginfo", {}, cb, err);
}
void RPCClient::getBalance(Callback cb, ErrorCallback err)
{
doRPC("getbalance", {}, cb, err);
}
void RPCClient::z_getTotalBalance(Callback cb, ErrorCallback err)
{
doRPC("z_gettotalbalance", {}, cb, err);
}
void RPCClient::listUnspent(Callback cb, ErrorCallback err)
{
doRPC("listunspent", {0}, cb, err);
}
void RPCClient::z_listUnspent(Callback cb, ErrorCallback err)
{
doRPC("z_listunspent", {0}, cb, err);
}
void RPCClient::getAddressesByAccount(Callback cb, ErrorCallback err)
{
doRPC("getaddressesbyaccount", {""}, cb, err);
}
void RPCClient::z_listAddresses(Callback cb, ErrorCallback err)
{
doRPC("z_listaddresses", {}, cb, err);
}
void RPCClient::getNewAddress(Callback cb, ErrorCallback err)
{
doRPC("getnewaddress", {}, cb, err);
}
void RPCClient::z_getNewAddress(Callback cb, ErrorCallback err)
{
doRPC("z_getnewaddress", {}, cb, err);
}
void RPCClient::listTransactions(int count, Callback cb, ErrorCallback err)
{
doRPC("listtransactions", {"", count}, cb, err);
}
void RPCClient::z_viewTransaction(const std::string& txid, Callback cb, ErrorCallback err)
{
doRPC("z_viewtransaction", {txid}, cb, err);
}
void RPCClient::getRawTransaction(const std::string& txid, Callback cb, ErrorCallback err)
{
doRPC("getrawtransaction", {txid, 1}, cb, err);
}
void RPCClient::sendToAddress(const std::string& address, double amount, Callback cb, ErrorCallback err)
{
doRPC("sendtoaddress", {address, amount}, cb, err);
}
void RPCClient::z_sendMany(const std::string& from, const json& recipients, Callback cb, ErrorCallback err)
{
doRPC("z_sendmany", {from, recipients}, cb, err);
}
void RPCClient::setGenerate(bool generate, int threads, Callback cb, ErrorCallback err)
{
doRPC("setgenerate", {generate, threads}, cb, err);
}
void RPCClient::getNetworkHashPS(Callback cb, ErrorCallback err)
{
doRPC("getnetworkhashps", {}, cb, err);
}
void RPCClient::getLocalHashrate(Callback cb, ErrorCallback err)
{
// RPC name is "getlocalsolps" (inherited from HUSH/Zcash daemon API)
// but DragonX uses RandomX, so the value is H/s not Sol/s
doRPC("getlocalsolps", {}, cb, err);
}
void RPCClient::getPeerInfo(Callback cb, ErrorCallback err)
{
doRPC("getpeerinfo", {}, cb, err);
}
void RPCClient::listBanned(Callback cb, ErrorCallback err)
{
doRPC("listbanned", {}, cb, err);
}
void RPCClient::setBan(const std::string& ip, const std::string& command, Callback cb, ErrorCallback err, int bantime)
{
// setban "ip" "add|remove" [bantime] [absolute]
doRPC("setban", {ip, command, bantime}, cb, err);
}
void RPCClient::clearBanned(Callback cb, ErrorCallback err)
{
doRPC("clearbanned", {}, cb, err);
}
void RPCClient::dumpPrivKey(const std::string& address, Callback cb, ErrorCallback err)
{
doRPC("dumpprivkey", {address}, cb, err);
}
void RPCClient::z_exportKey(const std::string& address, Callback cb, ErrorCallback err)
{
doRPC("z_exportkey", {address}, cb, err);
}
void RPCClient::z_exportViewingKey(const std::string& address, Callback cb, ErrorCallback err)
{
doRPC("z_exportviewingkey", {address}, cb, err);
}
void RPCClient::importPrivKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err)
{
doRPC("importprivkey", {key, "", rescan}, cb, err);
}
void RPCClient::z_importKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err)
{
doRPC("z_importkey", {key, rescan ? "yes" : "no"}, cb, err);
}
void RPCClient::validateAddress(const std::string& address, Callback cb, ErrorCallback err)
{
doRPC("validateaddress", {address}, cb, err);
}
void RPCClient::getBlock(const std::string& hash_or_height, Callback cb, ErrorCallback err)
{
doRPC("getblock", {hash_or_height}, cb, err);
}
void RPCClient::stop(Callback cb, ErrorCallback err)
{
doRPC("stop", {}, cb, err);
}
void RPCClient::rescanBlockchain(int startHeight, Callback cb, ErrorCallback err)
{
doRPC("rescanblockchain", {startHeight}, cb, err);
}
void RPCClient::z_validateAddress(const std::string& address, Callback cb, ErrorCallback err)
{
doRPC("z_validateaddress", {address}, cb, err);
}
void RPCClient::getBlockHash(int height, Callback cb, ErrorCallback err)
{
doRPC("getblockhash", {height}, cb, err);
}
void RPCClient::getTransaction(const std::string& txid, Callback cb, ErrorCallback err)
{
doRPC("gettransaction", {txid}, cb, err);
}
void RPCClient::getWalletInfo(Callback cb, ErrorCallback err)
{
doRPC("getwalletinfo", {}, cb, err);
}
void RPCClient::encryptWallet(const std::string& passphrase, Callback cb, ErrorCallback err)
{
doRPC("encryptwallet", {passphrase}, cb, err);
}
void RPCClient::walletPassphrase(const std::string& passphrase, int timeout, Callback cb, ErrorCallback err)
{
doRPC("walletpassphrase", {passphrase, timeout}, cb, err);
}
void RPCClient::walletLock(Callback cb, ErrorCallback err)
{
doRPC("walletlock", {}, cb, err);
}
void RPCClient::walletPassphraseChange(const std::string& oldPass, const std::string& newPass,
Callback cb, ErrorCallback err)
{
doRPC("walletpassphrasechange", {oldPass, newPass}, cb, err);
}
void RPCClient::z_exportWallet(const std::string& filename, Callback cb, ErrorCallback err)
{
doRPC("z_exportwallet", {filename}, cb, err);
}
void RPCClient::z_importWallet(const std::string& filename, Callback cb, ErrorCallback err)
{
doRPC("z_importwallet", {filename}, cb, err);
}
void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
double fee, int limit, Callback cb, ErrorCallback err)
{
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit}, cb, err);
}
void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
double fee, int limit, Callback cb, ErrorCallback err)
{
json addrs = json::array();
for (const auto& addr : fromAddrs) {
addrs.push_back(addr);
}
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit}, cb, err);
}
void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, Callback cb, ErrorCallback err)
{
json ids = json::array();
for (const auto& id : opids) {
ids.push_back(id);
}
doRPC("z_getoperationstatus", {ids}, cb, err);
}
void RPCClient::z_getOperationResult(const std::vector<std::string>& opids, Callback cb, ErrorCallback err)
{
json ids = json::array();
for (const auto& id : opids) {
ids.push_back(id);
}
doRPC("z_getoperationresult", {ids}, cb, err);
}
void RPCClient::z_listReceivedByAddress(const std::string& address, int minconf, Callback cb, ErrorCallback err)
{
doRPC("z_listreceivedbyaddress", {address, minconf}, cb, err);
}
// Unified callback versions
void RPCClient::getInfo(UnifiedCallback cb)
{
doRPC("getinfo", {},
[cb](const json& result) {
if (cb) cb(result, "");
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
}
void RPCClient::rescanBlockchain(int startHeight, UnifiedCallback cb)
{
doRPC("rescanblockchain", {startHeight},
[cb](const json& result) {
if (cb) cb(result, "");
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
}
void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
double fee, int limit, UnifiedCallback cb)
{
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit},
[cb](const json& result) {
if (cb) cb(result, "");
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
}
void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
double fee, int limit, UnifiedCallback cb)
{
json addrs = json::array();
for (const auto& addr : fromAddrs) {
addrs.push_back(addr);
}
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit},
[cb](const json& result) {
if (cb) cb(result, "");
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
}
void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb)
{
json ids = json::array();
for (const auto& id : opids) {
ids.push_back(id);
}
doRPC("z_getoperationstatus", {ids},
[cb](const json& result) {
if (cb) cb(result, "");
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
}
void RPCClient::getBlock(int height, UnifiedCallback cb)
{
// First get block hash, then get block
getBlockHash(height,
[this, cb](const json& hashResult) {
std::string hash = hashResult.get<std::string>();
getBlock(hash,
[cb](const json& blockResult) {
if (cb) cb(blockResult, "");
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
},
[cb](const std::string& error) {
if (cb) cb(json{}, error);
}
);
}
} // namespace rpc
} // namespace dragonx

179
src/rpc/rpc_client.h Normal file
View File

@@ -0,0 +1,179 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "types.h"
#include <string>
#include <functional>
#include <memory>
#include <mutex>
#include <nlohmann/json.hpp>
namespace dragonx {
namespace rpc {
using json = nlohmann::json;
using Callback = std::function<void(const json&)>;
using ErrorCallback = std::function<void(const std::string&)>;
/**
* @brief JSON-RPC client for dragonxd
*
* Handles all communication with the dragonxd daemon via JSON-RPC.
*/
class RPCClient {
public:
RPCClient();
~RPCClient();
// Non-copyable
RPCClient(const RPCClient&) = delete;
RPCClient& operator=(const RPCClient&) = delete;
/**
* @brief Connect to dragonxd
* @param host RPC host (default: 127.0.0.1)
* @param port RPC port (default: 18031)
* @param user RPC username
* @param password RPC password
* @return true if connection successful
*/
bool connect(const std::string& host, const std::string& port,
const std::string& user, const std::string& password);
/**
* @brief Disconnect from dragonxd
*/
void disconnect();
/**
* @brief Check if connected
*/
bool isConnected() const { return connected_; }
/**
* @brief Make a raw RPC call
* @param method RPC method name
* @param params Method parameters (optional)
* @return JSON response or null on error
*/
json call(const std::string& method, const json& params = json::array());
/**
* @brief Make a raw RPC call and return the result field as a string
* @param method RPC method name
* @param params Method parameters (optional)
* @return Raw JSON string of the "result" field (preserves key order)
*/
std::string callRaw(const std::string& method, const json& params = json::array());
// High-level API methods - mirror the Qt version
// Info methods
void getInfo(Callback cb, ErrorCallback err = nullptr);
void getBlockchainInfo(Callback cb, ErrorCallback err = nullptr);
void getMiningInfo(Callback cb, ErrorCallback err = nullptr);
// Balance methods
void getBalance(Callback cb, ErrorCallback err = nullptr);
void z_getTotalBalance(Callback cb, ErrorCallback err = nullptr);
void listUnspent(Callback cb, ErrorCallback err = nullptr);
void z_listUnspent(Callback cb, ErrorCallback err = nullptr);
// Address methods
void getAddressesByAccount(Callback cb, ErrorCallback err = nullptr);
void z_listAddresses(Callback cb, ErrorCallback err = nullptr);
void getNewAddress(Callback cb, ErrorCallback err = nullptr);
void z_getNewAddress(Callback cb, ErrorCallback err = nullptr);
// Transaction methods
void listTransactions(int count, Callback cb, ErrorCallback err = nullptr);
void z_viewTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
void getRawTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
void sendToAddress(const std::string& address, double amount, Callback cb, ErrorCallback err = nullptr);
void z_sendMany(const std::string& from, const json& recipients, Callback cb, ErrorCallback err = nullptr);
// Mining methods
void setGenerate(bool generate, int threads, Callback cb, ErrorCallback err = nullptr);
void getNetworkHashPS(Callback cb, ErrorCallback err = nullptr);
void getLocalHashrate(Callback cb, ErrorCallback err = nullptr);
// Peer methods
void getPeerInfo(Callback cb, ErrorCallback err = nullptr);
void listBanned(Callback cb, ErrorCallback err = nullptr);
void setBan(const std::string& ip, const std::string& command, Callback cb, ErrorCallback err = nullptr, int bantime = 86400);
void clearBanned(Callback cb, ErrorCallback err = nullptr);
// Key management
void dumpPrivKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
void z_exportKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
void z_exportViewingKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
void importPrivKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err = nullptr);
void z_importKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err = nullptr);
// Utility
void validateAddress(const std::string& address, Callback cb, ErrorCallback err = nullptr);
void z_validateAddress(const std::string& address, Callback cb, ErrorCallback err = nullptr);
void getBlock(const std::string& hash_or_height, Callback cb, ErrorCallback err = nullptr);
void getBlockHash(int height, Callback cb, ErrorCallback err = nullptr);
void getTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
void getWalletInfo(Callback cb, ErrorCallback err = nullptr);
void stop(Callback cb, ErrorCallback err = nullptr);
// Wallet maintenance
void rescanBlockchain(int startHeight, Callback cb, ErrorCallback err = nullptr);
// Wallet encryption & locking
void encryptWallet(const std::string& passphrase, Callback cb, ErrorCallback err = nullptr);
void walletPassphrase(const std::string& passphrase, int timeout, Callback cb, ErrorCallback err = nullptr);
void walletLock(Callback cb, ErrorCallback err = nullptr);
void walletPassphraseChange(const std::string& oldPass, const std::string& newPass,
Callback cb, ErrorCallback err = nullptr);
// Wallet export/import (for decrypt flow)
void z_exportWallet(const std::string& filename, Callback cb, ErrorCallback err = nullptr);
void z_importWallet(const std::string& filename, Callback cb, ErrorCallback err = nullptr);
// Shielding operations
void z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
double fee, int limit, Callback cb, ErrorCallback err = nullptr);
void z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
double fee, int limit, Callback cb, ErrorCallback err = nullptr);
// Operation status monitoring
void z_getOperationStatus(const std::vector<std::string>& opids, Callback cb, ErrorCallback err = nullptr);
void z_getOperationResult(const std::vector<std::string>& opids, Callback cb, ErrorCallback err = nullptr);
// Received transactions
void z_listReceivedByAddress(const std::string& address, int minconf, Callback cb, ErrorCallback err = nullptr);
// Unified callback versions (result + error)
using UnifiedCallback = std::function<void(const json& result, const std::string& error)>;
void getInfo(UnifiedCallback cb);
void rescanBlockchain(int startHeight, UnifiedCallback cb);
void z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
double fee, int limit, UnifiedCallback cb);
void z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
double fee, int limit, UnifiedCallback cb);
void z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb);
void getBlock(int height, UnifiedCallback cb);
private:
json makePayload(const std::string& method, const json& params = json::array());
void doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err);
std::string host_;
std::string port_;
std::string auth_; // Base64 encoded "user:password"
bool connected_ = false;
mutable std::recursive_mutex curl_mutex_; // serializes all curl handle access
// HTTP client (implementation hidden)
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace rpc
} // namespace dragonx

135
src/rpc/rpc_worker.cpp Normal file
View File

@@ -0,0 +1,135 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "rpc_worker.h"
#include <cstdio>
#include "../util/logger.h"
namespace dragonx {
namespace rpc {
RPCWorker::RPCWorker() = default;
RPCWorker::~RPCWorker()
{
stop();
}
void RPCWorker::start()
{
if (running_.load(std::memory_order_relaxed)) return;
running_.store(true, std::memory_order_release);
thread_ = std::thread(&RPCWorker::run, this);
}
void RPCWorker::stop()
{
if (!running_.load(std::memory_order_relaxed) && !thread_.joinable()) return;
// Signal stop if not already signaled
requestStop();
if (thread_.joinable()) {
thread_.join();
}
// Discard pending tasks
{
std::lock_guard<std::mutex> lk(taskMtx_);
tasks_.clear();
}
}
void RPCWorker::requestStop()
{
if (!running_.load(std::memory_order_relaxed)) return;
{
std::lock_guard<std::mutex> lk(taskMtx_);
running_.store(false, std::memory_order_release);
}
taskCv_.notify_one();
}
void RPCWorker::post(WorkFn work)
{
{
std::lock_guard<std::mutex> lk(taskMtx_);
tasks_.push_back(std::move(work));
}
taskCv_.notify_one();
}
int RPCWorker::drainResults()
{
// Swap the result queue under the lock, then execute outside the lock
// to minimise contention with the worker thread.
std::deque<MainCb> batch;
{
std::lock_guard<std::mutex> lk(resultMtx_);
batch.swap(results_);
}
int count = 0;
for (auto& cb : batch) {
if (cb) {
try {
cb();
} catch (const std::exception& e) {
DEBUG_LOGF("[RPCWorker] Main-thread callback threw: %s\n", e.what());
} catch (...) {
DEBUG_LOGF("[RPCWorker] Main-thread callback threw unknown exception\n");
}
++count;
}
}
return count;
}
bool RPCWorker::hasPendingResults() const
{
std::lock_guard<std::mutex> lk(resultMtx_);
return !results_.empty();
}
void RPCWorker::run()
{
while (true) {
WorkFn task;
// Wait for a task or stop signal
{
std::unique_lock<std::mutex> lk(taskMtx_);
taskCv_.wait(lk, [this] {
return !tasks_.empty() || !running_.load(std::memory_order_acquire);
});
if (!running_.load(std::memory_order_acquire) && tasks_.empty()) {
break;
}
if (!tasks_.empty()) {
task = std::move(tasks_.front());
tasks_.pop_front();
}
}
if (!task) continue;
// Execute the work function (blocking I/O happens here)
try {
MainCb result = task();
if (result) {
std::lock_guard<std::mutex> lk(resultMtx_);
results_.push_back(std::move(result));
}
} catch (const std::exception& e) {
DEBUG_LOGF("[RPCWorker] Task threw: %s\n", e.what());
}
}
}
} // namespace rpc
} // namespace dragonx

93
src/rpc/rpc_worker.h Normal file
View File

@@ -0,0 +1,93 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <mutex>
#include <thread>
namespace dragonx {
namespace rpc {
/**
* @brief Background worker thread for RPC calls
*
* Provides a single-threaded task queue so that all RPC/HTTP work happens
* off the UI thread. The caller submits a *work function* that runs on the
* worker thread and returns a *result callback* that is queued for execution
* on the main (UI) thread during drainResults().
*
* Usage from the main thread:
*
* worker.post([&rpc]() -> RPCWorker::MainCb {
* json r = rpc.call("getinfo"); // runs on worker thread
* return [r]() { applyToState(r); }; // runs on main thread
* });
*
* // Each frame:
* worker.drainResults();
*/
class RPCWorker {
public:
/// Callback executed on the main thread after work completes.
using MainCb = std::function<void()>;
/// Work function executed on the background thread.
/// Must return a MainCb (may return nullptr to skip main-thread step).
using WorkFn = std::function<MainCb()>;
RPCWorker();
~RPCWorker();
// Non-copyable
RPCWorker(const RPCWorker&) = delete;
RPCWorker& operator=(const RPCWorker&) = delete;
/// Start the worker thread. Safe to call if already running.
void start();
/// Stop the worker thread and join. Pending tasks are discarded.
void stop();
/// Signal the worker thread to stop (non-blocking, no join).
/// Call stop() later to join the thread.
void requestStop();
/// Submit work to run on the background thread.
/// @param work Function that performs blocking I/O and returns a MainCb.
void post(WorkFn work);
/// Drain completed result callbacks on the main thread.
/// Call once per frame from update().
/// @return Number of callbacks executed.
int drainResults();
/// True when there are completed results waiting for the main thread.
bool hasPendingResults() const;
/// True when the worker thread is running.
bool isRunning() const { return running_.load(std::memory_order_relaxed); }
private:
void run(); // worker thread entry point
std::thread thread_;
std::atomic<bool> running_{false};
// ---- Task queue (produced by main thread, consumed by worker) ----
std::mutex taskMtx_;
std::condition_variable taskCv_;
std::deque<WorkFn> tasks_;
// ---- Result queue (produced by worker, consumed by main thread) ----
mutable std::mutex resultMtx_;
std::deque<MainCb> results_;
};
} // namespace rpc
} // namespace dragonx

95
src/rpc/types.h Normal file
View File

@@ -0,0 +1,95 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <vector>
#include <cstdint>
namespace dragonx {
namespace rpc {
// Transaction types
enum class TxType {
Sent,
Received,
Mined,
Unknown
};
// Transaction info
struct Transaction {
std::string txid;
TxType type = TxType::Unknown;
int64_t timestamp = 0;
std::string address;
double amount = 0.0;
int confirmations = 0;
std::string memo;
std::string from_address;
};
// Peer info
struct Peer {
int64_t id = 0;
std::string address;
std::string tls_cipher;
bool tls_verified = false;
int64_t conntime = 0;
int banscore = 0;
int protocol_version = 0;
std::string subver;
int64_t bytes_sent = 0;
int64_t bytes_received = 0;
double pingtime = 0.0;
};
// Banned peer info
struct BannedPeer {
std::string address;
std::string subnet;
int64_t banned_until = 0;
int64_t asn = 0;
};
// Unspent output (UTXO)
struct UnspentOutput {
std::string address;
std::string txid;
int vout = 0;
double amount = 0.0;
int confirmations = 0;
bool spendable = true;
};
// Address balance
struct AddressBalance {
std::string address;
double balance = 0.0;
bool is_shielded = false;
};
// Blockchain info
struct BlockchainInfo {
int blocks = 0;
int headers = 0;
std::string bestblockhash;
double difficulty = 0.0;
double verificationprogress = 0.0;
bool syncing = false;
};
// Mining info
struct MiningInfo {
int blocks = 0;
double difficulty = 0.0;
double networkhashps = 0.0;
double localhashps = 0.0;
int genproclimit = 0;
bool generate = false;
};
} // namespace rpc
} // namespace dragonx

1695
src/ui/effects/acrylic.cpp Normal file

File diff suppressed because it is too large Load Diff

456
src/ui/effects/acrylic.h Normal file
View File

@@ -0,0 +1,456 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "framebuffer.h"
#include "blur_shader.h"
#include "noise_texture.h"
#include "imgui.h"
#include <memory>
#include <string>
#ifdef DRAGONX_USE_DX11
#include <d3d11.h>
#endif
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief Acrylic material parameters
*
* These parameters control the appearance of the acrylic effect,
* matching Microsoft's Fluent Design system.
*/
struct AcrylicParams {
// Tint color (RGBA, pre-multiplied alpha)
ImVec4 tintColor = ImVec4(0.1f, 0.1f, 0.12f, 1.0f);
// Tint opacity (0.0 = fully transparent, 1.0 = fully opaque)
float tintOpacity = 0.75f;
// Luminosity opacity (controls saturation pass-through)
// Lower values = more desaturated/milky appearance
float luminosityOpacity = 0.5f;
// Blur radius in pixels (typical: 20-60)
float blurRadius = 30.0f;
// Noise texture opacity (typical: 0.02-0.04)
float noiseOpacity = 0.02f;
// Fallback color when acrylic is disabled.
// The alpha channel also controls the glass opacity of the blurred
// background when acrylic IS active (lower alpha = more see-through).
ImVec4 fallbackColor = ImVec4(0.15f, 0.15f, 0.18f, 0.5f);
// Whether acrylic is enabled (false = use fallback)
bool enabled = true;
};
/**
* @brief Acrylic quality levels
*/
enum class AcrylicQuality {
Off, // Solid fallback color only
Low, // Single blur pass, downsampled
Medium, // Two blur passes, slight downsample
High // Full quality blur
};
/**
* @brief Acrylic fallback modes for graceful degradation
*/
enum class AcrylicFallback {
None, // Full acrylic (blur + tint + noise)
TintedOnly, // No blur, just semi-transparent tint overlay
Solid // Opaque fallback color only
};
/**
* @brief Acrylic settings for accessibility and performance
*/
struct AcrylicSettings {
bool enabled = true; // Master toggle
bool reducedTransparency = false; // Accessibility: use solid colors
float uiOpacity = 1.0f; // Card/sidebar opacity multiplier (0.31.0)
AcrylicQuality quality = AcrylicQuality::Medium; // Medium default for balance of quality/performance
float blurRadiusMultiplier = 1.0f; // Scales all blur radii (0.5 - 2.0)
float noiseOpacityMultiplier = 1.0f; // Scales noise opacity (0.0 - 2.0)
// Auto-disable conditions (checked each frame)
bool disableOnBattery = false; // Disable when on battery power
bool disableOnLowFPS = false; // Disable if FPS drops below threshold
float lowFPSThreshold = 30.0f; // FPS threshold for auto-disable
};
/**
* @brief System capabilities for acrylic effects
*/
struct AcrylicCapabilities {
bool hasFramebufferSupport = false;
bool hasShaderSupport = false;
bool hasTextureSupport = false;
int maxTextureSize = 0;
std::string glVersion;
std::string glRenderer;
bool isLowEndGPU = false;
bool isOnBattery = false; // Linux: check /sys/class/power_supply
};
/**
* @brief Main acrylic material rendering system
*
* Implements Microsoft Fluent Design's acrylic material effect:
* - Gaussian blur of background content
* - Tint color overlay
* - Luminosity blend for depth
* - Subtle noise texture for grain
*/
class AcrylicMaterial {
public:
AcrylicMaterial();
~AcrylicMaterial();
// Non-copyable
AcrylicMaterial(const AcrylicMaterial&) = delete;
AcrylicMaterial& operator=(const AcrylicMaterial&) = delete;
/**
* @brief Initialize the acrylic system
* @return true if successful
*/
bool init();
/**
* @brief Release all resources
*/
void shutdown();
/**
* @brief Check if system is initialized
*/
bool isInitialized() const { return initialized_; }
/**
* @brief Update internal buffers for new viewport size
* @param width Viewport width
* @param height Viewport height
*/
void resize(int width, int height);
/**
* @brief Capture current screen content for blur source
*
* Call this BEFORE rendering any acrylic surfaces.
* Only recaptures when the background is marked dirty.
*/
void captureBackground();
/**
* @brief Capture background directly from the current framebuffer.
*
* Intended to be called from an ImGui draw callback inserted at
* the end of the BackgroundDrawList. At that point during
* RenderDrawData(), only the background (gradient / image / noise)
* has been rasterized — no UI elements are in the framebuffer yet.
* This gives a clean background capture for acrylic blur.
*
* Respects the dirtyFrames_ counter so the capture and blur
* are only recomputed when the background actually changes
* (resize, theme change, etc.).
*/
void captureBackgroundDirect();
/**
* @brief Returns true once a valid background capture has been made.
*
* drawRect() checks this and falls back to a tinted fill on the
* very first frame before the callback has fired.
*/
bool hasValidCapture() const { return hasValidCapture_; }
/// Mark background as needing recapture (call on resize, theme change, etc.)
/// Uses a 2-frame counter so the capture re-runs after the new background
/// has actually been rendered (theme switch happens mid-frame, before the
/// BackgroundDrawList is rebuilt with new colors/images).
void markBackgroundDirty() { dirtyFrames_ = 2; }
/**
* @brief Render an acrylic-filled rectangle
*
* @param drawList ImGui draw list to add commands to
* @param pMin Top-left corner in screen coordinates
* @param pMax Bottom-right corner in screen coordinates
* @param params Acrylic parameters
* @param rounding Corner rounding
*/
void drawRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params, float rounding = 0.0f);
/**
* @brief Get the blurred background texture for manual rendering
*/
ImTextureID getBlurredTexture() const;
/**
* @brief Get the noise texture
*/
ImTextureID getNoiseTexture() const;
// ========================================================================
// Settings
// ========================================================================
/**
* @brief Set global acrylic quality
*/
void setQuality(AcrylicQuality quality);
AcrylicQuality getQuality() const { return settings_.quality; }
/**
* @brief Enable/disable acrylic globally
*/
void setEnabled(bool enabled) { settings_.enabled = enabled; }
bool isEnabled() const { return settings_.enabled; }
/**
* @brief Set blur radius multiplier (scales all blur radii)
* @param multiplier Value from 0.0 to 5.0
*/
void setBlurMultiplier(float multiplier) {
float clamped = std::max(0.0f, std::min(5.0f, multiplier));
settings_.blurRadiusMultiplier = clamped;
}
float getBlurMultiplier() const { return settings_.blurRadiusMultiplier; }
/**
* @brief Set reduced transparency mode (accessibility)
*/
void setReducedTransparency(bool reduced) { settings_.reducedTransparency = reduced; }
bool getReducedTransparency() const { return settings_.reducedTransparency; }
/**
* @brief Set UI opacity multiplier for cards/sidebar (0.31.0, 1=opaque)
*/
void setUIOpacity(float opacity) {
settings_.uiOpacity = std::max(0.3f, std::min(1.0f, opacity));
}
float getUIOpacity() const { return settings_.uiOpacity; }
/**
* @brief Set noise opacity multiplier (0.0 = no noise, 1.0 = default, 2.0 = double)
*/
void setNoiseOpacityMultiplier(float m) {
settings_.noiseOpacityMultiplier = std::max(0.0f, std::min(5.0f, m));
}
float getNoiseOpacityMultiplier() const { return settings_.noiseOpacityMultiplier; }
/**
* @brief Get full settings struct
*/
const AcrylicSettings& getSettings() const { return settings_; }
void setSettings(const AcrylicSettings& settings) { settings_ = settings; }
// ========================================================================
// Fallback System
// ========================================================================
/**
* @brief Detect system capabilities and determine fallback mode
*
* Checks OpenGL capabilities, GPU info, and user preferences to
* determine the appropriate rendering mode.
*
* @return The recommended fallback mode
*/
AcrylicFallback detectFallback() const;
/**
* @brief Get current fallback mode (cached)
*/
AcrylicFallback getCurrentFallback() const { return currentFallback_; }
/**
* @brief Force a specific fallback mode
*/
void setForcedFallback(AcrylicFallback fallback) {
forcedFallback_ = fallback;
hasForcedFallback_ = true;
}
/**
* @brief Clear forced fallback and use auto-detection
*/
void clearForcedFallback() { hasForcedFallback_ = false; }
/**
* @brief Get detected system capabilities
*/
const AcrylicCapabilities& getCapabilities() const { return capabilities_; }
/**
* @brief Refresh capability detection (call after context changes)
*/
void refreshCapabilities();
// ========================================================================
// Preset Parameters
// ========================================================================
/**
* @brief Get dark theme acrylic preset
*/
static AcrylicParams getDarkPreset();
/**
* @brief Get light theme acrylic preset
*/
static AcrylicParams getLightPreset();
/**
* @brief Get DragonX branded acrylic preset
*/
static AcrylicParams getDragonXPreset();
/**
* @brief Get popup/dialog acrylic preset
*/
static AcrylicParams getPopupPreset();
/**
* @brief Get sidebar acrylic preset
*/
static AcrylicParams getSidebarPreset();
private:
/**
* @brief Apply blur passes to captured content
*/
void applyBlur(float radius);
/**
* @brief Composite final acrylic appearance
*/
void compositeAcrylic(const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params);
/**
* @brief Check if effect should be skipped (settings/quality)
*/
bool shouldSkipEffect() const;
/**
* @brief Draw tinted-only fallback (no blur)
*/
void drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params, float rounding);
bool initialized_ = false;
AcrylicSettings settings_; // All settings in one struct
// Fallback system
AcrylicCapabilities capabilities_;
AcrylicFallback currentFallback_ = AcrylicFallback::None;
AcrylicFallback forcedFallback_ = AcrylicFallback::None;
bool hasForcedFallback_ = false;
// Screen dimensions
int viewportWidth_ = 0;
int viewportHeight_ = 0;
// Noise texture config
int noiseTextureSize_ = 256; // Texture resolution (larger = less repetition)
// Caching for performance
float lastBlurRadius_ = 0.0f;
bool blurCacheValid_ = false;
int dirtyFrames_ = 2; // >0 means recapture needed; counts down each frame
bool hasValidCapture_ = false; // true once a clean BG capture exists
// Capture and blur framebuffers
Framebuffer captureBuffer_;
FramebufferPingPong blurBuffers_;
// Shaders
BlurShader blurShader_;
// Fullscreen quad for post-processing
FullscreenQuad quad_;
// Noise texture
NoiseTexture noiseTexture_;
// Composite shader (tint + noise overlay)
GLuint compositeShader_ = 0;
GLint uCompositeTintColor_ = -1;
GLint uCompositeTintOpacity_ = -1;
GLint uCompositeLuminosity_ = -1;
GLint uCompositeNoiseOpacity_ = -1;
GLint uCompositeBlurTex_ = -1;
GLint uCompositeNoiseTex_ = -1;
GLint uCompositeTexScale_ = -1;
#ifdef DRAGONX_USE_DX11
// ---- DX11 acrylic resources (used instead of GL objects above) ----
ID3D11Device* dx_device_ = nullptr;
ID3D11DeviceContext* dx_context_ = nullptr;
// Capture buffer (full viewport resolution)
ID3D11Texture2D* dx_captureTex_ = nullptr;
ID3D11ShaderResourceView* dx_captureSRV_ = nullptr;
ID3D11RenderTargetView* dx_captureRTV_ = nullptr;
// Blur ping-pong buffers (possibly downscaled)
ID3D11Texture2D* dx_blurTex_[2] = {};
ID3D11ShaderResourceView* dx_blurSRV_[2] = {};
ID3D11RenderTargetView* dx_blurRTV_[2] = {};
int dx_blurWidth_ = 0;
int dx_blurHeight_ = 0;
int dx_blurCurrent_ = 0; // which buffer is "source"
// Shaders & pipeline objects
ID3D11VertexShader* dx_blurVS_ = nullptr;
ID3D11PixelShader* dx_blurPS_ = nullptr;
ID3D11InputLayout* dx_inputLayout_ = nullptr;
ID3D11Buffer* dx_blurCB_ = nullptr;
ID3D11Buffer* dx_vertexBuf_ = nullptr;
ID3D11SamplerState* dx_sampler_ = nullptr;
ID3D11RasterizerState* dx_blurRS_ = nullptr;
ID3D11BlendState* dx_blurBS_ = nullptr;
ID3D11DepthStencilState* dx_blurDSS_ = nullptr;
// Noise texture (DX11)
ID3D11Texture2D* dx_noiseTex_ = nullptr;
ID3D11ShaderResourceView* dx_noiseSRV_ = nullptr;
// Internal helpers
bool dx_initPipeline();
void dx_releasePipeline();
void dx_createRenderTarget(ID3D11Texture2D*& tex,
ID3D11ShaderResourceView*& srv,
ID3D11RenderTargetView*& rtv,
int w, int h,
DXGI_FORMAT format);
void dx_releaseRenderTarget(ID3D11Texture2D*& tex,
ID3D11ShaderResourceView*& srv,
ID3D11RenderTargetView*& rtv);
#endif // DRAGONX_USE_DX11
};
// ============================================================================
// Global Acrylic Instance
// ============================================================================
/**
* @brief Get the global acrylic material instance
*/
AcrylicMaterial& getAcrylicMaterial();
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,282 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "blur_shader.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include <vector>
#include "../../util/logger.h"
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// BlurShader Implementation (GLEW Available)
// ============================================================================
BlurShader::BlurShader() = default;
BlurShader::~BlurShader()
{
destroy();
}
bool BlurShader::init()
{
// Create shaders
vertexShader_ = glCreateShader(GL_VERTEX_SHADER);
fragmentShader_ = glCreateShader(GL_FRAGMENT_SHADER);
if (!compileShader(vertexShader_, ShaderSource::BLUR_VERTEX)) {
DEBUG_LOGF("BlurShader: Failed to compile vertex shader\n");
destroy();
return false;
}
if (!compileShader(fragmentShader_, ShaderSource::BLUR_FRAGMENT)) {
DEBUG_LOGF("BlurShader: Failed to compile fragment shader\n");
destroy();
return false;
}
// Create program
program_ = glCreateProgram();
glAttachShader(program_, vertexShader_);
glAttachShader(program_, fragmentShader_);
if (!linkProgram()) {
DEBUG_LOGF("BlurShader: Failed to link program\n");
destroy();
return false;
}
// Get uniform locations
uTexture_ = glGetUniformLocation(program_, "uTexture");
uDirection_ = glGetUniformLocation(program_, "uDirection");
uResolution_ = glGetUniformLocation(program_, "uResolution");
uRadius_ = glGetUniformLocation(program_, "uRadius");
DEBUG_LOGF("BlurShader: Initialized successfully\n");
DEBUG_LOGF(" Uniforms: uTexture=%d, uDirection=%d, uResolution=%d, uRadius=%d\n",
uTexture_, uDirection_, uResolution_, uRadius_);
return true;
}
void BlurShader::destroy()
{
if (program_) {
glDeleteProgram(program_);
program_ = 0;
}
if (vertexShader_) {
glDeleteShader(vertexShader_);
vertexShader_ = 0;
}
if (fragmentShader_) {
glDeleteShader(fragmentShader_);
fragmentShader_ = 0;
}
uTexture_ = -1;
uDirection_ = -1;
uResolution_ = -1;
uRadius_ = -1;
}
bool BlurShader::compileShader(unsigned int shader, const char* source)
{
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLint logLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
std::vector<char> log(logLength + 1);
glGetShaderInfoLog(shader, logLength, nullptr, log.data());
DEBUG_LOGF("Shader compile error:\n%s\n", log.data());
return false;
}
return true;
}
bool BlurShader::linkProgram()
{
glLinkProgram(program_);
GLint success;
glGetProgramiv(program_, GL_LINK_STATUS, &success);
if (!success) {
GLint logLength;
glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &logLength);
std::vector<char> log(logLength + 1);
glGetProgramInfoLog(program_, logLength, nullptr, log.data());
DEBUG_LOGF("Program link error:\n%s\n", log.data());
return false;
}
return true;
}
void BlurShader::bind()
{
glUseProgram(program_);
}
void BlurShader::unbind()
{
glUseProgram(0);
}
void BlurShader::setDirection(bool horizontal)
{
if (uDirection_ >= 0) {
if (horizontal) {
glUniform2f(uDirection_, 1.0f, 0.0f);
} else {
glUniform2f(uDirection_, 0.0f, 1.0f);
}
}
}
void BlurShader::setRadius(float radius)
{
if (uRadius_ >= 0) {
glUniform1f(uRadius_, radius);
}
}
void BlurShader::setResolution(int width, int height)
{
if (uResolution_ >= 0) {
glUniform2f(uResolution_, static_cast<float>(width), static_cast<float>(height));
}
}
void BlurShader::setTexture(int unit)
{
if (uTexture_ >= 0) {
glUniform1i(uTexture_, unit);
}
}
// ============================================================================
// FullscreenQuad Implementation
// ============================================================================
FullscreenQuad::FullscreenQuad() = default;
FullscreenQuad::~FullscreenQuad()
{
destroy();
}
bool FullscreenQuad::init()
{
// Fullscreen quad vertices: position (x,y) and texcoord (u,v)
// NDC coordinates: -1 to 1
static const float vertices[] = {
// Position // TexCoord
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
-1.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
1.0f, 1.0f, 1.0f, 1.0f // Top-right
};
glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_);
glBindVertexArray(vao_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Position attribute (location = 0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// TexCoord attribute (location = 1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return vao_ != 0;
}
void FullscreenQuad::destroy()
{
if (vbo_) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (vao_) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
}
void FullscreenQuad::draw()
{
glBindVertexArray(vao_);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
} // namespace effects
} // namespace ui
} // namespace dragonx
#else // !DRAGONX_HAS_GLAD
// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================
namespace dragonx {
namespace ui {
namespace effects {
BlurShader::BlurShader() = default;
BlurShader::~BlurShader() = default;
bool BlurShader::init() { return false; }
void BlurShader::destroy() {}
bool BlurShader::compileShader(unsigned int, const char*) { return false; }
bool BlurShader::linkProgram() { return false; }
void BlurShader::bind() {}
void BlurShader::unbind() {}
void BlurShader::setDirection(bool) {}
void BlurShader::setRadius(float) {}
void BlurShader::setResolution(int, int) {}
void BlurShader::setTexture(int) {}
FullscreenQuad::FullscreenQuad() = default;
FullscreenQuad::~FullscreenQuad() = default;
bool FullscreenQuad::init() { return false; }
void FullscreenQuad::destroy() {}
void FullscreenQuad::draw() {}
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // DRAGONX_HAS_GLAD

View File

@@ -0,0 +1,211 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#ifndef GLAD_GL_H_
typedef unsigned int GLuint;
typedef int GLint;
typedef unsigned int GLenum;
typedef int GLsizei;
#endif
#include <string>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief GLSL shader program for blur effects
*
* Implements a two-pass Gaussian blur using separable convolution.
* First pass blurs horizontally, second pass blurs vertically.
*/
class BlurShader {
public:
BlurShader();
~BlurShader();
// Non-copyable
BlurShader(const BlurShader&) = delete;
BlurShader& operator=(const BlurShader&) = delete;
/**
* @brief Compile and link the blur shader program
* @return true if successful
*/
bool init();
/**
* @brief Release shader resources
*/
void destroy();
/**
* @brief Use this shader program
*/
void bind();
/**
* @brief Stop using this shader
*/
void unbind();
/**
* @brief Set the blur direction
* @param horizontal true for horizontal pass, false for vertical
*/
void setDirection(bool horizontal);
/**
* @brief Set the blur radius/intensity
* @param radius Blur radius in pixels (typical: 1.0 - 10.0)
*/
void setRadius(float radius);
/**
* @brief Set the texture resolution for proper texel calculation
* @param width Texture width
* @param height Texture height
*/
void setResolution(int width, int height);
/**
* @brief Set the input texture unit
* @param unit Texture unit (0 = GL_TEXTURE0)
*/
void setTexture(int unit = 0);
/**
* @brief Check if shader is valid
*/
bool isValid() const { return program_ != 0; }
/**
* @brief Get the shader program ID
*/
GLuint getProgram() const { return program_; }
private:
bool compileShader(GLuint shader, const char* source);
bool linkProgram();
GLuint program_ = 0;
GLuint vertexShader_ = 0;
GLuint fragmentShader_ = 0;
// Uniform locations
GLint uTexture_ = -1;
GLint uDirection_ = -1;
GLint uResolution_ = -1;
GLint uRadius_ = -1;
};
/**
* @brief Manages the full-screen quad VAO for post-processing
*/
class FullscreenQuad {
public:
FullscreenQuad();
~FullscreenQuad();
/**
* @brief Initialize the quad VAO/VBO
*/
bool init();
/**
* @brief Release resources
*/
void destroy();
/**
* @brief Draw the fullscreen quad
*/
void draw();
bool isValid() const { return vao_ != 0; }
private:
GLuint vao_ = 0;
GLuint vbo_ = 0;
};
// ============================================================================
// Shader Source Code (embedded)
// ============================================================================
namespace ShaderSource {
// Simple passthrough vertex shader for fullscreen quad
constexpr const char* BLUR_VERTEX = R"glsl(
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
void main()
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = aTexCoord;
}
)glsl";
// Gaussian blur fragment shader (dynamic kernel, separable)
// Loop-based with 1-texel spacing + triangular dithering
constexpr const char* BLUR_FRAGMENT = R"glsl(
#version 330 core
in vec2 vTexCoord;
out vec4 FragColor;
uniform sampler2D uTexture;
uniform vec2 uDirection; // (1,0) for horizontal, (0,1) for vertical
uniform vec2 uResolution; // Texture dimensions
uniform float uRadius; // Gaussian sigma in texels
// Screen-space hash for dithering (returns 0..1)
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
void main()
{
vec2 texelSize = 1.0 / uResolution;
vec2 step = uDirection * texelSize;
// Dynamic Gaussian kernel: uRadius = sigma, extend to ~2.5 sigma
float sigma = max(uRadius, 0.5);
int iRadius = clamp(int(sigma * 2.5 + 0.5), 1, 40);
float inv2sigma2 = 1.0 / (2.0 * sigma * sigma);
vec3 sum = vec3(0.0);
float wSum = 0.0;
for (int i = -iRadius; i <= iRadius; i++) {
float w = exp(-float(i * i) * inv2sigma2);
sum += texture(uTexture, vTexCoord + step * float(i)).rgb * w;
wSum += w;
}
vec3 color = sum / wSum;
// Triangular-PDF dithering to break up 8-bit quantization banding.
// Two uniform hashes combined give a triangular distribution (-1..+1).
float d = hash12(gl_FragCoord.xy) + hash12(gl_FragCoord.xy + 71.37) - 1.0;
color += d / 255.0;
FragColor = vec4(color, 1.0);
}
)glsl";
} // namespace ShaderSource
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,302 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "framebuffer.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include "../../util/logger.h"
// GL 3.0 constants — may be missing from some GLAD configurations
#ifndef GL_RGBA16F
#define GL_RGBA16F 0x881A
#endif
#ifndef GL_HALF_FLOAT
#define GL_HALF_FLOAT 0x140B
#endif
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// Framebuffer Implementation (GLEW Available)
// ============================================================================
Framebuffer::Framebuffer() = default;
Framebuffer::~Framebuffer()
{
destroy();
}
Framebuffer::Framebuffer(Framebuffer&& other) noexcept
: fbo_(other.fbo_)
, colorTexture_(other.colorTexture_)
, depthRbo_(other.depthRbo_)
, width_(other.width_)
, height_(other.height_)
, isComplete_(other.isComplete_)
{
other.fbo_ = 0;
other.colorTexture_ = 0;
other.depthRbo_ = 0;
other.width_ = 0;
other.height_ = 0;
other.isComplete_ = false;
}
Framebuffer& Framebuffer::operator=(Framebuffer&& other) noexcept
{
if (this != &other) {
destroy();
fbo_ = other.fbo_;
colorTexture_ = other.colorTexture_;
depthRbo_ = other.depthRbo_;
width_ = other.width_;
height_ = other.height_;
isComplete_ = other.isComplete_;
other.fbo_ = 0;
other.colorTexture_ = 0;
other.depthRbo_ = 0;
other.width_ = 0;
other.height_ = 0;
other.isComplete_ = false;
}
return *this;
}
bool Framebuffer::init(int width, int height, bool useFloat16)
{
if (width <= 0 || height <= 0) {
DEBUG_LOGF("Framebuffer::init - Invalid dimensions: %dx%d\n", width, height);
return false;
}
// Clean up existing resources
cleanup();
width_ = width;
height_ = height;
useFloat16_ = useFloat16;
// Generate framebuffer
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Create color texture — use RGBA16F for blur intermediates to avoid banding
glGenTextures(1, &colorTexture_);
glBindTexture(GL_TEXTURE_2D, colorTexture_);
if (useFloat16) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, nullptr);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Attach color texture to framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture_, 0);
// Create depth renderbuffer (optional, but good for completeness)
glGenRenderbuffers(1, &depthRbo_);
glBindRenderbuffer(GL_RENDERBUFFER, depthRbo_);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbo_);
// Check completeness
isComplete_ = checkComplete();
// Unbind
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
if (!isComplete_) {
DEBUG_LOGF("Framebuffer::init - Framebuffer is not complete!\n");
cleanup();
return false;
}
return true;
}
bool Framebuffer::resize(int width, int height)
{
if (width == width_ && height == height_) {
return true; // No change needed
}
return init(width, height, useFloat16_);
}
void Framebuffer::destroy()
{
cleanup();
}
void Framebuffer::cleanup()
{
if (colorTexture_) {
glDeleteTextures(1, &colorTexture_);
colorTexture_ = 0;
}
if (depthRbo_) {
glDeleteRenderbuffers(1, &depthRbo_);
depthRbo_ = 0;
}
if (fbo_) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = 0;
}
width_ = 0;
height_ = 0;
isComplete_ = false;
}
bool Framebuffer::checkComplete()
{
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
const char* errorStr = "Unknown";
switch (status) {
case GL_FRAMEBUFFER_UNDEFINED: errorStr = "UNDEFINED"; break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: errorStr = "INCOMPLETE_ATTACHMENT"; break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: errorStr = "INCOMPLETE_MISSING_ATTACHMENT"; break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: errorStr = "INCOMPLETE_DRAW_BUFFER"; break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: errorStr = "INCOMPLETE_READ_BUFFER"; break;
case GL_FRAMEBUFFER_UNSUPPORTED: errorStr = "UNSUPPORTED"; break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: errorStr = "INCOMPLETE_MULTISAMPLE"; break;
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: errorStr = "INCOMPLETE_LAYER_TARGETS"; break;
}
DEBUG_LOGF("Framebuffer error: %s (0x%X)\n", errorStr, status);
return false;
}
return true;
}
void Framebuffer::bind()
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glViewport(0, 0, width_, height_);
}
void Framebuffer::unbind()
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Framebuffer::clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void Framebuffer::blitFrom(GLuint srcFbo, int srcX, int srcY, int srcWidth, int srcHeight,
int dstX, int dstY)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
glBlitFramebuffer(
srcX, srcY, srcX + srcWidth, srcY + srcHeight, // Source rect
dstX, dstY, dstX + srcWidth, dstY + srcHeight, // Dest rect
GL_COLOR_BUFFER_BIT,
GL_LINEAR
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Framebuffer::captureScreen(int viewportWidth, int viewportHeight)
{
// Blit from default framebuffer (0) to this framebuffer
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
glBlitFramebuffer(
0, 0, viewportWidth, viewportHeight, // Source (screen)
0, 0, width_, height_, // Dest (this FBO)
GL_COLOR_BUFFER_BIT,
GL_LINEAR
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
// ============================================================================
// FramebufferPingPong Implementation
// ============================================================================
bool FramebufferPingPong::init(int width, int height, bool useFloat16)
{
if (!buffers_[0].init(width, height, useFloat16)) return false;
if (!buffers_[1].init(width, height, useFloat16)) return false;
currentSource_ = 0;
return true;
}
bool FramebufferPingPong::resize(int width, int height)
{
if (!buffers_[0].resize(width, height)) return false;
if (!buffers_[1].resize(width, height)) return false;
return true;
}
void FramebufferPingPong::destroy()
{
buffers_[0].destroy();
buffers_[1].destroy();
currentSource_ = 0;
}
void FramebufferPingPong::swap()
{
currentSource_ = 1 - currentSource_;
}
} // namespace effects
} // namespace ui
} // namespace dragonx
#else // !DRAGONX_HAS_GLAD
// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================
namespace dragonx {
namespace ui {
namespace effects {
Framebuffer::Framebuffer() = default;
Framebuffer::~Framebuffer() = default;
Framebuffer::Framebuffer(Framebuffer&&) noexcept = default;
Framebuffer& Framebuffer::operator=(Framebuffer&&) noexcept = default;
bool Framebuffer::init(int, int, bool) { return false; }
bool Framebuffer::resize(int, int) { return false; }
void Framebuffer::destroy() {}
void Framebuffer::cleanup() {}
bool Framebuffer::checkComplete() { return false; }
void Framebuffer::bind() {}
void Framebuffer::unbind() {}
void Framebuffer::clear(float, float, float, float) {}
void Framebuffer::blitFrom(unsigned int, int, int, int, int, int, int) {}
void Framebuffer::captureScreen(int, int) {}
bool FramebufferPingPong::init(int, int, bool) { return false; }
bool FramebufferPingPong::resize(int, int) { return false; }
void FramebufferPingPong::destroy() {}
void FramebufferPingPong::swap() {}
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // DRAGONX_HAS_GLAD

View File

@@ -0,0 +1,193 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// GL type definitions needed by class declarations.
// If GLAD was already included, these types are already defined.
#ifndef GLAD_GL_H_
typedef unsigned int GLuint;
typedef int GLint;
typedef unsigned int GLenum;
typedef int GLsizei;
#endif
#include <cstdint>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief OpenGL Framebuffer Object manager for off-screen rendering
*
* Used for capturing screen content to use as blur source for acrylic effects.
*/
class Framebuffer {
public:
Framebuffer();
~Framebuffer();
// Non-copyable
Framebuffer(const Framebuffer&) = delete;
Framebuffer& operator=(const Framebuffer&) = delete;
// Move constructors
Framebuffer(Framebuffer&& other) noexcept;
Framebuffer& operator=(Framebuffer&& other) noexcept;
/**
* @brief Initialize framebuffer with given dimensions
* @param width Width in pixels
* @param height Height in pixels
* @param useFloat16 Use RGBA16F format for higher precision (reduces banding)
* @return true if successful
*/
bool init(int width, int height, bool useFloat16 = false);
/**
* @brief Resize framebuffer (recreates internal textures)
* @param width New width
* @param height New height
* @return true if successful
*/
bool resize(int width, int height);
/**
* @brief Release all OpenGL resources
*/
void destroy();
/**
* @brief Bind this framebuffer for rendering
*/
void bind();
/**
* @brief Unbind (return to default framebuffer)
*/
void unbind();
/**
* @brief Clear the framebuffer
* @param r Red component (0-1)
* @param g Green component (0-1)
* @param b Blue component (0-1)
* @param a Alpha component (0-1)
*/
void clear(float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f);
/**
* @brief Get the color texture attached to this framebuffer
* @return OpenGL texture ID
*/
GLuint getColorTexture() const { return colorTexture_; }
/**
* @brief Get the underlying framebuffer object ID
* @return OpenGL FBO ID
*/
GLuint getFbo() const { return fbo_; }
/**
* @brief Get framebuffer dimensions
*/
int getWidth() const { return width_; }
int getHeight() const { return height_; }
/**
* @brief Check if framebuffer is valid and complete
*/
bool isValid() const { return fbo_ != 0 && isComplete_; }
/**
* @brief Copy a region from the current framebuffer to this one
* @param srcX Source X position
* @param srcY Source Y position
* @param srcWidth Source width
* @param srcHeight Source height
* @param dstX Destination X position
* @param dstY Destination Y position
*/
void blitFrom(GLuint srcFbo, int srcX, int srcY, int srcWidth, int srcHeight,
int dstX = 0, int dstY = 0);
/**
* @brief Copy entire default framebuffer to this one
* @param viewportWidth Current viewport width
* @param viewportHeight Current viewport height
*/
void captureScreen(int viewportWidth, int viewportHeight);
private:
void cleanup();
bool checkComplete();
GLuint fbo_ = 0;
GLuint colorTexture_ = 0;
GLuint depthRbo_ = 0; // Renderbuffer for depth (optional)
int width_ = 0;
int height_ = 0;
bool useFloat16_ = false; // RGBA16F for blur precision
bool isComplete_ = false;
};
/**
* @brief Manages multiple framebuffers for ping-pong blur operations
*/
class FramebufferPingPong {
public:
FramebufferPingPong() = default;
~FramebufferPingPong() = default;
/**
* @brief Initialize both framebuffers
*/
bool init(int width, int height, bool useFloat16 = false);
/**
* @brief Resize both framebuffers
*/
bool resize(int width, int height);
/**
* @brief Destroy both framebuffers
*/
void destroy();
/**
* @brief Swap which buffer is source/destination
*/
void swap();
/**
* @brief Get current source framebuffer
*/
Framebuffer& getSource() { return buffers_[currentSource_]; }
/**
* @brief Get current destination framebuffer
*/
Framebuffer& getDest() { return buffers_[1 - currentSource_]; }
/**
* @brief Get source texture for reading
*/
GLuint getSourceTexture() const { return buffers_[currentSource_].getColorTexture(); }
/**
* @brief Get destination texture
*/
GLuint getDestTexture() const { return buffers_[1 - currentSource_].getColorTexture(); }
bool isValid() const { return buffers_[0].isValid() && buffers_[1].isValid(); }
private:
Framebuffer buffers_[2];
int currentSource_ = 0;
};
} // namespace effects
} // namespace ui
} // namespace dragonx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "acrylic.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief ImGui integration for acrylic effects
*
* Provides convenient wrappers around AcrylicMaterial for use with
* standard ImGui rendering patterns.
*/
namespace ImGuiAcrylic {
// ============================================================================
// Initialization
// ============================================================================
/**
* @brief Initialize the acrylic system
*
* Call once at application startup, after OpenGL context is created.
* @return true if successful
*/
bool Init();
/**
* @brief Shutdown the acrylic system
*
* Call at application shutdown before destroying OpenGL context.
*/
void Shutdown();
/**
* @brief Check if acrylic is available and initialized
*/
bool IsAvailable();
// ============================================================================
// Frame Operations
// ============================================================================
/**
* @brief Begin a new frame for acrylic rendering
*
* Call this at the start of your render loop, before any ImGui rendering.
* This captures the current screen content for blur source.
*
* @param width Viewport width
* @param height Viewport height
*/
void BeginFrame(int width, int height);
/**
* @brief End the acrylic frame
*
* Call this after all regular content is rendered but before acrylic overlays.
* Captures the current framebuffer content for blur.
*/
void CaptureBackground();
/**
* @brief Invalidate the acrylic capture so the next frame re-captures
* and re-blurs. Call after theme/skin changes that alter the
* background gradient, image, or colors.
*/
void InvalidateCapture();
/**
* @brief Get a draw callback for background capture.
*
* Insert this callback at the end of the BackgroundDrawList (after all
* background commands). During RenderDrawData() it fires right after
* the background is rasterized — before any UI windows — and blits
* the clean background into the acrylic capture buffer.
*
* Usage in main.cpp:
* @code
* bgDL->AddCallback(ImGuiAcrylic::GetBackgroundCaptureCallback(), nullptr);
* bgDL->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
* @endcode
*/
ImDrawCallback GetBackgroundCaptureCallback();
// ============================================================================
// Drawing Functions
// ============================================================================
/**
* @brief Draw an acrylic-filled rectangle
*
* @param drawList ImGui draw list to add the rectangle to
* @param pMin Top-left corner (screen coordinates)
* @param pMax Bottom-right corner (screen coordinates)
* @param params Acrylic appearance parameters
* @param rounding Corner rounding radius
*/
void DrawAcrylicRect(ImDrawList* drawList,
const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params,
float rounding = 0.0f);
/**
* @brief Draw an acrylic-filled rectangle with default preset
*/
void DrawAcrylicRect(ImDrawList* drawList,
const ImVec2& pMin, const ImVec2& pMax,
float rounding = 0.0f);
// ============================================================================
// Window Wrappers
// ============================================================================
/**
* @brief Begin an acrylic-background window
*
* Works like ImGui::Begin but with acrylic background.
* Must be paired with EndAcrylicWindow().
*
* @param name Window name/ID
* @param p_open Optional close button flag
* @param flags ImGui window flags
* @param params Acrylic appearance parameters
* @return true if window is visible
*/
bool BeginAcrylicWindow(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
/**
* @brief End an acrylic window
*/
void EndAcrylicWindow();
// ============================================================================
// Child Region Wrappers
// ============================================================================
/**
* @brief Begin an acrylic child region
*
* Works like ImGui::BeginChild but with acrylic background.
* Must be paired with EndAcrylicChild().
*
* @param str_id Child region ID
* @param size Child region size (0,0 = auto)
* @param params Acrylic appearance parameters
* @param child_flags ImGui child flags
* @param window_flags ImGui window flags for the child
* @return true if child is visible
*/
bool BeginAcrylicChild(const char* str_id,
const ImVec2& size = ImVec2(0, 0),
const AcrylicParams& params = AcrylicMaterial::getDarkPreset(),
ImGuiChildFlags child_flags = 0,
ImGuiWindowFlags window_flags = 0);
/**
* @brief End an acrylic child region
*/
void EndAcrylicChild();
// ============================================================================
// Popup Wrappers
// ============================================================================
/**
* @brief Begin an acrylic popup
*
* Works like ImGui::BeginPopup but with acrylic background.
* Must be paired with EndAcrylicPopup().
*
* @param str_id Popup ID
* @param flags ImGui window flags
* @param params Acrylic appearance parameters
* @return true if popup is open
*/
bool BeginAcrylicPopup(const char* str_id,
ImGuiWindowFlags flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
/**
* @brief End an acrylic popup
*/
void EndAcrylicPopup();
/**
* @brief Begin an acrylic modal popup
*
* Works like ImGui::BeginPopupModal but with acrylic background.
* Must be paired with EndAcrylicPopup().
*/
bool BeginAcrylicPopupModal(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
// ============================================================================
// Context Menu Helpers
// ============================================================================
/**
* @brief Begin an acrylic context menu for the last item
*
* Works like ImGui::BeginPopupContextItem but with acrylic background.
* Must be paired with EndAcrylicPopup().
*
* @param str_id Popup ID (nullptr to use last item ID)
* @param popup_flags ImGui popup flags
* @param params Acrylic appearance parameters
* @return true if context menu is open
*/
bool BeginAcrylicContextItem(const char* str_id = nullptr,
ImGuiPopupFlags popup_flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
/**
* @brief Begin an acrylic context menu for the current window
*
* Works like ImGui::BeginPopupContextWindow but with acrylic background.
* Must be paired with EndAcrylicPopup().
*/
bool BeginAcrylicContextWindow(const char* str_id = nullptr,
ImGuiPopupFlags popup_flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
// ============================================================================
// Sidebar Helper
// ============================================================================
/**
* @brief Draw an acrylic sidebar background
*
* Convenience function for drawing sidebar with DragonX branded acrylic.
*
* @param width Sidebar width
* @param params Optional custom parameters (default: sidebar preset)
*/
void DrawSidebarBackground(float width,
const AcrylicParams& params = AcrylicMaterial::getSidebarPreset());
// ============================================================================
// Settings
// ============================================================================
/**
* @brief Set global acrylic quality
*/
void SetQuality(AcrylicQuality quality);
/**
* @brief Get current quality setting
*/
AcrylicQuality GetQuality();
/**
* @brief Enable/disable acrylic globally
*
* When disabled, all acrylic functions fall back to solid colors.
*/
void SetEnabled(bool enabled);
/**
* @brief Check if acrylic is enabled
*/
bool IsEnabled();
/**
* @brief Set blur radius multiplier (scales all blur radii)
* @param multiplier Value from 0.5 to 2.0 (1.0 = default)
*/
void SetBlurMultiplier(float multiplier);
/**
* @brief Get current blur multiplier
*/
float GetBlurMultiplier();
/**
* @brief Set reduced transparency mode (accessibility)
*
* When enabled, uses solid fallback colors instead of blur effects.
*/
void SetReducedTransparency(bool reduced);
/**
* @brief Check if reduced transparency mode is active
*/
bool GetReducedTransparency();
/**
* @brief Set UI opacity multiplier for cards/sidebar (0.31.0, 1=opaque)
*/
void SetUIOpacity(float opacity);
/**
* @brief Get UI opacity multiplier
*/
float GetUIOpacity();
/**
* @brief Set noise opacity multiplier (0.0 = no noise, 1.0 = default, 2.0 = max)
*/
void SetNoiseOpacity(float multiplier);
/**
* @brief Get noise opacity multiplier
*/
float GetNoiseOpacity();
/**
* @brief Get full acrylic settings
*/
const AcrylicSettings& GetSettings();
/**
* @brief Set full acrylic settings
*/
void SetSettings(const AcrylicSettings& settings);
// ============================================================================
// Presets — combined quality + blur amount
// ============================================================================
/// Number of built-in presets (Off … Frosted)
constexpr int kPresetCount = 6;
/**
* @brief Apply a preset that sets both quality and blur multiplier.
* @param preset Index 05 (Off, Subtle, Light, Standard, Strong, Frosted)
*/
void SetPreset(int preset);
/**
* @brief Derive the closest preset index from current quality + blur.
*/
int GetPreset();
/**
* @brief Human-readable label for a preset index.
*/
const char* GetPresetLabel(int preset);
/**
* @brief Get the quality enum for a given preset index.
*/
AcrylicQuality PresetQuality(int preset);
/**
* @brief Get the blur multiplier for a given preset index.
*/
float PresetBlur(int preset);
/**
* @brief Find closest preset matching a quality + blur pair.
* Useful for migrating from legacy separate settings.
*/
int MatchPreset(AcrylicQuality quality, float blur);
/**
* @brief Apply a continuous blur amount.
*
* Uses Low quality (like the old "Subtle" preset) at any non-zero blur
* level, giving fine-grained control without stepped presets.
* When blur is near zero the effect is disabled entirely.
*
* @param blur Blur multiplier (0.0 = off, up to 4.0 = maximum)
*/
void ApplyBlurAmount(float blur);
} // namespace ImGuiAcrylic
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,24 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "low_spec.h"
#include <atomic>
namespace dragonx {
namespace ui {
namespace effects {
static std::atomic<bool> s_low_spec{false};
bool isLowSpecMode() {
return s_low_spec.load(std::memory_order_relaxed);
}
void setLowSpecMode(bool enabled) {
s_low_spec.store(enabled, std::memory_order_relaxed);
}
} // namespace effects
} // namespace ui
} // namespace dragonx

21
src/ui/effects/low_spec.h Normal file
View File

@@ -0,0 +1,21 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
namespace dragonx {
namespace ui {
namespace effects {
/// Global low-spec mode query. When enabled, heavy visual effects are
/// skipped: glass panel noise tiling, elevation shadow layers, ripple
/// circle expansion, viewport noise overlay, scroll-edge fade shader,
/// and mining pulse animations. Individual effect toggles (acrylic,
/// theme effects, scanline) are overridden via the settings page.
bool isLowSpecMode();
void setLowSpecMode(bool enabled);
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,354 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "noise_texture.h"
#include "../../util/logger.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// NoiseTexture Implementation (GLEW Available)
// ============================================================================
NoiseTexture::NoiseTexture() = default;
NoiseTexture::~NoiseTexture()
{
destroy();
}
float NoiseTexture::random(uint32_t& seed)
{
// Xorshift32 PRNG
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
}
float NoiseTexture::noise2D(float x, float y, int wrap)
{
// Simple value noise with smoothstep interpolation
int xi = static_cast<int>(std::floor(x)) & (wrap - 1);
int yi = static_cast<int>(std::floor(y)) & (wrap - 1);
float xf = x - std::floor(x);
float yf = y - std::floor(y);
// Smoothstep
float u = xf * xf * (3.0f - 2.0f * xf);
float v = yf * yf * (3.0f - 2.0f * yf);
// Hash function for corners
auto hash = [wrap](int x, int y) -> float {
uint32_t seed = static_cast<uint32_t>((x & (wrap-1)) * 374761393 + (y & (wrap-1)) * 668265263);
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
};
// Bilinear interpolation
float n00 = hash(xi, yi);
float n10 = hash(xi + 1, yi);
float n01 = hash(xi, yi + 1);
float n11 = hash(xi + 1, yi + 1);
float nx0 = n00 * (1.0f - u) + n10 * u;
float nx1 = n01 * (1.0f - u) + n11 * u;
return nx0 * (1.0f - v) + nx1 * v;
}
bool NoiseTexture::generate(int size, float intensity)
{
destroy();
if (size <= 0 || (size & (size - 1)) != 0) {
DEBUG_LOGF("NoiseTexture: Size must be power of 2, got %d\n", size);
return false;
}
size_ = size;
// Generate pure white noise — no coherent patterns, perfectly seamless
// when tiled because every pixel is independent.
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 12345;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
float r = random(seed);
// Apply intensity around mid-gray center
float value = 0.5f + (r - 0.5f) * intensity;
value = std::max(0.0f, std::min(1.0f, value));
uint8_t gray = static_cast<uint8_t>(value * 255.0f);
int idx = (y * size + x) * 4;
data[idx + 0] = gray;
data[idx + 1] = gray;
data[idx + 2] = gray;
data[idx + 3] = 255;
}
}
// Create OpenGL texture
glGenTextures(1, &texture_);
glBindTexture(GL_TEXTURE_2D, texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
// NEAREST filtering preserves individual grain pixels;
// REPEAT wrapping ensures seamless tiling.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
DEBUG_LOGF("NoiseTexture: Generated %dx%d white noise texture (intensity=%.2f)\n", size, size, intensity);
return texture_ != 0;
}
void NoiseTexture::destroy()
{
if (texture_) {
glDeleteTextures(1, &texture_);
texture_ = 0;
}
size_ = 0;
}
// ============================================================================
// NoiseUtils Implementation
// ============================================================================
namespace NoiseUtils {
unsigned int createWhiteNoiseTexture(int size)
{
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 42;
for (int i = 0; i < size * size; i++) {
// Simple white noise
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
uint8_t value = static_cast<uint8_t>((seed >> 24) & 0xFF);
data[i * 4 + 0] = value;
data[i * 4 + 1] = value;
data[i * 4 + 2] = value;
data[i * 4 + 3] = 255;
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
unsigned int createBlueNoiseTexture(int size)
{
// Blue noise has more even distribution - good for dithering
// Using a simple void-and-cluster algorithm approximation
std::vector<float> noise(size * size, 0.5f);
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 12345;
// Start with white noise
for (int i = 0; i < size * size; i++) {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
noise[i] = static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
}
// Apply a simple high-pass filter to reduce low frequencies
// (Very simplified blue noise approximation)
std::vector<float> filtered(size * size);
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
float center = noise[y * size + x];
float sum = 0.0f;
int count = 0;
// Sample neighbors
for (int dy = -2; dy <= 2; dy++) {
for (int dx = -2; dx <= 2; dx++) {
if (dx == 0 && dy == 0) continue;
int nx = (x + dx + size) % size;
int ny = (y + dy + size) % size;
sum += noise[ny * size + nx];
count++;
}
}
float avg = sum / count;
filtered[y * size + x] = 0.5f + (center - avg) * 2.0f;
}
}
// Convert to texture data
for (int i = 0; i < size * size; i++) {
float value = std::max(0.0f, std::min(1.0f, filtered[i]));
uint8_t gray = static_cast<uint8_t>(value * 255.0f);
data[i * 4 + 0] = gray;
data[i * 4 + 1] = gray;
data[i * 4 + 2] = gray;
data[i * 4 + 3] = 255;
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
unsigned int createAcrylicNoiseTexture(int size)
{
// Specially tuned noise for Microsoft Acrylic material look
// Very subtle, with a hint of blue-ish tint
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 98765;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
// Multiple noise layers
float n1 = NoiseTexture::noise2D(x * 0.5f, y * 0.5f, size);
float n2 = NoiseTexture::noise2D(x * 1.0f, y * 1.0f, size) * 0.5f;
float n3 = NoiseTexture::noise2D(x * 2.0f, y * 2.0f, size) * 0.25f;
// White noise component
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
float white = static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
// Combine
float combined = (n1 + n2 + n3) / 1.75f * 0.6f + white * 0.4f;
// Very subtle intensity (0.02 - 0.04 typical for acrylic)
float value = 0.5f + (combined - 0.5f) * 0.04f;
value = std::max(0.0f, std::min(1.0f, value));
uint8_t gray = static_cast<uint8_t>(value * 255.0f);
// Slight cool tint (very subtle)
int idx = (y * size + x) * 4;
data[idx + 0] = static_cast<uint8_t>(gray * 0.98f); // R slightly less
data[idx + 1] = gray; // G
data[idx + 2] = static_cast<uint8_t>(std::min(255, gray + 1)); // B slightly more
data[idx + 3] = 255;
}
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
} // namespace NoiseUtils
} // namespace effects
} // namespace ui
} // namespace dragonx
#else // !DRAGONX_HAS_GLAD
// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================
#include <cmath>
#include <cstdint>
namespace dragonx {
namespace ui {
namespace effects {
NoiseTexture::NoiseTexture() = default;
NoiseTexture::~NoiseTexture() = default;
float NoiseTexture::random(uint32_t& seed)
{
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
}
float NoiseTexture::noise2D(float x, float y, int wrap)
{
// Provide a basic implementation for potential use elsewhere
int xi = static_cast<int>(std::floor(x)) & (wrap - 1);
int yi = static_cast<int>(std::floor(y)) & (wrap - 1);
uint32_t seed = static_cast<uint32_t>(xi * 374761393 + yi * 668265263);
return random(seed);
}
bool NoiseTexture::generate(int, float) { return false; }
void NoiseTexture::destroy() {}
namespace NoiseUtils {
unsigned int createWhiteNoiseTexture(int) { return 0; }
unsigned int createBlueNoiseTexture(int) { return 0; }
unsigned int createAcrylicNoiseTexture(int) { return 0; }
} // namespace NoiseUtils
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // DRAGONX_HAS_GLAD

View File

@@ -0,0 +1,113 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#ifndef GLAD_GL_H_
typedef unsigned int GLuint;
typedef int GLint;
typedef unsigned int GLenum;
typedef int GLsizei;
#endif
#include <cstdint>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief Generates procedural noise textures for acrylic grain effect
*
* Creates a tileable noise texture that adds subtle grain to acrylic surfaces,
* matching Microsoft's Fluent Design acrylic material.
*/
class NoiseTexture {
public:
NoiseTexture();
~NoiseTexture();
// Non-copyable
NoiseTexture(const NoiseTexture&) = delete;
NoiseTexture& operator=(const NoiseTexture&) = delete;
/**
* @brief Generate a noise texture
* @param size Texture size (width = height, should be power of 2)
* @param intensity Noise intensity (0.0 - 1.0, typical: 0.02 - 0.04)
* @return true if successful
*/
bool generate(int size = 128, float intensity = 0.04f);
/**
* @brief Release texture resources
*/
void destroy();
/**
* @brief Get the OpenGL texture ID
*/
GLuint getTexture() const { return texture_; }
/**
* @brief Check if texture is valid
*/
bool isValid() const { return texture_ != 0; }
/**
* @brief Get texture size
*/
int getSize() const { return size_; }
/**
* @brief Simple pseudo-random number generator
* @param seed Seed value (modified in place)
* @return Random float in range [0, 1]
*/
static float random(uint32_t& seed);
/**
* @brief Generate tileable Perlin-style noise value
*/
static float noise2D(float x, float y, int wrap);
private:
GLuint texture_ = 0;
int size_ = 0;
};
/**
* @brief Static utility functions for noise generation
*/
namespace NoiseUtils {
/**
* @brief Create a simple white noise texture
* @param size Texture dimensions (square)
* @return OpenGL texture ID (0 on failure)
*/
GLuint createWhiteNoiseTexture(int size);
/**
* @brief Create a blue noise texture (better for dithering)
* @param size Texture dimensions (square)
* @return OpenGL texture ID (0 on failure)
*/
GLuint createBlueNoiseTexture(int size);
/**
* @brief Create a pre-computed acrylic noise texture
*
* This creates a specially tuned noise texture that matches
* Microsoft's acrylic material appearance.
*
* @param size Texture dimensions (128 recommended)
* @return OpenGL texture ID (0 on failure)
*/
GLuint createAcrylicNoiseTexture(int size = 128);
} // namespace NoiseUtils
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,418 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// Offscreen render-target scroll fade — the ImGui equivalent of CSS mask-image.
// Renders scrollable content to an offscreen surface, then composites it back
// as a textured mesh strip with vertex alpha for edge fading.
// This produces a true per-pixel fade that works with any background
// (including acrylic/backdrop transparency).
//
// Supports both OpenGL (DRAGONX_HAS_GLAD) and DX11 (DRAGONX_USE_DX11).
#pragma once
#include "imgui.h"
#include "imgui_internal.h"
#include <cstdio>
// ============================================================================
// Platform detection
// ============================================================================
#if defined(DRAGONX_USE_DX11)
#include <d3d11.h>
#define SCROLL_FADE_HAS_OFFSCREEN 1
#define SCROLL_FADE_DX11 1
#elif defined(DRAGONX_HAS_GLAD)
#include <glad/gl.h>
#include "../../util/logger.h"
#ifndef GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#endif
#ifndef GL_VIEWPORT
#define GL_VIEWPORT 0x0BA2
#endif
#ifndef GL_SCISSOR_TEST
#define GL_SCISSOR_TEST 0x0C11
#endif
#define SCROLL_FADE_HAS_OFFSCREEN 1
#define SCROLL_FADE_GL 1
#endif
#ifdef SCROLL_FADE_HAS_OFFSCREEN
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// ScrollFadeRT — manages an offscreen render target for scroll-fade rendering
// ============================================================================
class ScrollFadeRT {
public:
ScrollFadeRT() = default;
~ScrollFadeRT() { destroy(); }
// Non-copyable
ScrollFadeRT(const ScrollFadeRT&) = delete;
ScrollFadeRT& operator=(const ScrollFadeRT&) = delete;
/// Ensure RT matches the required dimensions. Returns true if ready.
bool ensure(int w, int h) {
if (w <= 0 || h <= 0) return false;
if (isValid() && w == width_ && h == height_) return true;
return init(w, h);
}
void destroy();
bool isValid() const;
/// Get the texture as an ImTextureID for compositing.
ImTextureID textureID() const;
int width() const { return width_; }
int height() const { return height_; }
#ifdef SCROLL_FADE_DX11
ID3D11RenderTargetView* rtv() const { return rtv_; }
#endif
#ifdef SCROLL_FADE_GL
unsigned int fbo() const { return fbo_; }
#endif
private:
bool init(int w, int h);
int width_ = 0;
int height_ = 0;
#ifdef SCROLL_FADE_DX11
ID3D11Texture2D* tex_ = nullptr;
ID3D11RenderTargetView* rtv_ = nullptr;
ID3D11ShaderResourceView* srv_ = nullptr;
#endif
#ifdef SCROLL_FADE_GL
unsigned int fbo_ = 0;
unsigned int colorTex_ = 0;
#endif
};
// ============================================================================
// Implementations
// ============================================================================
#ifdef SCROLL_FADE_DX11
// --- DX11 helpers to get device/context from ImGui backend ---
inline ID3D11Device* GetDX11Device() {
ImGuiIO& io = ImGui::GetIO();
if (!io.BackendRendererUserData) return nullptr;
return *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
}
inline ID3D11DeviceContext* GetDX11Context() {
ID3D11Device* dev = GetDX11Device();
if (!dev) return nullptr;
ID3D11DeviceContext* ctx = nullptr;
dev->GetImmediateContext(&ctx);
return ctx; // caller must Release()
}
inline bool ScrollFadeRT::init(int w, int h) {
destroy();
ID3D11Device* dev = GetDX11Device();
if (!dev) return false;
width_ = w;
height_ = h;
// Create texture
D3D11_TEXTURE2D_DESC td = {};
td.Width = (UINT)w;
td.Height = (UINT)h;
td.MipLevels = 1;
td.ArraySize = 1;
td.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
td.SampleDesc.Count = 1;
td.Usage = D3D11_USAGE_DEFAULT;
td.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
if (FAILED(dev->CreateTexture2D(&td, nullptr, &tex_))) {
DEBUG_LOGF("ScrollFadeRT: CreateTexture2D failed\n");
destroy();
return false;
}
// Render target view
if (FAILED(dev->CreateRenderTargetView(tex_, nullptr, &rtv_))) {
DEBUG_LOGF("ScrollFadeRT: CreateRenderTargetView failed\n");
destroy();
return false;
}
// Shader resource view (for sampling as texture)
if (FAILED(dev->CreateShaderResourceView(tex_, nullptr, &srv_))) {
DEBUG_LOGF("ScrollFadeRT: CreateShaderResourceView failed\n");
destroy();
return false;
}
return true;
}
inline void ScrollFadeRT::destroy() {
if (srv_) { srv_->Release(); srv_ = nullptr; }
if (rtv_) { rtv_->Release(); rtv_ = nullptr; }
if (tex_) { tex_->Release(); tex_ = nullptr; }
width_ = height_ = 0;
}
inline bool ScrollFadeRT::isValid() const { return rtv_ != nullptr; }
inline ImTextureID ScrollFadeRT::textureID() const {
return (ImTextureID)srv_;
}
#endif // SCROLL_FADE_DX11
#ifdef SCROLL_FADE_GL
inline bool ScrollFadeRT::init(int w, int h) {
destroy();
width_ = w;
height_ = h;
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glGenTextures(1, &colorTex_);
glBindTexture(GL_TEXTURE_2D, colorTex_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, colorTex_, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
DEBUG_LOGF("ScrollFadeRT: FBO incomplete (0x%X)\n", status);
destroy();
return false;
}
return true;
}
inline void ScrollFadeRT::destroy() {
if (colorTex_) { glDeleteTextures(1, &colorTex_); colorTex_ = 0; }
if (fbo_) { glDeleteFramebuffers(1, &fbo_); fbo_ = 0; }
width_ = height_ = 0;
}
inline bool ScrollFadeRT::isValid() const { return fbo_ != 0; }
inline ImTextureID ScrollFadeRT::textureID() const {
return (ImTextureID)(intptr_t)colorTex_;
}
#endif // SCROLL_FADE_GL
// ============================================================================
// Callback state — singleton storage for bind/unbind data
// ============================================================================
struct ScrollFadeState {
#ifdef SCROLL_FADE_DX11
ID3D11RenderTargetView* offscreenRTV = nullptr;
ID3D11RenderTargetView* savedRTV = nullptr;
ID3D11DepthStencilView* savedDSV = nullptr;
D3D11_VIEWPORT savedVP = {};
#endif
#ifdef SCROLL_FADE_GL
unsigned int fbo = 0;
int savedFBO = 0;
int savedVP[4] = {};
bool savedScissorEnabled = true; // ImGui always has scissor enabled
#endif
int vpW = 0, vpH = 0; // framebuffer pixel dimensions for viewport
};
inline ScrollFadeState& GetScrollFadeState() {
static ScrollFadeState s;
return s;
}
// ============================================================================
// Callbacks — inserted into the draw list via AddCallback
// ============================================================================
#ifdef SCROLL_FADE_DX11
inline void BindRTCallback(const ImDrawList*, const ImDrawCmd*) {
auto& st = GetScrollFadeState();
ID3D11DeviceContext* ctx = GetDX11Context();
if (!ctx) return;
// Save current RT and viewport
UINT numVP = 1;
ctx->OMGetRenderTargets(1, &st.savedRTV, &st.savedDSV);
ctx->RSGetViewports(&numVP, &st.savedVP);
// Bind offscreen RT
ctx->OMSetRenderTargets(1, &st.offscreenRTV, nullptr);
// Set viewport to match RT size
D3D11_VIEWPORT vp = {};
vp.Width = (FLOAT)st.vpW;
vp.Height = (FLOAT)st.vpH;
vp.MaxDepth = 1.0f;
ctx->RSSetViewports(1, &vp);
// Clear to transparent
float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
ctx->ClearRenderTargetView(st.offscreenRTV, clearColor);
ctx->Release();
}
inline void UnbindRTCallback(const ImDrawList*, const ImDrawCmd*) {
auto& st = GetScrollFadeState();
ID3D11DeviceContext* ctx = GetDX11Context();
if (!ctx) return;
// Restore previous RT and viewport
ctx->OMSetRenderTargets(1, &st.savedRTV, st.savedDSV);
ctx->RSSetViewports(1, &st.savedVP);
// Release the refs from OMGetRenderTargets
if (st.savedRTV) { st.savedRTV->Release(); st.savedRTV = nullptr; }
if (st.savedDSV) { st.savedDSV->Release(); st.savedDSV = nullptr; }
ctx->Release();
}
#endif // SCROLL_FADE_DX11
#ifdef SCROLL_FADE_GL
inline void BindRTCallback(const ImDrawList*, const ImDrawCmd*) {
auto& st = GetScrollFadeState();
// Save current FBO and viewport
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &st.savedFBO);
glGetIntegerv(GL_VIEWPORT, st.savedVP);
glBindFramebuffer(GL_FRAMEBUFFER, st.fbo);
glViewport(0, 0, st.vpW, st.vpH);
// Disable scissor test inside the FBO. ImGui's renderer computes
// scissor rects relative to the main framebuffer dimensions — those
// coordinates would be wrong for our offscreen surface. The child
// window's content is already bounded by ImGui's layout, and the
// composite step applies its own clip rect, so skipping scissor
// in the FBO is safe.
glDisable(GL_SCISSOR_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
inline void UnbindRTCallback(const ImDrawList*, const ImDrawCmd*) {
auto& st = GetScrollFadeState();
glBindFramebuffer(GL_FRAMEBUFFER, (unsigned int)st.savedFBO);
glViewport(st.savedVP[0], st.savedVP[1], st.savedVP[2], st.savedVP[3]);
if (st.savedScissorEnabled)
glEnable(GL_SCISSOR_TEST);
}
#endif // SCROLL_FADE_GL
// ============================================================================
// Composite helper — draw the RT texture as a mesh strip with alpha fade
// ============================================================================
/// Draw the offscreen texture onto `dl` as a vertical strip with alpha=0 at
/// the faded edges and alpha=1 in the middle. Produces a true CSS-like
/// mask-image: linear-gradient() result.
///
/// @param logicalW, logicalH Logical display dimensions (DisplaySize) for
/// UV calculation — NOT the RT pixel dimensions. ImGui screen coords
/// are in logical units, and the FBO projection maps them 1:1 to the
/// logical coordinate space, so UVs must divide by logical size.
inline void CompositeWithFade(ImDrawList* dl,
ImTextureID texID,
const ImVec2& screenMin,
const ImVec2& screenMax,
int logicalW, int logicalH,
float fadeTop, float fadeBot,
bool needTop, bool needBot)
{
float left = screenMin.x;
float right = screenMax.x;
float y0 = screenMin.y;
float y1 = screenMin.y + (needTop ? fadeTop : 0.0f);
float y2 = screenMax.y - (needBot ? fadeBot : 0.0f);
float y3 = screenMax.y;
// Clamp in case fade zones overlap
if (y1 > y2) { float mid = (y0 + y3) * 0.5f; y1 = y2 = mid; }
// UV coordinates — map screen position (logical) to render target texture.
// Screen coords are in logical (DisplaySize) space. The FBO projection
// maps these 1:1, so divide by logical dimensions to get [0,1] UVs.
float uL = screenMin.x / (float)logicalW;
float uR = screenMax.x / (float)logicalW;
#ifdef SCROLL_FADE_GL
// OpenGL: FBO Y is flipped (ImGui top=0 → GL bottom=0)
auto uvY = [&](float y) -> float { return 1.0f - y / (float)logicalH; };
#else
// DX11: no Y flip (both ImGui and DX11 have (0,0) at top-left)
auto uvY = [&](float y) -> float { return y / (float)logicalH; };
#endif
ImU32 colOpaque = IM_COL32(255, 255, 255, 255);
ImU32 colClear = IM_COL32(255, 255, 255, 0);
ImU32 colTop = needTop ? colClear : colOpaque;
ImU32 colBot = needBot ? colClear : colOpaque;
dl->PushTextureID(texID);
dl->PrimReserve(18, 8);
ImDrawVert* vtx = dl->_VtxWritePtr;
ImDrawIdx* idx = dl->_IdxWritePtr;
ImDrawIdx base = (ImDrawIdx)dl->_VtxCurrentIdx;
vtx[0] = { ImVec2(left, y0), ImVec2(uL, uvY(y0)), colTop };
vtx[1] = { ImVec2(right, y0), ImVec2(uR, uvY(y0)), colTop };
vtx[2] = { ImVec2(left, y1), ImVec2(uL, uvY(y1)), colOpaque };
vtx[3] = { ImVec2(right, y1), ImVec2(uR, uvY(y1)), colOpaque };
vtx[4] = { ImVec2(left, y2), ImVec2(uL, uvY(y2)), colOpaque };
vtx[5] = { ImVec2(right, y2), ImVec2(uR, uvY(y2)), colOpaque };
vtx[6] = { ImVec2(left, y3), ImVec2(uL, uvY(y3)), colBot };
vtx[7] = { ImVec2(right, y3), ImVec2(uR, uvY(y3)), colBot };
idx[0] = base+0; idx[1] = base+1; idx[2] = base+3;
idx[3] = base+0; idx[4] = base+3; idx[5] = base+2;
idx[6] = base+2; idx[7] = base+3; idx[8] = base+5;
idx[9] = base+2; idx[10] = base+5; idx[11] = base+4;
idx[12] = base+4; idx[13] = base+5; idx[14] = base+7;
idx[15] = base+4; idx[16] = base+7; idx[17] = base+6;
dl->_VtxWritePtr += 8;
dl->_IdxWritePtr += 18;
dl->_VtxCurrentIdx += 8;
dl->PopTextureID();
}
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // SCROLL_FADE_HAS_OFFSCREEN

View File

@@ -0,0 +1,416 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// Shader-based scroll-edge fade effect.
//
// Inserts ImDrawCallback entries into the draw list to switch from ImGui's
// default shader to a custom shader that multiplies output alpha by a
// smoothstep gradient based on screen-Y position. After the fade region,
// ImDrawCallback_ResetRenderState restores ImGui's default render state.
//
// GL path: custom GLSL 130 program (VS + FS), uses gl_FragCoord.y
// DX11 path: custom pixel shader only, uses SV_POSITION.y
#pragma once
#include "imgui.h"
#include "imgui_internal.h"
// ---- Platform headers ----
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#endif
#ifdef DRAGONX_USE_DX11
#include <d3d11.h>
#include <d3dcompiler.h>
#endif
#include <cstdio>
#include <cstring>
#include <cstddef>
#include "../../util/logger.h"
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// ScrollFadeShader — per-pixel scroll-edge alpha mask via ImDrawCallback
// ============================================================================
struct ScrollFadeShader {
// ---- Caller-set parameters (logical screen-space, per-frame) ----
float fadeTopY = 0.0f; // top of scrollable region (screen Y)
float fadeBottomY = 0.0f; // bottom of scrollable region (screen Y)
float fadeZoneTop = 0.0f; // top fade height (0 = disabled)
float fadeZoneBottom = 0.0f; // bottom fade height (0 = disabled)
// ---- Internal state ----
bool ready = false;
#ifdef DRAGONX_HAS_GLAD
GLuint program_ = 0;
GLint locProjMtx_ = -1;
GLint locTexture_ = -1;
GLint locFadeTopY_ = -1;
GLint locFadeBottomY_= -1;
GLint locFadeZoneTop_= -1;
GLint locFadeZoneBot_= -1;
GLint locViewportH_ = -1;
#endif
#ifdef DRAGONX_USE_DX11
ID3D11PixelShader* pFadePS_ = nullptr;
ID3D11Buffer* pFadeCB_ = nullptr;
#endif
// ----------------------------------------------------------------
// Init / Destroy
// ----------------------------------------------------------------
bool init() {
if (ready) return true;
#ifdef DRAGONX_HAS_GLAD
ready = initGL();
#elif defined(DRAGONX_USE_DX11)
ready = initDX11();
#endif
return ready;
}
void destroy() {
#ifdef DRAGONX_HAS_GLAD
destroyGL();
#elif defined(DRAGONX_USE_DX11)
destroyDX11();
#endif
ready = false;
}
// ----------------------------------------------------------------
// Draw-list integration helpers
// ----------------------------------------------------------------
/// Insert a "bind fade shader" callback into the draw list.
/// Must be followed later by addUnbind() or AddCallback(ImDrawCallback_ResetRenderState).
void addBind(ImDrawList* dl) {
dl->AddCallback(BindCB, this);
}
/// Insert ImDrawCallback_ResetRenderState to restore ImGui's default shader.
static void addUnbind(ImDrawList* dl) {
dl->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
}
// ================================================================
// GL implementation
// ================================================================
#ifdef DRAGONX_HAS_GLAD
static constexpr const char* kFadeVS_130 =
"#version 130\n"
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 UV;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main() {\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
"}\n";
static constexpr const char* kFadeFS_130 =
"#version 130\n"
"uniform sampler2D Texture;\n"
"uniform float u_fadeTopY;\n"
"uniform float u_fadeBottomY;\n"
"uniform float u_fadeZoneTop;\n"
"uniform float u_fadeZoneBot;\n"
"uniform float u_viewportH;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main() {\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
" float y = u_viewportH - gl_FragCoord.y;\n" // bottom-up → top-down
" float topA = (u_fadeZoneTop > 0.0)\n"
" ? smoothstep(u_fadeTopY, u_fadeTopY + u_fadeZoneTop, y)\n"
" : 1.0;\n"
" float botA = (u_fadeZoneBot > 0.0)\n"
" ? (1.0 - smoothstep(u_fadeBottomY - u_fadeZoneBot, u_fadeBottomY, y))\n"
" : 1.0;\n"
" Out_Color.a *= topA * botA;\n"
"}\n";
bool initGL() {
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vs, 1, &kFadeVS_130, nullptr);
glCompileShader(vs);
if (!checkShader(vs, "fade VS")) { glDeleteShader(vs); glDeleteShader(fs); return false; }
glShaderSource(fs, 1, &kFadeFS_130, nullptr);
glCompileShader(fs);
if (!checkShader(fs, "fade FS")) { glDeleteShader(vs); glDeleteShader(fs); return false; }
program_ = glCreateProgram();
glAttachShader(program_, vs);
glAttachShader(program_, fs);
// Force attribute locations to match ImGui's typical assignment
// (Position=0, UV=1, Color=2) so that the VAO set up by ImGui's
// backend is compatible with our program.
glBindAttribLocation(program_, 0, "Position");
glBindAttribLocation(program_, 1, "UV");
glBindAttribLocation(program_, 2, "Color");
glLinkProgram(program_);
glDetachShader(program_, vs);
glDetachShader(program_, fs);
glDeleteShader(vs);
glDeleteShader(fs);
if (!checkProgram(program_, "fade program")) {
glDeleteProgram(program_);
program_ = 0;
return false;
}
locProjMtx_ = glGetUniformLocation(program_, "ProjMtx");
locTexture_ = glGetUniformLocation(program_, "Texture");
locFadeTopY_ = glGetUniformLocation(program_, "u_fadeTopY");
locFadeBottomY_ = glGetUniformLocation(program_, "u_fadeBottomY");
locFadeZoneTop_ = glGetUniformLocation(program_, "u_fadeZoneTop");
locFadeZoneBot_ = glGetUniformLocation(program_, "u_fadeZoneBot");
locViewportH_ = glGetUniformLocation(program_, "u_viewportH");
DEBUG_LOGF("ScrollFadeShader: GL program %u (ProjMtx=%d Tex=%d top=%d bot=%d zt=%d zb=%d vh=%d)\n",
program_, locProjMtx_, locTexture_,
locFadeTopY_, locFadeBottomY_, locFadeZoneTop_, locFadeZoneBot_, locViewportH_);
return true;
}
void destroyGL() {
if (program_) { glDeleteProgram(program_); program_ = 0; }
}
static bool checkShader(GLuint s, const char* label) {
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
DEBUG_LOGF("ScrollFadeShader: %s compile error:\n%s\n", label, log);
}
return ok != 0;
}
static bool checkProgram(GLuint p, const char* label) {
GLint ok = 0;
glGetProgramiv(p, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(p, sizeof(log), nullptr, log);
DEBUG_LOGF("ScrollFadeShader: %s link error:\n%s\n", label, log);
}
return ok != 0;
}
#endif // DRAGONX_HAS_GLAD
// ================================================================
// DX11 implementation
// ================================================================
#ifdef DRAGONX_USE_DX11
// Pixel-shader constant buffer layout (must be 16-byte aligned)
struct alignas(16) FadeCBData {
float fadeTopY;
float fadeBottomY;
float fadeZoneTop;
float fadeZoneBot;
};
static constexpr const char* kFadePS_HLSL =
"cbuffer FadeParams : register(b1) {\n"
" float u_fadeTopY;\n"
" float u_fadeBottomY;\n"
" float u_fadeZoneTop;\n"
" float u_fadeZoneBot;\n"
"};\n"
"struct PS_INPUT {\n"
" float4 pos : SV_POSITION;\n"
" float4 col : COLOR0;\n"
" float2 uv : TEXCOORD0;\n"
"};\n"
"sampler sampler0;\n"
"Texture2D texture0;\n"
"float4 main(PS_INPUT input) : SV_Target {\n"
" float4 out_col = input.col * texture0.Sample(sampler0, input.uv);\n"
" float y = input.pos.y;\n" // SV_POSITION.y is top-down in FB pixels
" float topA = (u_fadeZoneTop > 0.0)\n"
" ? smoothstep(u_fadeTopY, u_fadeTopY + u_fadeZoneTop, y)\n"
" : 1.0;\n"
" float botA = (u_fadeZoneBot > 0.0)\n"
" ? (1.0 - smoothstep(u_fadeBottomY - u_fadeZoneBot, u_fadeBottomY, y))\n"
" : 1.0;\n"
" out_col.a *= topA * botA;\n"
" return out_col;\n"
"}\n";
bool initDX11() {
// Get DX11 device from ImGui backend
auto* bd = ImGui::GetIO().BackendRendererUserData;
if (!bd) { DEBUG_LOGF("ScrollFadeShader: no DX11 backend data\n"); return false; }
// ImGui_ImplDX11_Data layout: first field is ID3D11Device*
ID3D11Device* device = *(ID3D11Device**)bd;
if (!device) { DEBUG_LOGF("ScrollFadeShader: null DX11 device\n"); return false; }
// Compile pixel shader
ID3DBlob* psBlob = nullptr;
ID3DBlob* errBlob = nullptr;
HRESULT hr = D3DCompile(kFadePS_HLSL, strlen(kFadePS_HLSL),
nullptr, nullptr, nullptr,
"main", "ps_4_0", 0, 0,
&psBlob, &errBlob);
if (FAILED(hr)) {
if (errBlob) {
DEBUG_LOGF("ScrollFadeShader: PS compile error:\n%s\n",
(const char*)errBlob->GetBufferPointer());
errBlob->Release();
}
return false;
}
if (errBlob) errBlob->Release();
hr = device->CreatePixelShader(psBlob->GetBufferPointer(),
psBlob->GetBufferSize(),
nullptr, &pFadePS_);
psBlob->Release();
if (FAILED(hr)) {
DEBUG_LOGF("ScrollFadeShader: CreatePixelShader failed 0x%08lx\n", hr);
return false;
}
// Create constant buffer for fade params
D3D11_BUFFER_DESC cbDesc = {};
cbDesc.ByteWidth = sizeof(FadeCBData);
cbDesc.Usage = D3D11_USAGE_DYNAMIC;
cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
hr = device->CreateBuffer(&cbDesc, nullptr, &pFadeCB_);
if (FAILED(hr)) {
DEBUG_LOGF("ScrollFadeShader: CreateBuffer (CB) failed 0x%08lx\n", hr);
pFadePS_->Release(); pFadePS_ = nullptr;
return false;
}
DEBUG_LOGF("ScrollFadeShader: DX11 pixel shader + CB created\n");
return true;
}
void destroyDX11() {
if (pFadeCB_) { pFadeCB_->Release(); pFadeCB_ = nullptr; }
if (pFadePS_) { pFadePS_->Release(); pFadePS_ = nullptr; }
}
#endif // DRAGONX_USE_DX11
// ================================================================
// Callback — called by ImGui's render backend during RenderDrawData
// ================================================================
static void BindCB(const ImDrawList* /*dl*/, const ImDrawCmd* cmd) {
auto* self = static_cast<ScrollFadeShader*>(cmd->UserCallbackData);
if (!self || !self->ready) return;
ImGuiIO& io = ImGui::GetIO();
float fbScaleY = io.DisplayFramebufferScale.y;
float fb_height = io.DisplaySize.y * fbScaleY;
// In multi-viewport mode ImGui coordinates are OS-absolute, but
// SV_POSITION / gl_FragCoord are render-target-relative. Subtract
// the main viewport origin to convert screen-space → RT-local.
float vpY = ImGui::GetMainViewport()->Pos.y;
// Convert logical screen-space params to framebuffer pixels
float topY = (self->fadeTopY - vpY) * fbScaleY;
float botY = (self->fadeBottomY - vpY) * fbScaleY;
float zoneTop = self->fadeZoneTop * fbScaleY;
float zoneBot = self->fadeZoneBottom * fbScaleY;
#ifdef DRAGONX_HAS_GLAD
glUseProgram(self->program_);
// Rebind vertex attribute pointers so our forced locations (0=Position,
// 1=UV, 2=Color) match the already-bound VBO data. ImGui's linker may
// assign different locations (e.g. Mesa uses alphabetical order:
// Color=0, Position=1, UV=2), but the VBO layout is fixed, so we
// must explicitly point our attributes at the correct offsets.
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
// Ortho projection — must match ImGui's SetupRenderState which uses
// draw_data->DisplayPos as the origin. With ViewportsEnable, vertex
// positions are in OS screen-space; the projection matrix maps them
// to window-local framebuffer coordinates.
float vpX = ImGui::GetMainViewport()->Pos.x;
float L = vpX;
float R = vpX + io.DisplaySize.x;
float T = vpY;
float B = vpY + io.DisplaySize.y;
const float ortho[4][4] = {
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, -1.0f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
};
glUniformMatrix4fv(self->locProjMtx_, 1, GL_FALSE, &ortho[0][0]);
glUniform1i(self->locTexture_, 0);
glUniform1f(self->locFadeTopY_, topY);
glUniform1f(self->locFadeBottomY_, botY);
glUniform1f(self->locFadeZoneTop_, zoneTop);
glUniform1f(self->locFadeZoneBot_, zoneBot);
glUniform1f(self->locViewportH_, fb_height);
#endif
#ifdef DRAGONX_USE_DX11
(void)fb_height;
// Get device context from ImGui backend (first two fields of ImGui_ImplDX11_Data)
auto* bd = io.BackendRendererUserData;
if (!bd) return;
struct DX11BD { ID3D11Device* dev; ID3D11DeviceContext* ctx; };
auto* dx = reinterpret_cast<DX11BD*>(bd);
ID3D11DeviceContext* ctx = dx->ctx;
if (!ctx) return;
// Update constant buffer
D3D11_MAPPED_SUBRESOURCE mapped;
if (SUCCEEDED(ctx->Map(self->pFadeCB_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) {
auto* cb = static_cast<FadeCBData*>(mapped.pData);
cb->fadeTopY = topY;
cb->fadeBottomY = botY;
cb->fadeZoneTop = zoneTop;
cb->fadeZoneBot = zoneBot;
ctx->Unmap(self->pFadeCB_, 0);
}
// Bind our pixel shader + constant buffer (slot 1 = register(b1))
ctx->PSSetShader(self->pFadePS_, nullptr, 0);
ctx->PSSetConstantBuffers(1, 1, &self->pFadeCB_);
#endif
}
};
} // namespace effects
} // namespace ui
} // namespace dragonx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <imgui.h>
#include <vector>
#include <string>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief Per-theme visual effects system.
*
* Draws animated accents (hue-cycling, rainbow borders, shimmer sweeps,
* glow pulses, positional tinting) driven entirely by TOML theme config.
* All effects use ImDrawList primitives — no shaders or texture uploads.
*/
class ThemeEffects {
public:
static ThemeEffects& instance();
/// Called once per frame (timing updates)
void beginFrame();
/// Master toggle (from settings)
void setEnabled(bool enabled) { enabled_ = enabled; }
bool isEnabled() const { return enabled_; }
/// Background opacity (from window opacity slider). Multiplies
/// all effect alphas so they fade with the backdrop while UI stays opaque.
void setBackgroundOpacity(float o) { bgOpacity_ = std::max(0.0f, std::min(1.0f, o)); }
float backgroundOpacity() const { return bgOpacity_; }
/// Reduced transparency suppresses shimmer + rainbow border
void setReducedTransparency(bool rt) { reduced_transparency_ = rt; }
bool isReducedTransparency() const { return reduced_transparency_; }
/// Reload [effects] config from the active UISchema overlay
void loadFromTheme();
// === Drawing APIs ===
/// Get the current hue-cycled accent color (optionally phase-shifted)
ImU32 getAccentColor(float phaseOffset = 0.0f) const;
/// Draw rainbow gradient border around a rect (for glass panels)
void drawRainbowBorder(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding, float thickness) const;
/// Draw shimmer sweep over a rect
void drawShimmer(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Get positional hue tint for a given screen Y
ImU32 getPositionalTint(float screenY) const;
/// Tint an existing color by positional hue
ImU32 tintByPosition(ImU32 baseColor, float screenY) const;
/// Draw glow pulse behind a rect (for active elements)
void drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw a light that traces along the border perimeter
void drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw a border that shifts between two colors over time (gem-like)
void drawGradientBorderShift(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw ember particles that rise from an element (fire theme)
void drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const;
/// Draw specular glare highlights on a panel (polished surface look)
void drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw viewport-wide ambient ember particles (fire theme atmosphere)
void drawViewportEmbers(ImDrawList* dl) const;
/// Draw viewport-wide sandstorm particles (wind-driven diagonal sand/dust)
void drawSandstorm(ImDrawList* dl) const;
/// Draw shader-like viewport overlay (color wash + vignette post-processing)
void drawViewportOverlay(ImDrawList* dl) const;
/// Draw all applicable panel effects (edge trace + ember rise on glass panels)
/// phaseKey provides per-panel phase diversity based on panel position
void drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
// === Query which effects are available ===
bool hasRainbowBorder() const { return enabled_ && !reduced_transparency_ && rainbow_border_.enabled; }
bool hasShimmer() const { return enabled_ && !reduced_transparency_ && shimmer_.enabled; }
bool hasGlowPulse() const { return enabled_ && glow_pulse_.enabled; }
bool hasHueCycle() const { return enabled_ && hue_cycle_.enabled; }
bool hasPositionalHue() const { return enabled_ && positional_hue_.enabled; }
bool hasEdgeTrace() const { return enabled_ && edge_trace_.enabled; } bool hasGradientBorder() const { return enabled_ && gradient_border_.enabled; } bool hasEmberRise() const { return enabled_ && ember_rise_.enabled; }
bool hasSpecularGlare() const { return enabled_ && !reduced_transparency_ && specular_glare_.enabled; }
bool hasSandstorm() const { return enabled_ && sandstorm_.enabled; }
bool hasViewportOverlay() const { return enabled_ && (viewport_overlay_.colorWashEnabled || viewport_overlay_.vignetteEnabled); }
/// True when any time-dependent effect is active and needs continuous redraws
bool hasActiveAnimation() const {
if (!enabled_) return false;
return hue_cycle_.enabled || rainbow_border_.enabled || shimmer_.enabled
|| glow_pulse_.enabled || edge_trace_.enabled || ember_rise_.enabled
|| gradient_border_.enabled || specular_glare_.enabled || sandstorm_.enabled
|| (viewport_overlay_.colorWashEnabled
&& (viewport_overlay_.washRotateSpeed > 0.0f || viewport_overlay_.washPulseSpeed > 0.0f));
}
private:
ThemeEffects() = default;
bool enabled_ = true;
bool reduced_transparency_ = false;
float time_ = 0.0f;
float bgOpacity_ = 1.0f; ///< window-opacity multiplier for effect alphas
// Viewport bounds (set each frame)
float vpMinY_ = 0.0f;
float vpMaxY_ = 1.0f;
// ---- Cached config from [effects] ----
struct HueCycleConfig {
bool enabled = false;
float speed = 0.1f, sat = 0.6f, val = 0.85f;
float range = 1.0f, offset = 0.0f;
} hue_cycle_;
struct RainbowBorderConfig {
bool enabled = false;
float speed = 0.05f, alpha = 0.25f;
std::vector<ImU32> stops;
} rainbow_border_;
struct ShimmerConfig {
bool enabled = false;
float speed = 0.12f, width = 80.0f, alpha = 0.06f, angle = 30.0f;
ImU32 color = IM_COL32(255,255,255,255);
} shimmer_;
struct PositionalHueConfig {
bool enabled = false;
ImVec4 topColor{1,0.42f,0.62f,1};
ImVec4 bottomColor{0.4f,0.91f,0.98f,1};
float strength = 0.3f;
} positional_hue_;
struct GlowPulseConfig {
bool enabled = false;
float speed = 2.0f, minAlpha = 0.0f, maxAlpha = 0.15f, radius = 4.0f;
ImU32 color = IM_COL32(255,255,255,255);
} glow_pulse_;
struct EdgeTraceConfig {
bool enabled = false;
float speed = 0.3f; ///< full circuits per second
float length = 0.20f; ///< fraction of perimeter lit (tail length)
float thickness = 1.5f; ///< line thickness in pixels
float alpha = 0.6f; ///< peak alpha at head of trace
ImU32 color = IM_COL32(255,255,255,255);
} edge_trace_;
struct EmberRiseConfig {
bool enabled = false;
int count = 8; ///< number of ember particles
float speed = 0.4f; ///< rise cycles per second
float particleSize = 1.5f;///< ember radius in pixels
float alpha = 0.5f; ///< peak alpha
ImU32 color = IM_COL32(255, 120, 20, 255);
} ember_rise_;
struct GradientBorderConfig {
bool enabled = false;
float speed = 0.15f; ///< full color shift cycles per second
float thickness = 1.5f; ///< border line thickness in pixels
float alpha = 0.6f; ///< peak alpha
ImU32 colorA = IM_COL32(206, 147, 216, 255); ///< first color (amethyst)
ImU32 colorB = IM_COL32(26, 35, 126, 255); ///< second color (indigo)
} gradient_border_;
struct SpecularGlareConfig {
bool enabled = false;
float speed = 0.02f; ///< drift speed (Lissajous orbit cycles/sec)
float intensity = 0.06f; ///< peak alpha at glare center
float radius = 0.5f; ///< glare radius as fraction of panel min-dim
int count = 2; ///< number of specular spots per panel
ImU32 color = IM_COL32(255, 255, 255, 255);
} specular_glare_;
struct SandstormConfig {
bool enabled = false;
int count = 80; ///< number of sand particles
float speed = 0.35f; ///< base horizontal velocity (viewport widths/sec)
float windAngle = 15.0f; ///< degrees from horizontal (+ve = downward)
float particleSize = 1.5f; ///< base particle radius in pixels
float alpha = 0.35f; ///< peak particle alpha
float gustSpeed = 0.07f; ///< gust oscillation frequency (Hz)
float gustStrength = 0.4f; ///< speed variation from gusts (fraction)
float streakLength = 3.0f; ///< motion blur multiplier for fast particles
ImU32 color = IM_COL32(200, 160, 96, 255);
} sandstorm_;
struct ViewportOverlayConfig {
// --- Color wash: full-screen 4-corner gradient overlay ---
bool colorWashEnabled = false;
ImU32 cornerTL = IM_COL32(255,100,0,255); ///< top-left corner color (RGB; alpha applied separately)
ImU32 cornerTR = IM_COL32(68,34,0,255); ///< top-right
ImU32 cornerBL = IM_COL32(68,17,0,255); ///< bottom-left
ImU32 cornerBR = IM_COL32(255,153,0,255); ///< bottom-right
float washAlpha = 0.05f; ///< overall wash intensity
float washRotateSpeed = 0.0f; ///< corner color rotation (turns/sec)
float washPulseSpeed = 0.0f; ///< alpha breathing speed (Hz)
float washPulseDepth = 0.0f; ///< pulse depth (0=none, 1=full fade)
// --- Vignette: edge darkening/tinting ---
bool vignetteEnabled = false;
ImU32 vignetteColor = IM_COL32(0,0,0,255); ///< tint color at edges
float vignetteRadius = 0.35f; ///< fade zone as fraction of viewport
float vignetteAlpha = 0.30f; ///< max alpha at outer edge
} viewport_overlay_;
/// Map a 0..1 fraction to a point on the rounded rect perimeter
ImVec2 perimeterPoint(ImVec2 pMin, ImVec2 pMax, float t, float rounding = 0.0f) const;
// Helper: parse hex color string "#RRGGBB" or "#RRGGBBAA" to ImU32
static ImU32 parseHexColor(const std::string& hex, ImU32 fallback = IM_COL32(255,255,255,255));
static ImVec4 parseHexColorVec4(const std::string& hex, ImVec4 fallback = ImVec4(1,1,1,1));
// Helper: sample a rotating multi-stop gradient
ImU32 sampleGradient(float t) const;
};
} // namespace effects
} // namespace ui
} // namespace dragonx

543
src/ui/layout.h Normal file
View File

@@ -0,0 +1,543 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
#include "material/type.h"
#include "schema/ui_schema.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
namespace dragonx {
namespace ui {
// Import material helpers into ui namespace for convenience
using material::Type;
using material::OverlineLabel;
using material::OnSurface;
using material::OnSurfaceMedium;
using material::OnSurfaceDisabled;
using material::Primary;
using material::Secondary;
using material::Error;
/**
* @brief Centralized layout configuration for consistent UI across all tabs
*
* Values are now driven by UISchema (JSON-configurable with hot-reload).
* The k* names are preserved as inline accessors for backward compatibility.
*/
namespace Layout {
// ============================================================================
// DPI Scaling (must be first — other accessors multiply by dpiScale())
// ============================================================================
/**
* @brief Get the current display DPI scale factor.
*
* Returns the DPI scale set during typography initialization (e.g. 2.0 for
* 200 % Windows scaling). All pixel constants from TOML are in *logical*
* pixels and must be multiplied by this factor before being used as ImGui
* coordinates (which are physical pixels on Windows Per-Monitor DPI v2).
*/
inline float dpiScale() {
return dragonx::ui::material::Typography::instance().getDpiScale();
}
/**
* @brief Scale a logical pixel value by the current DPI factor.
*
* Convenience wrapper: Scale(16.0f) returns 16 * dpiScale.
*/
inline float Scale(float px) {
return px * dpiScale();
}
// ============================================================================
// Font Sizes (in pixels, before DPI scaling)
// ============================================================================
// These read from UISchema which loads layout from ui.toml.
// Editing res/themes/ui.toml will hot-reload these at runtime.
inline float kFontH1() { return schema::UI().drawElement("fonts", "h1").sizeOr(24.0f); }
inline float kFontH2() { return schema::UI().drawElement("fonts", "h2").sizeOr(20.0f); }
inline float kFontH3() { return schema::UI().drawElement("fonts", "h3").sizeOr(18.0f); }
inline float kFontH4() { return schema::UI().drawElement("fonts", "h4").sizeOr(16.0f); }
inline float kFontH5() { return schema::UI().drawElement("fonts", "h5").sizeOr(14.0f); }
inline float kFontH6() { return schema::UI().drawElement("fonts", "h6").sizeOr(14.0f); }
inline float kFontSubtitle1() { return schema::UI().drawElement("fonts", "subtitle1").sizeOr(16.0f); }
inline float kFontSubtitle2() { return schema::UI().drawElement("fonts", "subtitle2").sizeOr(14.0f); }
inline float kFontBody1() { return schema::UI().drawElement("fonts", "body1").sizeOr(14.0f); }
inline float kFontBody2() { return schema::UI().drawElement("fonts", "body2").sizeOr(12.0f); }
inline float kFontButton() { return schema::UI().drawElement("fonts", "button").sizeOr(13.0f); }
inline float kFontButtonSm() { return schema::UI().drawElement("fonts", "button-sm").sizeOr(10.0f); }
inline float kFontButtonLg() { return schema::UI().drawElement("fonts", "button-lg").sizeOr(14.0f); }
inline float kFontCaption() { return schema::UI().drawElement("fonts", "caption").sizeOr(11.0f); }
inline float kFontOverline() { return schema::UI().drawElement("fonts", "overline").sizeOr(11.0f); }
// Global font scale
inline float kFontScale() { return schema::UI().drawElement("fonts", "scale").sizeOr(1.0f); }
// ============================================================================
// Panel Sizing (responsive)
// ============================================================================
inline float kSummaryPanelMinWidth() { return schema::UI().drawElement("panels", "summary").getFloat("min-width", 280.0f) * dpiScale(); }
inline float kSummaryPanelMaxWidth() { return schema::UI().drawElement("panels", "summary").getFloat("max-width", 400.0f) * dpiScale(); }
inline float kSummaryPanelWidthRatio() { return schema::UI().drawElement("panels", "summary").getFloat("width-ratio", 0.32f); }
inline float kSummaryPanelMinHeight() { return schema::UI().drawElement("panels", "summary").getFloat("min-height", 200.0f) * dpiScale(); }
inline float kSummaryPanelMaxHeight() { return schema::UI().drawElement("panels", "summary").getFloat("max-height", 350.0f) * dpiScale(); }
inline float kSummaryPanelHeightRatio() { return schema::UI().drawElement("panels", "summary").getFloat("height-ratio", 0.8f); }
inline float kSidePanelMinWidth() { return schema::UI().drawElement("panels", "side-panel").getFloat("min-width", 280.0f) * dpiScale(); }
inline float kSidePanelMaxWidth() { return schema::UI().drawElement("panels", "side-panel").getFloat("max-width", 450.0f) * dpiScale(); }
inline float kSidePanelWidthRatio() { return schema::UI().drawElement("panels", "side-panel").getFloat("width-ratio", 0.4f); }
inline float kTableMinHeight() { return schema::UI().drawElement("panels", "table").getFloat("min-height", 150.0f) * dpiScale(); }
inline float kTableHeightRatio() { return schema::UI().drawElement("panels", "table").getFloat("height-ratio", 0.45f); }
// ============================================================================
// Spacing
// ============================================================================
inline float kSectionSpacing() { return schema::UI().drawElement("spacing", "section").sizeOr(16.0f) * dpiScale(); }
inline float kItemSpacing() { return schema::UI().drawElement("spacing", "item").sizeOr(8.0f) * dpiScale(); }
inline float kLabelValueGap() { return schema::UI().drawElement("spacing", "label-value").sizeOr(4.0f) * dpiScale(); }
inline float kSeparatorGap() { return schema::UI().drawElement("spacing", "separator").sizeOr(20.0f) * dpiScale(); }
// ============================================================================
// Layout Tier (responsive breakpoints)
// ============================================================================
/**
* @brief Three-tier layout system based on content area dimensions.
*
* Compact — narrow/short window: single-column, collapsed elements
* Normal — default layout
* Expanded — large window: extra spacing, optional extra columns
*/
enum class LayoutTier { Compact, Normal, Expanded };
/**
* @brief Determine the current layout tier from content area dimensions.
* Call after ImGui::BeginChild for the content area, or pass explicit avail.
*/
inline LayoutTier currentTier() {
const auto& S = schema::UI();
float dp = dpiScale();
float cw = S.drawElement("responsive", "compact-width").sizeOr(500.0f) * dp;
float ch = S.drawElement("responsive", "compact-height").sizeOr(450.0f) * dp;
float ew = S.drawElement("responsive", "expanded-width").sizeOr(900.0f) * dp;
float eh = S.drawElement("responsive", "expanded-height").sizeOr(750.0f) * dp;
ImVec2 avail = ImGui::GetContentRegionAvail();
if (avail.x < cw || avail.y < ch) return LayoutTier::Compact;
if (avail.x > ew && avail.y > eh) return LayoutTier::Expanded;
return LayoutTier::Normal;
}
inline LayoutTier currentTier(float availW, float availH) {
const auto& S = schema::UI();
float dp = dpiScale();
float cw = S.drawElement("responsive", "compact-width").sizeOr(500.0f) * dp;
float ch = S.drawElement("responsive", "compact-height").sizeOr(450.0f) * dp;
float ew = S.drawElement("responsive", "expanded-width").sizeOr(900.0f) * dp;
float eh = S.drawElement("responsive", "expanded-height").sizeOr(750.0f) * dp;
if (availW < cw || availH < ch) return LayoutTier::Compact;
if (availW > ew && availH > eh) return LayoutTier::Expanded;
return LayoutTier::Normal;
}
// ============================================================================
// Responsive Scale Factors
// ============================================================================
/**
* @brief Horizontal scale factor relative to reference width (default 1200px).
*
* The scale is decomposed into a *logical* portion (responsive to window
* size, clamped to the configured range) multiplied by the display DPI
* factor. This ensures a DPI transition produces the same logical scale
* while emitting physical-pixel results.
*/
inline float hScale(float availWidth) {
const auto& S = schema::UI();
float dp = dpiScale();
float rw = S.drawElement("responsive", "ref-width").sizeOr(1200.0f) * dp;
float minH = S.drawElement("responsive", "min-h-scale").sizeOr(0.5f);
float maxH = S.drawElement("responsive", "max-h-scale").sizeOr(1.5f);
// Clamp the logical (DPI-neutral) portion, then apply DPI.
float logical = std::clamp(availWidth / rw, minH, maxH);
return logical * dp;
}
inline float hScale() {
return hScale(ImGui::GetContentRegionAvail().x);
}
/**
* @brief Vertical scale factor relative to reference height (default 700px).
*
* Same decomposition as hScale — logical clamp × DPI.
*/
inline float vScale(float availHeight) {
const auto& S = schema::UI();
float dp = dpiScale();
float rh = S.drawElement("responsive", "ref-height").sizeOr(700.0f) * dp;
float minV = S.drawElement("responsive", "min-v-scale").sizeOr(0.5f);
float maxV = S.drawElement("responsive", "max-v-scale").sizeOr(1.4f);
float logical = std::clamp(availHeight / rh, minV, maxV);
return logical * dp;
}
inline float vScale() {
return vScale(ImGui::GetContentRegionAvail().y);
}
/**
* @brief Density scale factor for spacing tokens.
*
* Logical portion is clamped, then multiplied by DPI so pixel spacing
* values scale proportionally with fonts and style.
*/
inline float densityScale(float availHeight) {
const auto& S = schema::UI();
float dp = dpiScale();
float rh = S.drawElement("responsive", "ref-height").sizeOr(700.0f) * dp;
float minDen = S.drawElement("responsive", "min-density").sizeOr(0.6f);
float maxDen = S.drawElement("responsive", "max-density").sizeOr(1.2f);
float logical = std::clamp(availHeight / rh, minDen, maxDen);
return logical * dp;
}
inline float densityScale() {
return densityScale(ImGui::GetContentRegionAvail().y);
}
// ============================================================================
// Spacing Tokens (density-scaled)
// ============================================================================
/** @brief Get spacing token scaled by current density. */
inline float spacingXs() { return schema::UI().drawElement("spacing-tokens", "xs").sizeOr(2.0f) * densityScale(); }
inline float spacingSm() { return schema::UI().drawElement("spacing-tokens", "sm").sizeOr(4.0f) * densityScale(); }
inline float spacingMd() { return schema::UI().drawElement("spacing-tokens", "md").sizeOr(8.0f) * densityScale(); }
inline float spacingLg() { return schema::UI().drawElement("spacing-tokens", "lg").sizeOr(12.0f) * densityScale(); }
inline float spacingXl() { return schema::UI().drawElement("spacing-tokens", "xl").sizeOr(16.0f) * densityScale(); }
inline float spacingXxl() { return schema::UI().drawElement("spacing-tokens", "xxl").sizeOr(24.0f) * densityScale(); }
/** @brief Get raw (unscaled) spacing token. */
inline float spacingXsRaw() { return schema::UI().drawElement("spacing-tokens", "xs").sizeOr(2.0f); }
inline float spacingSmRaw() { return schema::UI().drawElement("spacing-tokens", "sm").sizeOr(4.0f); }
inline float spacingMdRaw() { return schema::UI().drawElement("spacing-tokens", "md").sizeOr(8.0f); }
inline float spacingLgRaw() { return schema::UI().drawElement("spacing-tokens", "lg").sizeOr(12.0f); }
inline float spacingXlRaw() { return schema::UI().drawElement("spacing-tokens", "xl").sizeOr(16.0f); }
inline float spacingXxlRaw() { return schema::UI().drawElement("spacing-tokens", "xxl").sizeOr(24.0f); }
// ============================================================================
// Responsive Globals Helpers
// ============================================================================
/** @brief Default glass panel rounding (8.0 default). */
inline float glassRounding() { return schema::UI().drawElement("responsive", "glass-rounding").sizeOr(8.0f) * dpiScale(); }
/** @brief Default card inner padding (12.0 default). */
inline float cardInnerPadding() { return schema::UI().drawElement("responsive", "card-inner-padding").sizeOr(12.0f) * dpiScale(); }
/** @brief Default card gap (8.0 default). */
inline float cardGap() { return schema::UI().drawElement("responsive", "card-gap").sizeOr(8.0f) * dpiScale(); }
/**
* @brief Compute a responsive card height from a base value.
* @param base Design-time card height (e.g. 110.0f) in logical pixels
* @param vs Vertical scale factor (from vScale(), already DPI-scaled)
* @return Scaled height with a DPI-aware floor of base * 0.4 * dpiScale
*/
inline float cardHeight(float base, float vs) {
return std::max(base * 0.4f * dpiScale(), base * vs);
}
/**
* @brief Compute a column offset as a ratio of available width.
* Replaces hardcoded "cx + 100" patterns.
* @param ratio Fraction of available width (e.g. 0.12)
* @param availW Available width
*/
inline float columnOffset(float ratio, float availW) {
return availW * ratio;
}
// ============================================================================
// Buttons
// ============================================================================
inline float kButtonMinWidth() { return schema::UI().drawElement("button", "min-width").sizeOr(180.0f) * dpiScale(); }
inline float kButtonStandardWidth() { return schema::UI().drawElement("button", "width").sizeOr(140.0f) * dpiScale(); }
inline float kButtonLargeWidth() { return schema::UI().drawElement("button", "width-lg").sizeOr(160.0f) * dpiScale(); }
inline float kButtonHeight() { float h = schema::UI().drawElement("button", "height").sizeOr(0.0f); return h > 0.0f ? h * dpiScale() : 0.0f; }
// ============================================================================
// Input Fields
// ============================================================================
inline float kInputMinWidth() { return schema::UI().drawElement("input", "min-width").sizeOr(150.0f) * dpiScale(); }
inline float kInputMediumWidth() { return schema::UI().drawElement("input", "width-md").sizeOr(200.0f) * dpiScale(); }
inline float kInputLargeWidth() { return schema::UI().drawElement("input", "width-lg").sizeOr(300.0f) * dpiScale(); }
inline float kSearchFieldWidthRatio() { return schema::UI().drawElement("input", "search-width-ratio").sizeOr(0.30f); }
// ============================================================================
// Status Bar
// ============================================================================
inline float kStatusBarHeight() { float dp = dpiScale(); auto h = schema::UI().window("components.status-bar", "window").height; return (h > 0 ? h : 60.0f) * dp; }
inline float kStatusBarPadding() { float dp = dpiScale(); auto w = schema::UI().window("components.status-bar", "window"); return (w.padding[0] > 0 ? w.padding[0] : 8.0f) * dp; }
// ============================================================================
// Helper Functions
// ============================================================================
/**
* @brief Calculate responsive width with min/max bounds
* @param availWidth Available width from GetContentRegionAvail().x
* @param ratio Ratio of available width (0.0-1.0)
* @param minWidth Minimum width in pixels
* @param maxWidth Maximum width in pixels
*/
inline float responsiveWidth(float availWidth, float ratio, float minWidth, float maxWidth) {
return std::max(minWidth, std::min(maxWidth, availWidth * ratio));
}
/**
* @brief Calculate responsive height with min/max bounds
*/
inline float responsiveHeight(float availHeight, float ratio, float minHeight, float maxHeight) {
return std::max(minHeight, std::min(maxHeight, availHeight * ratio));
}
/**
* @brief Get summary panel dimensions
*/
inline ImVec2 getSummaryPanelSize() {
ImVec2 avail = ImGui::GetContentRegionAvail();
return ImVec2(
responsiveWidth(avail.x, kSummaryPanelWidthRatio(), kSummaryPanelMinWidth(), kSummaryPanelMaxWidth()),
responsiveHeight(avail.y, kSummaryPanelHeightRatio(), kSummaryPanelMinHeight(), kSummaryPanelMaxHeight())
);
}
/**
* @brief Get side panel width (height fills available)
*/
inline float getSidePanelWidth() {
float avail = ImGui::GetContentRegionAvail().x;
return responsiveWidth(avail, kSidePanelWidthRatio(), kSidePanelMinWidth(), kSidePanelMaxWidth());
}
/**
* @brief Get table height for split view (two tables)
*/
inline float getTableHeight() {
float avail = ImGui::GetContentRegionAvail().y;
return responsiveHeight(avail, kTableHeightRatio(), kTableMinHeight(), avail * 0.6f);
}
/**
* @brief Get remaining height (for second table/section)
*/
inline float getRemainingHeight(float reserveSpace) {
float avail = ImGui::GetContentRegionAvail().y;
return std::max(schema::UI().drawElement("panels", "table").getFloat("min-remaining", 100.0f) * dpiScale(), avail - reserveSpace);
}
inline float getRemainingHeight() {
return getRemainingHeight(schema::UI().drawElement("panels", "table").getFloat("default-reserve", 30.0f) * dpiScale());
}
/**
* @brief Get search/filter input width
*/
inline float getSearchWidth() {
float avail = ImGui::GetContentRegionAvail().x;
return std::min(kInputLargeWidth(), avail * kSearchFieldWidthRatio());
}
/**
* @brief Calculate position for right-aligned buttons
* @param buttonCount Number of buttons
* @param buttonWidth Width of each button
* @param spacing Spacing between buttons
*/
inline float getRightButtonsPos(int buttonCount, float buttonWidth, float spacing) {
float dp = dpiScale();
float margin = schema::UI().drawElement("button", "right-align-margin").sizeOr(16.0f) * dp;
float minPos = schema::UI().drawElement("button", "right-align-min-pos").sizeOr(200.0f) * dp;
float totalWidth = buttonCount * buttonWidth + (buttonCount - 1) * spacing + margin;
return std::max(minPos, ImGui::GetWindowWidth() - totalWidth);
}
inline float getRightButtonsPos(int buttonCount, float buttonWidth) {
return getRightButtonsPos(buttonCount, buttonWidth, schema::UI().drawElement("button", "right-align-gap").sizeOr(8.0f) * dpiScale());
}
// ============================================================================
// Shared Card Height (send/receive tab parity)
// ============================================================================
/**
* @brief Compute the target glass card height for send/receive tabs.
*
* Both tabs call this with the same formW + vs so their cards match.
* The height models the receive tab's QR-code-driven layout:
* topPad + totalQrSize + innerGap + actionBtnH + bottomPad
*
* @param formW Total card / form width in pixels
* @param vs Vertical scale factor (from vScale())
*/
inline float mainCardTargetH(float formW, float vs) {
float dp = dpiScale();
float pad = spacingLg();
float innerW = formW - pad * 2;
float qrColW = innerW * 0.35f;
float qrPad = spacingMd();
float maxQrSz = std::min(qrColW - qrPad * 2, 280.0f * dp);
float qrSize = std::max(100.0f * dp, maxQrSz);
float totalQr = qrSize + qrPad * 2;
float innerGap = spacingLg();
float btnH = std::max(26.0f * dp, 30.0f * vs);
return pad + totalQr + innerGap + btnH + pad;
}
// ============================================================================
// Section Budget Allocator
// ============================================================================
/**
* @brief Proportional height-budget allocator for tab content.
*
* Each tab creates a SectionBudget from the available content height and
* allocates fractions to its sections. Sections receive a proportional
* share of the total height clamped to [minPx, maxPx], guaranteeing
* that all content fits without scrolling at any window size.
*
* Usage:
* SectionBudget b(ImGui::GetContentRegionAvail().y);
* float heroH = b.allocate(0.13f, 55.0f);
* float listH = b.rest(60.0f); // whatever is left
*/
struct SectionBudget {
float total; ///< Total available height passed at construction
float remaining; ///< Decrements as sections are allocated
explicit SectionBudget(float avail)
: total(avail), remaining(avail) {}
/**
* @brief Allocate a fraction of the total budget.
* @param fraction Fraction of *total* (e.g. 0.12 = 12%)
* @param minPx Minimum pixel height in logical pixels (auto DPI-scaled)
* @param maxPx Maximum pixel height in logical pixels (auto DPI-scaled, default unlimited)
* @return The allocated height in physical pixels.
*/
float allocate(float fraction, float minPx, float maxPx = FLT_MAX) {
float dp = dpiScale();
float scaledMin = minPx * dp;
float scaledMax = (maxPx >= FLT_MAX * 0.5f) ? FLT_MAX : maxPx * dp;
float h = std::clamp(total * fraction, scaledMin, scaledMax);
remaining -= h;
if (remaining < 0.0f) remaining = 0.0f;
return h;
}
/**
* @brief Allocate whatever height remains (for the final section).
* @param minPx Minimum logical pixels guaranteed even if budget is exhausted.
* @return Remaining height (at least minPx * dpiScale).
*/
float rest(float minPx = 0.0f) {
return std::max(minPx * dpiScale(), remaining);
}
};
} // namespace Layout
// ============================================================================
// Convenience Macros/Functions for Common Patterns
// ============================================================================
/**
* @brief Begin a summary panel with responsive sizing
*/
inline bool BeginSummaryPanel(const char* id) {
ImVec2 size = Layout::getSummaryPanelSize();
return ImGui::BeginChild(id, size, true);
}
/**
* @brief Begin a side panel with responsive width
*/
inline bool BeginSidePanel(const char* id) {
float width = Layout::getSidePanelWidth();
return ImGui::BeginChild(id, ImVec2(width, 0), true);
}
/**
* @brief Begin a content panel that fills remaining space
*/
inline bool BeginContentPanel(const char* id) {
return ImGui::BeginChild(id, ImVec2(0, 0), true);
}
/**
* @brief Add standard section header with separator
*/
inline void SectionHeader(const char* label) {
OverlineLabel(label);
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
}
/**
* @brief Add spacing between sections
*/
inline void SectionSpacing() {
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
}
/**
* @brief Render a label-value pair
*/
inline void LabelValue(const char* label, const char* value) {
Type().textColored(material::TypeStyle::Caption, OnSurfaceMedium(), label);
ImGui::Text("%s", value);
}
/**
* @brief Render a label-value pair with colored value
*/
inline void LabelValueColored(const char* label, const char* value, ImU32 color) {
Type().textColored(material::TypeStyle::Caption, OnSurfaceMedium(), label);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(color), "%s", value);
}
/**
* @brief Render a balance display (amount + ticker)
*/
inline void BalanceDisplay(double amount, const char* ticker, ImU32 color = 0) {
char buf[64];
snprintf(buf, sizeof(buf), "%.8f", amount);
if (color != 0) {
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(color), "%s", buf);
} else {
ImGui::Text("%s", buf);
}
ImGui::SameLine();
Type().textColored(material::TypeStyle::Body2, OnSurfaceMedium(), ticker);
}
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,501 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "layout.h"
#include "colors.h"
#include "typography.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// App Layout Manager
// ============================================================================
// Manages the overall application layout following Material Design patterns.
//
// Usage:
// // In your main render loop:
// auto& layout = AppLayout::instance();
// layout.beginFrame();
//
// // Render app bar
// if (layout.beginAppBar("DragonX Wallet")) {
// // App bar content (menu items, etc.)
// layout.endAppBar();
// }
//
// // Render navigation
// if (layout.beginNavigation()) {
// layout.navItem("Balance", ICON_WALLET, currentTab == 0);
// layout.navItem("Send", ICON_SEND, currentTab == 1);
// layout.endNavigation();
// }
//
// // Render main content
// if (layout.beginContent()) {
// // Your content here
// layout.endContent();
// }
//
// layout.endFrame();
class AppLayout {
public:
static AppLayout& instance() {
static AppLayout s_instance;
return s_instance;
}
// ========================================================================
// Frame Management
// ========================================================================
/**
* @brief Begin a new frame layout
*
* Call this at the start of each frame before any layout calls.
* Updates responsive breakpoints and calculates regions.
*/
void beginFrame();
/**
* @brief End the frame layout
*/
void endFrame();
// ========================================================================
// Layout Regions
// ========================================================================
/**
* @brief Begin the app bar region
*
* @param title App title to display
* @param showBack Show back button (for sub-pages)
* @return true if app bar is visible
*/
bool beginAppBar(const char* title, bool showBack = false);
void endAppBar();
/**
* @brief Begin the navigation region (drawer/rail/bottom)
*
* @return true if navigation region is visible
*/
bool beginNavigation();
void endNavigation();
/**
* @brief Render a navigation item
*
* @param label Item label
* @param icon Icon glyph (can be nullptr)
* @param selected Whether this item is currently selected
* @return true if clicked
*/
bool navItem(const char* label, const char* icon, bool selected);
/**
* @brief Add a navigation section divider
*
* @param title Optional section title
*/
void navSection(const char* title = nullptr);
/**
* @brief Begin the main content region
*
* @return true if content region is visible
*/
bool beginContent();
void endContent();
// ========================================================================
// Card Helpers
// ========================================================================
/**
* @brief Begin a Material Design card
*
* @param id Unique ID for the card
* @param layout Card layout configuration
* @return true if card is visible
*/
bool beginCard(const char* id, const CardLayout& layout = CardLayout());
void endCard();
// ========================================================================
// Layout Queries
// ========================================================================
/**
* @brief Get current breakpoint category
*/
breakpoint::Category getBreakpoint() const { return breakpoint_; }
/**
* @brief Get current navigation style
*/
breakpoint::NavStyle getNavStyle() const { return navStyle_; }
/**
* @brief Get content region available width
*/
float getContentWidth() const { return contentWidth_; }
/**
* @brief Get content region available height
*/
float getContentHeight() const { return contentHeight_; }
/**
* @brief Check if navigation drawer is expanded
*/
bool isNavExpanded() const { return navExpanded_; }
/**
* @brief Toggle navigation drawer expansion
*/
void toggleNav() { navExpanded_ = !navExpanded_; }
/**
* @brief Set navigation drawer expansion state
*/
void setNavExpanded(bool expanded) { navExpanded_ = expanded; }
private:
AppLayout();
~AppLayout() = default;
AppLayout(const AppLayout&) = delete;
AppLayout& operator=(const AppLayout&) = delete;
// Layout state
breakpoint::Category breakpoint_ = breakpoint::Category::Md;
breakpoint::NavStyle navStyle_ = breakpoint::NavStyle::NavDrawer;
float windowWidth_ = 0;
float windowHeight_ = 0;
float contentWidth_ = 0;
float contentHeight_ = 0;
bool navExpanded_ = true;
// Region tracking
bool inAppBar_ = false;
bool inNav_ = false;
bool inContent_ = false;
// Calculated regions
ImVec2 appBarPos_;
ImVec2 appBarSize_;
ImVec2 navPos_;
ImVec2 navSize_;
ImVec2 contentPos_;
ImVec2 contentSize_;
void calculateRegions();
};
// ============================================================================
// Inline Implementation
// ============================================================================
inline AppLayout::AppLayout() {
// Initialize with reasonable defaults
navExpanded_ = true;
}
inline void AppLayout::beginFrame() {
// Get main viewport size
ImGuiViewport* viewport = ImGui::GetMainViewport();
windowWidth_ = viewport->WorkSize.x;
windowHeight_ = viewport->WorkSize.y;
// Update responsive state
breakpoint_ = breakpoint::GetCategory(windowWidth_);
navStyle_ = breakpoint::GetNavStyle(breakpoint_);
// Auto-collapse nav on small screens
if (breakpoint_ == breakpoint::Category::Xs) {
navExpanded_ = false;
}
calculateRegions();
}
inline void AppLayout::endFrame() {
// Reset state
inAppBar_ = false;
inNav_ = false;
inContent_ = false;
}
inline void AppLayout::calculateRegions() {
// App bar at top
appBarPos_ = ImVec2(0, 0);
appBarSize_ = ImVec2(windowWidth_, size::AppBarHeight);
float belowAppBar = size::AppBarHeight;
float contentAreaHeight = windowHeight_ - belowAppBar;
// Navigation region
switch (navStyle_) {
case breakpoint::NavStyle::NavDrawer:
if (navExpanded_) {
navSize_ = ImVec2(size::NavDrawerWidth, contentAreaHeight);
} else {
navSize_ = ImVec2(size::NavRailWidth, contentAreaHeight);
}
navPos_ = ImVec2(0, belowAppBar);
break;
case breakpoint::NavStyle::NavRail:
navSize_ = ImVec2(size::NavRailWidth, contentAreaHeight);
navPos_ = ImVec2(0, belowAppBar);
break;
case breakpoint::NavStyle::BottomNav:
// Bottom nav handled separately
navSize_ = ImVec2(windowWidth_, size::NavItemHeight);
navPos_ = ImVec2(0, windowHeight_ - size::NavItemHeight);
contentAreaHeight -= size::NavItemHeight;
break;
}
// Content region
if (navStyle_ == breakpoint::NavStyle::BottomNav) {
contentPos_ = ImVec2(0, belowAppBar);
contentSize_ = ImVec2(windowWidth_, contentAreaHeight);
} else {
contentPos_ = ImVec2(navSize_.x, belowAppBar);
contentSize_ = ImVec2(windowWidth_ - navSize_.x, contentAreaHeight);
}
contentWidth_ = contentSize_.x;
contentHeight_ = contentSize_.y;
}
inline bool AppLayout::beginAppBar(const char* title, bool showBack) {
ImGui::SetNextWindowPos(appBarPos_);
ImGui::SetNextWindowSize(appBarSize_);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBringToFrontOnFocus;
// Use elevated surface color for app bar
ImGui::PushStyleColor(ImGuiCol_WindowBg, SurfaceVec4(4));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(size::AppBarPadding, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
bool visible = ImGui::Begin("##AppBar", nullptr, flags);
if (visible) {
inAppBar_ = true;
// Center content vertically
float centerY = (size::AppBarHeight - Typography::instance().getFont(TypeStyle::H6)->FontSize) * 0.5f;
ImGui::SetCursorPosY(centerY);
// Menu/back button
if (showBack) {
if (ImGui::Button("<")) {
// Back action - handled by caller
}
ImGui::SameLine();
} else if (navStyle_ != breakpoint::NavStyle::BottomNav) {
// Menu button to toggle nav
if (ImGui::Button("=")) {
toggleNav();
}
ImGui::SameLine();
}
// Title
Typography::instance().text(TypeStyle::H6, title);
}
return visible;
}
inline void AppLayout::endAppBar() {
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
inAppBar_ = false;
}
inline bool AppLayout::beginNavigation() {
if (navStyle_ == breakpoint::NavStyle::BottomNav) {
ImGui::SetNextWindowPos(navPos_);
} else {
ImGui::SetNextWindowPos(navPos_);
}
ImGui::SetNextWindowSize(navSize_);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBringToFrontOnFocus;
// Nav drawer has higher elevation
int elevation = (navStyle_ == breakpoint::NavStyle::NavDrawer) ? 16 : 0;
ImGui::PushStyleColor(ImGuiCol_WindowBg, SurfaceVec4(elevation));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, size::NavSectionPadding));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
bool visible = ImGui::Begin("##Navigation", nullptr, flags);
if (visible) {
inNav_ = true;
}
return visible;
}
inline void AppLayout::endNavigation() {
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
inNav_ = false;
}
inline bool AppLayout::navItem(const char* label, const char* icon, bool selected) {
bool compact = !navExpanded_ || navStyle_ == breakpoint::NavStyle::NavRail;
float itemWidth = navSize_.x;
float itemHeight = size::NavItemHeight;
ImGui::PushID(label);
// Selection highlight
if (selected) {
ImVec2 pos = ImGui::GetCursorScreenPos();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(
pos,
ImVec2(pos.x + itemWidth, pos.y + itemHeight),
StateSelected()
);
}
// Padding
ImGui::SetCursorPosX(size::NavItemPadding);
// Content
bool clicked = false;
ImGui::BeginGroup();
if (compact) {
// Rail/collapsed: Icon only, centered
CenterHorizontally(size::IconSize);
clicked = ImGui::Selectable(icon ? icon : "?", selected, 0, ImVec2(size::IconSize, itemHeight));
} else {
// Drawer: Icon + label
if (icon) {
ImGui::Text("%s", icon);
ImGui::SameLine();
}
float selectableWidth = itemWidth - size::NavItemPadding * 2 - (icon ? size::IconSize + spacing::Sm : 0);
clicked = ImGui::Selectable(label, selected, 0, ImVec2(selectableWidth, itemHeight));
}
ImGui::EndGroup();
ImGui::PopID();
return clicked;
}
inline void AppLayout::navSection(const char* title) {
VSpace(1);
if (title && navExpanded_) {
ImGui::SetCursorPosX(size::NavItemPadding);
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), title);
}
// Divider
ImGui::Separator();
VSpace(1);
}
inline bool AppLayout::beginContent() {
ImGui::SetNextWindowPos(contentPos_);
ImGui::SetNextWindowSize(contentSize_);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBringToFrontOnFocus;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGui::ColorConvertU32ToFloat4(Background()));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(spacing::Md, spacing::Md));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
bool visible = ImGui::Begin("##Content", nullptr, flags);
if (visible) {
inContent_ = true;
}
return visible;
}
inline void AppLayout::endContent() {
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
inContent_ = false;
}
inline bool AppLayout::beginCard(const char* id, const CardLayout& layout) {
float width = layout.width > 0 ? layout.width : ImGui::GetContentRegionAvail().x;
ImGui::PushID(id);
// Card background
ImGui::PushStyleColor(ImGuiCol_ChildBg, SurfaceVec4(layout.elevation));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, layout.cornerRadius);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(layout.padding, layout.padding));
ImVec2 size(width, layout.minHeight > 0 ? layout.minHeight : 0);
bool visible = ImGui::BeginChild(id, size, ImGuiChildFlags_AutoResizeY);
return visible;
}
inline void AppLayout::endCard() {
ImGui::EndChild();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::PopID();
// Add spacing after card
VSpace(2);
}
// ============================================================================
// Convenience Function
// ============================================================================
/**
* @brief Get the app layout instance
*/
inline AppLayout& Layout() {
return AppLayout::instance();
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,516 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "color_theme.h"
#include "../schema/ui_schema.h"
#include <algorithm>
#include <cmath>
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Color Utility Implementations
// ============================================================================
ImU32 BlendOverlay(ImU32 base, ImU32 overlay, float overlayOpacity)
{
// Extract components
float baseR = ((base >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
float baseG = ((base >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
float baseB = ((base >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
float baseA = ((base >> IM_COL32_A_SHIFT) & 0xFF) / 255.0f;
float overlayR = ((overlay >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
float overlayG = ((overlay >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
float overlayB = ((overlay >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
// Blend
float resultR = baseR + (overlayR - baseR) * overlayOpacity;
float resultG = baseG + (overlayG - baseG) * overlayOpacity;
float resultB = baseB + (overlayB - baseB) * overlayOpacity;
return IM_COL32(
static_cast<uint8_t>(resultR * 255.0f),
static_cast<uint8_t>(resultG * 255.0f),
static_cast<uint8_t>(resultB * 255.0f),
static_cast<uint8_t>(baseA * 255.0f)
);
}
ImU32 Lighten(ImU32 color, float amount)
{
return BlendOverlay(color, IM_COL32(255, 255, 255, 255), amount);
}
ImU32 Darken(ImU32 color, float amount)
{
return BlendOverlay(color, IM_COL32(0, 0, 0, 255), amount);
}
// ============================================================================
// Elevation System
// ============================================================================
// Material Design elevation overlay values for dark theme
// These values lighten the surface to indicate elevation
static const float kElevationOverlays[] = {
0.00f, // 0dp
0.05f, // 1dp
0.07f, // 2dp
0.08f, // 3dp
0.09f, // 4dp
0.10f, // 5dp
0.11f, // 6dp
0.115f, // 7dp
0.12f, // 8dp
0.125f, // 9dp
0.13f, // 10dp
0.135f, // 11dp
0.14f, // 12dp
0.14f, // 13dp
0.14f, // 14dp
0.14f, // 15dp
0.15f, // 16dp
0.15f, // 17dp
0.15f, // 18dp
0.15f, // 19dp
0.15f, // 20dp
0.15f, // 21dp
0.15f, // 22dp
0.15f, // 23dp
0.16f // 24dp
};
float GetElevationOverlay(Elevation level)
{
return GetElevationOverlay(static_cast<int>(level));
}
float GetElevationOverlay(int dp)
{
if (dp < 0) return 0.0f;
if (dp > 24) dp = 24;
return kElevationOverlays[dp];
}
ImU32 GetElevatedSurface(const ColorTheme& theme, Elevation level)
{
return GetElevatedSurface(theme, static_cast<int>(level));
}
ImU32 GetElevatedSurface(const ColorTheme& theme, int dp)
{
float overlay = GetElevationOverlay(dp);
if (overlay <= 0.0f) {
return theme.surface;
}
return BlendOverlay(theme.surface, IM_COL32(255, 255, 255, 255), overlay);
}
// ============================================================================
// Theme State
// ============================================================================
static ColorTheme s_currentTheme;
static bool s_themeInitialized = false;
static bool s_isDarkTheme = true;
static bool s_backdropActive = false;
// ============================================================================
// DragonX Theme (Dark with Red Primary)
// ============================================================================
ColorTheme GetDragonXColorTheme()
{
ColorTheme theme;
// Primary: DragonX Red
theme.primary = Hex(0xF64740); // Bright red accent
theme.primaryVariant = Hex(0x6F1A07); // Dark maroon variant
theme.primaryLight = Hex(0xF87A75); // Lighter red for highlights
// Secondary: Blue-gray from text palette
theme.secondary = Hex(0xBFD1E5); // Light blue-gray
theme.secondaryVariant = Hex(0x8BA3BD); // Mid blue-gray
theme.secondaryLight = Hex(0xDAE5F0); // Pale blue-gray
// Surfaces (Dark navy palette)
theme.background = Hex(0x121420); // Deep navy background
theme.surface = Hex(0x1B2432); // Dark navy surface
theme.surfaceVariant = Hex(0x253345); // Lighter navy variant
// "On" colors — blue-gray text tones
theme.onPrimary = Hex(0xFFFFFF); // White on red
theme.onSecondary = Hex(0x121420); // Navy on blue-gray
theme.onBackground = Hex(0xE2EDF8); // Bright blue-gray on dark
theme.onSurface = Hex(0xE2EDF8); // Bright blue-gray on surface
theme.onSurfaceMedium = HexA(0xE2EDF8, 179); // 70%
theme.onSurfaceDisabled = HexA(0xE2EDF8, 97); // 38%
// Error
theme.error = Hex(0xF64740); // Primary red
theme.onError = Hex(0xFFFFFF); // White on error
// Success/Warning (follow DragonX palette)
theme.success = Hex(0xBFD1E5); // Blue-gray secondary
theme.onSuccess = Hex(0x121420); // Dark on success
theme.warning = Hex(0xF64740); // Primary red
theme.onWarning = Hex(0xFFFFFF); // White on warning
// State overlays (blue-gray tinted for dark navy theme)
theme.stateHover = HexA(0xBFD1E5, 10); // 4%
theme.stateFocus = HexA(0xBFD1E5, 31); // 12%
theme.statePressed = HexA(0xBFD1E5, 25); // 10%
theme.stateSelected = HexA(0xBFD1E5, 20); // 8%
theme.stateDragged = HexA(0xBFD1E5, 20); // 8%
// UI elements
theme.divider = HexA(0xBFD1E5, 31); // 12% blue-gray
theme.outline = HexA(0xBFD1E5, 31); // 12% blue-gray
theme.scrim = HexA(0x000000, 128); // 50% black
return theme;
}
// ============================================================================
// Material Dark Theme
// ============================================================================
ColorTheme GetMaterialDarkTheme()
{
ColorTheme theme;
// Primary: Deep Purple (Material baseline)
theme.primary = Hex(0xBB86FC); // Purple 200
theme.primaryVariant = Hex(0x3700B3); // Purple 700
theme.primaryLight = Hex(0xE1BEE7); // Purple 100
// Secondary: Teal
theme.secondary = Hex(0x03DAC6); // Teal 200
theme.secondaryVariant = Hex(0x018786); // Teal 700
theme.secondaryLight = Hex(0x64FFDA); // Teal A200
// Surfaces
theme.background = Hex(0x121212);
theme.surface = Hex(0x1E1E1E);
theme.surfaceVariant = Hex(0x2D2D2D);
// "On" colors
theme.onPrimary = Hex(0x000000);
theme.onSecondary = Hex(0x000000);
theme.onBackground = Hex(0xFFFFFF);
theme.onSurface = Hex(0xFFFFFF);
theme.onSurfaceMedium = HexA(0xFFFFFF, 179);
theme.onSurfaceDisabled = HexA(0xFFFFFF, 97);
// Error
theme.error = Hex(0xF64740);
theme.onError = Hex(0xFFFFFF);
// Success/Warning (follow DragonX palette)
theme.success = Hex(0xBFD1E5);
theme.onSuccess = Hex(0x121420);
theme.warning = Hex(0xF64740);
theme.onWarning = Hex(0xFFFFFF);
// State overlays
theme.stateHover = HexA(0xFFFFFF, 10);
theme.stateFocus = HexA(0xFFFFFF, 31);
theme.statePressed = HexA(0xFFFFFF, 25);
theme.stateSelected = HexA(0xFFFFFF, 20);
theme.stateDragged = HexA(0xFFFFFF, 20);
// UI elements
theme.divider = HexA(0xFFFFFF, 31);
theme.outline = HexA(0xFFFFFF, 31);
theme.scrim = HexA(0x000000, 128);
return theme;
}
// ============================================================================
// Material Light Theme
// ============================================================================
ColorTheme GetMaterialLightTheme()
{
ColorTheme theme;
// Primary: Deep Purple
theme.primary = Hex(0x6200EE); // Purple 500
theme.primaryVariant = Hex(0x3700B3); // Purple 700
theme.primaryLight = Hex(0xBB86FC); // Purple 200
// Secondary: Teal
theme.secondary = Hex(0x03DAC6); // Teal 200
theme.secondaryVariant = Hex(0x018786); // Teal 700
theme.secondaryLight = Hex(0x64FFDA);
// Surfaces (light)
theme.background = Hex(0xFFFFFF);
theme.surface = Hex(0xFFFFFF);
theme.surfaceVariant = Hex(0xF5F5F5);
// "On" colors
theme.onPrimary = Hex(0xFFFFFF);
theme.onSecondary = Hex(0x000000);
theme.onBackground = Hex(0x000000);
theme.onSurface = Hex(0x000000);
theme.onSurfaceMedium = HexA(0x000000, 153); // 60%
theme.onSurfaceDisabled = HexA(0x000000, 97); // 38%
// Error
theme.error = Hex(0xF64740);
theme.onError = Hex(0xFFFFFF);
// Success/Warning (follow DragonX palette)
theme.success = Hex(0xBFD1E5); // Blue-gray
theme.onSuccess = Hex(0x121420);
theme.warning = Hex(0xF64740); // Primary red
theme.onWarning = Hex(0xFFFFFF);
// State overlays (black for light theme)
theme.stateHover = HexA(0x000000, 10);
theme.stateFocus = HexA(0x000000, 31);
theme.statePressed = HexA(0x000000, 25);
theme.stateSelected = HexA(0x000000, 20);
theme.stateDragged = HexA(0x000000, 20);
// UI elements
theme.divider = HexA(0x000000, 31);
theme.outline = HexA(0x000000, 31);
theme.scrim = HexA(0x000000, 128);
return theme;
}
// ============================================================================
// Theme Management
// ============================================================================
const ColorTheme& GetCurrentColorTheme()
{
if (!s_themeInitialized) {
s_currentTheme = GetDragonXColorTheme();
s_themeInitialized = true;
s_isDarkTheme = true;
}
return s_currentTheme;
}
void SetCurrentColorTheme(const ColorTheme& theme)
{
s_currentTheme = theme;
s_themeInitialized = true;
// Detect if dark theme based on background luminance
float bgR = ((theme.background >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
float bgG = ((theme.background >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
float bgB = ((theme.background >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
float luminance = 0.299f * bgR + 0.587f * bgG + 0.114f * bgB;
s_isDarkTheme = (luminance < 0.5f);
}
bool IsDarkTheme()
{
GetCurrentColorTheme(); // Ensure initialized
return s_isDarkTheme;
}
void SetBackdropActive(bool active)
{
s_backdropActive = active;
}
bool IsBackdropActive()
{
return s_backdropActive;
}
// ============================================================================
// Apply Theme to ImGui
// ============================================================================
void ApplyColorThemeToImGui(const ColorTheme& theme)
{
SetCurrentColorTheme(theme);
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
const auto& S = schema::UI();
// Helper to convert ImU32 to ImVec4
auto toVec4 = [](ImU32 col) -> ImVec4 {
return ImGui::ColorConvertU32ToFloat4(col);
};
// Backdrop transparency: when DWM Acrylic is active, make backgrounds
// semi-transparent so the blurred + noisy Acrylic material shows through.
// A dark blue gradient is drawn on the background draw list (in app.cpp)
// for a stylish tinted look while retaining blur + noise.
auto bdElem = [&](const char* key, float fb) {
float v = S.drawElement("backdrop", key).size;
return v >= 0 ? v : fb;
};
const float bgAlpha = s_backdropActive ? bdElem("background-alpha", 0.40f) : 1.0f;
// When acrylic is active, ChildBg must be fully transparent so the
// sharp background doesn't bleed through behind acrylic cards.
// DrawGlassPanel draws the blurred card fill directly.
const float surfAlpha = s_backdropActive ? 0.0f : 1.0f;
const float tabAlpha = s_backdropActive ? 0.75f : 1.0f;
// Background colors
colors[ImGuiCol_WindowBg] = toVec4(WithAlphaF(theme.background, bgAlpha));
colors[ImGuiCol_ChildBg] = toVec4(WithAlphaF(GetElevatedSurface(theme, 1), surfAlpha));
colors[ImGuiCol_PopupBg] = toVec4(WithAlphaF(GetElevatedSurface(theme, 8), 0.95f));
// Borders — dark themes use white outlines; light themes use dark outlines
if (s_isDarkTheme) {
colors[ImGuiCol_Border] = ImVec4(1.0f, 1.0f, 1.0f, 0.15f);
colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0.08f);
} else {
colors[ImGuiCol_Border] = ImVec4(0, 0, 0, 0.12f);
colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0.04f);
}
// Frame backgrounds (inputs, checkboxes) — glass-card in dark, subtle grey in light
if (s_isDarkTheme) {
colors[ImGuiCol_FrameBg] = ImVec4(1.0f, 1.0f, 1.0f, 0.07f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(1.0f, 1.0f, 1.0f, 0.10f);
colors[ImGuiCol_FrameBgActive] = ImVec4(1.0f, 1.0f, 1.0f, 0.15f);
} else {
colors[ImGuiCol_FrameBg] = ImVec4(0, 0, 0, 0.04f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0, 0, 0, 0.07f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0, 0, 0, 0.10f);
}
// Title bar
colors[ImGuiCol_TitleBg] = toVec4(WithAlphaF(GetElevatedSurface(theme, 4), surfAlpha));
colors[ImGuiCol_TitleBgActive] = toVec4(theme.primary);
colors[ImGuiCol_TitleBgCollapsed] = toVec4(WithAlphaF(GetElevatedSurface(theme, 4), 0.75f));
// Menu bar — fully transparent, no background
colors[ImGuiCol_MenuBarBg] = ImVec4(0, 0, 0, 0);
// Scrollbar — minimal style (dark overlays for light, white overlays for dark)
colors[ImGuiCol_ScrollbarBg] = ImVec4(0, 0, 0, 0);
if (s_isDarkTheme) {
colors[ImGuiCol_ScrollbarGrab] = ImVec4(1.0f, 1.0f, 1.0f, 15.0f / 255.0f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(1.0f, 1.0f, 1.0f, 30.0f / 255.0f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(1.0f, 1.0f, 1.0f, 45.0f / 255.0f);
} else {
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0, 0, 0, 0.10f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0, 0, 0, 0.18f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0, 0, 0, 0.25f);
}
// Checkmarks, sliders
colors[ImGuiCol_CheckMark] = toVec4(theme.primary);
colors[ImGuiCol_SliderGrab] = toVec4(theme.primary);
colors[ImGuiCol_SliderGrabActive] = toVec4(theme.primaryLight);
// Buttons — glass-style overlays (white on dark, dark on light)
if (s_isDarkTheme) {
colors[ImGuiCol_Button] = ImVec4(1.0f, 1.0f, 1.0f, 15.0f / 255.0f);
colors[ImGuiCol_ButtonHovered] = ImVec4(1.0f, 1.0f, 1.0f, 30.0f / 255.0f);
colors[ImGuiCol_ButtonActive] = ImVec4(1.0f, 1.0f, 1.0f, 45.0f / 255.0f);
} else {
colors[ImGuiCol_Button] = ImVec4(0, 0, 0, 0.05f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0, 0, 0, 0.08f);
colors[ImGuiCol_ButtonActive] = ImVec4(0, 0, 0, 0.12f);
}
// Headers (collapsing headers, tree nodes, selectable, menu items)
colors[ImGuiCol_Header] = toVec4(WithAlphaF(theme.primary, 0.6f));
colors[ImGuiCol_HeaderHovered] = toVec4(WithAlphaF(theme.primary, 0.8f));
colors[ImGuiCol_HeaderActive] = toVec4(theme.primary);
// Separator
colors[ImGuiCol_Separator] = toVec4(theme.divider);
colors[ImGuiCol_SeparatorHovered] = toVec4(theme.primaryLight);
colors[ImGuiCol_SeparatorActive] = toVec4(theme.primary);
// Resize grip
colors[ImGuiCol_ResizeGrip] = toVec4(WithAlphaF(theme.primary, 0.25f));
colors[ImGuiCol_ResizeGripHovered] = toVec4(WithAlphaF(theme.primary, 0.67f));
colors[ImGuiCol_ResizeGripActive] = toVec4(WithAlphaF(theme.primary, 0.95f));
// Tabs
ImU32 tabBg = GetElevatedSurface(theme, 4);
colors[ImGuiCol_Tab] = toVec4(WithAlphaF(tabBg, tabAlpha));
colors[ImGuiCol_TabHovered] = toVec4(WithAlphaF(Lighten(theme.primary, 0.15f), tabAlpha));
colors[ImGuiCol_TabSelected] = toVec4(WithAlphaF(theme.primary, tabAlpha));
colors[ImGuiCol_TabSelectedOverline] = toVec4(theme.secondary);
colors[ImGuiCol_TabDimmed] = toVec4(WithAlphaF(Darken(tabBg, 0.2f), tabAlpha));
colors[ImGuiCol_TabDimmedSelected] = toVec4(WithAlphaF(theme.primary, tabAlpha * 0.7f));
// Plot colors
colors[ImGuiCol_PlotLines] = toVec4(theme.primary);
colors[ImGuiCol_PlotLinesHovered] = toVec4(theme.secondary);
colors[ImGuiCol_PlotHistogram] = toVec4(theme.primary);
colors[ImGuiCol_PlotHistogramHovered] = toVec4(theme.secondary);
// Tables
colors[ImGuiCol_TableHeaderBg] = toVec4(WithAlphaF(GetElevatedSurface(theme, 2), surfAlpha));
colors[ImGuiCol_TableBorderStrong] = toVec4(theme.outline);
colors[ImGuiCol_TableBorderLight] = toVec4(theme.divider);
colors[ImGuiCol_TableRowBg] = ImVec4(0, 0, 0, 0);
colors[ImGuiCol_TableRowBgAlt] = toVec4(WithAlphaF(theme.onSurface, 0.03f));
// Text
colors[ImGuiCol_Text] = toVec4(theme.onSurface);
colors[ImGuiCol_TextDisabled] = toVec4(theme.onSurfaceDisabled);
colors[ImGuiCol_TextSelectedBg] = toVec4(WithAlphaF(theme.primary, 0.43f));
// Drag/drop
colors[ImGuiCol_DragDropTarget] = toVec4(WithAlphaF(theme.secondary, 0.9f));
// Navigation highlight
colors[ImGuiCol_NavCursor] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
colors[ImGuiCol_NavWindowingHighlight]= ImVec4(1, 1, 1, 0.7f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.8f, 0.8f, 0.8f, 0.2f);
// Modal window dim
colors[ImGuiCol_ModalWindowDimBg] = toVec4(theme.scrim);
// Style adjustments (from UISchema)
auto brElem = S.drawElement("style", "border-radius");
style.WindowRounding = brElem.extraFloats.count("window") ? brElem.extraFloats.at("window") : 6.0f;
style.ChildRounding = brElem.extraFloats.count("child") ? brElem.extraFloats.at("child") : 4.0f;
style.FrameRounding = brElem.extraFloats.count("frame") ? brElem.extraFloats.at("frame") : 4.0f;
style.PopupRounding = brElem.extraFloats.count("popup") ? brElem.extraFloats.at("popup") : 4.0f;
style.ScrollbarRounding = brElem.extraFloats.count("scrollbar") ? brElem.extraFloats.at("scrollbar") : 4.0f;
style.GrabRounding = brElem.extraFloats.count("grab") ? brElem.extraFloats.at("grab") : 4.0f;
style.TabRounding = brElem.extraFloats.count("tab") ? brElem.extraFloats.at("tab") : 4.0f;
style.WindowTitleAlign = ImVec2(0.0f, 0.5f); // Left-aligned titles (Material)
auto sElem = [&](const char* key, float fb) {
float v = S.drawElement("style", key).size;
return v >= 0 ? v : fb;
};
style.WindowPadding = ImVec2(sElem("window-padding-x", 10.0f), sElem("window-padding-y", 10.0f));
style.FramePadding = ImVec2(sElem("frame-padding-x", 8.0f), sElem("frame-padding-y", 4.0f));
style.ItemSpacing = ImVec2(sElem("item-spacing-x", 8.0f), sElem("item-spacing-y", 6.0f));
style.ItemInnerSpacing = ImVec2(sElem("item-inner-spacing-x", 6.0f), sElem("item-inner-spacing-y", 4.0f));
style.IndentSpacing = sElem("indent-spacing", 20.0f);
style.ScrollbarSize = sElem("scrollbar-size", 6.0f);
style.GrabMinSize = sElem("grab-min-size", 8.0f);
auto bwElem = S.drawElement("style", "border-width");
style.WindowBorderSize = bwElem.extraFloats.count("window") ? bwElem.extraFloats.at("window") : 1.0f;
style.ChildBorderSize = bwElem.extraFloats.count("child") ? bwElem.extraFloats.at("child") : 1.0f;
style.PopupBorderSize = bwElem.extraFloats.count("popup") ? bwElem.extraFloats.at("popup") : 1.0f;
style.FrameBorderSize = bwElem.extraFloats.count("frame") ? bwElem.extraFloats.at("frame") : 0.0f;
style.TabBorderSize = 0.0f;
style.AntiAliasedLines = true;
style.AntiAliasedFill = true;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,284 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// Material Design 2 Color Theme System
// Based on https://m2.material.io/design/color/the-color-system.html
#pragma once
#include "imgui.h"
#include <cstdint>
// Windows defines RGB as a macro - undefine it to avoid conflicts
#ifdef RGB
#undef RGB
#endif
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Color Utilities
// ============================================================================
/**
* @brief Convert ImU32 to ImVec4
*/
inline ImVec4 ColorU32ToVec4(ImU32 color) {
return ImGui::ColorConvertU32ToFloat4(color);
}
/**
* @brief Convert ImVec4 to ImU32
*/
inline ImU32 ColorVec4ToU32(const ImVec4& color) {
return ImGui::ColorConvertFloat4ToU32(color);
}
/**
* @brief Create color from RGB values (0-255)
*/
constexpr ImU32 MakeRGB(uint8_t r, uint8_t g, uint8_t b) {
return IM_COL32(r, g, b, 255);
}
/**
* @brief Create color from RGBA values (0-255)
*/
constexpr ImU32 MakeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return IM_COL32(r, g, b, a);
}
/**
* @brief Create color from hex value (0xRRGGBB)
*/
constexpr ImU32 Hex(uint32_t hex) {
return IM_COL32(
(hex >> 16) & 0xFF,
(hex >> 8) & 0xFF,
hex & 0xFF,
255
);
}
/**
* @brief Create color from hex value with alpha (0xRRGGBB, alpha 0-255)
*/
constexpr ImU32 HexA(uint32_t hex, uint8_t alpha) {
return IM_COL32(
(hex >> 16) & 0xFF,
(hex >> 8) & 0xFF,
hex & 0xFF,
alpha
);
}
/**
* @brief Apply alpha to existing color
*/
inline ImU32 WithAlpha(ImU32 color, uint8_t alpha) {
return (color & 0x00FFFFFF) | (static_cast<ImU32>(alpha) << 24);
}
/**
* @brief Apply alpha percentage (0.0 - 1.0) to existing color
*/
inline ImU32 WithAlphaF(ImU32 color, float alpha) {
return WithAlpha(color, static_cast<uint8_t>(alpha * 255.0f));
}
/**
* @brief Blend two colors with overlay opacity
*/
ImU32 BlendOverlay(ImU32 base, ImU32 overlay, float overlayOpacity);
/**
* @brief Lighten a color by percentage (0.0 - 1.0)
*/
ImU32 Lighten(ImU32 color, float amount);
/**
* @brief Darken a color by percentage (0.0 - 1.0)
*/
ImU32 Darken(ImU32 color, float amount);
// ============================================================================
// Material Design Color Theme
// ============================================================================
/**
* @brief Complete Material Design 2 color theme
*
* Based on the Material Design color system with:
* - Primary and secondary brand colors
* - Surface and background colors
* - "On" colors for content on surfaces
* - State overlay colors
* - Error colors
*/
struct ColorTheme {
// ========================================================================
// Brand Colors - Primary
// ========================================================================
ImU32 primary; // Main brand color (displayed most frequently)
ImU32 primaryVariant; // Darker variant for contrast
ImU32 primaryLight; // Lighter variant for highlights
// ========================================================================
// Brand Colors - Secondary (Accent)
// ========================================================================
ImU32 secondary; // Accent color for emphasis
ImU32 secondaryVariant; // Darker variant
ImU32 secondaryLight; // Lighter variant
// ========================================================================
// Surface Colors
// ========================================================================
ImU32 background; // App background
ImU32 surface; // Card/dialog surfaces (elevation 0dp)
ImU32 surfaceVariant; // Alternative surface
// ========================================================================
// "On" Colors - Content colors for surfaces
// ========================================================================
ImU32 onPrimary; // Text/icons on primary color
ImU32 onSecondary; // Text/icons on secondary color
ImU32 onBackground; // Text/icons on background
ImU32 onSurface; // Text/icons on surface (high emphasis)
ImU32 onSurfaceMedium; // Medium emphasis text (70% opacity)
ImU32 onSurfaceDisabled; // Disabled text (38% opacity)
// ========================================================================
// Error Colors
// ========================================================================
ImU32 error; // Error indication
ImU32 onError; // Text on error color
// ========================================================================
// Success/Warning Colors (Material extension)
// ========================================================================
ImU32 success; // Success indication
ImU32 onSuccess; // Text on success
ImU32 warning; // Warning indication
ImU32 onWarning; // Text on warning
// ========================================================================
// State Overlay Colors (applied to surfaces)
// ========================================================================
ImU32 stateHover; // Hover overlay (4% white/black)
ImU32 stateFocus; // Focus overlay (12%)
ImU32 statePressed; // Pressed overlay (10%)
ImU32 stateSelected; // Selected overlay (8%)
ImU32 stateDragged; // Dragged overlay (8%)
// ========================================================================
// Additional UI Colors
// ========================================================================
ImU32 divider; // Divider lines
ImU32 outline; // Outline/border color
ImU32 scrim; // Modal overlay background
};
// ============================================================================
// Elevation System (Dark Theme)
// ============================================================================
/**
* @brief Material Design elevation levels
*
* In dark theme, elevation is shown through surface color lightening
* rather than shadows.
*/
enum class Elevation {
Dp0 = 0, // 0dp - Background
Dp1 = 1, // 1dp - Cards at rest, Search bar
Dp2 = 2, // 2dp - Buttons at rest
Dp3 = 3, // 3dp - Refresh indicator
Dp4 = 4, // 4dp - App bar
Dp6 = 6, // 6dp - FAB, Snackbar
Dp8 = 8, // 8dp - Bottom sheet, Menu, Cards picked up
Dp12 = 12, // 12dp - FAB pressed
Dp16 = 16, // 16dp - Navigation drawer, Modal sheets
Dp24 = 24 // 24dp - Dialog
};
/**
* @brief Get overlay opacity for elevation level (dark theme)
*
* Material Design uses white overlay on dark surfaces to indicate elevation.
* Higher elevation = more white overlay = lighter surface.
*/
float GetElevationOverlay(Elevation level);
float GetElevationOverlay(int dp);
/**
* @brief Calculate surface color for given elevation
*
* Applies white overlay to base surface color based on elevation.
*/
ImU32 GetElevatedSurface(const ColorTheme& theme, Elevation level);
ImU32 GetElevatedSurface(const ColorTheme& theme, int dp);
// ============================================================================
// Predefined Themes
// ============================================================================
/**
* @brief DragonX branded dark theme with red primary color
*/
ColorTheme GetDragonXColorTheme();
/**
* @brief Standard Material dark theme
*/
ColorTheme GetMaterialDarkTheme();
/**
* @brief Standard Material light theme
*/
ColorTheme GetMaterialLightTheme();
// ============================================================================
// Theme Management
// ============================================================================
/**
* @brief Get current active color theme
*/
const ColorTheme& GetCurrentColorTheme();
/**
* @brief Set current color theme
*/
void SetCurrentColorTheme(const ColorTheme& theme);
/**
* @brief Check if current theme is dark mode
*/
bool IsDarkTheme();
/**
* @brief Set whether OS-level backdrop (DWM Mica/Acrylic) is active
*
* When active, background and surface colors become semi-transparent
* so the compositor's blur effect shows through.
*/
void SetBackdropActive(bool active);
/**
* @brief Check if OS-level backdrop is active
*/
bool IsBackdropActive();
/**
* @brief Apply Material Design color theme to ImGui
*
* This updates ImGui's color palette to use the Material theme colors.
*/
void ApplyColorThemeToImGui(const ColorTheme& theme);
} // namespace material
} // namespace ui
} // namespace dragonx

201
src/ui/material/colors.h Normal file
View File

@@ -0,0 +1,201 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// ============================================================================
// Material Design Color Tokens
// ============================================================================
//
// This header provides convenient access to Material Design color tokens.
// Use these instead of hardcoded colors to maintain theme consistency.
//
// Usage:
// #include "ui/material/colors.h"
//
// // Get surface colors at different elevations
// ImU32 cardBg = dragonx::ui::material::Surface(4); // 4dp elevation
//
// // Get state colors
// ImU32 hovered = dragonx::ui::material::Primary() | dragonx::ui::material::StateHover();
//
// // Use in ImGui
// ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(cardBg));
#pragma once
#include "color_theme.h"
#include "../schema/ui_schema.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Color Token Accessors (Inline for performance)
// ============================================================================
// Primary colors
inline ImU32 Primary() { return GetCurrentColorTheme().primary; }
inline ImU32 PrimaryVariant() { return GetCurrentColorTheme().primaryVariant; }
inline ImU32 PrimaryLight() { return GetCurrentColorTheme().primaryLight; }
// Secondary colors
inline ImU32 Secondary() { return GetCurrentColorTheme().secondary; }
inline ImU32 SecondaryVariant() { return GetCurrentColorTheme().secondaryVariant; }
inline ImU32 SecondaryLight() { return GetCurrentColorTheme().secondaryLight; }
// Surface colors (semi-transparent when OS backdrop is active)
inline ImU32 Background() {
ImU32 bg = GetCurrentColorTheme().background;
if (!IsBackdropActive()) return bg;
float alpha = dragonx::ui::schema::UI().drawElement("backdrop", "background-inline-alpha").size;
if (alpha < 0) alpha = 0.30f;
return WithAlphaF(bg, alpha);
}
inline ImU32 Surface() {
ImU32 s = GetCurrentColorTheme().surface;
if (!IsBackdropActive()) return s;
float alpha = dragonx::ui::schema::UI().drawElement("backdrop", "surface-inline-alpha").size;
if (alpha < 0) alpha = 0.55f;
return WithAlphaF(s, alpha);
}
inline ImU32 SurfaceVariant() { return GetCurrentColorTheme().surfaceVariant; }
// Elevated surfaces - use for cards, dialogs, menus
inline ImU32 Surface(int dp) {
ImU32 s = GetElevatedSurface(GetCurrentColorTheme(), dp);
if (!IsBackdropActive()) return s;
float alpha = dragonx::ui::schema::UI().drawElement("backdrop", "surface-inline-alpha").size;
if (alpha < 0) alpha = 0.55f;
return WithAlphaF(s, alpha);
}
inline ImU32 SurfaceAt(Elevation level) { return GetElevatedSurface(GetCurrentColorTheme(), level); }
// "On" colors (text on various backgrounds)
inline ImU32 OnPrimary() { return GetCurrentColorTheme().onPrimary; }
inline ImU32 OnSecondary() { return GetCurrentColorTheme().onSecondary; }
inline ImU32 OnBackground() { return GetCurrentColorTheme().onBackground; }
inline ImU32 OnSurface() { return GetCurrentColorTheme().onSurface; }
inline ImU32 OnSurfaceMedium() { return GetCurrentColorTheme().onSurfaceMedium; }
inline ImU32 OnSurfaceDisabled() { return GetCurrentColorTheme().onSurfaceDisabled; }
// Semantic colors
inline ImU32 Error() { return GetCurrentColorTheme().error; }
inline ImU32 OnError() { return GetCurrentColorTheme().onError; }
inline ImU32 Success() { return GetCurrentColorTheme().success; }
inline ImU32 OnSuccess() { return GetCurrentColorTheme().onSuccess; }
inline ImU32 Warning() { return GetCurrentColorTheme().warning; }
inline ImU32 OnWarning() { return GetCurrentColorTheme().onWarning; }
// State overlay colors (use with BlendOverlay)
inline ImU32 StateHover() { return GetCurrentColorTheme().stateHover; }
inline ImU32 StateFocus() { return GetCurrentColorTheme().stateFocus; }
inline ImU32 StatePressed() { return GetCurrentColorTheme().statePressed; }
inline ImU32 StateSelected() { return GetCurrentColorTheme().stateSelected; }
inline ImU32 StateDragged() { return GetCurrentColorTheme().stateDragged; }
// UI structure
inline ImU32 Divider() { return GetCurrentColorTheme().divider; }
inline ImU32 Outline() { return GetCurrentColorTheme().outline; }
inline ImU32 Scrim() { return GetCurrentColorTheme().scrim; }
// ============================================================================
// ImVec4 Variants (for direct use with ImGui style colors)
// ============================================================================
inline ImVec4 PrimaryVec4() { return ImGui::ColorConvertU32ToFloat4(Primary()); }
inline ImVec4 SecondaryVec4() { return ImGui::ColorConvertU32ToFloat4(Secondary()); }
inline ImVec4 SurfaceVec4() { return ImGui::ColorConvertU32ToFloat4(Surface()); }
inline ImVec4 SurfaceVec4(int dp) { return ImGui::ColorConvertU32ToFloat4(Surface(dp)); }
inline ImVec4 OnSurfaceVec4() { return ImGui::ColorConvertU32ToFloat4(OnSurface()); }
inline ImVec4 ErrorVec4() { return ImGui::ColorConvertU32ToFloat4(Error()); }
inline ImVec4 SuccessVec4() { return ImGui::ColorConvertU32ToFloat4(Success()); }
inline ImVec4 WarningVec4() { return ImGui::ColorConvertU32ToFloat4(Warning()); }
// ============================================================================
// Convenience Functions for Common Patterns
// ============================================================================
/**
* @brief Get color with applied state overlay
*
* @param base Base color
* @param state State overlay (StateHover, StateFocus, etc.)
* @return Color with state applied
*
* Usage:
* ImU32 hoveredButton = WithState(Primary(), StateHover());
*/
inline ImU32 WithState(ImU32 base, ImU32 state)
{
return BlendOverlay(base, state, 1.0f);
}
/**
* @brief Get button color for current state
*
* @param isHovered Button is hovered
* @param isActive Button is pressed
* @return Appropriate button color
*/
inline ImU32 ButtonColor(bool isHovered, bool isActive)
{
if (isActive) return WithState(Primary(), StatePressed());
if (isHovered) return WithState(Primary(), StateHover());
return Primary();
}
/**
* @brief Get surface color for card at specified elevation
*
* @param elevation Elevation in dp (0-24)
* @return Surface color with elevation overlay
*/
inline ImU32 CardSurface(int elevation = 2)
{
return Surface(elevation);
}
/**
* @brief Get surface color for dialog/modal
*
* @return Surface color at 24dp elevation
*/
inline ImU32 DialogSurface()
{
return SurfaceAt(Elevation::Dp24);
}
/**
* @brief Get surface color for menu/popup
*
* @return Surface color at 8dp elevation
*/
inline ImU32 MenuSurface()
{
return SurfaceAt(Elevation::Dp8);
}
/**
* @brief Get surface color for app bar/toolbar
*
* @return Surface color at 4dp elevation
*/
inline ImU32 AppBarSurface()
{
return SurfaceAt(Elevation::Dp4);
}
/**
* @brief Get surface color for nav drawer
*
* @return Surface color at 16dp elevation
*/
inline ImU32 NavDrawerSurface()
{
return SurfaceAt(Elevation::Dp16);
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,330 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "buttons.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design App Bar Component
// ============================================================================
// Based on https://m2.material.io/components/app-bars-top
//
// The top app bar displays information and actions relating to the current
// screen.
enum class AppBarType {
Regular, // Standard height (56/64dp)
Prominent, // Extended height for larger titles
Dense // Smaller height for desktop
};
/**
* @brief App bar configuration
*/
struct AppBarSpec {
AppBarType type = AppBarType::Regular;
ImU32 backgroundColor = 0; // 0 = use elevated surface
bool elevated = true; // Show elevation
bool centerTitle = false; // Center title (default: left)
float elevation = 4.0f; // Elevation in dp
};
/**
* @brief Begin a top app bar
*
* @param id Unique identifier
* @param spec App bar configuration
* @return true if app bar is visible
*/
bool BeginAppBar(const char* id, const AppBarSpec& spec = AppBarSpec());
/**
* @brief End app bar
*/
void EndAppBar();
/**
* @brief Set app bar navigation icon (left side)
*
* @param icon Icon text (e.g., "☰" for menu)
* @param tooltip Optional tooltip
* @return true if clicked
*/
bool AppBarNavIcon(const char* icon, const char* tooltip = nullptr);
/**
* @brief Set app bar title
*/
void AppBarTitle(const char* title);
/**
* @brief Add app bar action button (right side)
*
* @param icon Icon text
* @param tooltip Optional tooltip
* @return true if clicked
*/
bool AppBarAction(const char* icon, const char* tooltip = nullptr);
/**
* @brief Begin app bar action menu (for overflow)
*/
bool BeginAppBarMenu(const char* icon);
/**
* @brief End app bar action menu
*/
void EndAppBarMenu();
/**
* @brief Add menu item to app bar menu
*/
bool AppBarMenuItem(const char* label, const char* icon = nullptr);
// ============================================================================
// Implementation
// ============================================================================
struct AppBarState {
ImVec2 barMin;
ImVec2 barMax;
float height;
float navIconWidth;
float actionsStartX;
float titleX;
bool hasNavIcon;
bool centerTitle;
ImU32 backgroundColor;
};
static AppBarState g_appBarState;
inline bool BeginAppBar(const char* id, const AppBarSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(id);
ImGuiIO& io = ImGui::GetIO();
// Calculate height based on type
float barHeight;
switch (spec.type) {
case AppBarType::Prominent:
barHeight = 128.0f;
break;
case AppBarType::Dense:
barHeight = 48.0f;
break;
default:
barHeight = size::AppBarHeight; // 56dp
break;
}
g_appBarState.height = barHeight;
g_appBarState.hasNavIcon = false;
g_appBarState.centerTitle = spec.centerTitle;
g_appBarState.navIconWidth = 0;
g_appBarState.actionsStartX = io.DisplaySize.x; // Will be adjusted as actions added
// Bar position (always at top)
g_appBarState.barMin = ImVec2(0, 0);
g_appBarState.barMax = ImVec2(io.DisplaySize.x, barHeight);
// Background color
if (spec.backgroundColor != 0) {
g_appBarState.backgroundColor = spec.backgroundColor;
} else {
g_appBarState.backgroundColor = Surface(Elevation::Dp4);
}
// Draw app bar background
ImDrawList* drawList = window->DrawList;
drawList->AddRectFilled(g_appBarState.barMin, g_appBarState.barMax, g_appBarState.backgroundColor);
// Bottom divider/shadow
if (spec.elevated) {
drawList->AddLine(
ImVec2(g_appBarState.barMin.x, g_appBarState.barMax.y),
ImVec2(g_appBarState.barMax.x, g_appBarState.barMax.y),
schema::UI().resolveColor("var(--app-bar-shadow)", IM_COL32(0, 0, 0, 50))
);
}
// Set up layout
g_appBarState.titleX = spacing::dp(2); // Default title position
return true;
}
inline void EndAppBar() {
ImGui::PopID();
}
inline bool AppBarNavIcon(const char* icon, const char* tooltip) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
g_appBarState.hasNavIcon = true;
g_appBarState.navIconWidth = size::TouchTarget;
// Position nav icon on left
float iconX = spacing::dp(0.5f); // 4dp left margin
float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f;
ImVec2 buttonPos(iconX, centerY - size::TouchTarget * 0.5f);
// Draw icon button
ImGui::SetCursorScreenPos(buttonPos);
bool clicked = IconButton(icon, tooltip);
// Update title position
g_appBarState.titleX = iconX + size::TouchTarget + spacing::dp(1);
return clicked;
}
inline void AppBarTitle(const char* title) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f;
// Calculate title position
float titleX;
if (g_appBarState.centerTitle) {
// Center title between nav icon and actions
float availableWidth = g_appBarState.actionsStartX - g_appBarState.titleX;
Typography::instance().pushFont(TypeStyle::H6);
float titleWidth = ImGui::CalcTextSize(title).x;
Typography::instance().popFont();
titleX = g_appBarState.titleX + (availableWidth - titleWidth) * 0.5f;
} else {
titleX = g_appBarState.titleX;
}
// Render title
Typography::instance().pushFont(TypeStyle::H6);
float titleY = centerY - ImGui::GetFontSize() * 0.5f;
ImDrawList* drawList = window->DrawList;
drawList->AddText(ImVec2(titleX, titleY), OnSurface(), title);
Typography::instance().popFont();
}
inline bool AppBarAction(const char* icon, const char* tooltip) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
// Actions are positioned from right edge
g_appBarState.actionsStartX -= size::TouchTarget;
float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f;
ImVec2 buttonPos(g_appBarState.actionsStartX, centerY - size::TouchTarget * 0.5f);
ImGui::SetCursorScreenPos(buttonPos);
return IconButton(icon, tooltip);
}
inline bool BeginAppBarMenu(const char* icon) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
// Position menu button
g_appBarState.actionsStartX -= size::TouchTarget;
float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f;
ImVec2 buttonPos(g_appBarState.actionsStartX, centerY - size::TouchTarget * 0.5f);
ImGui::SetCursorScreenPos(buttonPos);
bool menuOpen = false;
if (IconButton(icon, "More options")) {
ImGui::OpenPopup("##appbar_menu");
}
// Style the popup menu
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, spacing::dp(1)));
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, size::MenuCornerRadius);
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::ColorConvertU32ToFloat4(Surface(Elevation::Dp8)));
if (ImGui::BeginPopup("##appbar_menu")) {
menuOpen = true;
}
return menuOpen;
}
inline void EndAppBarMenu() {
ImGui::EndPopup();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
}
inline bool AppBarMenuItem(const char* label, const char* icon) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
const float itemHeight = size::ListItemHeight;
const float itemWidth = 200.0f; // Menu min width
ImVec2 pos = window->DC.CursorPos;
ImRect itemBB(pos, ImVec2(pos.x + itemWidth, pos.y + itemHeight));
ImGuiID id = window->GetID(label);
ImGui::ItemSize(itemBB);
if (!ImGui::ItemAdd(itemBB, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(itemBB, id, &hovered, &held);
// Draw
ImDrawList* drawList = window->DrawList;
if (hovered) {
drawList->AddRectFilled(itemBB.Min, itemBB.Max, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 10)));
}
float contentX = pos.x + spacing::dp(2);
float centerY = pos.y + itemHeight * 0.5f;
// Icon
if (icon) {
drawList->AddText(ImVec2(contentX, centerY - 12.0f), OnSurfaceMedium(), icon);
contentX += 24.0f + spacing::dp(2);
}
// Label
Typography::instance().pushFont(TypeStyle::Body1);
float labelY = centerY - ImGui::GetFontSize() * 0.5f;
drawList->AddText(ImVec2(contentX, labelY), OnSurface(), label);
Typography::instance().popFont();
if (pressed) {
ImGui::CloseCurrentPopup();
}
return pressed;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,351 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Button Components
// ============================================================================
// Based on https://m2.material.io/components/buttons
//
// Three button variants:
// - Text Button: Low emphasis, no container
// - Outlined Button: Medium emphasis, border only
// - Contained Button: High emphasis, filled background
enum class ButtonStyle {
Text, // Low emphasis - text only
Outlined, // Medium emphasis - border
Contained // High emphasis - filled (default)
};
/**
* @brief Button configuration
*/
struct ButtonSpec {
ButtonStyle style = ButtonStyle::Contained;
bool enabled = true;
bool fullWidth = false;
const char* icon = nullptr; // Leading icon (text glyph)
ImU32 color = 0; // 0 = use primary color
};
/**
* @brief Render a Material Design button
*
* @param label Button text (will be uppercased per Material spec)
* @param spec Button configuration
* @return true if clicked
*/
bool Button(const char* label, const ButtonSpec& spec = ButtonSpec());
/**
* @brief Render a text-only button (low emphasis)
*/
inline bool TextButton(const char* label, bool enabled = true) {
ButtonSpec spec;
spec.style = ButtonStyle::Text;
spec.enabled = enabled;
return Button(label, spec);
}
/**
* @brief Render an outlined button (medium emphasis)
*/
inline bool OutlinedButton(const char* label, bool enabled = true) {
ButtonSpec spec;
spec.style = ButtonStyle::Outlined;
spec.enabled = enabled;
return Button(label, spec);
}
/**
* @brief Render a contained/filled button (high emphasis)
*/
inline bool ContainedButton(const char* label, bool enabled = true) {
ButtonSpec spec;
spec.style = ButtonStyle::Contained;
spec.enabled = enabled;
return Button(label, spec);
}
/**
* @brief Render an icon button (circular touch target)
*
* @param icon Icon glyph/character
* @param tooltip Hover tooltip text
* @param enabled Whether button is enabled
* @return true if clicked
*/
bool IconButton(const char* icon, const char* tooltip = nullptr, bool enabled = true);
/**
* @brief Render a Floating Action Button (FAB)
*
* @param icon Icon glyph
* @param label Optional label for extended FAB
* @param mini Use mini size (40dp instead of 56dp)
* @return true if clicked
*/
bool FAB(const char* icon, const char* label = nullptr, bool mini = false);
// ============================================================================
// Implementation
// ============================================================================
inline bool Button(const char* label, const ButtonSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
// Convert label to uppercase for Material Design
char upperLabel[256];
{
const char* src = label;
char* dst = upperLabel;
char* end = upperLabel + sizeof(upperLabel) - 1;
while (*src && dst < end) {
*dst++ = (char)toupper((unsigned char)*src++);
}
*dst = '\0';
}
// Get button font
ImFont* buttonFont = Typography::instance().button();
ImGui::PushFont(buttonFont);
// Calculate button size
ImVec2 labelSize = ImGui::CalcTextSize(upperLabel);
float iconWidth = spec.icon ? (size::IconSize + size::ButtonIconGap) : 0.0f;
ImVec2 buttonSize;
buttonSize.x = spec.fullWidth ? ImGui::GetContentRegionAvail().x
: ImMax(labelSize.x + iconWidth + size::ButtonPaddingH * 2, size::ButtonMinWidth);
buttonSize.y = size::ButtonHeight;
// Get position
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + buttonSize.x, pos.y + buttonSize.y));
ImGui::ItemSize(buttonSize, style.FramePadding.y);
if (!ImGui::ItemAdd(bb, id)) {
ImGui::PopFont();
return false;
}
// Handle interaction
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
if (!spec.enabled) {
hovered = held = false;
}
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
// Get colors
ImU32 primaryColor = spec.color ? spec.color : Primary();
ImU32 textColor;
ImU32 bgColor;
ImU32 borderColor = 0;
switch (spec.style) {
case ButtonStyle::Text:
bgColor = 0; // No background
textColor = spec.enabled ? primaryColor : OnSurfaceDisabled();
if (hovered) bgColor = BlendOverlay(Surface(), StateHover(), 1.0f);
if (held) bgColor = BlendOverlay(Surface(), StatePressed(), 1.0f);
break;
case ButtonStyle::Outlined:
bgColor = 0; // No background
textColor = spec.enabled ? primaryColor : OnSurfaceDisabled();
borderColor = spec.enabled ? primaryColor : OnSurfaceDisabled();
if (hovered) bgColor = BlendOverlay(Surface(), StateHover(), 1.0f);
if (held) bgColor = BlendOverlay(Surface(), StatePressed(), 1.0f);
break;
case ButtonStyle::Contained:
default:
if (spec.enabled) {
bgColor = primaryColor;
textColor = OnPrimary();
if (hovered) bgColor = BlendOverlay(primaryColor, StateHover(), 1.0f);
if (held) bgColor = BlendOverlay(primaryColor, StatePressed(), 1.0f);
} else {
bgColor = WithAlphaF(OnSurface(), 0.12f);
textColor = OnSurfaceDisabled();
}
break;
}
// Render
ImDrawList* drawList = window->DrawList;
// Background
if (bgColor) {
drawList->AddRectFilled(bb.Min, bb.Max, bgColor, size::ButtonCornerRadius);
}
// Border (for outlined)
if (borderColor) {
drawList->AddRect(bb.Min, bb.Max, borderColor, size::ButtonCornerRadius, 0, 1.0f);
}
// Icon
float contentX = bb.Min.x + size::ButtonPaddingH;
float contentY = bb.Min.y + (buttonSize.y - labelSize.y) * 0.5f;
if (spec.icon) {
ImFont* iconFont = Type().iconMed();
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, spec.icon);
ImVec2 iconPos(contentX, bb.Min.y + (buttonSize.y - iconSize.y) * 0.5f);
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, textColor, spec.icon);
contentX += iconSize.x + size::ButtonIconGap;
}
// Label
drawList->AddText(ImVec2(contentX, contentY), textColor, upperLabel);
ImGui::PopFont();
return pressed && spec.enabled;
}
inline bool IconButton(const char* icon, const char* tooltip, bool enabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGuiContext& g = *GImGui;
const ImGuiID id = window->GetID(icon);
ImVec2 pos = window->DC.CursorPos;
ImVec2 size(size::IconButtonSize, size::IconButtonSize);
ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
ImGui::ItemSize(size);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
if (!enabled) {
hovered = held = false;
}
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
// Render ripple/hover circle
ImDrawList* drawList = window->DrawList;
ImVec2 center = bb.GetCenter();
float radius = size::IconButtonSize * 0.5f;
if (hovered || held) {
ImU32 overlayColor = held ? StatePressed() : StateHover();
drawList->AddCircleFilled(center, radius, overlayColor);
}
// Render icon (use icon font)
ImFont* iconFont = Type().iconMed();
ImU32 iconColor = enabled ? OnSurface() : OnSurfaceDisabled();
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, icon);
ImVec2 iconPos(center.x - iconSize.x * 0.5f, center.y - iconSize.y * 0.5f);
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, iconColor, icon);
// Tooltip
if (tooltip && hovered) {
ImGui::SetTooltip("%s", tooltip);
}
return pressed && enabled;
}
inline bool FAB(const char* icon, const char* label, bool mini) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGuiContext& g = *GImGui;
const ImGuiID id = window->GetID(icon);
bool extended = (label != nullptr);
float fabSize = mini ? size::FabMiniSize : size::FabSize;
ImVec2 buttonSize;
if (extended) {
ImVec2 labelSize = ImGui::CalcTextSize(label);
buttonSize.x = size::FabExtendedPadding * 2 + size::IconSize + spacing::Sm + labelSize.x;
buttonSize.y = size::FabExtendedHeight;
} else {
buttonSize.x = fabSize;
buttonSize.y = fabSize;
}
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + buttonSize.x, pos.y + buttonSize.y));
ImGui::ItemSize(buttonSize);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
// Colors
ImU32 bgColor = Secondary();
ImU32 textColor = OnSecondary();
if (hovered) bgColor = BlendOverlay(bgColor, StateHover(), 1.0f);
if (held) bgColor = BlendOverlay(bgColor, StatePressed(), 1.0f);
// Render
ImDrawList* drawList = window->DrawList;
float radius = extended ? size::FabCornerRadius : (fabSize * 0.5f);
drawList->AddRectFilled(bb.Min, bb.Max, bgColor, radius);
// Icon
ImVec2 center = bb.GetCenter();
ImFont* iconFont = Type().iconMed();
if (extended) {
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, icon);
float iconX = bb.Min.x + size::FabExtendedPadding;
float iconY = center.y - iconSize.y * 0.5f;
drawList->AddText(iconFont, iconFont->LegacySize, ImVec2(iconX, iconY), textColor, icon);
// Label
Typography::instance().pushFont(TypeStyle::Button);
float labelX = iconX + iconSize.x + spacing::Sm;
float labelY = center.y - ImGui::GetFontSize() * 0.5f;
drawList->AddText(ImVec2(labelX, labelY), textColor, label);
Typography::instance().popFont();
} else {
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, icon);
ImVec2 iconPos(center.x - iconSize.x * 0.5f, center.y - iconSize.y * 0.5f);
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, textColor, icon);
}
return pressed;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,214 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../draw_helpers.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Card Component
// ============================================================================
// Based on https://m2.material.io/components/cards
//
// Cards contain content and actions about a single subject.
// They can be elevated (with shadow) or outlined (with border).
/**
* @brief Card configuration
*/
struct CardSpec {
int elevation = 1; // Elevation in dp (0-24)
bool outlined = false; // Use outline instead of elevation
float cornerRadius = 4.0f; // Corner radius in dp
bool clickable = false; // Make entire card clickable
float padding = 16.0f; // Content padding
float minHeight = 0.0f; // Minimum height (0 = auto)
};
/**
* @brief Begin a Material Design card
*
* @param id Unique identifier for the card
* @param spec Card configuration
* @return true if card is visible and content should be rendered
*/
bool BeginCard(const char* id, const CardSpec& spec = CardSpec());
/**
* @brief End the card
*/
void EndCard();
/**
* @brief Begin a clickable card that returns click state
*
* @param id Unique identifier
* @param spec Card configuration
* @param clicked Output: true if card was clicked
* @return true if card is visible
*/
bool BeginClickableCard(const char* id, const CardSpec& spec, bool* clicked);
/**
* @brief Card header with title and optional subtitle
*
* @param title Primary title text
* @param subtitle Optional secondary text
* @param avatar Optional avatar texture (rendered as circle)
*/
void CardHeader(const char* title, const char* subtitle = nullptr);
/**
* @brief Card supporting text/content
*
* @param text Body text content
*/
void CardContent(const char* text);
/**
* @brief Begin card action area (for buttons)
*
* Actions are typically placed at the bottom of the card.
*/
void CardActions();
/**
* @brief End card action area
*/
void CardActionsEnd();
/**
* @brief Add divider within card
*/
void CardDivider();
// ============================================================================
// Implementation
// ============================================================================
inline bool BeginCard(const char* id, const CardSpec& spec) {
ImGui::PushID(id);
// Calculate surface color based on elevation
ImU32 bgColor = spec.outlined ? Surface() : GetElevatedSurface(GetCurrentColorTheme(), spec.elevation);
// When acrylic backdrop is active, scale card bg alpha by UI opacity
// so cards smoothly transition from opaque (1.0) to see-through.
bool opaqueCards = dragonx::ui::effects::isLowSpecMode();
if (IsBackdropActive() && !opaqueCards) {
ImVec4 c = ImGui::ColorConvertU32ToFloat4(bgColor);
float uiOp = dragonx::ui::effects::ImGuiAcrylic::GetUIOpacity();
c.w *= uiOp;
ImGui::PushStyleColor(ImGuiCol_ChildBg, c);
} else {
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(bgColor));
}
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, spec.cornerRadius);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(spec.padding, spec.padding));
// Border for outlined variant
if (spec.outlined) {
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::ColorConvertU32ToFloat4(Outline()));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
} else {
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
}
ImVec2 size(0, spec.minHeight); // 0 width = use available width
ImGuiChildFlags flags = ImGuiChildFlags_AutoResizeY;
if (spec.outlined) {
flags |= ImGuiChildFlags_Borders;
}
bool visible = ImGui::BeginChild(id, size, flags);
return visible;
}
inline void EndCard() {
ImGui::EndChild();
ImGui::PopStyleVar(3); // ChildRounding, WindowPadding, ChildBorderSize
ImGui::PopStyleColor(1); // ChildBg
// Check if we used outline style (need to pop extra color)
// Note: We always push the border size var, handle outline color in BeginCard
ImGui::PopID();
// Add spacing after card
VSpace(2);
}
inline bool BeginClickableCard(const char* id, const CardSpec& spec, bool* clicked) {
*clicked = false;
ImGui::PushID(id);
ImVec2 startPos = ImGui::GetCursorScreenPos();
// Render card background
ImU32 bgColor = spec.outlined ? Surface() : GetElevatedSurface(GetCurrentColorTheme(), spec.elevation);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(bgColor));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, spec.cornerRadius);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(spec.padding, spec.padding));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, spec.outlined ? 1.0f : 0.0f);
if (spec.outlined) {
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::ColorConvertU32ToFloat4(Outline()));
}
ImVec2 size(0, spec.minHeight);
ImGuiChildFlags flags = ImGuiChildFlags_AutoResizeY;
if (spec.outlined) {
flags |= ImGuiChildFlags_Borders;
}
bool visible = ImGui::BeginChild(id, size, flags);
return visible;
}
inline void CardHeader(const char* title, const char* subtitle) {
Typography::instance().text(TypeStyle::H6, title);
if (subtitle) {
Typography::instance().textColored(TypeStyle::Body2, OnSurfaceMedium(), subtitle);
}
VSpace(1);
}
inline void CardContent(const char* text) {
Typography::instance().textWrapped(TypeStyle::Body2, text);
VSpace(1);
}
inline void CardActions() {
ImGui::Separator();
VSpace(1);
ImGui::BeginGroup();
}
inline void CardActionsEnd() {
ImGui::EndGroup();
}
inline void CardDivider() {
ImGui::Separator();
VSpace(1);
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,296 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Chips Component
// ============================================================================
// Based on https://m2.material.io/components/chips
//
// Chips are compact elements that represent an input, attribute, or action.
enum class ChipType {
Input, // User input (deletable)
Choice, // Single selection from set
Filter, // Filter/checkbox style
Action // Triggers action
};
/**
* @brief Chip configuration
*/
struct ChipSpec {
ChipType type = ChipType::Action;
const char* label = nullptr;
const char* icon = nullptr; // Leading icon
const char* avatar = nullptr; // Avatar text (overrides icon)
ImU32 avatarColor = 0; // Avatar background color
bool selected = false; // For choice/filter chips
bool disabled = false;
bool outlined = false; // Outlined vs filled style
};
/**
* @brief Render a chip
*
* @param spec Chip configuration
* @return For filter/choice: true if clicked (toggle selection)
* For input: true if delete clicked
* For action: true if clicked
*/
bool Chip(const ChipSpec& spec);
/**
* @brief Simple action chip
*/
bool Chip(const char* label);
/**
* @brief Filter chip (toggleable)
*/
bool FilterChip(const char* label, bool* selected);
/**
* @brief Choice chip (radio-style)
*/
bool ChoiceChip(const char* label, bool selected);
/**
* @brief Input chip with delete
*/
bool InputChip(const char* label, const char* avatar = nullptr);
/**
* @brief Begin a chip group for layout
*/
void BeginChipGroup();
/**
* @brief End a chip group
*/
void EndChipGroup();
// ============================================================================
// Implementation
// ============================================================================
inline bool Chip(const ChipSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(spec.label);
// Chip dimensions
const float chipHeight = 32.0f;
const float cornerRadius = chipHeight * 0.5f;
const float horizontalPadding = 12.0f;
const float iconSize = 18.0f;
const float avatarSize = 24.0f;
const float deleteIconSize = 18.0f;
// Calculate content width
float contentWidth = horizontalPadding * 2;
bool hasLeading = spec.icon || spec.avatar;
bool hasDelete = (spec.type == ChipType::Input);
bool hasCheckmark = (spec.type == ChipType::Filter && spec.selected);
if (spec.avatar) {
contentWidth += avatarSize + 8.0f;
} else if (spec.icon || hasCheckmark) {
contentWidth += iconSize + 8.0f;
}
contentWidth += ImGui::CalcTextSize(spec.label).x;
if (hasDelete) {
contentWidth += deleteIconSize + 8.0f;
}
// Layout
ImVec2 pos = window->DC.CursorPos;
ImRect chipBB(pos, ImVec2(pos.x + contentWidth, pos.y + chipHeight));
// Interaction
ImGuiID id = window->GetID("##chip");
ImGui::ItemSize(chipBB);
if (!ImGui::ItemAdd(chipBB, id))
return false;
bool hovered, held;
bool clicked = ImGui::ButtonBehavior(chipBB, id, &hovered, &held) && !spec.disabled;
// Delete button hit test (for input chips)
bool deleteClicked = false;
if (hasDelete) {
float deleteX = chipBB.Max.x - horizontalPadding - deleteIconSize;
ImRect deleteBB(
ImVec2(deleteX, pos.y + (chipHeight - deleteIconSize) * 0.5f),
ImVec2(deleteX + deleteIconSize, pos.y + (chipHeight + deleteIconSize) * 0.5f)
);
ImGuiID deleteId = window->GetID("##delete");
bool deleteHovered, deleteHeld;
deleteClicked = ImGui::ButtonBehavior(deleteBB, deleteId, &deleteHovered, &deleteHeld);
}
// Draw
ImDrawList* drawList = window->DrawList;
// Background
ImU32 bgColor;
ImU32 borderColor = 0;
if (spec.disabled) {
bgColor = schema::UI().resolveColor("var(--surface-hover)", IM_COL32(255, 255, 255, 30));
} else if (spec.selected) {
bgColor = WithAlpha(Primary(), 51); // Primary at 20%
} else if (spec.outlined) {
bgColor = 0; // Transparent
borderColor = OnSurfaceMedium();
} else {
bgColor = schema::UI().resolveColor("var(--surface-hover)", IM_COL32(255, 255, 255, 30));
}
// Hover/press overlay
if (!spec.disabled) {
if (held) {
bgColor = IM_COL32_ADD(bgColor, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
} else if (hovered) {
bgColor = IM_COL32_ADD(bgColor, schema::UI().resolveColor("var(--active-overlay)", IM_COL32(255, 255, 255, 10)));
}
}
// Draw background
if (bgColor) {
drawList->AddRectFilled(chipBB.Min, chipBB.Max, bgColor, cornerRadius);
}
// Draw border for outlined
if (borderColor) {
drawList->AddRect(chipBB.Min, chipBB.Max, borderColor, cornerRadius, 0, 1.0f);
}
// Content
float currentX = pos.x + horizontalPadding;
float centerY = pos.y + chipHeight * 0.5f;
ImU32 contentColor = spec.disabled ? OnSurfaceDisabled() : OnSurface();
ImU32 iconColor = spec.disabled ? OnSurfaceDisabled() :
spec.selected ? Primary() : OnSurfaceMedium();
// Avatar or icon
if (spec.avatar) {
// Avatar circle
ImVec2 avatarCenter(currentX + avatarSize * 0.5f - 4.0f, centerY);
ImU32 avatarBg = spec.avatarColor ? spec.avatarColor : Primary();
drawList->AddCircleFilled(avatarCenter, avatarSize * 0.5f, avatarBg);
// Avatar text
ImVec2 textSize = ImGui::CalcTextSize(spec.avatar);
ImVec2 textPos(avatarCenter.x - textSize.x * 0.5f, avatarCenter.y - textSize.y * 0.5f);
drawList->AddText(textPos, OnPrimary(), spec.avatar);
currentX += avatarSize + 4.0f;
} else if (hasCheckmark) {
// Checkmark for selected filter chips
ImFont* iconFont = Typography::instance().iconSmall();
ImVec2 chkSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, ICON_MD_CHECK);
drawList->AddText(iconFont, iconFont->LegacySize,
ImVec2(currentX, centerY - chkSz.y * 0.5f), Primary(), ICON_MD_CHECK);
currentX += iconSize + 4.0f;
} else if (spec.icon) {
drawList->AddText(ImVec2(currentX, centerY - iconSize * 0.5f), iconColor, spec.icon);
currentX += iconSize + 4.0f;
}
// Label
Typography::instance().pushFont(TypeStyle::Body2);
float labelY = centerY - ImGui::GetFontSize() * 0.5f;
drawList->AddText(ImVec2(currentX, labelY), contentColor, spec.label);
Typography::instance().popFont();
// Delete icon (for input chips)
if (hasDelete) {
float deleteX = chipBB.Max.x - horizontalPadding - deleteIconSize;
ImFont* iconFont = Typography::instance().iconSmall();
ImVec2 delSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, ICON_MD_CLOSE);
drawList->AddText(iconFont, iconFont->LegacySize,
ImVec2(deleteX, centerY - delSz.y * 0.5f),
OnSurfaceMedium(), ICON_MD_CLOSE
);
}
ImGui::PopID();
// Return value depends on chip type
if (spec.type == ChipType::Input) {
return deleteClicked;
}
return clicked;
}
inline bool Chip(const char* label) {
ChipSpec spec;
spec.label = label;
spec.type = ChipType::Action;
return Chip(spec);
}
inline bool FilterChip(const char* label, bool* selected) {
ChipSpec spec;
spec.label = label;
spec.type = ChipType::Filter;
spec.selected = *selected;
bool clicked = Chip(spec);
if (clicked) {
*selected = !*selected;
}
return clicked;
}
inline bool ChoiceChip(const char* label, bool selected) {
ChipSpec spec;
spec.label = label;
spec.type = ChipType::Choice;
spec.selected = selected;
return Chip(spec);
}
inline bool InputChip(const char* label, const char* avatar) {
ChipSpec spec;
spec.label = label;
spec.type = ChipType::Input;
spec.avatar = avatar;
return Chip(spec);
}
inline void BeginChipGroup() {
ImGui::BeginGroup();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing::dp(1), spacing::dp(1))); // 8dp spacing
}
inline void EndChipGroup() {
ImGui::PopStyleVar();
ImGui::EndGroup();
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,122 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// ============================================================================
// Material Design Components - Unified Header
// ============================================================================
// Include this single header to get all Material Design components.
//
// Based on Material Design 2 (m2.material.io)
//
// All components are in the namespace: dragonx::ui::material
// Core dependencies
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
// Components
#include "buttons.h" // Button, IconButton, FAB
#include "cards.h" // Card, CardHeader, CardContent, CardActions
#include "text_fields.h" // TextField
#include "lists.h" // ListItem, ListDivider, ListSubheader
#include "dialogs.h" // Dialog, ConfirmDialog, AlertDialog
#include "inputs.h" // Switch, Checkbox, RadioButton
#include "progress.h" // LinearProgress, CircularProgress
#include "snackbar.h" // Snackbar, ShowSnackbar
#include "slider.h" // Slider, SliderDiscrete, SliderRange
#include "tabs.h" // TabBar, Tab
#include "chips.h" // Chip, FilterChip, ChoiceChip, InputChip
#include "nav_drawer.h" // NavDrawer, NavItem
#include "app_bar.h" // AppBar, AppBarTitle, AppBarAction
// ============================================================================
// Quick Reference
// ============================================================================
//
// BUTTONS:
// Button(label, spec) - Generic button with style config
// TextButton(label) - Text-only button
// OutlinedButton(label) - Button with outline
// ContainedButton(label) - Filled button (primary)
// IconButton(icon, tooltip) - Circular icon button
// FAB(icon) - Floating action button
//
// CARDS:
// BeginCard(spec)/EndCard() - Card container
// CardHeader(title, subtitle) - Card header section
// CardContent(text) - Card body text
// CardActions()/EndCardActions()- Card button area
//
// TEXT FIELDS:
// TextField(label, buf, size) - Text input field
// TextField(id, buf, size, spec)- Configurable text field
//
// LISTS:
// BeginList(id)/EndList() - List container
// ListItem(text) - Simple list item
// ListItem(primary, secondary) - Two-line item
// ListItem(spec) - Full config item
// ListDivider(inset) - Divider line
// ListSubheader(text) - Section header
//
// DIALOGS:
// BeginDialog(id, &open, spec) - Modal dialog
// EndDialog()
// ConfirmDialog(...) - Confirm/cancel dialog
// AlertDialog(...) - Single-action alert
//
// SELECTION CONTROLS:
// Switch(label, &value) - Toggle switch
// Checkbox(label, &value) - Checkbox
// RadioButton(label, active) - Radio button
// RadioButton(label, &sel, val) - Radio with int selection
//
// PROGRESS:
// LinearProgress(fraction) - Determinate progress bar
// LinearProgressIndeterminate() - Indeterminate progress bar
// CircularProgress(fraction) - Circular progress
// CircularProgressIndeterminate()- Spinner
//
// SNACKBAR:
// ShowSnackbar(msg, action) - Show notification
// DismissSnackbar() - Dismiss current snackbar
// RenderSnackbar() - Call each frame to render
//
// SLIDER:
// Slider(label, &val, min, max) - Continuous slider
// SliderInt(label, &val, ...) - Integer slider
// SliderDiscrete(...) - Stepped slider
// SliderRange(...) - Two-thumb range slider
//
// TABS:
// BeginTabBar(id, &idx) - Tab bar container
// Tab(label) - Tab item
// EndTabBar()
// TabBar(id, labels, count, &idx) - Simple tab bar
//
// CHIPS:
// Chip(label) - Action chip
// FilterChip(label, &selected) - Toggleable filter chip
// ChoiceChip(label, selected) - Choice chip
// InputChip(label, avatar) - Deletable input chip
// BeginChipGroup()/EndChipGroup()- Chip layout helper
//
// NAVIGATION DRAWER:
// BeginNavDrawer(id, &open, spec) - Navigation drawer
// EndNavDrawer()
// NavItem(icon, label, selected) - Navigation item
// NavDivider() - Drawer divider
// NavSubheader(text) - Section header
//
// APP BAR:
// BeginAppBar(id, spec) - Top app bar
// EndAppBar()
// AppBarNavIcon(icon) - Navigation icon (left)
// AppBarTitle(title) - App bar title
// AppBarAction(icon) - Action button (right)
// BeginAppBarMenu(icon) - Overflow menu
// AppBarMenuItem(label) - Menu item

View File

@@ -0,0 +1,293 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "buttons.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Dialog Component
// ============================================================================
// Based on https://m2.material.io/components/dialogs
//
// Dialogs inform users about a task and can contain critical information,
// require decisions, or involve multiple tasks.
/**
* @brief Dialog configuration
*/
struct DialogSpec {
const char* title = nullptr; // Dialog title
float width = 560.0f; // Dialog width (280-560dp typical)
float maxHeight = 0; // Max height (0 = auto)
bool scrollableContent = false; // Enable content scrolling
bool fullWidth = false; // Actions span full width
};
/**
* @brief Begin a modal dialog
*
* @param id Unique identifier
* @param open Pointer to open state (will be set false when closed)
* @param spec Dialog configuration
* @return true if dialog is open
*/
bool BeginDialog(const char* id, bool* open, const DialogSpec& spec = DialogSpec());
/**
* @brief End a dialog
*/
void EndDialog();
/**
* @brief Simple dialog with just text content
*/
bool BeginDialog(const char* id, bool* open, const char* title);
/**
* @brief Dialog content area (scrollable if configured)
*/
void BeginDialogContent();
/**
* @brief End dialog content area
*/
void EndDialogContent();
/**
* @brief Dialog actions area (buttons)
*/
void BeginDialogActions();
/**
* @brief End dialog actions area
*/
void EndDialogActions();
/**
* @brief Standard confirm dialog
*
* @param id Unique identifier
* @param open Pointer to open state
* @param title Dialog title
* @param message Dialog message
* @param confirmText Confirm button text
* @param cancelText Cancel button text (nullptr for no cancel)
* @return 0 = still open, 1 = confirmed, -1 = cancelled
*/
int ConfirmDialog(const char* id, bool* open, const char* title, const char* message,
const char* confirmText = "Confirm", const char* cancelText = "Cancel");
/**
* @brief Alert dialog (single action)
*
* @param id Unique identifier
* @param open Pointer to open state
* @param title Dialog title
* @param message Dialog message
* @param buttonText Button text
* @return true when dismissed
*/
bool AlertDialog(const char* id, bool* open, const char* title, const char* message,
const char* buttonText = "OK");
// ============================================================================
// Implementation
// ============================================================================
// Internal state for dialog rendering
struct DialogState {
ImVec2 contentMin;
ImVec2 contentMax;
float contentScrollY;
bool scrollable;
float width;
};
static DialogState g_currentDialog;
inline bool BeginDialog(const char* id, bool* open, const DialogSpec& spec) {
if (!*open)
return false;
// Center dialog on screen
ImGuiIO& io = ImGui::GetIO();
ImVec2 center = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
// Set dialog size
float dialogWidth = spec.width;
ImGui::SetNextWindowSizeConstraints(
ImVec2(280.0f, 0), // Min size
ImVec2(dialogWidth, spec.maxHeight > 0 ? spec.maxHeight : io.DisplaySize.y * 0.9f)
);
// Style dialog
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, size::DialogCornerRadius);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGui::ColorConvertU32ToFloat4(Surface(Elevation::Dp24)));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::ColorConvertU32ToFloat4(Surface(Elevation::Dp24)));
// Modal background (scrim)
ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList();
bgDrawList->AddRectFilled(
ImVec2(0, 0), io.DisplaySize,
schema::UI().resolveColor("var(--scrim)", IM_COL32(0, 0, 0, (int)(0.32f * 255)))
);
// Open popup
ImGui::OpenPopup(id);
bool isOpen = ImGui::BeginPopupModal(id, open,
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoTitleBar);
if (isOpen) {
g_currentDialog.scrollable = spec.scrollableContent;
g_currentDialog.width = dialogWidth;
// Title
if (spec.title) {
ImGui::Dummy(ImVec2(0, spacing::dp(3))); // 24dp top padding
ImGui::SetCursorPosX(spacing::dp(3)); // 24dp left padding
Typography::instance().text(TypeStyle::H6, spec.title);
ImGui::Dummy(ImVec2(0, spacing::dp(2))); // 16dp below title
} else {
ImGui::Dummy(ImVec2(0, spacing::dp(2))); // 16dp top padding without title
}
}
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(2);
return isOpen;
}
inline void EndDialog() {
ImGui::EndPopup();
}
inline bool BeginDialog(const char* id, bool* open, const char* title) {
DialogSpec spec;
spec.title = title;
return BeginDialog(id, open, spec);
}
inline void BeginDialogContent() {
ImGui::SetCursorPosX(spacing::dp(3)); // 24dp left padding
// Start content region
float maxWidth = g_currentDialog.width - spacing::dp(6); // 24dp padding each side
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + maxWidth);
if (g_currentDialog.scrollable) {
ImGui::BeginChild("##dialogContent", ImVec2(maxWidth, 200), false);
}
}
inline void EndDialogContent() {
if (g_currentDialog.scrollable) {
ImGui::EndChild();
}
ImGui::PopTextWrapPos();
ImGui::Dummy(ImVec2(0, spacing::dp(3))); // 24dp after content
}
inline void BeginDialogActions() {
// Actions area - right-aligned buttons with 8dp spacing
float contentWidth = ImGui::GetContentRegionAvail().x;
ImGui::SetCursorPosX(spacing::dp(1)); // 8dp left padding for actions
// Push style for action buttons
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing::dp(1), 0)); // 8dp between buttons
// Right-align: use a dummy to push buttons right
// Buttons will be added inline with SameLine
}
inline void EndDialogActions() {
ImGui::PopStyleVar();
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp bottom padding
}
inline int ConfirmDialog(const char* id, bool* open, const char* title, const char* message,
const char* confirmText, const char* cancelText) {
int result = 0;
if (BeginDialog(id, open, title)) {
BeginDialogContent();
Typography::instance().textWrapped(TypeStyle::Body1, message);
EndDialogContent();
BeginDialogActions();
// Calculate button positions for right alignment
float cancelWidth = cancelText ? ImGui::CalcTextSize(cancelText).x + spacing::dp(2) : 0;
float confirmWidth = ImGui::CalcTextSize(confirmText).x + spacing::dp(2);
float totalButtonWidth = cancelWidth + confirmWidth + (cancelText ? spacing::dp(1) : 0);
float startX = ImGui::GetContentRegionAvail().x - totalButtonWidth - spacing::dp(2);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + startX);
if (cancelText) {
if (TextButton(cancelText)) {
*open = false;
result = -1;
}
ImGui::SameLine();
}
if (ContainedButton(confirmText)) {
*open = false;
result = 1;
}
EndDialogActions();
EndDialog();
}
return result;
}
inline bool AlertDialog(const char* id, bool* open, const char* title, const char* message,
const char* buttonText) {
bool dismissed = false;
if (BeginDialog(id, open, title)) {
BeginDialogContent();
Typography::instance().textWrapped(TypeStyle::Body1, message);
EndDialogContent();
BeginDialogActions();
// Right-align single button
float buttonWidth = ImGui::CalcTextSize(buttonText).x + spacing::dp(2);
float startX = ImGui::GetContentRegionAvail().x - buttonWidth - spacing::dp(2);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + startX);
if (ContainedButton(buttonText)) {
*open = false;
dismissed = true;
}
EndDialogActions();
EndDialog();
}
return dismissed;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,414 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Input Controls
// ============================================================================
// Based on https://m2.material.io/components/selection-controls
//
// Selection controls allow users to complete tasks that involve making choices:
// - Switch: Toggle single option on/off
// - Checkbox: Select multiple options
// - Radio: Select one option from a set
// ============================================================================
// Switch
// ============================================================================
/**
* @brief Material Design switch (toggle)
*
* @param label Text label
* @param value Pointer to boolean value
* @param disabled If true, switch is non-interactive
* @return true if value changed
*/
bool Switch(const char* label, bool* value, bool disabled = false);
// ============================================================================
// Checkbox
// ============================================================================
/**
* @brief Checkbox state
*/
enum class CheckboxState {
Unchecked,
Checked,
Indeterminate // For parent with mixed children
};
/**
* @brief Material Design checkbox
*
* @param label Text label
* @param value Pointer to boolean value
* @param disabled If true, checkbox is non-interactive
* @return true if value changed
*/
bool Checkbox(const char* label, bool* value, bool disabled = false);
/**
* @brief Tri-state checkbox
*/
bool Checkbox(const char* label, CheckboxState* state, bool disabled = false);
// ============================================================================
// Radio Button
// ============================================================================
/**
* @brief Material Design radio button
*
* @param label Text label
* @param active true if this option is selected
* @param disabled If true, radio is non-interactive
* @return true if clicked (caller should update selection)
*/
bool RadioButton(const char* label, bool active, bool disabled = false);
/**
* @brief Radio button with int selection
*
* @param label Text label
* @param selection Pointer to current selection
* @param value Value this radio represents
* @return true if clicked
*/
bool RadioButton(const char* label, int* selection, int value, bool disabled = false);
// ============================================================================
// Implementation
// ============================================================================
inline bool Switch(const char* label, bool* value, bool disabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Switch dimensions (Material spec: 36x14 track, 20dp thumb)
const float trackWidth = 36.0f;
const float trackHeight = 14.0f;
const float thumbRadius = 10.0f; // 20dp diameter
const float thumbTravel = trackWidth - thumbRadius * 2;
// Calculate layout
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = trackWidth + spacing::dp(2) + labelWidth;
float totalHeight = ImMax(trackHeight + 6.0f, size::TouchTarget); // Min 48dp touch target
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
// Interaction
ImGuiID id = window->GetID("##switch");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
bool changed = false;
if (pressed) {
*value = !*value;
changed = true;
}
// Animation (simple snap for now)
float thumbX = *value ? (thumbTravel) : 0;
// Draw track
ImDrawList* drawList = window->DrawList;
float trackY = pos.y + totalHeight * 0.5f;
ImVec2 trackMin(pos.x, trackY - trackHeight * 0.5f);
ImVec2 trackMax(pos.x + trackWidth, trackY + trackHeight * 0.5f);
ImU32 trackColor;
if (disabled) {
trackColor = schema::UI().resolveColor("var(--switch-track-off)", IM_COL32(255, 255, 255, 30));
} else if (*value) {
trackColor = PrimaryVariant(); // Primary at 50% opacity
} else {
trackColor = schema::UI().resolveColor("var(--switch-track-on)", IM_COL32(255, 255, 255, 97));
}
drawList->AddRectFilled(trackMin, trackMax, trackColor, trackHeight * 0.5f);
// Draw thumb
ImVec2 thumbCenter(pos.x + thumbRadius + thumbX, trackY);
ImU32 thumbColor;
if (disabled) {
thumbColor = schema::UI().resolveColor("var(--switch-thumb-off)", IM_COL32(189, 189, 189, 255));
} else if (*value) {
thumbColor = Primary();
} else {
thumbColor = schema::UI().resolveColor("var(--switch-thumb-on)", IM_COL32(250, 250, 250, 255));
}
// Thumb shadow
drawList->AddCircleFilled(ImVec2(thumbCenter.x + 1, thumbCenter.y + 2), thumbRadius, schema::UI().resolveColor("var(--control-shadow)", IM_COL32(0, 0, 0, 60)));
drawList->AddCircleFilled(thumbCenter, thumbRadius, thumbColor);
// Hover ripple effect
if (hovered && !disabled) {
ImU32 ripple = *value ? WithAlpha(Primary(), 25) : schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25));
drawList->AddCircleFilled(thumbCenter, thumbRadius + 12.0f, ripple);
}
// Draw label
ImVec2 labelPos(pos.x + trackWidth + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return changed;
}
inline bool Checkbox(const char* label, bool* value, bool disabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Checkbox dimensions (18dp box, 48dp touch target)
const float boxSize = 18.0f;
// Calculate layout
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = boxSize + spacing::dp(2) + labelWidth;
float totalHeight = size::TouchTarget;
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
// Interaction
ImGuiID id = window->GetID("##checkbox");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
bool changed = false;
if (pressed) {
*value = !*value;
changed = true;
}
// Draw checkbox
ImDrawList* drawList = window->DrawList;
float centerY = pos.y + totalHeight * 0.5f;
ImVec2 boxMin(pos.x, centerY - boxSize * 0.5f);
ImVec2 boxMax(pos.x + boxSize, centerY + boxSize * 0.5f);
ImU32 boxColor, checkColor;
if (disabled) {
boxColor = OnSurfaceDisabled();
checkColor = schema::UI().resolveColor("var(--checkbox-check)", IM_COL32(0, 0, 0, 255));
} else if (*value) {
boxColor = Primary();
checkColor = OnPrimary();
} else {
boxColor = OnSurfaceMedium();
checkColor = OnPrimary();
}
if (*value) {
// Filled checkbox with checkmark
drawList->AddRectFilled(boxMin, boxMax, boxColor, 2.0f);
// Draw checkmark
ImVec2 checkStart(boxMin.x + 4, centerY);
ImVec2 checkMid(boxMin.x + 7, centerY + 3);
ImVec2 checkEnd(boxMin.x + 14, centerY - 4);
drawList->AddLine(checkStart, checkMid, checkColor, 2.0f);
drawList->AddLine(checkMid, checkEnd, checkColor, 2.0f);
} else {
// Empty checkbox border
drawList->AddRect(boxMin, boxMax, boxColor, 2.0f, 0, 2.0f);
}
// Hover ripple
if (hovered && !disabled) {
ImVec2 boxCenter((boxMin.x + boxMax.x) * 0.5f, centerY);
drawList->AddCircleFilled(boxCenter, boxSize,
*value ? WithAlpha(Primary(), 25) : schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
}
// Draw label
ImVec2 labelPos(pos.x + boxSize + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return changed;
}
inline bool Checkbox(const char* label, CheckboxState* state, bool disabled) {
bool checked = (*state == CheckboxState::Checked);
bool indeterminate = (*state == CheckboxState::Indeterminate);
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
const float boxSize = 18.0f;
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = boxSize + spacing::dp(2) + labelWidth;
float totalHeight = size::TouchTarget;
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
ImGuiID id = window->GetID("##checkbox");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
bool changed = false;
if (pressed) {
// Cycle: Unchecked -> Checked -> Unchecked (indeterminate only set programmatically)
*state = (*state == CheckboxState::Checked) ? CheckboxState::Unchecked : CheckboxState::Checked;
changed = true;
}
ImDrawList* drawList = window->DrawList;
float centerY = pos.y + totalHeight * 0.5f;
ImVec2 boxMin(pos.x, centerY - boxSize * 0.5f);
ImVec2 boxMax(pos.x + boxSize, centerY + boxSize * 0.5f);
ImU32 boxColor = disabled ? OnSurfaceDisabled() : (checked || indeterminate) ? Primary() : OnSurfaceMedium();
if (checked || indeterminate) {
drawList->AddRectFilled(boxMin, boxMax, boxColor, 2.0f);
if (indeterminate) {
// Horizontal line for indeterminate
drawList->AddLine(
ImVec2(boxMin.x + 4, centerY),
ImVec2(boxMax.x - 4, centerY),
OnPrimary(), 2.0f
);
} else {
// Checkmark
ImVec2 checkStart(boxMin.x + 4, centerY);
ImVec2 checkMid(boxMin.x + 7, centerY + 3);
ImVec2 checkEnd(boxMin.x + 14, centerY - 4);
drawList->AddLine(checkStart, checkMid, OnPrimary(), 2.0f);
drawList->AddLine(checkMid, checkEnd, OnPrimary(), 2.0f);
}
} else {
drawList->AddRect(boxMin, boxMax, boxColor, 2.0f, 0, 2.0f);
}
if (hovered && !disabled) {
ImVec2 boxCenter((boxMin.x + boxMax.x) * 0.5f, centerY);
drawList->AddCircleFilled(boxCenter, boxSize, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
}
ImVec2 labelPos(pos.x + boxSize + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return changed;
}
inline bool RadioButton(const char* label, bool active, bool disabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Radio button dimensions (20dp outer, 10dp inner when selected)
const float outerRadius = 10.0f;
const float innerRadius = 5.0f;
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = outerRadius * 2 + spacing::dp(2) + labelWidth;
float totalHeight = size::TouchTarget;
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
ImGuiID id = window->GetID("##radio");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
ImDrawList* drawList = window->DrawList;
float centerY = pos.y + totalHeight * 0.5f;
ImVec2 center(pos.x + outerRadius, centerY);
ImU32 ringColor = disabled ? OnSurfaceDisabled() : active ? Primary() : OnSurfaceMedium();
// Outer ring
drawList->AddCircle(center, outerRadius, ringColor, 0, 2.0f);
// Inner dot when active
if (active) {
drawList->AddCircleFilled(center, innerRadius, ringColor);
}
// Hover ripple
if (hovered && !disabled) {
drawList->AddCircleFilled(center, outerRadius + 12.0f,
active ? WithAlpha(Primary(), 25) : schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
}
// Label
ImVec2 labelPos(pos.x + outerRadius * 2 + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return pressed;
}
inline bool RadioButton(const char* label, int* selection, int value, bool disabled) {
bool active = (*selection == value);
if (RadioButton(label, active, disabled)) {
*selection = value;
return true;
}
return false;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,306 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design List Component
// ============================================================================
// Based on https://m2.material.io/components/lists
//
// Lists present content in a way that makes it easy to identify a specific
// item in a collection and act on it.
/**
* @brief List item configuration
*/
struct ListItemSpec {
const char* leadingIcon = nullptr; // Optional leading icon (text representation)
const char* leadingAvatar = nullptr; // Optional avatar text (for initials)
ImU32 leadingAvatarColor = 0; // Avatar background color (0 = primary)
bool leadingCheckbox = false; // Show checkbox instead of icon
bool checkboxChecked = false; // Checkbox state
const char* primaryText = nullptr; // Main text (required)
const char* secondaryText = nullptr; // Secondary text (optional)
const char* trailingIcon = nullptr; // Optional trailing icon
const char* trailingText = nullptr; // Optional trailing metadata text
bool selected = false; // Selected state (highlight)
bool disabled = false; // Disabled state
bool dividerBelow = false; // Draw divider below item
};
/**
* @brief Begin a list container
*
* @param id Unique identifier
* @param withPadding Add top/bottom padding
*/
void BeginList(const char* id, bool withPadding = true);
/**
* @brief End a list container
*/
void EndList();
/**
* @brief Render a list item
*
* @param spec Item configuration
* @return true if clicked
*/
bool ListItem(const ListItemSpec& spec);
/**
* @brief Simple single-line list item
*/
bool ListItem(const char* text);
/**
* @brief Two-line list item with primary and secondary text
*/
bool ListItem(const char* primary, const char* secondary);
/**
* @brief List divider (full width or inset)
*
* @param inset If true, indented to align with text
*/
void ListDivider(bool inset = false);
/**
* @brief List subheader text
*/
void ListSubheader(const char* text);
// ============================================================================
// Implementation
// ============================================================================
inline void BeginList(const char* id, bool withPadding) {
ImGui::PushID(id);
if (withPadding) {
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp top padding
}
}
inline void EndList() {
ImGui::PopID();
}
inline bool ListItem(const ListItemSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
// Calculate item height based on content
bool hasSecondary = (spec.secondaryText != nullptr);
bool hasLeadingElement = (spec.leadingIcon || spec.leadingAvatar || spec.leadingCheckbox);
float itemHeight;
if (hasSecondary) {
itemHeight = size::ListItemTwoLineHeight; // 72dp for two-line
} else if (hasLeadingElement) {
itemHeight = size::ListItemHeight; // 56dp with leading element
} else {
itemHeight = 48.0f; // 48dp simple one-line
}
// Item dimensions
ImVec2 pos = window->DC.CursorPos;
float itemWidth = ImGui::GetContentRegionAvail().x;
ImRect bb(pos, ImVec2(pos.x + itemWidth, pos.y + itemHeight));
// Interaction
ImGuiID itemId = window->GetID(spec.primaryText);
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, itemId))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, itemId, &hovered, &held) && !spec.disabled;
// Draw background
ImDrawList* drawList = window->DrawList;
ImU32 bgColor = 0;
if (spec.selected) {
bgColor = PrimaryContainer();
} else if (held && !spec.disabled) {
bgColor = schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25));
} else if (hovered && !spec.disabled) {
bgColor = schema::UI().resolveColor("var(--active-overlay)", IM_COL32(255, 255, 255, 10));
}
if (bgColor) {
drawList->AddRectFilled(bb.Min, bb.Max, bgColor);
}
// Layout positions
float leftPadding = spacing::dp(2); // 16dp left padding
float currentX = bb.Min.x + leftPadding;
float centerY = bb.Min.y + itemHeight * 0.5f;
// Draw leading element
if (spec.leadingAvatar) {
// Avatar circle with text
float avatarRadius = 20.0f; // 40dp diameter
ImVec2 avatarCenter(currentX + avatarRadius, centerY);
ImU32 avatarBg = spec.leadingAvatarColor ? spec.leadingAvatarColor : Primary();
drawList->AddCircleFilled(avatarCenter, avatarRadius, avatarBg);
// Avatar text (centered)
ImVec2 textSize = ImGui::CalcTextSize(spec.leadingAvatar);
ImVec2 textPos(avatarCenter.x - textSize.x * 0.5f, avatarCenter.y - textSize.y * 0.5f);
drawList->AddText(textPos, OnPrimary(), spec.leadingAvatar);
currentX += 40.0f + spacing::dp(2); // 40dp avatar + 16dp gap
} else if (spec.leadingIcon) {
// Icon
ImVec2 iconSize = ImGui::CalcTextSize(spec.leadingIcon);
float iconY = centerY - iconSize.y * 0.5f;
ImU32 iconColor = spec.disabled ? OnSurfaceDisabled() : OnSurfaceMedium();
drawList->AddText(ImVec2(currentX, iconY), iconColor, spec.leadingIcon);
currentX += 24.0f + spacing::dp(2); // 24dp icon + 16dp gap
} else if (spec.leadingCheckbox) {
// Checkbox (simplified visual)
float checkboxSize = 18.0f;
ImVec2 checkMin(currentX, centerY - checkboxSize * 0.5f);
ImVec2 checkMax(currentX + checkboxSize, centerY + checkboxSize * 0.5f);
if (spec.checkboxChecked) {
drawList->AddRectFilled(checkMin, checkMax, Primary(), 2.0f);
// Checkmark
ImFont* iconFont = Typography::instance().iconSmall();
ImVec2 chkSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, ICON_MD_CHECK);
drawList->AddText(iconFont, iconFont->LegacySize,
ImVec2(checkMin.x + (checkboxSize - chkSz.x) * 0.5f, checkMin.y + (checkboxSize - chkSz.y) * 0.5f),
OnPrimary(), ICON_MD_CHECK);
} else {
drawList->AddRect(checkMin, checkMax, OnSurfaceMedium(), 2.0f, 0, 2.0f);
}
currentX += checkboxSize + spacing::dp(2);
}
// Calculate text area
float rightPadding = spacing::dp(2); // 16dp right padding
float trailingSpace = 0;
if (spec.trailingIcon) trailingSpace += 24.0f + spacing::dp(1);
if (spec.trailingText) trailingSpace += ImGui::CalcTextSize(spec.trailingText).x + spacing::dp(1);
float textMaxX = bb.Max.x - rightPadding - trailingSpace;
// Draw text
ImU32 primaryColor = spec.disabled ? OnSurfaceDisabled() : OnSurface();
ImU32 secondaryColor = spec.disabled ? OnSurfaceDisabled() : OnSurfaceMedium();
if (hasSecondary) {
// Two-line layout
float primaryY = bb.Min.y + 16.0f;
float secondaryY = primaryY + 20.0f;
Typography::instance().pushFont(TypeStyle::Body1);
drawList->AddText(ImVec2(currentX, primaryY), primaryColor, spec.primaryText);
Typography::instance().popFont();
Typography::instance().pushFont(TypeStyle::Body2);
drawList->AddText(ImVec2(currentX, secondaryY), secondaryColor, spec.secondaryText);
Typography::instance().popFont();
} else {
// Single-line layout
Typography::instance().pushFont(TypeStyle::Body1);
float textY = centerY - Typography::instance().getFont(TypeStyle::Body1)->FontSize * 0.5f;
drawList->AddText(ImVec2(currentX, textY), primaryColor, spec.primaryText);
Typography::instance().popFont();
}
// Draw trailing elements
float trailingX = bb.Max.x - rightPadding;
if (spec.trailingText) {
ImVec2 textSize = ImGui::CalcTextSize(spec.trailingText);
trailingX -= textSize.x;
float textY = centerY - textSize.y * 0.5f;
drawList->AddText(ImVec2(trailingX, textY), secondaryColor, spec.trailingText);
trailingX -= spacing::dp(1);
}
if (spec.trailingIcon) {
ImVec2 iconSize = ImGui::CalcTextSize(spec.trailingIcon);
trailingX -= 24.0f;
float iconY = centerY - iconSize.y * 0.5f;
drawList->AddText(ImVec2(trailingX, iconY), OnSurfaceMedium(), spec.trailingIcon);
}
// Draw divider
if (spec.dividerBelow) {
float dividerY = bb.Max.y - 0.5f;
drawList->AddLine(
ImVec2(bb.Min.x + leftPadding, dividerY),
ImVec2(bb.Max.x, dividerY),
OnSurfaceDisabled()
);
}
return pressed;
}
inline bool ListItem(const char* text) {
ListItemSpec spec;
spec.primaryText = text;
return ListItem(spec);
}
inline bool ListItem(const char* primary, const char* secondary) {
ListItemSpec spec;
spec.primaryText = primary;
spec.secondaryText = secondary;
return ListItem(spec);
}
inline void ListDivider(bool inset) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
float width = ImGui::GetContentRegionAvail().x;
float leftOffset = inset ? 72.0f : 0; // Align with text after avatar/icon
ImVec2 pos = window->DC.CursorPos;
ImDrawList* drawList = window->DrawList;
drawList->AddLine(
ImVec2(pos.x + leftOffset, pos.y),
ImVec2(pos.x + width, pos.y),
OnSurfaceDisabled()
);
ImGui::Dummy(ImVec2(width, 1.0f));
}
inline void ListSubheader(const char* text) {
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp top padding
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + spacing::dp(2)); // 16dp left padding
Typography::instance().textColored(TypeStyle::Caption, Primary(), text);
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp bottom padding
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,379 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Navigation Drawer Component
// ============================================================================
// Based on https://m2.material.io/components/navigation-drawer
//
// Navigation drawers provide access to destinations in your app.
enum class NavDrawerType {
Standard, // Permanent, always visible
Modal, // Overlay with scrim, can be dismissed
Dismissible // Can be shown/hidden, no scrim
};
/**
* @brief Navigation drawer configuration
*/
struct NavDrawerSpec {
NavDrawerType type = NavDrawerType::Standard;
float width = 256.0f; // 256dp standard width
bool showDividerBottom = true; // Divider at bottom
const char* headerTitle = nullptr; // Optional header title
const char* headerSubtitle = nullptr;
};
/**
* @brief Navigation item configuration
*/
struct NavItemSpec {
const char* icon = nullptr; // Leading icon
const char* label = nullptr; // Item label (required)
bool selected = false; // Selected state
bool disabled = false;
int badgeCount = 0; // Badge (0 = no badge)
};
/**
* @brief Begin a navigation drawer
*
* @param id Unique identifier
* @param open Pointer to open state (for modal/dismissible)
* @param spec Drawer configuration
* @return true if drawer is visible
*/
bool BeginNavDrawer(const char* id, bool* open, const NavDrawerSpec& spec = NavDrawerSpec());
/**
* @brief Begin standard (always visible) navigation drawer
*/
bool BeginNavDrawer(const char* id, const NavDrawerSpec& spec = NavDrawerSpec());
/**
* @brief End navigation drawer
*/
void EndNavDrawer();
/**
* @brief Render a navigation item
*
* @param spec Item configuration
* @return true if clicked
*/
bool NavItem(const NavItemSpec& spec);
/**
* @brief Simple navigation item
*/
bool NavItem(const char* icon, const char* label, bool selected = false);
/**
* @brief Navigation divider
*/
void NavDivider();
/**
* @brief Navigation subheader
*/
void NavSubheader(const char* text);
// ============================================================================
// Implementation
// ============================================================================
struct NavDrawerState {
float width;
ImVec2 contentMin;
ImVec2 contentMax;
bool isModal;
};
static NavDrawerState g_navDrawerState;
inline bool BeginNavDrawer(const char* id, bool* open, const NavDrawerSpec& spec) {
// For modal drawers, check open state
if (spec.type == NavDrawerType::Modal && !*open) {
return false;
}
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(id);
g_navDrawerState.width = spec.width;
g_navDrawerState.isModal = (spec.type == NavDrawerType::Modal);
ImGuiIO& io = ImGui::GetIO();
ImDrawList* drawList = window->DrawList;
// For modal, draw scrim and handle dismiss
if (spec.type == NavDrawerType::Modal) {
ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList();
bgDrawList->AddRectFilled(
ImVec2(0, 0), io.DisplaySize,
schema::UI().resolveColor("var(--scrim)", IM_COL32(0, 0, 0, (int)(0.32f * 255)))
);
// Click outside to dismiss
if (ImGui::IsMouseClicked(0)) {
ImVec2 mousePos = io.MousePos;
if (mousePos.x > spec.width) {
*open = false;
}
}
}
// Drawer position and size
ImVec2 drawerPos(0, 0);
ImVec2 drawerSize(spec.width, io.DisplaySize.y);
// If not modal, account for app bar
if (spec.type != NavDrawerType::Modal) {
drawerPos.y = size::AppBarHeight;
drawerSize.y = io.DisplaySize.y - size::AppBarHeight;
}
ImRect drawerBB(drawerPos, ImVec2(drawerPos.x + drawerSize.x, drawerPos.y + drawerSize.y));
// Draw drawer background
ImU32 bgColor = Surface(Elevation::Dp16);
drawList->AddRectFilled(drawerBB.Min, drawerBB.Max, bgColor);
// Store content region
g_navDrawerState.contentMin = ImVec2(drawerBB.Min.x, drawerBB.Min.y);
g_navDrawerState.contentMax = drawerBB.Max;
// Header
float currentY = drawerBB.Min.y;
if (spec.headerTitle || spec.headerSubtitle) {
// Header area (optional)
float headerHeight = 64.0f;
ImVec2 headerMin(drawerBB.Min.x, currentY);
ImVec2 headerMax(drawerBB.Max.x, currentY + headerHeight);
// Header background (slightly elevated)
drawList->AddRectFilled(headerMin, headerMax, Surface(Elevation::Dp16));
// Header title
if (spec.headerTitle) {
ImGui::SetCursorScreenPos(ImVec2(drawerBB.Min.x + spacing::dp(2), currentY + 20.0f));
Typography::instance().text(TypeStyle::H6, spec.headerTitle);
}
if (spec.headerSubtitle) {
ImGui::SetCursorScreenPos(ImVec2(drawerBB.Min.x + spacing::dp(2), currentY + 42.0f));
Typography::instance().textColored(TypeStyle::Body2, OnSurfaceMedium(), spec.headerSubtitle);
}
currentY += headerHeight;
// Divider under header
drawList->AddLine(
ImVec2(drawerBB.Min.x, currentY),
ImVec2(drawerBB.Max.x, currentY),
OnSurfaceDisabled()
);
}
// Set cursor for nav items
ImGui::SetCursorScreenPos(ImVec2(drawerBB.Min.x, currentY + spacing::dp(1)));
ImGui::BeginGroup();
return true;
}
inline bool BeginNavDrawer(const char* id, const NavDrawerSpec& spec) {
static bool alwaysOpen = true;
NavDrawerSpec standardSpec = spec;
standardSpec.type = NavDrawerType::Standard;
return BeginNavDrawer(id, &alwaysOpen, standardSpec);
}
inline void EndNavDrawer() {
ImGui::EndGroup();
// Divider at bottom if configured
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImDrawList* drawList = window->DrawList;
// Right edge divider
drawList->AddLine(
ImVec2(g_navDrawerState.contentMax.x - 1, g_navDrawerState.contentMin.y),
ImVec2(g_navDrawerState.contentMax.x - 1, g_navDrawerState.contentMax.y),
OnSurfaceDisabled()
);
ImGui::PopID();
}
inline bool NavItem(const NavItemSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(spec.label);
// Item dimensions
const float itemHeight = 48.0f;
const float iconSize = 24.0f;
const float horizontalPadding = spacing::dp(2); // 16dp
const float iconLabelGap = spacing::dp(4); // 32dp from left edge to label
float itemWidth = g_navDrawerState.width - spacing::dp(1); // 8dp margin right
ImVec2 pos = window->DC.CursorPos;
pos.x += spacing::dp(1); // 8dp margin left
ImRect itemBB(pos, ImVec2(pos.x + itemWidth, pos.y + itemHeight));
// Interaction
ImGuiID id = window->GetID("##navitem");
ImGui::ItemSize(ImRect(window->DC.CursorPos, ImVec2(window->DC.CursorPos.x + g_navDrawerState.width, window->DC.CursorPos.y + itemHeight)));
if (!ImGui::ItemAdd(itemBB, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(itemBB, id, &hovered, &held) && !spec.disabled;
// Draw background
ImDrawList* drawList = window->DrawList;
ImU32 bgColor = 0;
if (spec.selected) {
bgColor = WithAlpha(Primary(), 30); // Primary at ~12%
} else if (held && !spec.disabled) {
bgColor = schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25));
} else if (hovered && !spec.disabled) {
bgColor = schema::UI().resolveColor("var(--active-overlay)", IM_COL32(255, 255, 255, 10));
}
if (bgColor) {
drawList->AddRectFilled(itemBB.Min, itemBB.Max, bgColor, size::ButtonCornerRadius);
}
// Selected indicator (left edge)
if (spec.selected) {
drawList->AddRectFilled(
ImVec2(itemBB.Min.x, itemBB.Min.y + 8.0f),
ImVec2(itemBB.Min.x + 4.0f, itemBB.Max.y - 8.0f),
Primary(), 2.0f
);
}
// Content
float contentX = pos.x + horizontalPadding;
float centerY = pos.y + itemHeight * 0.5f;
ImU32 iconColor = spec.disabled ? OnSurfaceDisabled() :
spec.selected ? Primary() : OnSurfaceMedium();
ImU32 labelColor = spec.disabled ? OnSurfaceDisabled() :
spec.selected ? Primary() : OnSurface();
// Icon
if (spec.icon) {
drawList->AddText(
ImVec2(contentX, centerY - iconSize * 0.5f),
iconColor, spec.icon
);
contentX += iconSize + spacing::dp(2); // 16dp gap after icon
}
// Label
Typography::instance().pushFont(TypeStyle::Body1);
float labelY = centerY - ImGui::GetFontSize() * 0.5f;
drawList->AddText(ImVec2(contentX, labelY), labelColor, spec.label);
Typography::instance().popFont();
// Badge
if (spec.badgeCount > 0) {
char badgeText[8];
if (spec.badgeCount > 999) {
snprintf(badgeText, sizeof(badgeText), "999+");
} else {
snprintf(badgeText, sizeof(badgeText), "%d", spec.badgeCount);
}
ImVec2 badgeSize = ImGui::CalcTextSize(badgeText);
float badgeWidth = ImMax(24.0f, badgeSize.x + 12.0f);
float badgeHeight = 20.0f;
float badgeX = itemBB.Max.x - horizontalPadding - badgeWidth;
float badgeY = centerY - badgeHeight * 0.5f;
drawList->AddRectFilled(
ImVec2(badgeX, badgeY),
ImVec2(badgeX + badgeWidth, badgeY + badgeHeight),
Primary(), badgeHeight * 0.5f
);
Typography::instance().pushFont(TypeStyle::Caption);
ImVec2 textPos(badgeX + (badgeWidth - badgeSize.x) * 0.5f, badgeY + (badgeHeight - badgeSize.y) * 0.5f);
drawList->AddText(textPos, OnPrimary(), badgeText);
Typography::instance().popFont();
}
ImGui::PopID();
return pressed;
}
inline bool NavItem(const char* icon, const char* label, bool selected) {
NavItemSpec spec;
spec.icon = icon;
spec.label = label;
spec.selected = selected;
return NavItem(spec);
}
inline void NavDivider() {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
ImVec2 pos = window->DC.CursorPos;
ImDrawList* drawList = window->DrawList;
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp spacing above
drawList->AddLine(
ImVec2(pos.x + spacing::dp(2), pos.y + spacing::dp(1)),
ImVec2(pos.x + g_navDrawerState.width - spacing::dp(2), pos.y + spacing::dp(1)),
OnSurfaceDisabled()
);
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp spacing below
}
inline void NavSubheader(const char* text) {
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp above
ImVec2 pos = ImGui::GetCursorScreenPos();
ImGui::SetCursorScreenPos(ImVec2(pos.x + spacing::dp(2), pos.y));
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), text);
ImGui::Dummy(ImVec2(0, spacing::dp(1))); // 8dp below
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,303 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Progress Indicators
// ============================================================================
// Based on https://m2.material.io/components/progress-indicators
//
// Progress indicators express an unspecified wait time or display the length
// of a process.
// ============================================================================
// Linear Progress
// ============================================================================
/**
* @brief Determinate linear progress bar
*
* @param fraction Progress value 0.0 to 1.0
* @param width Width of bar (0 = full available width)
*/
void LinearProgress(float fraction, float width = 0);
/**
* @brief Indeterminate linear progress bar (animated)
*
* @param width Width of bar (0 = full available width)
*/
void LinearProgressIndeterminate(float width = 0);
/**
* @brief Buffer linear progress bar
*
* @param fraction Primary progress 0.0 to 1.0
* @param buffer Buffer progress 0.0 to 1.0
* @param width Width of bar (0 = full available width)
*/
void LinearProgressBuffer(float fraction, float buffer, float width = 0);
// ============================================================================
// Circular Progress
// ============================================================================
/**
* @brief Determinate circular progress indicator
*
* @param fraction Progress value 0.0 to 1.0
* @param radius Radius of circle (default 20dp)
*/
void CircularProgress(float fraction, float radius = 20.0f);
/**
* @brief Indeterminate circular progress (spinner)
*
* @param radius Radius of circle (default 20dp)
*/
void CircularProgressIndeterminate(float radius = 20.0f);
// ============================================================================
// Implementation
// ============================================================================
inline void LinearProgress(float fraction, float width) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
const float barHeight = 4.0f; // Material spec: 4dp height
float barWidth = width > 0 ? width : ImGui::GetContentRegionAvail().x;
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + barWidth, pos.y + barHeight));
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, 0))
return;
ImDrawList* drawList = window->DrawList;
// Track (background)
ImU32 trackColor = WithAlpha(Primary(), 64); // Primary at 25%
drawList->AddRectFilled(bb.Min, bb.Max, trackColor, 0);
// Progress indicator
float progressWidth = barWidth * ImClamp(fraction, 0.0f, 1.0f);
if (progressWidth > 0) {
drawList->AddRectFilled(
bb.Min,
ImVec2(bb.Min.x + progressWidth, bb.Max.y),
Primary(), 0
);
}
}
inline void LinearProgressIndeterminate(float width) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
const float barHeight = 4.0f;
float barWidth = width > 0 ? width : ImGui::GetContentRegionAvail().x;
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + barWidth, pos.y + barHeight));
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, 0))
return;
ImDrawList* drawList = window->DrawList;
// Track
ImU32 trackColor = WithAlpha(Primary(), 64);
drawList->AddRectFilled(bb.Min, bb.Max, trackColor, 0);
// Animated indicator - sliding back and forth
float time = (float)ImGui::GetTime();
float cycleTime = fmodf(time, 2.0f); // 2 second cycle
// Two bars: primary and secondary with different phases
float indicatorWidth = barWidth * 0.3f; // 30% of track
// Primary indicator
float primaryPhase = fmodf(time * 1.2f, 2.0f);
float primaryPos;
if (primaryPhase < 1.0f) {
// Accelerating from left
primaryPos = primaryPhase * primaryPhase * (barWidth + indicatorWidth) - indicatorWidth;
} else {
// Continue off right (reset happens at 2.0)
primaryPos = (2.0f - primaryPhase) * (2.0f - primaryPhase) * -(barWidth + indicatorWidth) + barWidth;
}
float primaryStart = ImMax(bb.Min.x, bb.Min.x + primaryPos);
float primaryEnd = ImMin(bb.Max.x, bb.Min.x + primaryPos + indicatorWidth);
if (primaryEnd > primaryStart) {
drawList->AddRectFilled(
ImVec2(primaryStart, bb.Min.y),
ImVec2(primaryEnd, bb.Max.y),
Primary(), 0
);
}
}
inline void LinearProgressBuffer(float fraction, float buffer, float width) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
const float barHeight = 4.0f;
float barWidth = width > 0 ? width : ImGui::GetContentRegionAvail().x;
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + barWidth, pos.y + barHeight));
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, 0))
return;
ImDrawList* drawList = window->DrawList;
// Track
ImU32 trackColor = WithAlpha(Primary(), 38); // Primary at 15%
drawList->AddRectFilled(bb.Min, bb.Max, trackColor, 0);
// Buffer (lighter than progress)
float bufferWidth = barWidth * ImClamp(buffer, 0.0f, 1.0f);
if (bufferWidth > 0) {
drawList->AddRectFilled(
bb.Min,
ImVec2(bb.Min.x + bufferWidth, bb.Max.y),
WithAlpha(Primary(), 102), 0 // Primary at 40%
);
}
// Progress
float progressWidth = barWidth * ImClamp(fraction, 0.0f, 1.0f);
if (progressWidth > 0) {
drawList->AddRectFilled(
bb.Min,
ImVec2(bb.Min.x + progressWidth, bb.Max.y),
Primary(), 0
);
}
}
inline void CircularProgress(float fraction, float radius) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
const float thickness = 4.0f; // Stroke width
float diameter = radius * 2;
ImVec2 pos = window->DC.CursorPos;
ImVec2 center(pos.x + radius, pos.y + radius);
ImRect bb(pos, ImVec2(pos.x + diameter, pos.y + diameter));
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, 0))
return;
ImDrawList* drawList = window->DrawList;
// Track circle
ImU32 trackColor = WithAlpha(Primary(), 64);
drawList->AddCircle(center, radius - thickness * 0.5f, trackColor, 0, thickness);
// Progress arc
float clampedFraction = ImClamp(fraction, 0.0f, 1.0f);
if (clampedFraction > 0) {
float startAngle = -IM_PI * 0.5f; // Start at top (12 o'clock)
float endAngle = startAngle + IM_PI * 2.0f * clampedFraction;
// Draw arc as line segments
const int segments = (int)(32 * clampedFraction) + 1;
float angleStep = (endAngle - startAngle) / segments;
for (int i = 0; i < segments; i++) {
float a1 = startAngle + angleStep * i;
float a2 = startAngle + angleStep * (i + 1);
ImVec2 p1(center.x + cosf(a1) * (radius - thickness * 0.5f),
center.y + sinf(a1) * (radius - thickness * 0.5f));
ImVec2 p2(center.x + cosf(a2) * (radius - thickness * 0.5f),
center.y + sinf(a2) * (radius - thickness * 0.5f));
drawList->AddLine(p1, p2, Primary(), thickness);
}
}
}
inline void CircularProgressIndeterminate(float radius) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
const float thickness = 4.0f;
float diameter = radius * 2;
ImVec2 pos = window->DC.CursorPos;
ImVec2 center(pos.x + radius, pos.y + radius);
ImRect bb(pos, ImVec2(pos.x + diameter, pos.y + diameter));
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, 0))
return;
ImDrawList* drawList = window->DrawList;
float time = (float)ImGui::GetTime();
// Rotation animation
float rotation = fmodf(time * 2.0f * IM_PI / 1.4f, IM_PI * 2.0f); // ~1.4s rotation
// Arc length animation (grows and shrinks)
float cycleTime = fmodf(time, 1.333f); // ~1.333s cycle
float arcLength;
if (cycleTime < 0.666f) {
// Growing phase
arcLength = (cycleTime / 0.666f) * 0.75f + 0.1f; // 10% to 85%
} else {
// Shrinking phase
arcLength = ((1.333f - cycleTime) / 0.666f) * 0.75f + 0.1f;
}
float startAngle = rotation - IM_PI * 0.5f;
float endAngle = startAngle + IM_PI * 2.0f * arcLength;
// Draw arc
const int segments = (int)(32 * arcLength) + 1;
float angleStep = (endAngle - startAngle) / segments;
for (int i = 0; i < segments; i++) {
float a1 = startAngle + angleStep * i;
float a2 = startAngle + angleStep * (i + 1);
ImVec2 p1(center.x + cosf(a1) * (radius - thickness * 0.5f),
center.y + sinf(a1) * (radius - thickness * 0.5f));
ImVec2 p2(center.x + cosf(a2) * (radius - thickness * 0.5f),
center.y + sinf(a2) * (radius - thickness * 0.5f));
drawList->AddLine(p1, p2, Primary(), thickness);
}
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,402 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Slider Component
// ============================================================================
// Based on https://m2.material.io/components/sliders
//
// Sliders allow users to make selections from a range of values.
/**
* @brief Continuous slider
*
* @param label Label for the slider (hidden, used for ID)
* @param value Pointer to current value
* @param minValue Minimum value
* @param maxValue Maximum value
* @param format Printf format for value display (nullptr = no display)
* @param width Slider width (0 = full available)
* @return true if value changed
*/
bool Slider(const char* label, float* value, float minValue, float maxValue,
const char* format = nullptr, float width = 0);
/**
* @brief Integer slider
*/
bool SliderInt(const char* label, int* value, int minValue, int maxValue,
const char* format = nullptr, float width = 0);
/**
* @brief Discrete slider with steps
*
* @param label Label for the slider
* @param value Pointer to current value
* @param minValue Minimum value
* @param maxValue Maximum value
* @param step Step size
* @param showTicks Show tick marks
* @return true if value changed
*/
bool SliderDiscrete(const char* label, float* value, float minValue, float maxValue,
float step, bool showTicks = true, float width = 0);
/**
* @brief Range slider (two thumbs)
*
* @param label Label for the slider
* @param minVal Pointer to range minimum
* @param maxVal Pointer to range maximum
* @param rangeMin Allowed minimum
* @param rangeMax Allowed maximum
* @return true if either value changed
*/
bool SliderRange(const char* label, float* minVal, float* maxVal,
float rangeMin, float rangeMax, float width = 0);
// ============================================================================
// Implementation
// ============================================================================
inline bool Slider(const char* label, float* value, float minValue, float maxValue,
const char* format, float width) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Slider dimensions
const float trackHeight = 4.0f;
const float thumbRadius = 10.0f; // 20dp diameter
float sliderWidth = width > 0 ? width : ImGui::GetContentRegionAvail().x;
float totalHeight = size::TouchTarget; // 48dp touch target
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + sliderWidth, pos.y + totalHeight));
// Item interaction
ImGuiID id = window->GetID("##slider");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
// Calculate thumb position
float trackLeft = pos.x + thumbRadius;
float trackRight = pos.x + sliderWidth - thumbRadius;
float trackWidth = trackRight - trackLeft;
float centerY = pos.y + totalHeight * 0.5f;
float fraction = (*value - minValue) / (maxValue - minValue);
fraction = ImClamp(fraction, 0.0f, 1.0f);
float thumbX = trackLeft + trackWidth * fraction;
// Handle dragging
bool changed = false;
if (held) {
float mouseX = ImGui::GetIO().MousePos.x;
float newFraction = (mouseX - trackLeft) / trackWidth;
newFraction = ImClamp(newFraction, 0.0f, 1.0f);
float newValue = minValue + newFraction * (maxValue - minValue);
if (newValue != *value) {
*value = newValue;
changed = true;
}
thumbX = trackLeft + trackWidth * newFraction;
}
// Draw
ImDrawList* drawList = window->DrawList;
// Track (inactive part)
ImU32 trackInactiveColor = WithAlpha(Primary(), 64); // Primary at 25%
drawList->AddRectFilled(
ImVec2(trackLeft, centerY - trackHeight * 0.5f),
ImVec2(trackRight, centerY + trackHeight * 0.5f),
trackInactiveColor, trackHeight * 0.5f
);
// Track (active part)
drawList->AddRectFilled(
ImVec2(trackLeft, centerY - trackHeight * 0.5f),
ImVec2(thumbX, centerY + trackHeight * 0.5f),
Primary(), trackHeight * 0.5f
);
// Thumb shadow
drawList->AddCircleFilled(ImVec2(thumbX + 1, centerY + 2), thumbRadius, schema::UI().resolveColor("var(--control-shadow)", IM_COL32(0, 0, 0, 60)));
// Thumb
drawList->AddCircleFilled(ImVec2(thumbX, centerY), thumbRadius, Primary());
// Hover/pressed ripple
if (hovered || held) {
ImU32 rippleColor = WithAlpha(Primary(), held ? 51 : 25);
drawList->AddCircleFilled(ImVec2(thumbX, centerY), thumbRadius + 12.0f, rippleColor);
}
// Value label (when held)
if (held && format) {
char valueText[64];
snprintf(valueText, sizeof(valueText), format, *value);
ImVec2 textSize = ImGui::CalcTextSize(valueText);
float labelY = centerY - thumbRadius - 32.0f;
float labelX = thumbX - textSize.x * 0.5f;
// Label background (rounded rectangle)
float labelPadX = 8.0f;
float labelPadY = 4.0f;
ImVec2 labelMin(labelX - labelPadX, labelY - labelPadY);
ImVec2 labelMax(labelX + textSize.x + labelPadX, labelY + textSize.y + labelPadY);
drawList->AddRectFilled(labelMin, labelMax, Primary(), 4.0f);
drawList->AddText(ImVec2(labelX, labelY), OnPrimary(), valueText);
}
ImGui::PopID();
return changed;
}
inline bool SliderInt(const char* label, int* value, int minValue, int maxValue,
const char* format, float width) {
float floatVal = (float)*value;
bool changed = Slider(label, &floatVal, (float)minValue, (float)maxValue, format, width);
if (changed) {
*value = (int)roundf(floatVal);
}
return changed;
}
inline bool SliderDiscrete(const char* label, float* value, float minValue, float maxValue,
float step, bool showTicks, float width) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
const float trackHeight = 4.0f;
const float thumbRadius = 10.0f;
const float tickRadius = 2.0f;
float sliderWidth = width > 0 ? width : ImGui::GetContentRegionAvail().x;
float totalHeight = size::TouchTarget;
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + sliderWidth, pos.y + totalHeight));
ImGuiID id = window->GetID("##slider");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
ImGui::ButtonBehavior(bb, id, &hovered, &held);
float trackLeft = pos.x + thumbRadius;
float trackRight = pos.x + sliderWidth - thumbRadius;
float trackWidth = trackRight - trackLeft;
float centerY = pos.y + totalHeight * 0.5f;
// Snap to step
float snappedValue = roundf((*value - minValue) / step) * step + minValue;
snappedValue = ImClamp(snappedValue, minValue, maxValue);
float fraction = (snappedValue - minValue) / (maxValue - minValue);
float thumbX = trackLeft + trackWidth * fraction;
bool changed = false;
if (held) {
float mouseX = ImGui::GetIO().MousePos.x;
float newFraction = (mouseX - trackLeft) / trackWidth;
newFraction = ImClamp(newFraction, 0.0f, 1.0f);
float rawValue = minValue + newFraction * (maxValue - minValue);
float newValue = roundf((rawValue - minValue) / step) * step + minValue;
newValue = ImClamp(newValue, minValue, maxValue);
if (newValue != *value) {
*value = newValue;
changed = true;
}
fraction = (newValue - minValue) / (maxValue - minValue);
thumbX = trackLeft + trackWidth * fraction;
}
ImDrawList* drawList = window->DrawList;
// Track
drawList->AddRectFilled(
ImVec2(trackLeft, centerY - trackHeight * 0.5f),
ImVec2(trackRight, centerY + trackHeight * 0.5f),
WithAlpha(Primary(), 64), trackHeight * 0.5f
);
drawList->AddRectFilled(
ImVec2(trackLeft, centerY - trackHeight * 0.5f),
ImVec2(thumbX, centerY + trackHeight * 0.5f),
Primary(), trackHeight * 0.5f
);
// Tick marks
if (showTicks) {
int numSteps = (int)((maxValue - minValue) / step);
for (int i = 0; i <= numSteps; i++) {
float tickFraction = (float)i / numSteps;
float tickX = trackLeft + trackWidth * tickFraction;
ImU32 tickColor = (tickX <= thumbX) ? OnPrimary() : WithAlpha(Primary(), 128);
drawList->AddCircleFilled(ImVec2(tickX, centerY), tickRadius, tickColor);
}
}
// Thumb
drawList->AddCircleFilled(ImVec2(thumbX + 1, centerY + 2), thumbRadius, schema::UI().resolveColor("var(--control-shadow)", IM_COL32(0, 0, 0, 60)));
drawList->AddCircleFilled(ImVec2(thumbX, centerY), thumbRadius, Primary());
if (hovered || held) {
ImU32 rippleColor = WithAlpha(Primary(), held ? 51 : 25);
drawList->AddCircleFilled(ImVec2(thumbX, centerY), thumbRadius + 12.0f, rippleColor);
}
ImGui::PopID();
return changed;
}
inline bool SliderRange(const char* label, float* minVal, float* maxVal,
float rangeMin, float rangeMax, float width) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
const float trackHeight = 4.0f;
const float thumbRadius = 10.0f;
float sliderWidth = width > 0 ? width : ImGui::GetContentRegionAvail().x;
float totalHeight = size::TouchTarget;
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + sliderWidth, pos.y + totalHeight));
ImGuiID id = window->GetID("##slider");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
float trackLeft = pos.x + thumbRadius;
float trackRight = pos.x + sliderWidth - thumbRadius;
float trackWidth = trackRight - trackLeft;
float centerY = pos.y + totalHeight * 0.5f;
float minFraction = (*minVal - rangeMin) / (rangeMax - rangeMin);
float maxFraction = (*maxVal - rangeMin) / (rangeMax - rangeMin);
float minThumbX = trackLeft + trackWidth * minFraction;
float maxThumbX = trackLeft + trackWidth * maxFraction;
// Hit test both thumbs
ImVec2 mousePos = ImGui::GetIO().MousePos;
float distToMin = fabsf(mousePos.x - minThumbX);
float distToMax = fabsf(mousePos.x - maxThumbX);
bool nearMin = distToMin < distToMax;
ImGuiID minId = window->GetID("##min");
ImGuiID maxId = window->GetID("##max");
bool minHovered, minHeld;
bool maxHovered, maxHeld;
ImRect minHitBox(ImVec2(minThumbX - thumbRadius - 8, centerY - thumbRadius - 8),
ImVec2(minThumbX + thumbRadius + 8, centerY + thumbRadius + 8));
ImRect maxHitBox(ImVec2(maxThumbX - thumbRadius - 8, centerY - thumbRadius - 8),
ImVec2(maxThumbX + thumbRadius + 8, centerY + thumbRadius + 8));
ImGui::ButtonBehavior(nearMin ? minHitBox : maxHitBox, nearMin ? minId : maxId,
nearMin ? &minHovered : &maxHovered,
nearMin ? &minHeld : &maxHeld);
bool changed = false;
if (minHeld) {
float newFraction = (mousePos.x - trackLeft) / trackWidth;
newFraction = ImClamp(newFraction, 0.0f, maxFraction - 0.01f);
float newValue = rangeMin + newFraction * (rangeMax - rangeMin);
if (newValue != *minVal) {
*minVal = newValue;
changed = true;
}
minThumbX = trackLeft + trackWidth * newFraction;
}
if (maxHeld) {
float newFraction = (mousePos.x - trackLeft) / trackWidth;
newFraction = ImClamp(newFraction, minFraction + 0.01f, 1.0f);
float newValue = rangeMin + newFraction * (rangeMax - rangeMin);
if (newValue != *maxVal) {
*maxVal = newValue;
changed = true;
}
maxThumbX = trackLeft + trackWidth * newFraction;
}
ImDrawList* drawList = window->DrawList;
// Inactive track
drawList->AddRectFilled(
ImVec2(trackLeft, centerY - trackHeight * 0.5f),
ImVec2(trackRight, centerY + trackHeight * 0.5f),
WithAlpha(Primary(), 64), trackHeight * 0.5f
);
// Active track (between thumbs)
drawList->AddRectFilled(
ImVec2(minThumbX, centerY - trackHeight * 0.5f),
ImVec2(maxThumbX, centerY + trackHeight * 0.5f),
Primary(), trackHeight * 0.5f
);
// Min thumb
drawList->AddCircleFilled(ImVec2(minThumbX + 1, centerY + 2), thumbRadius, schema::UI().resolveColor("var(--control-shadow)", IM_COL32(0, 0, 0, 60)));
drawList->AddCircleFilled(ImVec2(minThumbX, centerY), thumbRadius, Primary());
if (minHovered || minHeld) {
ImU32 rippleColor = WithAlpha(Primary(), minHeld ? 51 : 25);
drawList->AddCircleFilled(ImVec2(minThumbX, centerY), thumbRadius + 12.0f, rippleColor);
}
// Max thumb
drawList->AddCircleFilled(ImVec2(maxThumbX + 1, centerY + 2), thumbRadius, schema::UI().resolveColor("var(--control-shadow)", IM_COL32(0, 0, 0, 60)));
drawList->AddCircleFilled(ImVec2(maxThumbX, centerY), thumbRadius, Primary());
if (maxHovered || maxHeld) {
ImU32 rippleColor = WithAlpha(Primary(), maxHeld ? 51 : 25);
drawList->AddCircleFilled(ImVec2(maxThumbX, centerY), thumbRadius + 12.0f, rippleColor);
}
ImGui::PopID();
return changed;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,242 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "../draw_helpers.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Snackbar Component
// ============================================================================
// Based on https://m2.material.io/components/snackbars
//
// Snackbars provide brief messages about app processes at the bottom of the
// screen. They can include a single action.
/**
* @brief Snackbar configuration
*/
struct SnackbarSpec {
const char* message = nullptr; // Message text
const char* actionText = nullptr; // Optional action button text
float duration = 4.0f; // Duration in seconds (0 = indefinite)
bool multiLine = false; // Allow multi-line message
};
/**
* @brief Snackbar manager for showing notifications
*/
class Snackbar {
public:
static Snackbar& instance();
/**
* @brief Show a snackbar message
*
* @param message Message text
* @param actionText Optional action text
* @param duration Display duration (0 = until dismissed)
*/
void show(const char* message, const char* actionText = nullptr, float duration = 4.0f);
/**
* @brief Show a snackbar with full configuration
*/
void show(const SnackbarSpec& spec);
/**
* @brief Dismiss current snackbar
*/
void dismiss();
/**
* @brief Render snackbar (call each frame)
*
* @return true if action was clicked
*/
bool render();
/**
* @brief Check if snackbar is visible
*/
bool isVisible() const { return m_visible; }
private:
Snackbar() = default;
bool m_visible = false;
SnackbarSpec m_currentSpec;
float m_showTime = 0;
float m_animProgress = 0; // 0 = hidden, 1 = fully shown
};
// ============================================================================
// Convenience Functions
// ============================================================================
/**
* @brief Show a snackbar message
*/
inline void ShowSnackbar(const char* message, const char* action = nullptr, float duration = 4.0f) {
Snackbar::instance().show(message, action, duration);
}
/**
* @brief Dismiss current snackbar
*/
inline void DismissSnackbar() {
Snackbar::instance().dismiss();
}
/**
* @brief Render snackbar system (call once per frame in main render loop)
*
* @return true if action was clicked
*/
inline bool RenderSnackbar() {
return Snackbar::instance().render();
}
// ============================================================================
// Implementation
// ============================================================================
inline Snackbar& Snackbar::instance() {
static Snackbar s_instance;
return s_instance;
}
inline void Snackbar::show(const char* message, const char* actionText, float duration) {
SnackbarSpec spec;
spec.message = message;
spec.actionText = actionText;
spec.duration = duration;
show(spec);
}
inline void Snackbar::show(const SnackbarSpec& spec) {
m_currentSpec = spec;
m_visible = true;
m_showTime = (float)ImGui::GetTime();
m_animProgress = 0;
}
inline void Snackbar::dismiss() {
m_visible = false;
}
inline bool Snackbar::render() {
if (!m_visible && m_animProgress <= 0)
return false;
bool actionClicked = false;
float currentTime = (float)ImGui::GetTime();
// Check auto-dismiss
if (m_visible && m_currentSpec.duration > 0) {
if (currentTime - m_showTime > m_currentSpec.duration) {
m_visible = false;
}
}
// Animate in/out
float animTarget = m_visible ? 1.0f : 0.0f;
float animSpeed = 8.0f; // Animation speed
if (m_animProgress < animTarget) {
m_animProgress = ImMin(m_animProgress + ImGui::GetIO().DeltaTime * animSpeed, animTarget);
} else if (m_animProgress > animTarget) {
m_animProgress = ImMax(m_animProgress - ImGui::GetIO().DeltaTime * animSpeed, animTarget);
}
if (m_animProgress <= 0)
return false;
// Snackbar dimensions
const float snackbarHeight = m_currentSpec.multiLine ? 68.0f : 48.0f;
const float snackbarMinWidth = 344.0f;
const float snackbarMaxWidth = 672.0f;
const float margin = spacing::dp(3); // 24dp from edges
ImGuiIO& io = ImGui::GetIO();
// Calculate width based on content
float messageWidth = ImGui::CalcTextSize(m_currentSpec.message).x;
float actionWidth = m_currentSpec.actionText ?
ImGui::CalcTextSize(m_currentSpec.actionText).x + spacing::dp(2) : 0;
float contentWidth = messageWidth + actionWidth + spacing::dp(4); // 32dp padding
float snackbarWidth = ImClamp(contentWidth, snackbarMinWidth, snackbarMaxWidth);
// Position at bottom center
float bottomY = io.DisplaySize.y - margin - snackbarHeight;
float slideOffset = (1.0f - m_animProgress) * (snackbarHeight + margin);
ImVec2 snackbarPos(
(io.DisplaySize.x - snackbarWidth) * 0.5f,
bottomY + slideOffset
);
// Draw snackbar
ImDrawList* drawList = ImGui::GetForegroundDrawList();
// Background (elevation dp6 equivalent)
ImU32 snackBg = schema::UI().resolveColor("var(--snackbar-bg)", IM_COL32(50, 50, 50, 255));
ImU32 bgColor = ScaleAlpha(snackBg, m_animProgress);
ImVec2 snackbarMin = snackbarPos;
ImVec2 snackbarMax(snackbarPos.x + snackbarWidth, snackbarPos.y + snackbarHeight);
drawList->AddRectFilled(snackbarMin, snackbarMax, bgColor, 4.0f);
// Message text
float textY = snackbarPos.y + (snackbarHeight - ImGui::GetFontSize()) * 0.5f;
float textX = snackbarPos.x + spacing::dp(2); // 16dp left padding
ImU32 snackText = schema::UI().resolveColor("var(--snackbar-text)", IM_COL32(255, 255, 255, 222));
ImU32 textColor = ScaleAlpha(snackText, m_animProgress);
drawList->AddText(ImVec2(textX, textY), textColor, m_currentSpec.message);
// Action button
if (m_currentSpec.actionText) {
float actionX = snackbarMax.x - spacing::dp(2) - actionWidth;
// Hit test for action
ImVec2 actionMin(actionX, snackbarPos.y);
ImVec2 actionMax(snackbarMax.x, snackbarMax.y);
ImVec2 mousePos = io.MousePos;
bool hovered = (mousePos.x >= actionMin.x && mousePos.x < actionMax.x &&
mousePos.y >= actionMin.y && mousePos.y < actionMax.y);
// Action text color
ImU32 actionColor;
if (hovered) {
actionColor = ScaleAlpha(schema::UI().resolveColor("var(--snackbar-action-hover)", IM_COL32(255, 213, 79, 255)), m_animProgress);
} else {
actionColor = ScaleAlpha(schema::UI().resolveColor("var(--snackbar-action)", IM_COL32(255, 193, 7, 255)), m_animProgress);
}
drawList->AddText(ImVec2(actionX, textY), actionColor, m_currentSpec.actionText);
// Check click
if (hovered && io.MouseClicked[0]) {
actionClicked = true;
dismiss();
}
}
return actionClicked;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,319 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "../../schema/ui_schema.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Tabs Component
// ============================================================================
// Based on https://m2.material.io/components/tabs
//
// Tabs organize content across different screens, data sets, and other
// interactions.
/**
* @brief Tab bar configuration
*/
struct TabBarSpec {
bool scrollable = false; // Enable horizontal scrolling
bool fullWidth = true; // Tabs fill available width
bool showIndicator = true; // Show selection indicator
bool centered = false; // Center tabs (when not full width)
};
/**
* @brief Individual tab configuration
*/
struct TabSpec {
const char* label = nullptr;
const char* icon = nullptr; // Optional icon (text representation)
bool disabled = false;
int badgeCount = 0; // Badge count (0 = no badge)
};
/**
* @brief Begin a tab bar
*
* @param id Unique identifier
* @param selectedIndex Pointer to selected tab index
* @param spec Tab bar configuration
* @return true if tab bar is visible
*/
bool BeginTabBar(const char* id, int* selectedIndex, const TabBarSpec& spec = TabBarSpec());
/**
* @brief End a tab bar
*/
void EndTabBar();
/**
* @brief Add a tab to current tab bar
*
* @param spec Tab configuration
* @return true if this tab is selected
*/
bool Tab(const TabSpec& spec);
/**
* @brief Simple tab with just label
*/
bool Tab(const char* label);
/**
* @brief Simple tab bar - returns selected index
*
* @param id Unique identifier
* @param labels Array of tab labels
* @param count Number of tabs
* @param selectedIndex Current selected index (will be updated)
* @return true if selection changed
*/
bool TabBar(const char* id, const char** labels, int count, int* selectedIndex);
// ============================================================================
// Implementation
// ============================================================================
// Internal state for tab rendering
struct TabBarState {
int* selectedIndex;
int currentTabIndex;
TabBarSpec spec;
float tabBarWidth;
float tabWidth;
float indicatorX;
float indicatorWidth;
ImVec2 barPos;
};
static TabBarState g_tabBarState;
inline bool BeginTabBar(const char* id, int* selectedIndex, const TabBarSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(id);
g_tabBarState.selectedIndex = selectedIndex;
g_tabBarState.currentTabIndex = 0;
g_tabBarState.spec = spec;
g_tabBarState.tabBarWidth = ImGui::GetContentRegionAvail().x;
g_tabBarState.tabWidth = 0; // Will be calculated if fullWidth
g_tabBarState.barPos = window->DC.CursorPos;
g_tabBarState.indicatorX = 0;
g_tabBarState.indicatorWidth = 0;
// Reserve space for tab bar
float barHeight = size::TabBarHeight;
ImRect bb(g_tabBarState.barPos,
ImVec2(g_tabBarState.barPos.x + g_tabBarState.tabBarWidth,
g_tabBarState.barPos.y + barHeight));
ImGui::ItemSize(bb);
// Draw tab bar background
ImDrawList* drawList = window->DrawList;
drawList->AddRectFilled(bb.Min, bb.Max, Surface(Elevation::Dp4));
// Begin horizontal layout for tabs
ImGui::SetCursorScreenPos(g_tabBarState.barPos);
ImGui::BeginGroup();
return true;
}
inline void EndTabBar() {
ImGui::EndGroup();
// Draw indicator line
if (g_tabBarState.spec.showIndicator && g_tabBarState.indicatorWidth > 0) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImDrawList* drawList = window->DrawList;
float indicatorY = g_tabBarState.barPos.y + size::TabBarHeight - 2.0f;
drawList->AddRectFilled(
ImVec2(g_tabBarState.indicatorX, indicatorY),
ImVec2(g_tabBarState.indicatorX + g_tabBarState.indicatorWidth, indicatorY + 2.0f),
Primary()
);
}
// Add bottom divider
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImDrawList* drawList = window->DrawList;
float dividerY = g_tabBarState.barPos.y + size::TabBarHeight;
drawList->AddLine(
ImVec2(g_tabBarState.barPos.x, dividerY),
ImVec2(g_tabBarState.barPos.x + g_tabBarState.tabBarWidth, dividerY),
OnSurfaceDisabled()
);
ImGui::PopID();
}
inline bool Tab(const TabSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
int tabIndex = g_tabBarState.currentTabIndex++;
bool isSelected = (*g_tabBarState.selectedIndex == tabIndex);
// Calculate tab dimensions
float minTabWidth = spec.icon ? 72.0f : 90.0f; // Material min widths
float maxTabWidth = 360.0f;
float labelWidth = ImGui::CalcTextSize(spec.label).x;
float iconWidth = spec.icon ? 24.0f + spacing::dp(1) : 0;
float contentWidth = labelWidth + iconWidth + spacing::dp(4); // 32dp padding
float tabWidth;
if (g_tabBarState.spec.fullWidth) {
// Divide evenly (assuming we don't know total count here - simplified)
tabWidth = ImMax(minTabWidth, contentWidth);
} else {
tabWidth = ImClamp(contentWidth, minTabWidth, maxTabWidth);
}
float tabHeight = size::TabBarHeight;
ImVec2 tabPos = window->DC.CursorPos;
ImRect tabBB(tabPos, ImVec2(tabPos.x + tabWidth, tabPos.y + tabHeight));
// Interaction
ImGuiID id = window->GetID(spec.label);
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(tabBB, id, &hovered, &held) && !spec.disabled;
if (pressed && !isSelected) {
*g_tabBarState.selectedIndex = tabIndex;
}
// Update indicator position for selected tab
if (isSelected) {
g_tabBarState.indicatorX = tabPos.x;
g_tabBarState.indicatorWidth = tabWidth;
}
// Draw
ImDrawList* drawList = window->DrawList;
// Hover/press state overlay
if (!spec.disabled) {
if (held) {
drawList->AddRectFilled(tabBB.Min, tabBB.Max, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
} else if (hovered) {
drawList->AddRectFilled(tabBB.Min, tabBB.Max, schema::UI().resolveColor("var(--active-overlay)", IM_COL32(255, 255, 255, 10)));
}
}
// Content color
ImU32 contentColor;
if (spec.disabled) {
contentColor = OnSurfaceDisabled();
} else if (isSelected) {
contentColor = Primary();
} else {
contentColor = OnSurfaceMedium();
}
// Draw content (icon and/or label)
float contentX = tabPos.x + (tabWidth - labelWidth - iconWidth) * 0.5f;
float centerY = tabPos.y + tabHeight * 0.5f;
if (spec.icon) {
ImFont* iconFont = Type().iconMed();
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, spec.icon);
ImVec2 iconPos(contentX, centerY - iconSize.y * 0.5f);
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, contentColor, spec.icon);
contentX += iconSize.x + spacing::Xs;
}
// Label (uppercase)
Typography::instance().pushFont(TypeStyle::Button);
float labelY = centerY - ImGui::GetFontSize() * 0.5f;
// Convert to uppercase
char upperLabel[128];
size_t i = 0;
for (const char* p = spec.label; *p && i < sizeof(upperLabel) - 1; p++, i++) {
upperLabel[i] = (*p >= 'a' && *p <= 'z') ? (*p - 32) : *p;
}
upperLabel[i] = '\0';
drawList->AddText(ImVec2(contentX, labelY), contentColor, upperLabel);
Typography::instance().popFont();
// Badge
if (spec.badgeCount > 0) {
float badgeX = tabPos.x + tabWidth - 16.0f;
float badgeY = tabPos.y + 8.0f;
float badgeRadius = 8.0f;
drawList->AddCircleFilled(ImVec2(badgeX, badgeY), badgeRadius, Error());
char badgeText[8];
if (spec.badgeCount > 99) {
snprintf(badgeText, sizeof(badgeText), "99+");
} else {
snprintf(badgeText, sizeof(badgeText), "%d", spec.badgeCount);
}
ImVec2 badgeTextSize = ImGui::CalcTextSize(badgeText);
ImVec2 badgeTextPos(badgeX - badgeTextSize.x * 0.5f, badgeY - badgeTextSize.y * 0.5f);
Typography::instance().pushFont(TypeStyle::Caption);
drawList->AddText(badgeTextPos, OnError(), badgeText);
Typography::instance().popFont();
}
// Advance cursor
ImGui::SameLine(0, 0);
ImGui::SetCursorScreenPos(ImVec2(tabPos.x + tabWidth, tabPos.y));
return isSelected;
}
inline bool Tab(const char* label) {
TabSpec spec;
spec.label = label;
return Tab(spec);
}
inline bool TabBar(const char* id, const char** labels, int count, int* selectedIndex) {
int oldIndex = *selectedIndex;
TabBarSpec spec;
spec.fullWidth = true;
if (BeginTabBar(id, selectedIndex, spec)) {
// Calculate tab width for full-width mode
float tabWidth = ImGui::GetContentRegionAvail().x / count;
for (int i = 0; i < count; i++) {
TabSpec tabSpec;
tabSpec.label = labels[i];
Tab(tabSpec);
}
EndTabBar();
}
return (*selectedIndex != oldIndex);
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,227 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "../colors.h"
#include "../typography.h"
#include "../layout.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Text Field Component
// ============================================================================
// Based on https://m2.material.io/components/text-fields
//
// Two variants:
// - Filled: Background fill with bottom line indicator
// - Outlined: Border around entire field
enum class TextFieldStyle {
Filled, // Background fill
Outlined // Border only
};
/**
* @brief Text field configuration
*/
struct TextFieldSpec {
TextFieldStyle style = TextFieldStyle::Outlined;
const char* label = nullptr; // Floating label text
const char* hint = nullptr; // Placeholder when empty
const char* helperText = nullptr; // Helper text below field
const char* errorText = nullptr; // Error message (shows in error state)
const char* prefix = nullptr; // Prefix text (e.g., "$")
const char* suffix = nullptr; // Suffix text (e.g., "DRGX")
bool password = false; // Mask input
bool readOnly = false; // Read-only field
bool multiline = false; // Multi-line text area
int multilineRows = 3; // Number of rows for multiline
float width = 0; // Width (0 = full available)
};
/**
* @brief Render a Material Design text field
*
* @param id Unique identifier
* @param buf Text buffer
* @param bufSize Buffer size
* @param spec Field configuration
* @return true if value changed
*/
bool TextField(const char* id, char* buf, size_t bufSize, const TextFieldSpec& spec = TextFieldSpec());
/**
* @brief Render a simple text field with label
*/
inline bool TextField(const char* label, char* buf, size_t bufSize) {
TextFieldSpec spec;
spec.label = label;
return TextField(label, buf, bufSize, spec);
}
// ============================================================================
// Implementation
// ============================================================================
inline bool TextField(const char* id, char* buf, size_t bufSize, const TextFieldSpec& spec) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(id);
bool hasError = (spec.errorText != nullptr);
bool hasValue = (buf[0] != '\0');
// Calculate dimensions
float fieldWidth = spec.width > 0 ? spec.width : ImGui::GetContentRegionAvail().x;
float fieldHeight = spec.multiline ?
(size::TextFieldHeight + (spec.multilineRows - 1) * Typography::instance().getFont(TypeStyle::Body1)->FontSize * 1.5f) :
size::TextFieldHeight;
ImVec2 pos = window->DC.CursorPos;
ImRect bb(pos, ImVec2(pos.x + fieldWidth, pos.y + fieldHeight));
// Interaction
ImGuiID inputId = window->GetID("##input");
bool focused = (ImGui::GetFocusID() == inputId);
// Colors
ImU32 bgColor, borderColor, labelColor;
if (hasError) {
borderColor = Error();
labelColor = Error();
} else if (focused) {
borderColor = Primary();
labelColor = Primary();
} else {
borderColor = OnSurfaceMedium();
labelColor = OnSurfaceMedium();
}
if (spec.style == TextFieldStyle::Filled) {
bgColor = GetElevatedSurface(GetCurrentColorTheme(), 1);
} else {
bgColor = 0; // Transparent for outlined
}
// Draw background/border
ImDrawList* drawList = window->DrawList;
if (spec.style == TextFieldStyle::Filled) {
// Filled style: background with bottom line
drawList->AddRectFilled(bb.Min, bb.Max, bgColor,
size::TextFieldCornerRadius, ImDrawFlags_RoundCornersTop);
// Bottom indicator line
float lineThickness = focused ? 2.0f : 1.0f;
drawList->AddLine(
ImVec2(bb.Min.x, bb.Max.y - lineThickness),
ImVec2(bb.Max.x, bb.Max.y - lineThickness),
borderColor, lineThickness
);
} else {
// Outlined style: border around entire field
float lineThickness = focused ? 2.0f : 1.0f;
drawList->AddRect(bb.Min, bb.Max, borderColor,
size::TextFieldCornerRadius, 0, lineThickness);
}
// Label (floating or inline)
bool labelFloating = focused || hasValue;
if (spec.label) {
ImVec2 labelPos;
TypeStyle labelStyle;
if (labelFloating) {
// Floating label (smaller, at top)
labelPos.x = bb.Min.x + size::TextFieldPadding;
labelPos.y = bb.Min.y + 4.0f;
labelStyle = TypeStyle::Caption;
} else {
// Inline label (body size, centered)
labelPos.x = bb.Min.x + size::TextFieldPadding;
labelPos.y = bb.Min.y + (fieldHeight - Typography::instance().getFont(TypeStyle::Body1)->FontSize) * 0.5f;
labelStyle = TypeStyle::Body1;
}
// For outlined style, need to clear background behind floating label
if (spec.style == TextFieldStyle::Outlined && labelFloating) {
ImVec2 labelSize = ImGui::CalcTextSize(spec.label);
ImVec2 clearMin(labelPos.x - 4.0f, bb.Min.y - 1.0f);
ImVec2 clearMax(labelPos.x + labelSize.x + 4.0f, bb.Min.y + Typography::instance().getFont(TypeStyle::Caption)->FontSize);
drawList->AddRectFilled(clearMin, clearMax, Background());
}
Typography::instance().pushFont(labelStyle);
drawList->AddText(labelPos, labelColor, spec.label);
Typography::instance().popFont();
}
// Input field
float inputY = spec.label && labelFloating ? bb.Min.y + 20.0f : bb.Min.y + 12.0f;
float inputHeight = bb.Max.y - inputY - 8.0f;
ImGui::SetCursorScreenPos(ImVec2(bb.Min.x + size::TextFieldPadding, inputY));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(OnSurface()));
ImGuiInputTextFlags flags = 0;
if (spec.password) flags |= ImGuiInputTextFlags_Password;
if (spec.readOnly) flags |= ImGuiInputTextFlags_ReadOnly;
float inputWidth = fieldWidth - size::TextFieldPadding * 2;
if (spec.prefix) {
ImGui::TextUnformatted(spec.prefix);
ImGui::SameLine();
inputWidth -= ImGui::CalcTextSize(spec.prefix).x + 4.0f;
}
ImGui::PushItemWidth(inputWidth);
bool changed;
if (spec.multiline) {
changed = ImGui::InputTextMultiline("##input", buf, bufSize,
ImVec2(inputWidth, inputHeight), flags);
} else {
changed = ImGui::InputText("##input", buf, bufSize, flags);
}
ImGui::PopItemWidth();
if (spec.suffix) {
ImGui::SameLine();
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", spec.suffix);
}
ImGui::PopStyleColor(2);
ImGui::PopStyleVar();
// Helper/Error text below field
ImGui::SetCursorScreenPos(ImVec2(bb.Min.x, bb.Max.y + 4.0f));
if (spec.errorText) {
Typography::instance().textColored(TypeStyle::Caption, Error(), spec.errorText);
ImGui::SetCursorScreenPos(ImVec2(bb.Min.x, bb.Max.y + 4.0f + Typography::instance().getFont(TypeStyle::Caption)->FontSize + 4.0f));
} else if (spec.helperText) {
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), spec.helperText);
ImGui::SetCursorScreenPos(ImVec2(bb.Min.x, bb.Max.y + 4.0f + Typography::instance().getFont(TypeStyle::Caption)->FontSize + 4.0f));
}
// Advance cursor
ImGui::SetCursorScreenPos(ImVec2(pos.x, bb.Max.y + (spec.errorText || spec.helperText ? 24.0f : 8.0f)));
ImGui::PopID();
return changed;
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,779 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "colors.h"
#include "type.h"
#include "../layout.h"
#include "../schema/element_styles.h"
#include "../schema/color_var_resolver.h"
#include "../schema/ui_schema.h"
#include "../effects/theme_effects.h"
#include "../effects/low_spec.h"
#include "../effects/imgui_acrylic.h"
#include "../theme.h"
#include "../../util/noise_texture.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <algorithm>
#include <unordered_map>
#include <cmath>
namespace dragonx {
namespace ui {
namespace material {
// Scale the alpha channel of an ImU32 color by a float factor.
inline ImU32 ScaleAlpha(ImU32 col, float scale) {
int a = static_cast<int>(((col >> IM_COL32_A_SHIFT) & 0xFF) * scale);
if (a > 255) a = 255;
return (col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
}
// ============================================================================
// Text Drop Shadow
// ============================================================================
// Draw text with a subtle dark shadow behind it for readability on
// translucent / glassy surfaces. The shadow is a 1px offset copy of
// the text in a near-black colour. When the OS backdrop (DWM Acrylic)
// is inactive, the shadow is skipped since opaque backgrounds already
// provide enough contrast.
inline void DrawTextShadow(ImDrawList* dl, ImFont* font, float fontSize,
const ImVec2& pos, ImU32 col, const char* text,
float offsetX = 1.0f, float offsetY = 1.0f,
ImU32 shadowCol = 0)
{
if (IsBackdropActive()) {
if (!shadowCol) {
static uint32_t s_gen = 0;
static ImU32 s_shadowCol = 0;
uint32_t g = schema::UI().generation();
if (g != s_gen) { s_gen = g; s_shadowCol = schema::UI().resolveColor("var(--text-shadow)", IM_COL32(0, 0, 0, 120)); }
shadowCol = s_shadowCol;
}
dl->AddText(font, fontSize,
ImVec2(pos.x + offsetX, pos.y + offsetY),
shadowCol, text);
}
dl->AddText(font, fontSize, pos, col, text);
}
// Convenience overload that uses the current default font.
inline void DrawTextShadow(ImDrawList* dl, const ImVec2& pos, ImU32 col,
const char* text,
float offsetX = 1.0f, float offsetY = 1.0f,
ImU32 shadowCol = 0)
{
if (IsBackdropActive()) {
if (!shadowCol) {
static uint32_t s_gen = 0;
static ImU32 s_shadowCol = 0;
uint32_t g = schema::UI().generation();
if (g != s_gen) { s_gen = g; s_shadowCol = schema::UI().resolveColor("var(--text-shadow)", IM_COL32(0, 0, 0, 120)); }
shadowCol = s_shadowCol;
}
dl->AddText(ImVec2(pos.x + offsetX, pos.y + offsetY),
shadowCol, text);
}
dl->AddText(pos, col, text);
}
// ============================================================================
// Modal-Aware Hover Check
// ============================================================================
// Drop-in replacement for ImGui::IsMouseHoveringRect that also respects
// modal popup blocking. The raw ImGui helper is a pure geometric test
// and will return true even when a modal popup covers the rect, which
// causes background elements to show hover highlights through dialogs.
inline bool IsRectHovered(const ImVec2& r_min, const ImVec2& r_max, bool clip = true)
{
if (!ImGui::IsMouseHoveringRect(r_min, r_max, clip))
return false;
// If a modal popup is open and it is not the current window, treat
// the content as non-hoverable (same logic ImGui uses internally
// inside IsWindowContentHoverable for modal blocking).
//
// We cannot rely solely on GetTopMostAndVisiblePopupModal() because
// it checks Active, which is only set when BeginPopupModal() is
// called in the current frame. Content tabs render BEFORE their
// associated dialogs, so the modal's Active flag is still false at
// this point. Checking WasActive (set from the previous frame)
// covers this render-order gap.
ImGuiContext& g = *ImGui::GetCurrentContext();
for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--) {
ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window;
if (popup && (popup->Flags & ImGuiWindowFlags_Modal)) {
if ((popup->Active || popup->WasActive) && !popup->Hidden) {
if (popup != ImGui::GetCurrentWindow())
return false;
}
}
}
return true;
}
// ============================================================================
// Tactile Button Overlay
// ============================================================================
// Adds a subtle top-edge highlight and bottom-edge shadow to a button rect,
// creating the illusion of a raised physical surface. On active (pressed),
// the highlight/shadow swap to create an inset "pushed" feel.
// Call this AFTER ImGui::Button() using GetItemRectMin()/GetItemRectMax().
inline void DrawTactileOverlay(ImDrawList* dl, const ImVec2& bMin,
const ImVec2& bMax, float rounding,
bool active = false)
{
float h = bMax.y - bMin.y;
float edgeH = std::min(h * 0.38f, 6.0f); // highlight/shadow strip height
// AddRectFilledMultiColor does not support rounding, so clip to the
// button's rounded rect to prevent sharp corners from poking out.
if (rounding > 0.0f) {
float inset = rounding * 0.29f; // enough to hide corners
dl->PushClipRect(
ImVec2(bMin.x + inset, bMin.y + inset),
ImVec2(bMax.x - inset, bMax.y - inset), true);
}
ImU32 tHi = schema::UI().resolveColor("var(--tactile-top)", IM_COL32(255, 255, 255, 18));
ImU32 tHiT = tHi & ~IM_COL32_A_MASK; // transparent version
if (!active) {
// Raised: bright top edge, dark bottom edge
// Top highlight
dl->AddRectFilledMultiColor(
bMin, ImVec2(bMax.x, bMin.y + edgeH),
tHi, tHi, tHiT, tHiT);
// Bottom shadow
dl->AddRectFilledMultiColor(
ImVec2(bMin.x, bMax.y - edgeH), bMax,
IM_COL32(0, 0, 0, 0), // top-left
IM_COL32(0, 0, 0, 0), // top-right
IM_COL32(0, 0, 0, 22), // bottom-right
IM_COL32(0, 0, 0, 22)); // bottom-left
} else {
// Pressed: dark top edge, bright bottom edge (inset)
// Top shadow
dl->AddRectFilledMultiColor(
bMin, ImVec2(bMax.x, bMin.y + edgeH),
IM_COL32(0, 0, 0, 20),
IM_COL32(0, 0, 0, 20),
IM_COL32(0, 0, 0, 0),
IM_COL32(0, 0, 0, 0));
// Bottom highlight (dimmer when pressed)
ImU32 pHi = ScaleAlpha(tHi, 0.55f);
ImU32 pHiT = pHi & ~IM_COL32_A_MASK;
dl->AddRectFilledMultiColor(
ImVec2(bMin.x, bMax.y - edgeH), bMax,
pHiT, pHiT, pHi, pHi);
}
if (rounding > 0.0f)
dl->PopClipRect();
}
// Convenience: call right after any ImGui::Button() / SmallButton() call
// to add tactile depth. Uses the last item rect automatically.
inline void ApplyTactile(ImDrawList* dl = nullptr)
{
if (!dl) dl = ImGui::GetWindowDrawList();
ImVec2 bMin = ImGui::GetItemRectMin();
ImVec2 bMax = ImGui::GetItemRectMax();
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
}
// ── Button font tier helper ─────────────────────────────────────────────
// Resolves an int tier (0=sm, 1=md/default, 2=lg) to the matching ImFont*.
// Passing -1 or any out-of-range value returns the default button font.
inline ImFont* resolveButtonFont(int tier)
{
switch (tier) {
case 0: return Type().buttonSm();
case 2: return Type().buttonLg();
default: return Type().button(); // 1 or any other value
}
}
// Resolve per-button font: if perButton >= 0 use it, else fall back to sectionDefault
inline ImFont* resolveButtonFont(int perButton, int sectionDefault)
{
return resolveButtonFont(perButton >= 0 ? perButton : sectionDefault);
}
// ── Tactile wrappers ────────────────────────────────────────────────────
// Drop-in replacements for ImGui::Button / SmallButton that automatically
// add the tactile highlight overlay after rendering.
inline bool TactileButton(const char* label, const ImVec2& size = ImVec2(0, 0), ImFont* font = nullptr)
{
// Draw button with glass-card styling: translucent fill + border + tactile overlay
ImDrawList* dl = ImGui::GetWindowDrawList();
ImFont* useFont = font ? font : Type().button();
// For icon fonts, use InvisibleButton + manual centered text rendering
// to ensure perfect centering (ImGui::Button alignment can be off for icons)
bool isIconFont = font && (font == Type().iconSmall() || font == Type().iconMed() ||
font == Type().iconLarge() || font == Type().iconXL());
bool pressed;
if (isIconFont && size.x > 0 && size.y > 0) {
pressed = ImGui::InvisibleButton(label, size);
} else {
ImGui::PushFont(useFont);
pressed = ImGui::Button(label, size);
ImGui::PopFont();
}
// Glass overlay on the button rect
ImVec2 bMin = ImGui::GetItemRectMin();
ImVec2 bMax = ImGui::GetItemRectMax();
// For icon fonts, manually draw centered icon after getting button rect
if (isIconFont && size.x > 0 && size.y > 0) {
ImVec2 textSz = useFont->CalcTextSizeA(useFont->LegacySize, FLT_MAX, 0, label);
ImVec2 textPos(bMin.x + (size.x - textSz.x) * 0.5f, bMin.y + (size.y - textSz.y) * 0.5f);
dl->AddText(useFont, useFont->LegacySize, textPos, ImGui::GetColorU32(ImGuiCol_Text), label);
}
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
bool hovered = ImGui::IsItemHovered();
// Frosted glass highlight — subtle fill on top
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
// Rim light
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
// Tactile depth
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
return pressed;
}
inline bool TactileSmallButton(const char* label, ImFont* font = nullptr)
{
ImDrawList* dl = ImGui::GetWindowDrawList();
ImGui::PushFont(font ? font : Type().button());
bool pressed = ImGui::SmallButton(label);
ImGui::PopFont();
ImVec2 bMin = ImGui::GetItemRectMin();
ImVec2 bMax = ImGui::GetItemRectMax();
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
bool hovered = ImGui::IsItemHovered();
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
return pressed;
}
// ============================================================================
// Glass Panel (glassmorphism card)
// ============================================================================
// Draws a frosted-glass style panel: a semi-transparent fill with a
// subtle light border. Used for card/panel containers that sit above
// the blurred background. When the backdrop is inactive the fill is
// simply the normal surface colour (fully opaque).
struct GlassPanelSpec {
float rounding = 6.0f;
int fillAlpha = 18; // fill brightness (white, 0-255)
int borderAlpha = 30; // border brightness (white, 0-255)
float borderWidth = 1.0f;
};
inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
const ImVec2& pMax,
const GlassPanelSpec& spec = GlassPanelSpec())
{
if (IsBackdropActive() && !dragonx::ui::effects::isLowSpecMode()) {
// --- Cached color lookups (invalidated on theme change) ---
// These 3 resolveColor() calls do string parsing + map lookup
// each time. Cache them per-frame using the schema generation
// counter so they resolve at most once per theme load.
static uint32_t s_gen = 0;
static ImU32 s_glassFill = 0;
static ImU32 s_glassNoiseTint = 0;
static ImU32 s_glassBorder = 0;
uint32_t curGen = schema::UI().generation();
if (curGen != s_gen) {
s_gen = curGen;
s_glassFill = schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 18));
s_glassNoiseTint = schema::UI().resolveColor("var(--glass-noise-tint)", IM_COL32(255, 255, 255, 10));
s_glassBorder = schema::UI().resolveColor("var(--glass-border)", IM_COL32(255, 255, 255, 30));
}
float uiOp = effects::ImGuiAcrylic::GetUIOpacity();
bool useAcrylic = effects::ImGuiAcrylic::IsEnabled()
&& effects::ImGuiAcrylic::IsAvailable();
// Glass / acrylic layer — only rendered when not fully opaque
// (skip the blur pass at 100% for performance)
if (uiOp < 0.99f) {
if (useAcrylic) {
const auto& acrylicTheme = GetCurrentAcrylicTheme();
effects::ImGuiAcrylic::DrawAcrylicRect(dl, pMin, pMax,
acrylicTheme.card, spec.rounding);
} else {
// Lightweight fake-glass: translucent fill
ImU32 fill = (spec.fillAlpha == 18) ? s_glassFill : ScaleAlpha(s_glassFill, spec.fillAlpha / 18.0f);
dl->AddRectFilled(pMin, pMax, fill, spec.rounding);
}
}
// Surface overlay — provides smooth transition from glass to opaque.
// At uiOp=1.0 this fully covers the panel (opaque card).
// As uiOp decreases, glass/blur progressively shows through.
dl->AddRectFilled(pMin, pMax,
WithAlphaF(GetElevatedSurface(GetCurrentColorTheme(), 1), uiOp),
spec.rounding);
// Noise grain overlay — drawn OVER the surface overlay so card
// opacity doesn't hide it. Gives cards a tactile paper feel.
{
float noiseMul = dragonx::ui::effects::ImGuiAcrylic::GetNoiseOpacity();
if (noiseMul > 0.0f) {
uint8_t origAlpha = (s_glassNoiseTint >> IM_COL32_A_SHIFT) & 0xFF;
uint8_t scaledAlpha = static_cast<uint8_t>(std::min(255.0f, origAlpha * noiseMul));
ImU32 noiseTint = (s_glassNoiseTint & ~(0xFFu << IM_COL32_A_SHIFT)) | (scaledAlpha << IM_COL32_A_SHIFT);
float inset = spec.rounding * 0.3f;
ImVec2 clipMin(pMin.x + inset, pMin.y + inset);
ImVec2 clipMax(pMax.x - inset, pMax.y - inset);
dl->PushClipRect(clipMin, clipMax, true);
dragonx::util::DrawTiledNoiseRect(dl, clipMin, clipMax, noiseTint);
dl->PopClipRect();
}
}
// Border — fades with UI opacity
ImU32 border = (spec.borderAlpha == 30) ? s_glassBorder : ScaleAlpha(s_glassBorder, spec.borderAlpha / 30.0f);
if (uiOp < 0.99f) border = ScaleAlpha(border, uiOp);
dl->AddRect(pMin, pMax, border,
spec.rounding, 0, spec.borderWidth);
// Theme visual effects drawn on ForegroundDrawList so they
// render above card content (text, values, etc.), not below.
auto& fx = effects::ThemeEffects::instance();
ImDrawList* fxDl = ImGui::GetForegroundDrawList();
if (fx.hasRainbowBorder()) {
fx.drawRainbowBorder(fxDl, pMin, pMax, spec.rounding, spec.borderWidth);
}
if (fx.hasShimmer()) {
fx.drawShimmer(fxDl, pMin, pMax, spec.rounding);
}
if (fx.hasSpecularGlare()) {
fx.drawSpecularGlare(fxDl, pMin, pMax, spec.rounding);
}
// Per-panel theme effects: edge trace + ember rise
fx.drawPanelEffects(fxDl, pMin, pMax, spec.rounding);
} else {
// Low-spec opaque fallback
dl->AddRectFilled(pMin, pMax,
GetElevatedSurface(GetCurrentColorTheme(), 1), spec.rounding);
}
}
// ============================================================================
// Stat Card — reusable card with overline / value / subtitle + accent stripe
// ============================================================================
struct StatCardSpec {
const char* overline = nullptr; // small label at top (e.g. "LOCAL HASHRATE")
const char* value = nullptr; // main value text (e.g. "1.23 kH/s")
const char* subtitle = nullptr; // optional small line (e.g. "3 blocks")
ImU32 valueCol = 0; // value text colour (0 = OnSurface)
ImU32 accentCol = 0; // left-stripe colour (0 = no stripe)
bool centered = true; // centre text horizontally
bool hovered = false; // draw hover glow border
};
/// Compute a generous stat-card height with breathing room.
inline float StatCardHeight(float vs, float minH = 56.0f) {
float padV = Layout::spacingLg() * 2; // top + bottom
float content = Type().overline()->LegacySize
+ Layout::spacingMd()
+ Type().subtitle1()->LegacySize;
return std::max(minH, (content + padV) * std::max(vs, 0.85f));
}
inline void DrawStatCard(ImDrawList* dl,
const ImVec2& cMin, const ImVec2& cMax,
const StatCardSpec& card,
const GlassPanelSpec& glass = GlassPanelSpec())
{
float cardW = cMax.x - cMin.x;
float cardH = cMax.y - cMin.y;
float rnd = glass.rounding;
// 1. Glass background
DrawGlassPanel(dl, cMin, cMax, glass);
// 2. Accent stripe (left edge, clipped to card rounded corners).
// Draw a full-height rounded rect with card rounding (left corners)
// and clip to stripe width so the shape follows the corner radius.
if ((card.accentCol & IM_COL32_A_MASK) != 0) {
float stripeW = 4.0f;
dl->PushClipRect(cMin, ImVec2(cMin.x + stripeW, cMax.y), true);
dl->AddRectFilled(cMin, cMax, card.accentCol, rnd,
ImDrawFlags_RoundCornersLeft);
dl->PopClipRect();
}
// 3. Compute content block height
ImFont* ovFont = Type().overline();
ImFont* valFont = Type().subtitle1();
ImFont* subFont = Type().caption();
float blockH = 0;
if (card.overline) blockH += ovFont->LegacySize;
if (card.overline && card.value) blockH += Layout::spacingMd();
if (card.value) blockH += valFont->LegacySize;
if (card.subtitle) blockH += Layout::spacingSm() + subFont->LegacySize;
// 4. Vertically centre
float topY = cMin.y + (cardH - blockH) * 0.5f;
float padX = Layout::spacingLg();
float cy = topY;
// 5. Overline
if (card.overline) {
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, card.overline);
float tx = card.centered ? cMin.x + (cardW - sz.x) * 0.5f : cMin.x + padX;
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(tx, cy), OnSurfaceMedium(), card.overline);
cy += ovFont->LegacySize + Layout::spacingMd();
}
// 6. Value (with text shadow)
if (card.value) {
ImU32 col = card.valueCol ? card.valueCol : OnSurface();
ImVec2 sz = valFont->CalcTextSizeA(valFont->LegacySize, 10000, 0, card.value);
float tx = card.centered ? cMin.x + (cardW - sz.x) * 0.5f : cMin.x + padX;
DrawTextShadow(dl, valFont, valFont->LegacySize, ImVec2(tx, cy), col, card.value);
cy += valFont->LegacySize;
}
// 7. Subtitle
if (card.subtitle) {
cy += Layout::spacingSm();
ImVec2 sz = subFont->CalcTextSizeA(subFont->LegacySize, 10000, 0, card.subtitle);
float tx = card.centered ? cMin.x + (cardW - sz.x) * 0.5f : cMin.x + padX;
dl->AddText(subFont, subFont->LegacySize, ImVec2(tx, cy), OnSurfaceDisabled(), card.subtitle);
}
// 8. Hover glow
if (card.hovered) {
dl->AddRect(cMin, cMax,
schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 20)),
rnd, 0, 1.5f);
}
}
// ── Styled Button (font-only wrapper) ───────────────────────────────────
// Drop-in replacement for ImGui::Button that pushes the Button font style
// (from JSON config) without adding tactile visual effects.
inline bool StyledButton(const char* label, const ImVec2& size = ImVec2(0, 0), ImFont* font = nullptr)
{
ImGui::PushFont(font ? font : Type().button());
bool pressed = ImGui::Button(label, size);
ImGui::PopFont();
return pressed;
}
inline bool StyledSmallButton(const char* label, ImFont* font = nullptr)
{
ImGui::PushFont(font ? font : Type().button());
bool pressed = ImGui::SmallButton(label);
ImGui::PopFont();
return pressed;
}
// ============================================================================
// ButtonStyle-driven overloads (unified UI schema)
// ============================================================================
// These accept a ButtonStyle + ColorVarResolver to push font, colors,
// and size from the schema. Existing call sites are unaffected.
inline bool StyledButton(const char* label, const schema::ButtonStyle& style,
const schema::ColorVarResolver& colors)
{
// Resolve font
ImFont* font = nullptr;
if (!style.font.empty()) {
font = Type().resolveByName(style.font);
}
ImGui::PushFont(font ? font : Type().button());
// Push color overrides
int colorCount = 0;
if (!style.colors.background.empty()) {
ImGui::PushStyleColor(ImGuiCol_Button,
colors.resolve(style.colors.background));
colorCount++;
}
if (!style.colors.backgroundHover.empty()) {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
colors.resolve(style.colors.backgroundHover));
colorCount++;
}
if (!style.colors.backgroundActive.empty()) {
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
colors.resolve(style.colors.backgroundActive));
colorCount++;
}
if (!style.colors.color.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text,
colors.resolve(style.colors.color));
colorCount++;
}
// Push style overrides
int styleCount = 0;
if (style.borderRadius >= 0) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, style.borderRadius);
styleCount++;
}
ImVec2 size(style.width > 0 ? style.width : 0,
style.height > 0 ? style.height : 0);
bool pressed = ImGui::Button(label, size);
ImGui::PopStyleVar(styleCount);
ImGui::PopStyleColor(colorCount);
ImGui::PopFont();
return pressed;
}
inline bool TactileButton(const char* label, const schema::ButtonStyle& style,
const schema::ColorVarResolver& colors)
{
// Resolve font
ImFont* font = nullptr;
if (!style.font.empty()) {
font = Type().resolveByName(style.font);
}
ImGui::PushFont(font ? font : Type().button());
// Push color overrides
int colorCount = 0;
if (!style.colors.background.empty()) {
ImGui::PushStyleColor(ImGuiCol_Button,
colors.resolve(style.colors.background));
colorCount++;
}
if (!style.colors.backgroundHover.empty()) {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
colors.resolve(style.colors.backgroundHover));
colorCount++;
}
if (!style.colors.backgroundActive.empty()) {
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
colors.resolve(style.colors.backgroundActive));
colorCount++;
}
if (!style.colors.color.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text,
colors.resolve(style.colors.color));
colorCount++;
}
// Push style overrides
int styleCount = 0;
if (style.borderRadius >= 0) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, style.borderRadius);
styleCount++;
}
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 size(style.width > 0 ? style.width : 0,
style.height > 0 ? style.height : 0);
bool pressed = ImGui::Button(label, size);
// Glass overlay on the button rect
ImVec2 bMin = ImGui::GetItemRectMin();
ImVec2 bMax = ImGui::GetItemRectMax();
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
bool hovered = ImGui::IsItemHovered();
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
ImGui::PopStyleVar(styleCount);
ImGui::PopStyleColor(colorCount);
ImGui::PopFont();
return pressed;
}
} // namespace material
} // namespace ui
} // namespace dragonx
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Scroll-edge clipping mask — CSS mask-image style vertex alpha fade.
// Call ApplyScrollEdgeMask after EndChild() to fade content at the
// top/bottom edges of a scrollable panel. Works by walking vertices
// added during rendering and scaling their alpha based on distance
// to the panel edges. No opaque overlay rectangles — content becomes
// truly transparent, revealing whatever is behind the panel.
//
// Usage pattern:
// float scrollY = 0, scrollMaxY = 0;
// int parentVtx = parentDL->VtxBuffer.Size;
// ImGui::BeginChild(...);
// ImDrawList* childDL = ImGui::GetWindowDrawList();
// int childVtx = childDL->VtxBuffer.Size;
// { ... scrollY = GetScrollY(); scrollMaxY = GetScrollMaxY(); ... }
// ImGui::EndChild();
// ApplyScrollEdgeMask(parentDL, parentVtx, childDL, childVtx,
// panelMin.y, panelMax.y, fadeZone, scrollY, scrollMaxY);
// ============================================================================
inline void ApplyScrollEdgeMask(ImDrawList* parentDL, int parentVtxStart,
ImDrawList* childDL, int childVtxStart,
float topEdge, float bottomEdge,
float fadeZone,
float scrollY, float scrollMaxY)
{
auto mask = [&](ImDrawList* dl, int startIdx) {
for (int vi = startIdx; vi < dl->VtxBuffer.Size; vi++) {
ImDrawVert& v = dl->VtxBuffer[vi];
float alpha = 1.0f;
// Top fade — only when scrolled down
if (scrollY > 1.0f) {
float d = v.pos.y - topEdge;
if (d < fadeZone)
alpha = std::min(alpha, std::max(0.0f, d / fadeZone));
}
// Bottom fade — only when not at scroll bottom
if (scrollMaxY > 0 && scrollY < scrollMaxY - 1.0f) {
float d = bottomEdge - v.pos.y;
if (d < fadeZone)
alpha = std::min(alpha, std::max(0.0f, d / fadeZone));
}
if (alpha < 1.0f) {
int a = (v.col >> IM_COL32_A_SHIFT) & 0xFF;
a = static_cast<int>(a * alpha);
v.col = (v.col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
}
}
};
if (parentDL) mask(parentDL, parentVtxStart);
if (childDL) mask(childDL, childVtxStart);
}
// ============================================================================
// Smooth scrolling — exponential decay interpolation for mouse wheel.
// Call immediately after BeginChild() on a child that was created with
// ImGuiWindowFlags_NoScrollWithMouse. Captures wheel input and lerps
// scroll position each frame for a smooth feel.
//
// Usage:
// ImGui::BeginChild("##List", size, false,
// ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
// ApplySmoothScroll();
// ... render content ...
// ImGui::EndChild();
// ============================================================================
/// Returns true when any smooth-scroll child is still interpolating toward
/// its target. Checked by the idle-rendering logic in main.cpp to keep
/// rendering frames until the animation settles.
inline bool& SmoothScrollAnimating() {
static bool sAnimating = false;
return sAnimating;
}
inline void ApplySmoothScroll(float speed = 12.0f)
{
struct ScrollState {
float target = 0.0f;
float current = 0.0f;
bool init = false;
};
static std::unordered_map<ImGuiID, ScrollState> sStates;
ImGuiWindow* win = ImGui::GetCurrentWindow();
if (!win) return;
ScrollState& s = sStates[win->ID];
float scrollMaxY = ImGui::GetScrollMaxY();
if (!s.init) {
s.target = ImGui::GetScrollY();
s.current = s.target;
s.init = true;
}
// If something external set scroll position (e.g. SetScrollHereY for
// auto-scroll), sync our state so the next wheel-up starts from the
// actual current position rather than an old remembered target.
float actualY = ImGui::GetScrollY();
if (std::abs(s.current - actualY) > 1.0f) {
s.target = actualY;
s.current = actualY;
}
// Capture mouse wheel when hovered
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f) {
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
s.target -= wheel * step;
s.target = ImClamp(s.target, 0.0f, scrollMaxY);
}
}
// Clamp target if scrollMax changed (e.g., content resized)
if (s.target > scrollMaxY) s.target = scrollMaxY;
// Exponential decay lerp
float dt = ImGui::GetIO().DeltaTime;
s.current += (s.target - s.current) * (1.0f - expf(-speed * dt));
// Snap when close
if (std::abs(s.current - s.target) < 0.5f)
s.current = s.target;
else
SmoothScrollAnimating() = true; // still interpolating — keep rendering
ImGui::SetScrollY(s.current);
}
} // namespace material
} // namespace ui
} // namespace dragonx

345
src/ui/material/elevation.h Normal file
View File

@@ -0,0 +1,345 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "colors.h"
#include "../effects/low_spec.h"
#include "../schema/ui_schema.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <cmath>
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Elevation and Shadow System
// ============================================================================
// Based on https://m2.material.io/design/environment/elevation.html
//
// Material Design uses two light sources to create shadows:
// - Key light: Creates sharper, directional shadows
// - Ambient light: Creates softer, omnidirectional shadows
//
// In dark themes, elevation is primarily shown through surface color overlays
// rather than shadows. However, shadows can still enhance depth perception.
// ============================================================================
// Shadow Specifications
// ============================================================================
/**
* @brief Individual shadow layer specification
*
* Material shadows are composed of multiple layers with different
* blur radii and offsets to simulate real-world lighting.
*/
struct ShadowLayer {
float offsetX; // Horizontal offset (typically 0)
float offsetY; // Vertical offset (key light from above)
float blurRadius; // Blur spread
float spreadRadius; // Size adjustment
float opacity; // Alpha 0.0-1.0
};
/**
* @brief Complete shadow specification for an elevation level
*/
struct ShadowSpec {
ShadowLayer umbra; // Darkest part, sharp edge
ShadowLayer penumbra; // Mid-tone, softer
ShadowLayer ambient; // Lightest, most diffuse
};
/**
* @brief Get shadow specification for elevation level
*
* @param elevationDp Elevation in dp (0, 1, 2, 3, 4, 6, 8, 12, 16, 24)
* @return ShadowSpec for the elevation
*/
ShadowSpec GetShadowSpec(int elevationDp);
// ============================================================================
// Shadow Rendering
// ============================================================================
/**
* @brief Draw Material Design shadow for a rectangle
*
* Uses multi-layer soft shadow rendering to approximate Material shadows.
*
* @param drawList ImGui draw list
* @param rect Rectangle bounds
* @param elevationDp Elevation in dp
* @param cornerRadius Corner radius for rounded rectangles
*/
void DrawShadow(ImDrawList* drawList, const ImRect& rect, int elevationDp, float cornerRadius = 0);
/**
* @brief Draw shadow with position/size parameters
*/
void DrawShadow(ImDrawList* drawList, const ImVec2& pos, const ImVec2& size,
int elevationDp, float cornerRadius = 0);
/**
* @brief Draw soft shadow (single layer, for custom effects)
*
* @param drawList ImGui draw list
* @param rect Rectangle bounds
* @param color Shadow color with alpha
* @param blurRadius Blur amount
* @param offset Shadow offset
* @param cornerRadius Corner radius
*/
void DrawSoftShadow(ImDrawList* drawList, const ImRect& rect, ImU32 color,
float blurRadius, const ImVec2& offset = ImVec2(0, 0),
float cornerRadius = 0);
// ============================================================================
// Elevation Transition Helper
// ============================================================================
/**
* @brief Animated elevation value
*
* Use this to smoothly transition between elevation levels (e.g., card hover)
*/
class ElevationAnimator {
public:
ElevationAnimator(int initialElevation = 0);
/**
* @brief Set target elevation (will animate towards it)
*/
void setTarget(int targetElevation);
/**
* @brief Update animation (call each frame)
* @param deltaTime Frame delta time
*/
void update(float deltaTime);
/**
* @brief Get current animated elevation value
*/
float getCurrent() const { return m_current; }
/**
* @brief Get current elevation as integer (for shadow lookup)
*/
int getCurrentInt() const { return static_cast<int>(m_current + 0.5f); }
/**
* @brief Check if currently animating
*/
bool isAnimating() const { return m_current != m_target; }
private:
float m_current;
float m_target;
float m_animationSpeed = 16.0f; // dp per second
};
// ============================================================================
// Implementation
// ============================================================================
inline ShadowSpec GetShadowSpec(int elevationDp) {
// Material Design shadow values adapted from the spec
// These approximate the CSS box-shadow values from material.io
switch (elevationDp) {
case 0:
return {
{0, 0, 0, 0, 0}, // No shadow
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
case 1:
return {
{0, 2, 1, -1, 0.2f}, // Umbra
{0, 1, 1, 0, 0.14f}, // Penumbra
{0, 1, 3, 0, 0.12f} // Ambient
};
case 2:
return {
{0, 3, 1, -2, 0.2f},
{0, 2, 2, 0, 0.14f},
{0, 1, 5, 0, 0.12f}
};
case 3:
return {
{0, 3, 3, -2, 0.2f},
{0, 3, 4, 0, 0.14f},
{0, 1, 8, 0, 0.12f}
};
case 4:
return {
{0, 2, 4, -1, 0.2f},
{0, 4, 5, 0, 0.14f},
{0, 1, 10, 0, 0.12f}
};
case 6:
return {
{0, 3, 5, -1, 0.2f},
{0, 6, 10, 0, 0.14f},
{0, 1, 18, 0, 0.12f}
};
case 8:
return {
{0, 5, 5, -3, 0.2f},
{0, 8, 10, 1, 0.14f},
{0, 3, 14, 2, 0.12f}
};
case 12:
return {
{0, 7, 8, -4, 0.2f},
{0, 12, 17, 2, 0.14f},
{0, 5, 22, 4, 0.12f}
};
case 16:
return {
{0, 8, 10, -5, 0.2f},
{0, 16, 24, 2, 0.14f},
{0, 6, 30, 5, 0.12f}
};
case 24:
return {
{0, 11, 15, -7, 0.2f},
{0, 24, 38, 3, 0.14f},
{0, 9, 46, 8, 0.12f}
};
default:
// Interpolate for non-standard elevations
if (elevationDp < 0) return GetShadowSpec(0);
if (elevationDp > 24) return GetShadowSpec(24);
// Find nearest standard elevation
int lower = 0, upper = 1;
int standards[] = {0, 1, 2, 3, 4, 6, 8, 12, 16, 24};
for (int i = 0; i < 9; i++) {
if (standards[i] <= elevationDp && standards[i + 1] >= elevationDp) {
lower = standards[i];
upper = standards[i + 1];
break;
}
}
// Use nearest
return GetShadowSpec((elevationDp - lower < upper - elevationDp) ? lower : upper);
}
}
inline void DrawSoftShadow(ImDrawList* drawList, const ImRect& rect, ImU32 color,
float blurRadius, const ImVec2& offset, float cornerRadius) {
if (blurRadius <= 0 || (color & IM_COL32_A_MASK) == 0)
return;
// For ImGui, we'll simulate soft shadows using multiple semi-transparent layers
// This is a performance-friendly approximation
// In low-spec mode use only 1 layer instead of up to 8
const int numLayers = dragonx::ui::effects::isLowSpecMode()
? 1
: ImClamp((int)(blurRadius / 2), 2, 8);
const float layerStep = blurRadius / numLayers;
// Extract base alpha
float baseAlpha = ((color >> IM_COL32_A_SHIFT) & 0xFF) / 255.0f;
ImU32 baseColor = color & ~IM_COL32_A_MASK;
for (int i = numLayers - 1; i >= 0; i--) {
float expansion = layerStep * (i + 1);
float alpha = baseAlpha * (1.0f - (float)i / numLayers) / numLayers;
ImU32 layerColor = baseColor | (((ImU32)(alpha * 255)) << IM_COL32_A_SHIFT);
ImRect expandedRect(
rect.Min.x - expansion + offset.x,
rect.Min.y - expansion + offset.y,
rect.Max.x + expansion + offset.x,
rect.Max.y + expansion + offset.y
);
drawList->AddRectFilled(expandedRect.Min, expandedRect.Max, layerColor,
cornerRadius + expansion * 0.5f);
}
}
inline void DrawShadow(ImDrawList* drawList, const ImRect& rect, int elevationDp, float cornerRadius) {
if (elevationDp <= 0)
return;
ShadowSpec spec = GetShadowSpec(elevationDp);
// Shadow multiplier: light themes need stronger shadows for card depth,
// dark themes rely more on surface color overlay for elevation.
// Configurable via ui.toml [style] shadow-multiplier / shadow-multiplier-light.
const float shadowMultiplier = schema::UI().isDarkTheme()
? schema::UI().drawElement("style", "shadow-multiplier").sizeOr(0.6f)
: schema::UI().drawElement("style", "shadow-multiplier-light").sizeOr(1.0f);
// Draw ambient shadow (largest, most diffuse)
if (spec.ambient.opacity > 0) {
ImU32 ambientColor = IM_COL32(0, 0, 0, (int)(spec.ambient.opacity * shadowMultiplier * 255));
ImRect ambientRect = rect;
ambientRect.Expand(spec.ambient.spreadRadius);
DrawSoftShadow(drawList, ambientRect, ambientColor, spec.ambient.blurRadius,
ImVec2(spec.ambient.offsetX, spec.ambient.offsetY), cornerRadius);
}
// Draw penumbra (medium)
if (spec.penumbra.opacity > 0) {
ImU32 penumbraColor = IM_COL32(0, 0, 0, (int)(spec.penumbra.opacity * shadowMultiplier * 255));
ImRect penumbraRect = rect;
penumbraRect.Expand(spec.penumbra.spreadRadius);
DrawSoftShadow(drawList, penumbraRect, penumbraColor, spec.penumbra.blurRadius,
ImVec2(spec.penumbra.offsetX, spec.penumbra.offsetY), cornerRadius);
}
// Draw umbra (sharpest, darkest)
if (spec.umbra.opacity > 0) {
ImU32 umbraColor = IM_COL32(0, 0, 0, (int)(spec.umbra.opacity * shadowMultiplier * 255));
ImRect umbraRect = rect;
umbraRect.Expand(spec.umbra.spreadRadius);
DrawSoftShadow(drawList, umbraRect, umbraColor, spec.umbra.blurRadius,
ImVec2(spec.umbra.offsetX, spec.umbra.offsetY), cornerRadius);
}
}
inline void DrawShadow(ImDrawList* drawList, const ImVec2& pos, const ImVec2& size,
int elevationDp, float cornerRadius) {
ImRect rect(pos, ImVec2(pos.x + size.x, pos.y + size.y));
DrawShadow(drawList, rect, elevationDp, cornerRadius);
}
inline ElevationAnimator::ElevationAnimator(int initialElevation)
: m_current(static_cast<float>(initialElevation))
, m_target(static_cast<float>(initialElevation))
{
}
inline void ElevationAnimator::setTarget(int targetElevation) {
m_target = static_cast<float>(targetElevation);
}
inline void ElevationAnimator::update(float deltaTime) {
if (m_current == m_target)
return;
float diff = m_target - m_current;
float change = m_animationSpeed * deltaTime;
if (std::abs(diff) <= change) {
m_current = m_target;
} else {
m_current += (diff > 0 ? 1 : -1) * change;
}
}
} // namespace material
} // namespace ui
} // namespace dragonx

190
src/ui/material/gpu_mask.h Normal file
View File

@@ -0,0 +1,190 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// GPU alpha mask — the ImGui equivalent of CSS mask-image: linear-gradient().
// Uses AddCallback to switch the GPU blend mode so that gradient quads
// multiply the framebuffer's alpha (and RGB) by the source alpha, producing
// a smooth per-pixel fade without vertex-spacing artefacts.
#pragma once
#include "imgui.h"
#ifdef DRAGONX_USE_DX11
#include <d3d11.h>
#else
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#else
#include <SDL3/SDL_opengl.h>
#endif
#include <SDL3/SDL.h> // for SDL_GL_GetProcAddress
#endif
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Blend-mode callbacks — called by ImGui's backend during draw list rendering
// ============================================================================
#ifdef DRAGONX_USE_DX11
// Cached DX11 blend state for the mask pass
inline ID3D11BlendState* GetMaskBlendState() {
static ID3D11BlendState* s_maskBlend = nullptr;
if (!s_maskBlend) {
ImGuiIO& io = ImGui::GetIO();
if (!io.BackendRendererUserData) return nullptr;
ID3D11Device* dev = *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
if (!dev) return nullptr;
D3D11_BLEND_DESC desc = {};
desc.RenderTarget[0].BlendEnable = TRUE;
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO;
desc.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_ALPHA;
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO;
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_SRC_ALPHA;
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
dev->CreateBlendState(&desc, &s_maskBlend);
}
return s_maskBlend;
}
// Switch to mask blend: dst *= srcAlpha (both RGB and A)
inline void MaskBlendCallback(const ImDrawList*, const ImDrawCmd*) {
ImGuiIO& io = ImGui::GetIO();
if (!io.BackendRendererUserData) return;
// The ImGui DX11 backend stores the device as the first pointer
ID3D11Device* dev = *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
if (!dev) return;
ID3D11DeviceContext* ctx = nullptr;
dev->GetImmediateContext(&ctx);
if (!ctx) return;
ID3D11BlendState* bs = GetMaskBlendState();
if (bs) {
float blendFactor[4] = {0, 0, 0, 0};
ctx->OMSetBlendState(bs, blendFactor, 0xFFFFFFFF);
}
ctx->Release();
}
// Restore normal ImGui blend: src*srcA + dst*(1-srcA)
inline void RestoreBlendCallback(const ImDrawList*, const ImDrawCmd*) {
ImGuiIO& io = ImGui::GetIO();
if (!io.BackendRendererUserData) return;
ID3D11Device* dev = *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
if (!dev) return;
ID3D11DeviceContext* ctx = nullptr;
dev->GetImmediateContext(&ctx);
if (!ctx) return;
// Setting nullptr restores the default blend state that ImGui's DX11
// backend configures at the start of each frame.
ctx->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
ctx->Release();
}
#else // OpenGL
// glBlendFuncSeparate may not be in the GLAD profile — load it once via SDL.
typedef void (*PFN_glBlendFuncSeparate)(GLenum, GLenum, GLenum, GLenum);
inline PFN_glBlendFuncSeparate GetBlendFuncSeparate() {
static PFN_glBlendFuncSeparate fn = nullptr;
static bool resolved = false;
if (!resolved) {
resolved = true;
fn = (PFN_glBlendFuncSeparate)(void*)SDL_GL_GetProcAddress("glBlendFuncSeparate");
}
return fn;
}
inline void MaskBlendCallback(const ImDrawList*, const ImDrawCmd*) {
// dst.rgb = dst.rgb * srcAlpha (erase content where mask alpha < 1)
// dst.a = dst.a * srcAlpha (match alpha channel)
auto fn = GetBlendFuncSeparate();
if (fn)
fn(GL_ZERO, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA);
else
glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
}
inline void RestoreBlendCallback(const ImDrawList*, const ImDrawCmd*) {
// Restore ImGui's exact blend state:
// RGB: src*srcA + dst*(1-srcA)
// Alpha: src*1 + dst*(1-srcA)
auto fn = GetBlendFuncSeparate();
if (fn)
fn(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
else
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
#endif
// ============================================================================
// DrawScrollFadeMask — draw gradient quads that mask the top/bottom edges
// of a scrollable child region, producing a smooth per-pixel fade.
//
// Call this on the child's draw list BEFORE EndChild().
// The gradient quads use the mask blend mode to multiply the existing
// framebuffer content by their alpha, so alpha=1 means "keep" and
// alpha=0 means "erase to transparent/black".
//
// Parameters:
// dl — the child window's draw list (ImGui::GetWindowDrawList())
// clipMin/Max — the child window's visible area (screen coords)
// fadeH — the height of the fade zone in pixels
// scrollY — current scroll offset (ImGui::GetScrollY())
// scrollMaxY — maximum scroll offset (ImGui::GetScrollMaxY())
// ============================================================================
inline void DrawScrollFadeMask(ImDrawList* dl,
const ImVec2& clipMin, const ImVec2& clipMax,
float fadeH,
float scrollY, float scrollMaxY)
{
if (fadeH <= 0.0f) return;
bool needTop = scrollY > 1.0f;
bool needBottom = scrollMaxY > 0 && scrollY < scrollMaxY - 1.0f;
if (!needTop && !needBottom) return;
float left = clipMin.x;
float right = clipMax.x;
// Switch to mask blend mode
dl->AddCallback(MaskBlendCallback, nullptr);
if (needTop) {
// Top gradient: alpha=0 at top edge (erase) → alpha=1 at top+fadeH (keep)
ImVec2 tMin(left, clipMin.y);
ImVec2 tMax(right, clipMin.y + fadeH);
ImU32 transparent = IM_COL32(0, 0, 0, 0);
ImU32 opaque = IM_COL32(0, 0, 0, 255);
dl->AddRectFilledMultiColor(tMin, tMax,
transparent, transparent, // top-left, top-right
opaque, opaque); // bottom-left, bottom-right
}
if (needBottom) {
// Bottom gradient: alpha=1 at bottom-fadeH (keep) → alpha=0 at bottom (erase)
ImVec2 bMin(left, clipMax.y - fadeH);
ImVec2 bMax(right, clipMax.y);
ImU32 opaque = IM_COL32(0, 0, 0, 255);
ImU32 transparent = IM_COL32(0, 0, 0, 0);
dl->AddRectFilledMultiColor(bMin, bMax,
opaque, opaque, // top-left, top-right
transparent, transparent); // bottom-left, bottom-right
}
// Restore normal blend mode
dl->AddCallback(RestoreBlendCallback, nullptr);
}
} // namespace material
} // namespace ui
} // namespace dragonx

351
src/ui/material/layout.h Normal file
View File

@@ -0,0 +1,351 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design 8dp Grid System
// ============================================================================
// All spacing in Material Design is based on an 8dp grid.
// https://m2.material.io/design/layout/spacing-methods.html
constexpr float kGridUnit = 8.0f; // Base unit in dp (device-independent pixels)
// ============================================================================
// Spacing Scale
// ============================================================================
// Use these values for all margins, padding, and gaps
namespace spacing {
constexpr float Xxs = 2.0f; // 0.25x - Very tight spacing
constexpr float Xs = 4.0f; // 0.5x - Extra small
constexpr float Sm = 8.0f; // 1x - Small (default minimum)
constexpr float Md = 16.0f; // 2x - Medium (default standard)
constexpr float Lg = 24.0f; // 3x - Large
constexpr float Xl = 32.0f; // 4x - Extra large
constexpr float Xxl = 40.0f; // 5x - Double extra large
constexpr float Xxxl = 48.0f; // 6x - Triple extra large
// Multiplier helpers
constexpr float Grid(float multiplier) { return kGridUnit * multiplier; }
} // namespace spacing
// ============================================================================
// Component Dimensions
// ============================================================================
// Standard sizes for Material Design components
namespace size {
// Buttons
constexpr float ButtonHeight = 36.0f;
constexpr float ButtonMinWidth = 64.0f;
constexpr float ButtonPaddingH = 16.0f;
constexpr float ButtonPaddingV = 8.0f;
constexpr float ButtonIconGap = 8.0f; // Gap between icon and label
constexpr float ButtonCornerRadius = 4.0f;
// FAB (Floating Action Button)
constexpr float FabSize = 56.0f;
constexpr float FabMiniSize = 40.0f;
constexpr float FabExtendedHeight = 48.0f;
constexpr float FabExtendedPadding = 16.0f;
constexpr float FabCornerRadius = 16.0f; // Half of mini size for circle
// Icons
constexpr float IconSize = 24.0f;
constexpr float IconSizeSm = 18.0f;
constexpr float IconSizeLg = 36.0f;
constexpr float IconButtonSize = 48.0f; // Touch target for icon buttons
// App Bar / Toolbar
constexpr float AppBarHeight = 56.0f; // Mobile
constexpr float AppBarHeightLarge = 64.0f; // Desktop
constexpr float AppBarPadding = 16.0f;
// Navigation
constexpr float NavDrawerWidth = 256.0f;
constexpr float NavRailWidth = 72.0f;
constexpr float NavItemHeight = 48.0f;
constexpr float NavItemPadding = 12.0f;
constexpr float NavSectionPadding = 8.0f;
// Cards
constexpr float CardCornerRadius = 4.0f;
constexpr float CardPadding = 16.0f;
constexpr float CardElevation = 1.0f; // Default elevation in dp
// Dialogs
constexpr float DialogMinWidth = 280.0f;
constexpr float DialogMaxWidth = 560.0f;
constexpr float DialogPadding = 24.0f;
constexpr float DialogTitlePadding = 20.0f;
constexpr float DialogActionGap = 8.0f;
constexpr float DialogCornerRadius = 4.0f;
// Lists
constexpr float ListItemHeight = 48.0f; // Single line
constexpr float ListItemHeightTwoLine = 64.0f;
constexpr float ListItemHeightThreeLine = 88.0f;
constexpr float ListItemPaddingH = 16.0f;
constexpr float ListAvatarSize = 40.0f;
constexpr float ListIconMargin = 32.0f;
// Text Fields
constexpr float TextFieldHeight = 56.0f;
constexpr float TextFieldPadding = 16.0f;
constexpr float TextFieldDenseHeight = 40.0f;
constexpr float TextFieldCornerRadius = 4.0f;
// Chips
constexpr float ChipHeight = 32.0f;
constexpr float ChipPaddingH = 12.0f;
constexpr float ChipCornerRadius = 16.0f;
// Tabs
constexpr float TabHeight = 48.0f;
constexpr float TabMinWidth = 90.0f;
constexpr float TabMaxWidth = 360.0f;
constexpr float TabPaddingH = 16.0f;
constexpr float TabIndicatorHeight = 2.0f;
// Snackbar
constexpr float SnackbarMinWidth = 288.0f;
constexpr float SnackbarMaxWidth = 568.0f;
constexpr float SnackbarHeight = 48.0f;
constexpr float SnackbarPadding = 8.0f;
constexpr float SnackbarMargin = 8.0f;
// Dividers
constexpr float DividerThickness = 1.0f;
constexpr float DividerInset = 16.0f; // For inset dividers
} // namespace size
// ============================================================================
// Responsive Breakpoints
// ============================================================================
// Based on Material Design responsive layout grid
namespace breakpoint {
constexpr float Xs = 0.0f; // Extra small: 0-599dp
constexpr float Sm = 600.0f; // Small: 600-904dp
constexpr float Md = 905.0f; // Medium: 905-1239dp
constexpr float Lg = 1240.0f; // Large: 1240-1439dp
constexpr float Xl = 1440.0f; // Extra large: 1440dp+
// Returns the current breakpoint category
enum class Category { Xs, Sm, Md, Lg, Xl };
inline Category GetCategory(float windowWidth) {
if (windowWidth >= Xl) return Category::Xl;
if (windowWidth >= Lg) return Category::Lg;
if (windowWidth >= Md) return Category::Md;
if (windowWidth >= Sm) return Category::Sm;
return Category::Xs;
}
// Margin values for each breakpoint
inline float GetMargin(Category cat) {
switch (cat) {
case Category::Xs: return 16.0f;
case Category::Sm: return 32.0f;
case Category::Md: return 32.0f; // Or fluid centering
case Category::Lg: return 200.0f;
case Category::Xl: return 200.0f; // Or fluid centering
}
return 16.0f;
}
// Column count for each breakpoint
inline int GetColumns(Category cat) {
switch (cat) {
case Category::Xs: return 4;
case Category::Sm: return 8;
default: return 12;
}
}
// Recommended navigation style
enum class NavStyle { BottomNav, NavRail, NavDrawer };
inline NavStyle GetNavStyle(Category cat) {
switch (cat) {
case Category::Xs: return NavStyle::BottomNav;
case Category::Sm:
case Category::Md: return NavStyle::NavRail;
default: return NavStyle::NavDrawer;
}
}
} // namespace breakpoint
// ============================================================================
// Layout Helpers
// ============================================================================
/**
* @brief Get current window's breakpoint category
*/
inline breakpoint::Category GetCurrentBreakpoint() {
ImVec2 windowSize = ImGui::GetWindowSize();
return breakpoint::GetCategory(windowSize.x);
}
/**
* @brief Get recommended margin for current window
*/
inline float GetCurrentMargin() {
return breakpoint::GetMargin(GetCurrentBreakpoint());
}
/**
* @brief Add horizontal spacing (uses 8dp grid)
* @param units Number of 8dp units (default 1)
*/
inline void HSpace(float units = 1.0f) {
ImGui::SameLine(0, kGridUnit * units);
}
/**
* @brief Add vertical spacing (uses 8dp grid)
* @param units Number of 8dp units (default 1)
*/
inline void VSpace(float units = 1.0f) {
ImGui::Dummy(ImVec2(0, kGridUnit * units));
}
/**
* @brief Begin a padded region
* @param padding Padding in pixels (use spacing:: constants)
*/
inline void BeginPadding(float padding) {
ImGui::BeginGroup();
ImGui::Dummy(ImVec2(0, padding));
ImGui::Indent(padding);
}
/**
* @brief End a padded region
* @param padding Same padding used in BeginPadding
*/
inline void EndPadding(float padding) {
ImGui::Unindent(padding);
ImGui::Dummy(ImVec2(0, padding));
ImGui::EndGroup();
}
/**
* @brief Center the next item horizontally within available width
* @param itemWidth Width of the item to center
*/
inline void CenterHorizontally(float itemWidth) {
float availWidth = ImGui::GetContentRegionAvail().x;
float offset = (availWidth - itemWidth) * 0.5f;
if (offset > 0) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset);
}
}
/**
* @brief Calculate column width for responsive grid
* @param numColumns Number of columns to span
* @param gutterWidth Gap between columns (default 16dp)
*/
inline float GetColumnWidth(int numColumns = 1, float gutterWidth = spacing::Md) {
float availWidth = ImGui::GetContentRegionAvail().x;
auto cat = GetCurrentBreakpoint();
int totalColumns = breakpoint::GetColumns(cat);
float totalGutter = gutterWidth * (totalColumns - 1);
float columnWidth = (availWidth - totalGutter) / totalColumns;
return columnWidth * numColumns + gutterWidth * (numColumns - 1);
}
// ============================================================================
// Card Layout Helper
// ============================================================================
struct CardLayout {
float width;
float minHeight;
float padding;
float cornerRadius;
int elevation;
CardLayout()
: width(0) // 0 = auto width
, minHeight(0)
, padding(size::CardPadding)
, cornerRadius(size::CardCornerRadius)
, elevation(1)
{}
CardLayout& Width(float w) { width = w; return *this; }
CardLayout& MinHeight(float h) { minHeight = h; return *this; }
CardLayout& Padding(float p) { padding = p; return *this; }
CardLayout& CornerRadius(float r) { cornerRadius = r; return *this; }
CardLayout& Elevation(int e) { elevation = e; return *this; }
};
// ============================================================================
// Navigation Drawer Item
// ============================================================================
struct NavItem {
const char* label;
const char* icon; // Icon font glyph or nullptr
bool selected;
bool enabled;
NavItem(const char* lbl, const char* ico = nullptr)
: label(lbl), icon(ico), selected(false), enabled(true)
{}
NavItem& Selected(bool s) { selected = s; return *this; }
NavItem& Enabled(bool e) { enabled = e; return *this; }
};
// ============================================================================
// Alignment Helpers
// ============================================================================
/**
* @brief Align item to right edge of available space
* @param itemWidth Width of the item
*/
inline void AlignRight(float itemWidth) {
float availWidth = ImGui::GetContentRegionAvail().x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + availWidth - itemWidth);
}
/**
* @brief Push items to start from right side (call before group of items)
*/
inline void BeginRightAlign() {
ImGui::BeginGroup();
}
/**
* @brief End right-aligned group and position it
*/
inline void EndRightAlign() {
ImGui::EndGroup();
float groupWidth = ImGui::GetItemRectSize().x;
AlignRight(groupWidth);
}
} // namespace material
} // namespace ui
} // namespace dragonx

160
src/ui/material/material.h Normal file
View File

@@ -0,0 +1,160 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// ============================================================================
// Material Design 2 - Complete UI System
// ============================================================================
// Based on https://m2.material.io/design/foundation-overview
//
// This header provides the complete Material Design 2 implementation for
// the DragonX Wallet ImGui interface.
//
// Namespace: dragonx::ui::material
// Foundation
#include "color_theme.h" // ColorTheme struct, theme presets
#include "colors.h" // Color accessor functions
#include "typography.h" // Typography system, type scale
#include "layout.h" // Spacing grid, breakpoints, sizes
// Effects
#include "elevation.h" // Shadow rendering, elevation animation
#include "ripple.h" // Touch ripple effect
#include "draw_helpers.h" // DrawTextShadow, DrawGlassPanel
// Motion
#include "motion.h" // Easing curves, AnimatedValue, StaggerAnimation
#include "transitions.h" // View transitions, FadeTransition, ExpandableSection
// Layout
#include "app_layout.h" // Application layout manager
// Components
#include "components/components.h" // All Material components
// ============================================================================
// Quick Start Guide
// ============================================================================
//
// 1. INITIALIZATION
// In your app startup, initialize the material system:
//
// ```cpp
// using namespace dragonx::ui::material;
//
// // Initialize color theme (creates global theme)
// SetDragonXTheme(); // or SetHushTheme() for HUSH variant
//
// // Initialize typography (load fonts)
// Typography::instance().initialize(io);
// ```
//
// 2. FRAME SETUP
// At the start of each frame:
//
// ```cpp
// // Update ripple animations
// UpdateRipples();
// ```
//
// 3. USING COLORS
// Access theme colors with helper functions:
//
// ```cpp
// ImU32 bg = Background(); // App background
// ImU32 primary = Primary(); // Brand color
// ImU32 cardBg = Surface(Elevation::Dp4); // Elevated surface
// ImU32 text = OnSurface(); // Text on surfaces
// ```
//
// 4. USING TYPOGRAPHY
// Render text with the type scale:
//
// ```cpp
// Typography::instance().text(TypeStyle::H6, "Section Title");
// Typography::instance().text(TypeStyle::Body1, "Body text here...");
// Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), "Hint");
// ```
//
// 5. USING COMPONENTS
// Components follow Material Design patterns:
//
// ```cpp
// // Buttons
// if (ContainedButton("Send")) { ... }
// if (OutlinedButton("Cancel")) { ... }
// if (TextButton("Learn More")) { ... }
//
// // Cards
// BeginCard(myCardSpec);
// CardHeader("Card Title", "Subtitle");
// CardContent("Card body content...");
// CardActions();
// TextButton("Action 1");
// TextButton("Action 2");
// CardActionsEnd();
// EndCard();
//
// // Lists
// BeginList("myList");
// if (ListItem("Item 1")) { ... }
// if (ListItem("Item 2", "Secondary text")) { ... }
// ListDivider();
// if (ListItem("Item 3")) { ... }
// EndList();
//
// // Dialogs
// static bool showDialog = false;
// if (ContainedButton("Open Dialog")) showDialog = true;
// int result = ConfirmDialog("confirm", &showDialog, "Confirm",
// "Are you sure?", "Yes", "No");
// ```
//
// 6. LAYOUT
// Use the spacing system for consistent layouts:
//
// ```cpp
// ImGui::Dummy(ImVec2(0, spacing::dp(2))); // 16dp vertical space
// ImGui::SetCursorPosX(spacing::dp(3)); // 24dp indent
// ```
//
// ============================================================================
// Module Reference
// ============================================================================
//
// COLORS (colors.h)
// Primary(), PrimaryVariant(), PrimaryContainer()
// Secondary(), SecondaryVariant()
// Background(), Surface(elevation), SurfaceVariant()
// OnPrimary(), OnSecondary(), OnBackground(), OnSurface()
// OnSurfaceMedium(), OnSurfaceDisabled()
// Error(), OnError()
// StateHover(), StateFocus(), StatePressed(), StateSelected()
//
// TYPOGRAPHY (typography.h)
// TypeStyle: H1-H6, Subtitle1-2, Body1-2, Button, Caption, Overline
// Typography::text(style, text)
// Typography::textColored(style, color, text)
// Typography::textWrapped(style, text)
// Typography::pushFont(style) / popFont()
//
// LAYOUT (layout.h)
// spacing::dp(n) - n * 8dp
// spacing::Unit - 8dp
// size::TouchTarget - 48dp
// size::ButtonHeight - 36dp
// breakpoint::current() - Get current breakpoint
//
// ELEVATION (elevation.h)
// DrawShadow(drawList, rect, elevationDp, cornerRadius)
// ElevationAnimator - Smooth elevation transitions
//
// RIPPLE (ripple.h)
// DrawRippleEffect(drawList, rect, id, cornerRadius, hovered, held)
// UpdateRipples() - Call each frame
//
// COMPONENTS (components/components.h)
// See components.h for full component reference

452
src/ui/material/motion.h Normal file
View File

@@ -0,0 +1,452 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
#include <cmath>
#include <functional>
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Motion System
// ============================================================================
// Based on https://m2.material.io/design/motion/speed.html
// and https://m2.material.io/design/motion/customization.html
//
// Material motion uses specific easing curves and durations to create
// natural, responsive animations that feel connected to user input.
// ============================================================================
// Standard Durations (in seconds)
// ============================================================================
namespace duration {
// Simple transitions (toggle, fade)
constexpr float Instant = 0.0f;
constexpr float VeryFast = 0.05f; // 50ms
constexpr float Fast = 0.1f; // 100ms - simple toggles
constexpr float Short = 0.15f; // 150ms
// Standard transitions
constexpr float Medium = 0.2f; // 200ms - collapse, simple move
constexpr float Standard = 0.25f; // 250ms - expand, standard
constexpr float Long = 0.3f; // 300ms - large transforms
// Complex transitions
constexpr float Complex = 0.375f; // 375ms
constexpr float VeryLong = 0.5f; // 500ms - elaborate sequences
// Screen transitions
constexpr float EnterScreen = 0.225f; // Entering screen
constexpr float ExitScreen = 0.195f; // Leaving screen
constexpr float ScreenChange = 0.3f; // Full screen transition
}
// ============================================================================
// Easing Curves
// ============================================================================
/**
* @brief Cubic bezier curve evaluation
*
* Evaluates a cubic bezier curve defined by control points (0,0), (x1,y1), (x2,y2), (1,1)
*
* @param t Progress 0.0-1.0
* @param x1 First control point X
* @param y1 First control point Y
* @param x2 Second control point X
* @param y2 Second control point Y
* @return Eased value
*/
float CubicBezier(float t, float x1, float y1, float x2, float y2);
/**
* @brief Standard easing - for objects moving between on-screen positions
*
* CSS: cubic-bezier(0.4, 0.0, 0.2, 1.0)
* Starts quickly, slows down to rest
*/
float EaseStandard(float t);
/**
* @brief Deceleration easing - for objects entering the screen
*
* CSS: cubic-bezier(0.0, 0.0, 0.2, 1.0)
* Starts at full velocity, decelerates to rest
*/
float EaseDecelerate(float t);
/**
* @brief Acceleration easing - for objects leaving the screen
*
* CSS: cubic-bezier(0.4, 0.0, 1.0, 1.0)
* Accelerates from rest, exits at full speed
*/
float EaseAccelerate(float t);
/**
* @brief Sharp easing - for objects that may return to screen
*
* CSS: cubic-bezier(0.4, 0.0, 0.6, 1.0)
* Quicker than standard, maintains connection
*/
float EaseSharp(float t);
/**
* @brief Linear interpolation (no easing)
*/
float EaseLinear(float t);
/**
* @brief Overshoot easing - goes past target then settles
*
* Good for bouncy, playful animations
*/
float EaseOvershoot(float t, float overshoot = 1.70158f);
/**
* @brief Elastic easing - springy oscillation
*/
float EaseElastic(float t);
// ============================================================================
// Easing Function Type
// ============================================================================
using EasingFunction = float(*)(float);
// ============================================================================
// Animated Value
// ============================================================================
/**
* @brief Animated value with automatic interpolation
*
* Template class for smooth value transitions.
*/
template<typename T>
class AnimatedValue {
public:
AnimatedValue(const T& initialValue = T())
: m_current(initialValue)
, m_target(initialValue)
, m_start(initialValue)
, m_duration(duration::Standard)
, m_elapsed(0)
, m_easingFunc(EaseStandard)
, m_animating(false)
{}
/**
* @brief Set target value with animation
*/
void animateTo(const T& target, float dur = duration::Standard,
EasingFunction easing = EaseStandard) {
if (target == m_target && m_animating)
return; // Already animating to this target
m_start = m_current;
m_target = target;
m_duration = dur;
m_elapsed = 0;
m_easingFunc = easing;
m_animating = true;
}
/**
* @brief Set value immediately (no animation)
*/
void set(const T& value) {
m_current = value;
m_target = value;
m_start = value;
m_animating = false;
}
/**
* @brief Update animation (call each frame)
* @param deltaTime Frame delta time in seconds
*/
void update(float deltaTime) {
if (!m_animating)
return;
m_elapsed += deltaTime;
if (m_elapsed >= m_duration) {
m_current = m_target;
m_animating = false;
} else {
float t = m_elapsed / m_duration;
float eased = m_easingFunc(t);
m_current = lerp(m_start, m_target, eased);
}
}
/**
* @brief Get current value
*/
const T& get() const { return m_current; }
/**
* @brief Get target value
*/
const T& getTarget() const { return m_target; }
/**
* @brief Check if currently animating
*/
bool isAnimating() const { return m_animating; }
/**
* @brief Get animation progress (0-1)
*/
float getProgress() const {
if (!m_animating) return 1.0f;
return m_elapsed / m_duration;
}
/**
* @brief Implicit conversion to value type
*/
operator const T&() const { return m_current; }
private:
T m_current;
T m_target;
T m_start;
float m_duration;
float m_elapsed;
EasingFunction m_easingFunc;
bool m_animating;
// Lerp specializations
static T lerp(const T& a, const T& b, float t) {
return a + (b - a) * t;
}
};
// Specialization for ImVec2
template<>
inline ImVec2 AnimatedValue<ImVec2>::lerp(const ImVec2& a, const ImVec2& b, float t) {
return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
}
// Specialization for ImVec4/color
template<>
inline ImVec4 AnimatedValue<ImVec4>::lerp(const ImVec4& a, const ImVec4& b, float t) {
return ImVec4(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t,
a.z + (b.z - a.z) * t,
a.w + (b.w - a.w) * t
);
}
// ============================================================================
// Animation Sequencer
// ============================================================================
/**
* @brief Staggered animation for lists
*
* Creates staggered entrance animations for list items.
*/
class StaggerAnimation {
public:
StaggerAnimation(int itemCount, float staggerDelay = 0.05f,
float itemDuration = duration::EnterScreen)
: m_itemCount(itemCount)
, m_staggerDelay(staggerDelay)
, m_itemDuration(itemDuration)
, m_elapsed(0)
, m_running(false)
{}
/**
* @brief Start the stagger animation
*/
void start() {
m_elapsed = 0;
m_running = true;
}
/**
* @brief Update animation
*/
void update(float deltaTime) {
if (!m_running) return;
m_elapsed += deltaTime;
// Check if all items have finished
float totalDuration = m_staggerDelay * (m_itemCount - 1) + m_itemDuration;
if (m_elapsed >= totalDuration) {
m_running = false;
}
}
/**
* @brief Get animation progress for a specific item
*
* @param itemIndex Item index (0-based)
* @return Progress 0.0-1.0 (clamped)
*/
float getItemProgress(int itemIndex) const {
if (!m_running && m_elapsed > 0)
return 1.0f; // Animation complete
if (itemIndex < 0 || itemIndex >= m_itemCount)
return 0.0f;
float itemStart = m_staggerDelay * itemIndex;
float itemElapsed = m_elapsed - itemStart;
if (itemElapsed <= 0) return 0.0f;
if (itemElapsed >= m_itemDuration) return 1.0f;
return EaseDecelerate(itemElapsed / m_itemDuration);
}
/**
* @brief Get eased alpha for item (for fade-in)
*/
float getItemAlpha(int itemIndex) const {
return getItemProgress(itemIndex);
}
/**
* @brief Get Y offset for item (for slide-in from bottom)
*/
float getItemYOffset(int itemIndex, float maxOffset = 20.0f) const {
float progress = getItemProgress(itemIndex);
return maxOffset * (1.0f - progress);
}
bool isRunning() const { return m_running; }
private:
int m_itemCount;
float m_staggerDelay;
float m_itemDuration;
float m_elapsed;
bool m_running;
};
// ============================================================================
// Container Transform
// ============================================================================
/**
* @brief Container transform animation state
*
* For hero-style transitions where a card expands into a full dialog/page.
*/
struct ContainerTransform {
ImRect startRect; // Starting bounds (e.g., card)
ImRect endRect; // Ending bounds (e.g., dialog)
float progress; // 0 = start, 1 = end
bool expanding; // Direction
ContainerTransform()
: progress(0)
, expanding(true)
{}
/**
* @brief Get interpolated bounds at current progress
*/
ImRect getCurrentRect() const {
float t = expanding ? progress : (1.0f - progress);
float eased = EaseStandard(t);
return ImRect(
ImLerp(startRect.Min, endRect.Min, eased),
ImLerp(startRect.Max, endRect.Max, eased)
);
}
/**
* @brief Get corner radius (shrinks as container expands)
*/
float getCornerRadius(float startRadius, float endRadius) const {
float t = expanding ? progress : (1.0f - progress);
float eased = EaseStandard(t);
return startRadius + (endRadius - startRadius) * eased;
}
};
// ============================================================================
// Implementation
// ============================================================================
inline float CubicBezier(float t, float x1, float y1, float x2, float y2) {
// Attempt to find t value for given x (Newton-Raphson approximation)
// This is needed because CSS bezier curves are defined in terms of x
// For simplicity, we'll use a direct parametric approach
// which is accurate enough for UI animations
float cx = 3.0f * x1;
float bx = 3.0f * (x2 - x1) - cx;
float ax = 1.0f - cx - bx;
float cy = 3.0f * y1;
float by = 3.0f * (y2 - y1) - cy;
float ay = 1.0f - cy - by;
// Sample y at parameter t
// Note: This assumes t directly maps to time, which is an approximation
// For more accuracy, we'd need to solve for the bezier parameter given x=t
float t2 = t * t;
float t3 = t2 * t;
return ay * t3 + by * t2 + cy * t;
}
inline float EaseStandard(float t) {
// cubic-bezier(0.4, 0.0, 0.2, 1.0)
return CubicBezier(t, 0.4f, 0.0f, 0.2f, 1.0f);
}
inline float EaseDecelerate(float t) {
// cubic-bezier(0.0, 0.0, 0.2, 1.0)
return CubicBezier(t, 0.0f, 0.0f, 0.2f, 1.0f);
}
inline float EaseAccelerate(float t) {
// cubic-bezier(0.4, 0.0, 1.0, 1.0)
return CubicBezier(t, 0.4f, 0.0f, 1.0f, 1.0f);
}
inline float EaseSharp(float t) {
// cubic-bezier(0.4, 0.0, 0.6, 1.0)
return CubicBezier(t, 0.4f, 0.0f, 0.6f, 1.0f);
}
inline float EaseLinear(float t) {
return t;
}
inline float EaseOvershoot(float t, float overshoot) {
// Back ease out
t = t - 1.0f;
return t * t * ((overshoot + 1.0f) * t + overshoot) + 1.0f;
}
inline float EaseElastic(float t) {
if (t == 0.0f || t == 1.0f) return t;
float p = 0.3f;
float s = p / 4.0f;
return std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * IM_PI) / p) + 1.0f;
}
} // namespace material
} // namespace ui
} // namespace dragonx

290
src/ui/material/ripple.h Normal file
View File

@@ -0,0 +1,290 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "colors.h"
#include "../effects/low_spec.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <vector>
#include <cmath>
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Ripple Effect
// ============================================================================
// Based on https://m2.material.io/design/motion/customization.html
//
// Ripple effects provide visual feedback when users touch interactive elements.
// The ripple emanates from the touch point and expands outward.
/**
* @brief Individual ripple instance
*/
struct Ripple {
ImVec2 center; // Ripple center point
float startTime; // When ripple started
float maxRadius; // Maximum expansion radius
ImU32 color; // Ripple color
bool releasing; // True when finger lifted
float releaseTime; // When release started
Ripple(const ImVec2& center, float maxRadius, ImU32 color, float currentTime)
: center(center)
, startTime(currentTime)
, maxRadius(maxRadius)
, color(color)
, releasing(false)
, releaseTime(0)
{}
};
/**
* @brief Ripple effect manager
*
* Manages ripple animations for interactive elements.
*/
class RippleManager {
public:
static RippleManager& instance();
/**
* @brief Start a new ripple
*
* @param id Widget ID
* @param center Ripple center (touch point)
* @param maxRadius Maximum ripple radius
* @param color Ripple color (typically primary with low alpha)
*/
void startRipple(ImGuiID id, const ImVec2& center, float maxRadius, ImU32 color);
/**
* @brief Release current ripple (finger lifted)
*/
void releaseRipple(ImGuiID id);
/**
* @brief Draw ripple effect for a widget
*
* @param drawList Draw list to use
* @param id Widget ID
* @param clipRect Clip rectangle for the ripple
* @param cornerRadius Corner radius for clip shape
*/
void drawRipple(ImDrawList* drawList, ImGuiID id, const ImRect& clipRect,
float cornerRadius = 0);
/**
* @brief Update all ripples (call once per frame)
*/
void update();
/**
* @brief Clear all ripples
*/
void clear();
private:
RippleManager() = default;
struct RippleEntry {
ImGuiID id;
Ripple ripple;
};
std::vector<RippleEntry> m_ripples;
static constexpr float kExpandDuration = 0.3f; // 300ms to full size
static constexpr float kFadeDuration = 0.2f; // 200ms fade out
static constexpr float kMaxOpacity = 0.12f; // 12% max opacity
};
// ============================================================================
// Convenience Functions
// ============================================================================
/**
* @brief Draw ripple effect for a button/interactive element
*
* Call this after drawing the element background but before text/content.
*
* @param drawList Draw list
* @param rect Element bounds
* @param id Element ID
* @param cornerRadius Corner radius
* @param hovered Is element hovered
* @param held Is element being pressed
* @param color Optional ripple color (default: white for dark theme)
*/
void DrawRippleEffect(ImDrawList* drawList, const ImRect& rect, ImGuiID id,
float cornerRadius = 0, bool hovered = false, bool held = false,
ImU32 color = IM_COL32(255, 255, 255, 255));
/**
* @brief Update ripple system (call once per frame in main loop)
*/
void UpdateRipples();
// ============================================================================
// Implementation
// ============================================================================
inline RippleManager& RippleManager::instance() {
static RippleManager s_instance;
return s_instance;
}
inline void RippleManager::startRipple(ImGuiID id, const ImVec2& center,
float maxRadius, ImU32 color) {
float currentTime = (float)ImGui::GetTime();
// Check if ripple already exists for this ID
for (auto& entry : m_ripples) {
if (entry.id == id) {
// Reset existing ripple
entry.ripple = Ripple(center, maxRadius, color, currentTime);
return;
}
}
// Add new ripple
m_ripples.push_back({id, Ripple(center, maxRadius, color, currentTime)});
}
inline void RippleManager::releaseRipple(ImGuiID id) {
float currentTime = (float)ImGui::GetTime();
for (auto& entry : m_ripples) {
if (entry.id == id && !entry.ripple.releasing) {
entry.ripple.releasing = true;
entry.ripple.releaseTime = currentTime;
}
}
}
inline void RippleManager::drawRipple(ImDrawList* drawList, ImGuiID id,
const ImRect& clipRect, float cornerRadius) {
float currentTime = (float)ImGui::GetTime();
for (const auto& entry : m_ripples) {
if (entry.id != id)
continue;
const Ripple& ripple = entry.ripple;
// Calculate expansion progress
float expandProgress = (currentTime - ripple.startTime) / kExpandDuration;
expandProgress = ImClamp(expandProgress, 0.0f, 1.0f);
// Deceleration easing for expansion
expandProgress = 1.0f - (1.0f - expandProgress) * (1.0f - expandProgress);
float currentRadius = ripple.maxRadius * expandProgress;
// Calculate opacity
float opacity = kMaxOpacity;
if (ripple.releasing) {
float fadeProgress = (currentTime - ripple.releaseTime) / kFadeDuration;
fadeProgress = ImClamp(fadeProgress, 0.0f, 1.0f);
opacity *= (1.0f - fadeProgress);
}
if (opacity <= 0.001f)
continue;
// Extract color components
float r = ((ripple.color >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
float g = ((ripple.color >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
float b = ((ripple.color >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
ImU32 rippleColor = ImGui::ColorConvertFloat4ToU32(ImVec4(r, g, b, opacity));
// Push clip rect for rounded corners
if (cornerRadius > 0) {
// For rounded rectangles, we approximate with the clip rect
// A perfect solution would require custom clipping
drawList->PushClipRect(clipRect.Min, clipRect.Max, true);
}
// Draw ripple circle (skip expanding animation in low-spec mode)
if (!dragonx::ui::effects::isLowSpecMode())
drawList->AddCircleFilled(ripple.center, currentRadius, rippleColor, 32);
if (cornerRadius > 0) {
drawList->PopClipRect();
}
break; // Only one ripple per ID
}
}
inline void RippleManager::update() {
float currentTime = (float)ImGui::GetTime();
// Remove completed ripples
m_ripples.erase(
std::remove_if(m_ripples.begin(), m_ripples.end(),
[currentTime](const RippleEntry& entry) {
if (!entry.ripple.releasing)
return false;
float fadeProgress = (currentTime - entry.ripple.releaseTime) / kFadeDuration;
return fadeProgress >= 1.0f;
}
),
m_ripples.end()
);
}
inline void RippleManager::clear() {
m_ripples.clear();
}
inline void DrawRippleEffect(ImDrawList* drawList, const ImRect& rect, ImGuiID id,
float cornerRadius, bool hovered, bool held, ImU32 color) {
RippleManager& manager = RippleManager::instance();
// Start ripple on press
if (held && ImGui::IsMouseClicked(0)) {
ImVec2 mousePos = ImGui::GetIO().MousePos;
// Calculate max radius (distance from click to farthest corner)
float dx1 = mousePos.x - rect.Min.x;
float dy1 = mousePos.y - rect.Min.y;
float dx2 = rect.Max.x - mousePos.x;
float dy2 = rect.Max.y - mousePos.y;
float maxDx = ImMax(dx1, dx2);
float maxDy = ImMax(dy1, dy2);
float maxRadius = std::sqrt(maxDx * maxDx + maxDy * maxDy) * 1.2f;
manager.startRipple(id, mousePos, maxRadius, color);
}
// Release ripple when mouse released
if (!held && !ImGui::IsMouseDown(0)) {
manager.releaseRipple(id);
}
// Draw the ripple
manager.drawRipple(drawList, id, rect, cornerRadius);
// Also draw hover state overlay
if (hovered && !held) {
float r = ((color >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
float g = ((color >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
float b = ((color >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
ImU32 hoverColor = ImGui::ColorConvertFloat4ToU32(ImVec4(r, g, b, 0.04f)); // 4% overlay
drawList->AddRectFilled(rect.Min, rect.Max, hoverColor, cornerRadius);
}
}
inline void UpdateRipples() {
RippleManager::instance().update();
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,467 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "motion.h"
#include "colors.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <functional>
#include <string>
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Transitions
// ============================================================================
// Based on https://m2.material.io/design/motion/the-motion-system.html
//
// Transition patterns for navigating between views and states.
// ============================================================================
// Transition Types
// ============================================================================
enum class TransitionType {
None, // Instant switch
Fade, // Cross-fade
SlideLeft, // Slide from right to left (forward navigation)
SlideRight, // Slide from left to right (back navigation)
SlideUp, // Slide from bottom (modal entry)
SlideDown, // Slide to bottom (modal exit)
Scale, // Scale up/down
SharedAxis, // Material shared axis transition
ContainerTransform // Card to full-screen
};
// ============================================================================
// View Transition Manager
// ============================================================================
/**
* @brief Manages transitions between views/screens
*/
class TransitionManager {
public:
static TransitionManager& instance();
/**
* @brief Start a transition to a new view
*
* @param newViewId Identifier for the new view
* @param type Transition type
* @param duration Transition duration (default from motion.h)
*/
void transitionTo(const std::string& newViewId,
TransitionType type = TransitionType::Fade,
float duration = duration::ScreenChange);
/**
* @brief Go back with reverse transition
*/
void goBack(TransitionType type = TransitionType::SlideRight,
float duration = duration::ExitScreen);
/**
* @brief Update transition state (call each frame)
*/
void update(float deltaTime);
/**
* @brief Get current view ID
*/
const std::string& getCurrentView() const { return m_currentView; }
/**
* @brief Get previous view ID (during transition)
*/
const std::string& getPreviousView() const { return m_previousView; }
/**
* @brief Check if transition is in progress
*/
bool isTransitioning() const { return m_transitioning; }
/**
* @brief Get transition progress (0-1)
*/
float getProgress() const { return m_progress; }
/**
* @brief Get current transition type
*/
TransitionType getType() const { return m_type; }
/**
* @brief Apply transition effect to outgoing content
*
* Call before rendering the previous view content.
* Returns false if outgoing content should be skipped.
*/
bool beginOutgoingTransition();
/**
* @brief End outgoing content transition
*/
void endOutgoingTransition();
/**
* @brief Apply transition effect to incoming content
*
* Call before rendering the new view content.
* Returns false if incoming content should be skipped.
*/
bool beginIncomingTransition();
/**
* @brief End incoming content transition
*/
void endIncomingTransition();
/**
* @brief Get alpha for fading effects
*/
float getOutgoingAlpha() const;
float getIncomingAlpha() const;
/**
* @brief Get offset for sliding effects
*/
ImVec2 getOutgoingOffset() const;
ImVec2 getIncomingOffset() const;
private:
TransitionManager() = default;
std::string m_currentView;
std::string m_previousView;
TransitionType m_type = TransitionType::None;
float m_duration = 0;
float m_elapsed = 0;
float m_progress = 0;
bool m_transitioning = false;
};
// ============================================================================
// Fade Transition Helper
// ============================================================================
/**
* @brief Simple fade transition between two states
*/
class FadeTransition {
public:
FadeTransition() : m_alpha(1.0f) {}
/**
* @brief Fade out
*/
void fadeOut(float duration = duration::Fast) {
m_alpha.animateTo(0.0f, duration, EaseAccelerate);
}
/**
* @brief Fade in
*/
void fadeIn(float duration = duration::Fast) {
m_alpha.animateTo(1.0f, duration, EaseDecelerate);
}
/**
* @brief Update (call each frame)
*/
void update(float deltaTime) {
m_alpha.update(deltaTime);
}
/**
* @brief Get current alpha
*/
float getAlpha() const { return m_alpha.get(); }
/**
* @brief Check if visible (alpha > 0)
*/
bool isVisible() const { return m_alpha.get() > 0.001f; }
/**
* @brief Check if animating
*/
bool isAnimating() const { return m_alpha.isAnimating(); }
/**
* @brief Push alpha to ImGui
*/
void pushAlpha() const {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_alpha.get());
}
/**
* @brief Pop alpha from ImGui
*/
void popAlpha() const {
ImGui::PopStyleVar();
}
private:
AnimatedValue<float> m_alpha;
};
// ============================================================================
// Expandable/Collapsible Section
// ============================================================================
/**
* @brief Animated expandable section
*/
class ExpandableSection {
public:
ExpandableSection(bool initiallyExpanded = false)
: m_expanded(initiallyExpanded)
, m_height(initiallyExpanded ? 1.0f : 0.0f)
{}
/**
* @brief Toggle expansion state
*/
void toggle() {
setExpanded(!m_expanded);
}
/**
* @brief Set expansion state
*/
void setExpanded(bool expanded) {
m_expanded = expanded;
m_height.animateTo(expanded ? 1.0f : 0.0f,
expanded ? duration::Standard : duration::Medium,
EaseStandard);
}
/**
* @brief Update animation
*/
void update(float deltaTime) {
m_height.update(deltaTime);
}
/**
* @brief Check if expanded
*/
bool isExpanded() const { return m_expanded; }
/**
* @brief Get height multiplier (0-1)
*/
float getHeightMultiplier() const { return m_height.get(); }
/**
* @brief Check if animating
*/
bool isAnimating() const { return m_height.isAnimating(); }
/**
* @brief Begin expandable content (applies clipping)
*
* @param maxHeight Maximum content height when fully expanded
* @return true if content should be rendered
*/
bool beginContent(float maxHeight) {
float currentHeight = maxHeight * m_height.get();
if (currentHeight < 1.0f)
return false;
ImGui::BeginChild("##expandable", ImVec2(0, currentHeight), false,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
return true;
}
/**
* @brief End expandable content
*/
void endContent() {
ImGui::EndChild();
}
private:
bool m_expanded;
AnimatedValue<float> m_height;
};
// ============================================================================
// Implementation
// ============================================================================
inline TransitionManager& TransitionManager::instance() {
static TransitionManager s_instance;
return s_instance;
}
inline void TransitionManager::transitionTo(const std::string& newViewId,
TransitionType type, float duration) {
if (m_transitioning)
return; // Don't interrupt ongoing transition
m_previousView = m_currentView;
m_currentView = newViewId;
m_type = type;
m_duration = duration;
m_elapsed = 0;
m_progress = 0;
m_transitioning = true;
}
inline void TransitionManager::goBack(TransitionType type, float duration) {
if (m_transitioning || m_previousView.empty())
return;
std::string temp = m_currentView;
m_currentView = m_previousView;
m_previousView = temp;
m_type = type;
m_duration = duration;
m_elapsed = 0;
m_progress = 0;
m_transitioning = true;
}
inline void TransitionManager::update(float deltaTime) {
if (!m_transitioning)
return;
m_elapsed += deltaTime;
m_progress = m_elapsed / m_duration;
if (m_progress >= 1.0f) {
m_progress = 1.0f;
m_transitioning = false;
m_previousView.clear();
}
}
inline bool TransitionManager::beginOutgoingTransition() {
if (!m_transitioning || m_type == TransitionType::None)
return false;
float alpha = getOutgoingAlpha();
if (alpha < 0.001f)
return false;
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
ImVec2 offset = getOutgoingOffset();
if (offset.x != 0 || offset.y != 0) {
ImVec2 cursor = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(cursor.x + offset.x, cursor.y + offset.y));
}
return true;
}
inline void TransitionManager::endOutgoingTransition() {
ImGui::PopStyleVar();
}
inline bool TransitionManager::beginIncomingTransition() {
if (!m_transitioning && m_progress >= 1.0f)
return true; // No transition, just render normally
if (m_type == TransitionType::None)
return true;
float alpha = getIncomingAlpha();
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
ImVec2 offset = getIncomingOffset();
if (offset.x != 0 || offset.y != 0) {
ImVec2 cursor = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(cursor.x + offset.x, cursor.y + offset.y));
}
return true;
}
inline void TransitionManager::endIncomingTransition() {
if (m_transitioning || m_type != TransitionType::None) {
ImGui::PopStyleVar();
}
}
inline float TransitionManager::getOutgoingAlpha() const {
float eased = EaseAccelerate(m_progress);
switch (m_type) {
case TransitionType::Fade:
return 1.0f - eased;
case TransitionType::Scale:
return 1.0f - eased;
default:
// For slides, fade quickly in first half
return m_progress < 0.5f ? 1.0f - eased * 2.0f : 0.0f;
}
}
inline float TransitionManager::getIncomingAlpha() const {
float eased = EaseDecelerate(m_progress);
switch (m_type) {
case TransitionType::Fade:
return eased;
case TransitionType::Scale:
return eased;
default:
// For slides, fade in during second half
return m_progress > 0.5f ? (eased - 0.5f) * 2.0f : 0.0f;
}
}
inline ImVec2 TransitionManager::getOutgoingOffset() const {
ImGuiIO& io = ImGui::GetIO();
float eased = EaseAccelerate(m_progress);
float slideDistance = 100.0f; // Pixels to slide
switch (m_type) {
case TransitionType::SlideLeft:
return ImVec2(-slideDistance * eased, 0);
case TransitionType::SlideRight:
return ImVec2(slideDistance * eased, 0);
case TransitionType::SlideUp:
return ImVec2(0, -slideDistance * eased);
case TransitionType::SlideDown:
return ImVec2(0, slideDistance * eased);
case TransitionType::SharedAxis:
return ImVec2(-slideDistance * 0.3f * eased, 0);
default:
return ImVec2(0, 0);
}
}
inline ImVec2 TransitionManager::getIncomingOffset() const {
float eased = EaseDecelerate(m_progress);
float slideDistance = 100.0f;
float remaining = 1.0f - eased;
switch (m_type) {
case TransitionType::SlideLeft:
return ImVec2(slideDistance * remaining, 0);
case TransitionType::SlideRight:
return ImVec2(-slideDistance * remaining, 0);
case TransitionType::SlideUp:
return ImVec2(0, slideDistance * remaining);
case TransitionType::SlideDown:
return ImVec2(0, -slideDistance * remaining);
case TransitionType::SharedAxis:
return ImVec2(slideDistance * 0.3f * remaining, 0);
default:
return ImVec2(0, 0);
}
}
} // namespace material
} // namespace ui
} // namespace dragonx

144
src/ui/material/type.h Normal file
View File

@@ -0,0 +1,144 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// ============================================================================
// Material Design Typography Quick Reference
// ============================================================================
//
// Include this header for convenient access to the typography system.
//
// Usage:
// #include "ui/material/type.h"
//
// // Access fonts directly
// ImGui::PushFont(Type().h5());
// ImGui::Text("Card Title");
// ImGui::PopFont();
//
// // Use text helpers
// Type().text(TypeStyle::H5, "Card Title");
// Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), "Timestamp");
//
// // Scoped font (RAII)
// MATERIAL_FONT(Body1) {
// ImGui::TextWrapped("Body text goes here...");
// }
#pragma once
#include "typography.h"
#include "colors.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Convenience Accessor
// ============================================================================
/**
* @brief Quick access to Typography singleton
*
* Usage:
* ImFont* title = Type().h5();
* Type().text(TypeStyle::Body1, "Hello");
*/
inline Typography& Type() {
return Typography::instance();
}
// ============================================================================
// Text Style Constants for Quick Reference
// ============================================================================
// Headers (Large display text)
// H1: 96px Light - Rare, used for massive display numbers
// H2: 60px Light - Section banners
// H3: 48px Regular - Major section titles
// H4: 34px Regular - Page titles
// H5: 24px Regular - Card titles, dialog titles
// H6: 20px Medium - Section headers, sidebar items
// Body (Primary content)
// Subtitle1: 16px Regular - Secondary information, list item primary text
// Subtitle2: 14px Medium - List item secondary text, menu items
// Body1: 16px Regular - Primary body text (default for content)
// Body2: 14px Regular - Secondary body text
// UI Elements
// Button: 14px Medium, UPPERCASE - Button labels
// Caption: 12px Regular - Timestamps, hints, helper text
// Overline: 10px Regular, UPPERCASE - Section labels, tabs
// ============================================================================
// Common Typography Patterns
// ============================================================================
/**
* @brief Render a card header with title and optional subtitle
*/
inline void CardHeader(const char* title, const char* subtitle = nullptr)
{
Type().text(TypeStyle::H5, title);
if (subtitle) {
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), subtitle);
}
}
/**
* @brief Render an overline label (uppercase section marker)
*/
inline void OverlineLabel(const char* text)
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), text);
}
/**
* @brief Render body text
*/
inline void BodyText(const char* text, bool secondary = false)
{
Type().text(secondary ? TypeStyle::Body2 : TypeStyle::Body1, text);
}
/**
* @brief Render caption/helper text
*/
inline void Caption(const char* text)
{
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), text);
}
/**
* @brief Render error text
*/
inline void ErrorText(const char* text)
{
Type().textColored(TypeStyle::Body2, Error(), text);
}
/**
* @brief Render success text
*/
inline void SuccessText(const char* text)
{
Type().textColored(TypeStyle::Body2, Success(), text);
}
/**
* @brief Render a balance/amount display (large number)
*/
inline void AmountDisplay(const char* amount, const char* unit = nullptr)
{
Type().text(TypeStyle::H4, amount);
if (unit) {
ImGui::SameLine();
Type().textColored(TypeStyle::H6, OnSurfaceMedium(), unit);
}
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,401 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "typography.h"
#include "../layout.h"
#include "../schema/ui_schema.h"
#include <cstring>
#include <cstdio>
#include <cctype>
#include <string>
// Embedded font data (via INCBIN assembler .incbin directive)
#include "../embedded/embedded_fonts.h" // g_ubuntu_*_data/size, g_material_icons_*
#include "../embedded/IconsMaterialDesign.h" // Icon codepoint defines
#include "../../util/logger.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Type Scale Specifications
// ============================================================================
// Font sizes are configured in ui/layout.h for easy global adjustment.
// Letter spacing, line height and text-transform are loaded from ui.toml
// via the UISchema typography section.
// Font sizes come from UISchema (JSON-configurable) via Layout:: accessors.
const TypeSpec* Typography::getTypeSpecs()
{
// Rebuild each call to pick up hot-reloaded values
static TypeSpec specs[kNumStyles];
// Helper: read typography entry from UISchema
auto& S = schema::UI();
auto tspec = [&](const char* key, float defLS, float defLH, bool defUpper) -> TypeSpec {
auto e = S.drawElement("typography", key);
float ls = e.getFloat("letter-spacing", defLS);
float lh = e.getFloat("line-height", defLH);
// text-transform stored as extraColors (string map)
bool upper = defUpper;
auto it = e.extraColors.find("text-transform");
if (it != e.extraColors.end()) {
upper = (it->second == "uppercase");
}
return TypeSpec{0.0f, ls, lh, upper};
};
// Font size comes from Layout:: accessors; typography from UISchema
auto ts0 = tspec("h1", -1.0f, 1.167f, false);
auto ts1 = tspec("h2", -0.5f, 1.2f, false);
auto ts2 = tspec("h3", 0.0f, 1.167f, false);
auto ts3 = tspec("h4", 0.25f, 1.235f, false);
auto ts4 = tspec("h5", 0.0f, 1.334f, false);
auto ts5 = tspec("h6", 0.15f, 1.6f, false);
auto ts6 = tspec("subtitle1", 0.15f, 1.75f, false);
auto ts7 = tspec("subtitle2", 0.1f, 1.57f, false);
auto ts8 = tspec("body1", 0.5f, 1.5f, false);
auto ts9 = tspec("body2", 0.25f, 1.43f, false);
auto ts10 = tspec("button", 1.25f, 1.75f, true);
auto ts11 = tspec("caption", 0.4f, 1.66f, false);
auto ts12 = tspec("overline", 1.5f, 2.66f, true);
specs[0] = { Layout::kFontH1(), ts0.letterSpacing, ts0.lineHeight, ts0.uppercase };
specs[1] = { Layout::kFontH2(), ts1.letterSpacing, ts1.lineHeight, ts1.uppercase };
specs[2] = { Layout::kFontH3(), ts2.letterSpacing, ts2.lineHeight, ts2.uppercase };
specs[3] = { Layout::kFontH4(), ts3.letterSpacing, ts3.lineHeight, ts3.uppercase };
specs[4] = { Layout::kFontH5(), ts4.letterSpacing, ts4.lineHeight, ts4.uppercase };
specs[5] = { Layout::kFontH6(), ts5.letterSpacing, ts5.lineHeight, ts5.uppercase };
specs[6] = { Layout::kFontSubtitle1(), ts6.letterSpacing, ts6.lineHeight, ts6.uppercase };
specs[7] = { Layout::kFontSubtitle2(), ts7.letterSpacing, ts7.lineHeight, ts7.uppercase };
specs[8] = { Layout::kFontBody1(), ts8.letterSpacing, ts8.lineHeight, ts8.uppercase };
specs[9] = { Layout::kFontBody2(), ts9.letterSpacing, ts9.lineHeight, ts9.uppercase };
specs[10] = { Layout::kFontButton(), ts10.letterSpacing, ts10.lineHeight, ts10.uppercase };
specs[11] = { Layout::kFontCaption(), ts11.letterSpacing, ts11.lineHeight, ts11.uppercase };
specs[12] = { Layout::kFontOverline(), ts12.letterSpacing, ts12.lineHeight, ts12.uppercase };
specs[13] = { Layout::kFontButtonSm(), ts10.letterSpacing, ts10.lineHeight, ts10.uppercase };
specs[14] = { Layout::kFontButtonLg(), ts10.letterSpacing, ts10.lineHeight, ts10.uppercase };
return specs;
}
// ============================================================================
// Typography Implementation
// ============================================================================
Typography& Typography::instance()
{
static Typography s_instance;
return s_instance;
}
bool Typography::reload(ImGuiIO& io, float dpiScale)
{
if (!loaded_) {
return load(io, dpiScale);
}
DEBUG_LOGF("Typography: Reloading fonts for DPI scale %.2f\n", dpiScale);
// Clear existing fonts and reload
io.Fonts->Clear();
loaded_ = false;
for (int i = 0; i < kNumStyles; ++i) fonts_[i] = nullptr;
for (int i = 0; i < kNumIconSizes; ++i) iconFonts_[i] = nullptr;
return load(io, dpiScale);
}
bool Typography::load(ImGuiIO& io, float dpiScale)
{
if (loaded_) {
DEBUG_LOGF("Typography: Already loaded\n");
return true;
}
dpiScale_ = dpiScale;
// Multiply by dpiScale so fonts are the correct physical size.
// On Windows Per-Monitor DPI v2, SDL3 coordinates are physical pixels
// and DisplayFramebufferScale is 1.0 (no automatic upscaling).
// The window is resized by dpiScale in main.cpp so that fonts at
// size*dpiScale fit proportionally (no overflow).
float scale = dpiScale * Layout::kFontScale();
DEBUG_LOGF("Typography: Loading Material Design type scale (DPI: %.2f, fontScale: %.2f, combined: %.2f)\n", dpiScale, Layout::kFontScale(), scale);
// For ImGui, we need to load fonts at specific pixel sizes.
// Font sizes come from Layout:: accessors (backed by UISchema JSON)
// Load fonts in order of TypeStyle enum
// H1: Light
fonts_[0] = loadFont(io, kWeightLight, Layout::kFontH1() * scale, "H1");
// H2: Light
fonts_[1] = loadFont(io, kWeightLight, Layout::kFontH2() * scale, "H2");
// H3: Regular
fonts_[2] = loadFont(io, kWeightRegular, Layout::kFontH3() * scale, "H3");
// H4: Regular
fonts_[3] = loadFont(io, kWeightRegular, Layout::kFontH4() * scale, "H4");
// H5: Regular
fonts_[4] = loadFont(io, kWeightRegular, Layout::kFontH5() * scale, "H5");
// H6: Medium
fonts_[5] = loadFont(io, kWeightMedium, Layout::kFontH6() * scale, "H6");
// Subtitle1: Regular
fonts_[6] = loadFont(io, kWeightRegular, Layout::kFontSubtitle1() * scale, "Subtitle1");
// Subtitle2: Medium
fonts_[7] = loadFont(io, kWeightMedium, Layout::kFontSubtitle2() * scale, "Subtitle2");
// Body1: Regular (shares with Subtitle1 if same size)
if (Layout::kFontBody1() == Layout::kFontSubtitle1()) {
fonts_[8] = fonts_[6]; // Reuse Subtitle1 font
} else {
fonts_[8] = loadFont(io, kWeightRegular, Layout::kFontBody1() * scale, "Body1");
}
// Body2: Regular
fonts_[9] = loadFont(io, kWeightRegular, Layout::kFontBody2() * scale, "Body2");
// Button: Medium (shares with Subtitle2 if same size)
if (Layout::kFontButton() == Layout::kFontSubtitle2()) {
fonts_[10] = fonts_[7]; // Reuse Subtitle2 font
} else {
fonts_[10] = loadFont(io, kWeightMedium, Layout::kFontButton() * scale, "Button");
}
// Caption: Regular
fonts_[11] = loadFont(io, kWeightRegular, Layout::kFontCaption() * scale, "Caption");
// Overline: Regular
fonts_[12] = loadFont(io, kWeightRegular, Layout::kFontOverline() * scale, "Overline");
// ButtonSm: Medium
fonts_[13] = loadFont(io, kWeightMedium, Layout::kFontButtonSm() * scale, "ButtonSm");
// ButtonLg: Medium
fonts_[14] = loadFont(io, kWeightMedium, Layout::kFontButtonLg() * scale, "ButtonLg");
// --- Icon fonts at multiple sizes ---
// These are standalone fonts (not merged) so we can PushFont(iconXxx) for icon rendering.
iconFonts_[0] = loadIconFont(io, 14.0f * scale, "IconSmall");
iconFonts_[1] = loadIconFont(io, 18.0f * scale, "IconMed");
iconFonts_[2] = loadIconFont(io, 24.0f * scale, "IconLarge");
iconFonts_[3] = loadIconFont(io, 40.0f * scale, "IconXL");
// Verify all fonts loaded
bool allLoaded = true;
for (int i = 0; i < kNumStyles; ++i) {
if (!fonts_[i]) {
DEBUG_LOGF("Typography: Warning - Font for style %d not loaded\n", i);
fonts_[i] = io.Fonts->Fonts.Size > 0 ? io.Fonts->Fonts[0] : nullptr;
allLoaded = false;
}
}
// Set Body1 as the default font so that bare ImGui::Text() calls
// (status bar, dialogs, notifications, etc.) render at Body1 size
// instead of H1 (the first font loaded).
io.FontDefault = fonts_[static_cast<int>(TypeStyle::Body1)];
loaded_ = true;
DEBUG_LOGF("Typography: Loaded %d font styles (default=Body1)\n", allLoaded ? kNumStyles : -1);
return allLoaded;
}
ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* name)
{
// Select font data based on weight
const unsigned char* fontData = nullptr;
unsigned int fontDataLen = 0;
switch (weight) {
case kWeightLight:
fontData = g_ubuntu_light_data;
fontDataLen = g_ubuntu_light_size;
break;
case kWeightMedium:
fontData = g_ubuntu_medium_data;
fontDataLen = g_ubuntu_medium_size;
break;
case kWeightRegular:
default:
fontData = g_ubuntu_regular_data;
fontDataLen = g_ubuntu_regular_size;
break;
}
// ImGui needs to own a copy of the font data
void* fontDataCopy = IM_ALLOC(fontDataLen);
memcpy(fontDataCopy, fontData, fontDataLen);
ImFontConfig cfg;
cfg.FontDataOwnedByAtlas = true; // ImGui will free this
cfg.OversampleH = 2;
cfg.OversampleV = 1;
cfg.PixelSnapH = true;
// Include default ASCII + Latin, Latin Extended (for Spanish/multilingual),
// plus arrows (⇄ U+21C4), math (≈ U+2248),
// general punctuation (— U+2014, … U+2026, etc.)
static const ImWchar glyphRanges[] = {
0x0020, 0x00FF, // Basic Latin + Latin-1 Supplement
0x0100, 0x024F, // Latin Extended-A + Latin Extended-B (Spanish, etc.)
0x2000, 0x206F, // General Punctuation (em dash, ellipsis, etc.)
0x2190, 0x21FF, // Arrows (includes ⇄ U+21C4, ↻ U+21BB)
0x2200, 0x22FF, // Mathematical Operators (includes ≈ U+2248)
0x2600, 0x26FF, // Miscellaneous Symbols (includes ⛏ U+26CF)
0,
};
cfg.GlyphRanges = glyphRanges;
// Create a unique name for this font variant
snprintf(cfg.Name, sizeof(cfg.Name), "Ubuntu %s %.0fpx",
weight == kWeightLight ? "Light" :
weight == kWeightMedium ? "Medium" : "Regular",
size);
ImFont* font = io.Fonts->AddFontFromMemoryTTF(fontDataCopy, fontDataLen, size, &cfg);
if (font) {
DEBUG_LOGF("Typography: Loaded %s (%.0fpx) as '%s'\n", name, size, cfg.Name);
} else {
DEBUG_LOGF("Typography: Failed to load %s\n", name);
IM_FREE(fontDataCopy);
}
return font;
}
ImFont* Typography::loadIconFont(ImGuiIO& io, float size, const char* name)
{
// ImGui needs to own a copy of the font data
void* fontDataCopy = IM_ALLOC(g_material_icons_size);
memcpy(fontDataCopy, g_material_icons_data, g_material_icons_size);
ImFontConfig cfg;
cfg.FontDataOwnedByAtlas = true;
cfg.OversampleH = 2;
cfg.OversampleV = 1;
cfg.PixelSnapH = true;
cfg.MergeMode = false; // standalone icon font
cfg.GlyphMinAdvanceX = size; // monospace icons
static const ImWchar iconRanges[] = { ICON_MIN_MD, ICON_MAX_16_MD, 0 };
cfg.GlyphRanges = iconRanges;
snprintf(cfg.Name, sizeof(cfg.Name), "MDIcons %.0fpx", size);
ImFont* font = io.Fonts->AddFontFromMemoryTTF(fontDataCopy, g_material_icons_size, size, &cfg);
if (font) {
DEBUG_LOGF("Typography: Loaded icon font %s (%.0fpx)\n", name, size);
} else {
DEBUG_LOGF("Typography: Failed to load icon font %s\n", name);
IM_FREE(fontDataCopy);
}
return font;
}
ImFont* Typography::getFont(TypeStyle style) const
{
int index = static_cast<int>(style);
if (index >= 0 && index < kNumStyles && fonts_[index]) {
return fonts_[index];
}
// Return default font if not found
return ImGui::GetIO().Fonts->Fonts.Size > 0 ?
ImGui::GetIO().Fonts->Fonts[0] : nullptr;
}
const TypeSpec& Typography::getSpec(TypeStyle style) const
{
const TypeSpec* specs = getTypeSpecs();
int index = static_cast<int>(style);
if (index >= 0 && index < kNumStyles) {
return specs[index];
}
// Return Body1 as default
return specs[static_cast<int>(TypeStyle::Body1)];
}
void Typography::pushFont(TypeStyle style) const
{
ImGui::PushFont(getFont(style));
}
void Typography::popFont() const
{
ImGui::PopFont();
}
// ============================================================================
// Text Rendering Helpers
// ============================================================================
void Typography::text(TypeStyle style, const char* text) const
{
const TypeSpec& spec = getSpec(style);
pushFont(style);
if (spec.uppercase) {
// Convert to uppercase
std::string upper;
upper.reserve(strlen(text));
for (const char* p = text; *p; ++p) {
upper += static_cast<char>(std::toupper(static_cast<unsigned char>(*p)));
}
ImGui::TextUnformatted(upper.c_str());
} else {
ImGui::TextUnformatted(text);
}
popFont();
}
void Typography::textWrapped(TypeStyle style, const char* text) const
{
const TypeSpec& spec = getSpec(style);
pushFont(style);
if (spec.uppercase) {
std::string upper;
upper.reserve(strlen(text));
for (const char* p = text; *p; ++p) {
upper += static_cast<char>(std::toupper(static_cast<unsigned char>(*p)));
}
ImGui::TextWrapped("%s", upper.c_str());
} else {
ImGui::TextWrapped("%s", text);
}
popFont();
}
void Typography::textColored(TypeStyle style, ImU32 color, const char* text) const
{
const TypeSpec& spec = getSpec(style);
pushFont(style);
ImVec4 colorVec = ImGui::ColorConvertU32ToFloat4(color);
if (spec.uppercase) {
std::string upper;
upper.reserve(strlen(text));
for (const char* p = text; *p; ++p) {
upper += static_cast<char>(std::toupper(static_cast<unsigned char>(*p)));
}
ImGui::TextColored(colorVec, "%s", upper.c_str());
} else {
ImGui::TextColored(colorVec, "%s", text);
}
popFont();
}
} // namespace material
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,288 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
#include <string>
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Type Scale
// ============================================================================
// Based on https://m2.material.io/design/typography/the-type-system.html
//
// The type scale is a combination of 13 styles that are supported by the
// type system. It contains reusable categories of text, each with an
// intended application and meaning.
/**
* @brief Material Design typography style identifiers
*/
enum class TypeStyle {
H1, // 96sp Light - Large display headers
H2, // 60sp Light - Display headers
H3, // 48sp Regular - Section titles
H4, // 34sp Regular - Section titles
H5, // 24sp Regular - Card titles, dialog titles
H6, // 20sp Medium - Section headers, subtitles
Subtitle1, // 16sp Regular - Secondary information
Subtitle2, // 14sp Medium - Secondary information
Body1, // 16sp Regular - Primary body text
Body2, // 14sp Regular - Secondary body text
Button, // 14sp Medium - Button labels (uppercase)
Caption, // 12sp Regular - Timestamps, labels
Overline, // 10sp Regular - Overline labels (uppercase)
ButtonSm, // Small button labels
ButtonLg // Large button labels
};
/**
* @brief Typography specification for a type style
*/
struct TypeSpec {
float size; // Font size in sp (scaled pixels)
float letterSpacing; // Letter spacing in px (applied after scaling)
float lineHeight; // Line height multiplier
bool uppercase; // Whether text should be uppercase
// Font weight is handled by which font is used (Light/Regular/Medium)
};
/**
* @brief Typography system managing Material Design fonts
*
* Loads Ubuntu fonts in Light, Regular, and Medium weights at multiple
* sizes to implement the Material Design type scale.
*
* Usage:
* // Initialize during startup (after ImGui context)
* dragonx::ui::material::Typography::instance().load(io);
*
* // Use fonts throughout the application
* ImGui::PushFont(Typography::instance().getFont(TypeStyle::H5));
* ImGui::Text("Card Title");
* ImGui::PopFont();
*
* // Or use convenience functions
* Typography::instance().pushFont(TypeStyle::Body1);
* ImGui::TextWrapped("Body text here...");
* Typography::instance().popFont();
*/
class Typography {
public:
/**
* @brief Get the singleton instance
*/
static Typography& instance();
/**
* @brief Load all fonts for the type scale
*
* Must be called after ImGui context is created but before first frame.
*
* @param io ImGui IO reference
* @param dpiScale DPI scale factor (default 1.0)
* @return true if fonts loaded successfully
*/
bool load(ImGuiIO& io, float dpiScale = 1.0f);
/**
* @brief Reload fonts at a new DPI scale
*
* Call when the display scale changes (e.g., window moved to a different
* DPI monitor). Clears the existing font atlas and reloads all fonts.
*
* @param io ImGui IO reference
* @param dpiScale New DPI scale factor
* @return true if fonts reloaded successfully
*/
bool reload(ImGuiIO& io, float dpiScale);
/**
* @brief Check if typography system is loaded
*/
bool isLoaded() const { return loaded_; }
/**
* @brief Get the current DPI scale
*/
float getDpiScale() const { return dpiScale_; }
/**
* @brief Get font for a type style
*
* @param style The Material Design type style
* @return ImFont pointer (never null, returns default if not loaded)
*/
ImFont* getFont(TypeStyle style) const;
/**
* @brief Get the spec for a type style
*
* @param style The Material Design type style
* @return TypeSpec with size, spacing, and line height
*/
const TypeSpec& getSpec(TypeStyle style) const;
/**
* @brief Push font for a type style
*
* Convenience wrapper around ImGui::PushFont(getFont(style))
*/
void pushFont(TypeStyle style) const;
/**
* @brief Pop the current font
*
* Convenience wrapper around ImGui::PopFont()
*/
void popFont() const;
// ========================================================================
// Font Accessors (for common cases)
// ========================================================================
// Headers
ImFont* h1() const { return getFont(TypeStyle::H1); }
ImFont* h2() const { return getFont(TypeStyle::H2); }
ImFont* h3() const { return getFont(TypeStyle::H3); }
ImFont* h4() const { return getFont(TypeStyle::H4); }
ImFont* h5() const { return getFont(TypeStyle::H5); }
ImFont* h6() const { return getFont(TypeStyle::H6); }
// Body text
ImFont* subtitle1() const { return getFont(TypeStyle::Subtitle1); }
ImFont* subtitle2() const { return getFont(TypeStyle::Subtitle2); }
ImFont* body1() const { return getFont(TypeStyle::Body1); }
ImFont* body2() const { return getFont(TypeStyle::Body2); }
// UI elements
ImFont* button() const { return getFont(TypeStyle::Button); }
ImFont* buttonSm() const { return getFont(TypeStyle::ButtonSm); }
ImFont* buttonLg() const { return getFont(TypeStyle::ButtonLg); }
ImFont* caption() const { return getFont(TypeStyle::Caption); }
ImFont* overline() const { return getFont(TypeStyle::Overline); }
// Icon fonts — Material Design Icons merged at specific pixel sizes
// Use these when you need to render an icon at a specific size via AddText/ImGui::Text.
// Common sizes: iconSmall (14px), iconMed (18px), iconLarge (24px).
ImFont* iconSmall() const { return iconFonts_[0] ? iconFonts_[0] : getFont(TypeStyle::Body2); }
ImFont* iconMed() const { return iconFonts_[1] ? iconFonts_[1] : getFont(TypeStyle::Body1); }
ImFont* iconLarge() const { return iconFonts_[2] ? iconFonts_[2] : getFont(TypeStyle::H5); }
ImFont* iconXL() const { return iconFonts_[3] ? iconFonts_[3] : getFont(TypeStyle::H3); }
/**
* @brief Resolve a font name string to ImFont*
* @param name Font name like "button", "button-sm", "button-lg", "h4", "body1"
* @return ImFont* pointer, or nullptr if name is empty/unknown
*/
ImFont* resolveByName(const std::string& name) const {
if (name.empty()) return nullptr;
if (name == "h1") return h1();
if (name == "h2") return h2();
if (name == "h3") return h3();
if (name == "h4") return h4();
if (name == "h5") return h5();
if (name == "h6") return h6();
if (name == "subtitle1") return subtitle1();
if (name == "subtitle2") return subtitle2();
if (name == "body1") return body1();
if (name == "body2") return body2();
if (name == "button") return button();
if (name == "button-sm") return buttonSm();
if (name == "button-lg") return buttonLg();
if (name == "caption") return caption();
if (name == "overline") return overline();
return nullptr;
}
// ========================================================================
// Text Rendering Helpers
// ========================================================================
/**
* @brief Render text with a specific type style
*
* Handles font push/pop and optional uppercase transformation.
*
* @param style The type style to use
* @param text The text to render
*/
void text(TypeStyle style, const char* text) const;
/**
* @brief Render wrapped text with a specific type style
*
* @param style The type style to use
* @param text The text to render
*/
void textWrapped(TypeStyle style, const char* text) const;
/**
* @brief Render colored text with a specific type style
*
* @param style The type style to use
* @param color Text color
* @param text The text to render
*/
void textColored(TypeStyle style, ImU32 color, const char* text) const;
private:
Typography() = default;
~Typography() = default;
Typography(const Typography&) = delete;
Typography& operator=(const Typography&) = delete;
// Load fonts at a specific size with a specific weight
ImFont* loadFont(ImGuiIO& io, int weight, float size, const char* name);
// Font weight constants
static constexpr int kWeightLight = 300;
static constexpr int kWeightRegular = 400;
static constexpr int kWeightMedium = 500;
bool loaded_ = false;
float dpiScale_ = 1.0f;
// Fonts for each type style
ImFont* fonts_[15] = {};
// Icon fonts at different sizes: [0]=small(14), [1]=med(18), [2]=large(24), [3]=xl(40)
ImFont* iconFonts_[4] = {};
static constexpr int kNumIconSizes = 4;
// Load an icon-only font at a specific pixel size
ImFont* loadIconFont(ImGuiIO& io, float size, const char* name);
// Type specifications
static const TypeSpec* getTypeSpecs();
static constexpr int kNumStyles = 15;
};
// ============================================================================
// Convenience Macros (Optional)
// ============================================================================
// Scoped font push/pop using RAII
class ScopedFont {
public:
explicit ScopedFont(TypeStyle style) {
Typography::instance().pushFont(style);
}
~ScopedFont() {
Typography::instance().popFont();
}
};
// Usage: MATERIAL_FONT(H5) { ImGui::Text("Title"); }
#define MATERIAL_FONT(style) \
if (dragonx::ui::material::ScopedFont _font{dragonx::ui::material::TypeStyle::style}; true)
} // namespace material
} // namespace ui
} // namespace dragonx

156
src/ui/notifications.cpp Normal file
View File

@@ -0,0 +1,156 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "notifications.h"
#include "schema/ui_schema.h"
#include "material/type.h"
#include "material/draw_helpers.h"
#include "../embedded/IconsMaterialDesign.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
void Notifications::render()
{
// Remove expired notifications
while (!notifications_.empty() && notifications_.front().isExpired()) {
notifications_.pop_front();
}
if (notifications_.empty()) {
return;
}
// Only show the most recent (last) notification as a compact status-bar pill
const auto& notif = notifications_.back();
const auto& S = schema::UI();
auto nde = [&](const char* key, float fb) {
float v = S.drawElement("components.notifications", key).size;
return v >= 0 ? v : fb;
};
// Status bar geometry
float sbHeight = S.window("components.status-bar").height;
if (sbHeight <= 0.0f) sbHeight = 30.0f;
ImGuiViewport* viewport = ImGui::GetMainViewport();
float viewBottom = viewport->WorkPos.y + viewport->WorkSize.y;
float viewCenterX = viewport->WorkPos.x + viewport->WorkSize.x * 0.5f;
// Toast pill sizing — fit inside status bar with margin
float pillMarginY = nde("pill-margin-y", 3.0f);
float pillHeight = sbHeight - pillMarginY * 2.0f;
float pillPadX = nde("padding-x", 12.0f);
float pillRounding = nde("pill-rounding", 12.0f);
// Get accent color based on type — resolved from theme palette
ImVec4 accent_color, text_color;
const char* icon = "";
switch (notif.type) {
case NotificationType::Success:
accent_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-success-accent)", IM_COL32(50, 180, 80, 255)));
text_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-success-text)", IM_COL32(180, 255, 180, 255)));
icon = ICON_MD_CHECK_CIRCLE;
break;
case NotificationType::Warning:
accent_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-warning-accent)", IM_COL32(204, 166, 50, 255)));
text_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-warning-text)", IM_COL32(255, 230, 130, 255)));
icon = ICON_MD_WARNING;
break;
case NotificationType::Error:
accent_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-error-accent)", IM_COL32(204, 64, 64, 255)));
text_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-error-text)", IM_COL32(255, 153, 153, 255)));
icon = ICON_MD_ERROR;
break;
case NotificationType::Info:
default:
accent_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-info-accent)", IM_COL32(100, 160, 220, 255)));
text_color = ImGui::ColorConvertU32ToFloat4(S.resolveColor("var(--toast-info-text)", IM_COL32(215, 235, 255, 255)));
icon = ICON_MD_INFO;
break;
}
// Calculate fade based on progress
float progress = notif.getProgress();
float alpha = 1.0f;
if (progress > 0.7f) {
// Fade out in last 30%
alpha = (1.0f - progress) / 0.3f;
}
accent_color.w *= alpha;
text_color.w *= alpha;
// Measure text width to auto-size the pill
ImFont* textFont = material::Type().caption();
ImFont* iconFont = material::Type().iconSmall();
float iconW = iconFont ? iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0.0f, icon).x : 0.0f;
float iconGap = 4.0f;
float msgW = textFont ? textFont->CalcTextSizeA(textFont->LegacySize, FLT_MAX, 0.0f, notif.message.c_str()).x : 100.0f;
float pillWidth = pillPadX + iconW + iconGap + msgW + pillPadX;
// Clamp to reasonable bounds
float maxPillW = viewport->WorkSize.x * 0.5f;
if (pillWidth > maxPillW) pillWidth = maxPillW;
// Position: centered horizontally, inside status bar vertically
float pillX = viewCenterX - pillWidth * 0.5f;
float pillY = viewBottom - sbHeight + pillMarginY;
// Draw directly on foreground draw list (no ImGui window overhead)
ImDrawList* dl = ImGui::GetForegroundDrawList();
ImVec2 pMin(pillX, pillY);
ImVec2 pMax(pillX + pillWidth, pillY + pillHeight);
// Glass card background — translucent white fill + noise grain + light border
int glassAlpha = (int)(nde("glass-fill-alpha", 18.0f) * alpha);
int glassBorderAlpha = (int)(nde("glass-border-alpha", 35.0f) * alpha);
material::GlassPanelSpec glassSpec;
glassSpec.rounding = pillRounding;
glassSpec.fillAlpha = glassAlpha;
glassSpec.borderAlpha = glassBorderAlpha;
glassSpec.borderWidth = 1.0f;
material::DrawGlassPanel(dl, pMin, pMax, glassSpec);
// Colored accent border on top of the glass border for type indication
ImU32 accentCol = ImGui::ColorConvertFloat4ToU32(accent_color);
dl->AddRect(pMin, pMax, accentCol, pillRounding, 0, 1.0f);
// Progress bar at bottom of pill (accent-colored), clipped to pill rounded
// corners. Draw a full-pill-size rounded rect and clip it to just the
// bottom-left progress strip so both bottom corners are respected.
float progH = nde("progress-bar-height", 2.0f);
float progW = pillWidth * (1.0f - progress);
if (progW > 0.0f) {
ImVec2 clipMin(pillX, pMax.y - progH);
ImVec2 clipMax(pillX + progW, pMax.y);
dl->PushClipRect(clipMin, clipMax, true);
dl->AddRectFilled(pMin, pMax, accentCol, pillRounding);
dl->PopClipRect();
}
// Icon + text vertically centered
float contentY = pillY + (pillHeight - (textFont ? textFont->LegacySize : 14.0f)) * 0.5f;
float cursorX = pillX + pillPadX;
// Icon (accent colored)
if (iconFont) {
float iconY = pillY + (pillHeight - iconFont->LegacySize) * 0.5f;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cursorX, iconY), accentCol, icon);
cursorX += iconW + iconGap;
}
// Message text (clipped to pill bounds)
if (textFont) {
ImU32 textCol = ImGui::ColorConvertFloat4ToU32(text_color);
dl->PushClipRect(ImVec2(cursorX, pillY), ImVec2(pMax.x - pillPadX, pMax.y), true);
dl->AddText(textFont, textFont->LegacySize, ImVec2(cursorX, contentY), textCol, notif.message.c_str());
dl->PopClipRect();
}
}
} // namespace ui
} // namespace dragonx

147
src/ui/notifications.h Normal file
View File

@@ -0,0 +1,147 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <deque>
#include <chrono>
#include <functional>
#include <cstdio>
#include "../util/logger.h"
#include "schema/ui_schema.h"
namespace dragonx {
namespace ui {
enum class NotificationType {
Info,
Success,
Warning,
Error
};
struct Notification {
std::string message;
NotificationType type;
std::chrono::steady_clock::time_point created_at;
float duration_seconds;
Notification(const std::string& msg, NotificationType t, float duration = 5.0f)
: message(msg)
, type(t)
, created_at(std::chrono::steady_clock::now())
, duration_seconds(duration)
{}
bool isExpired() const {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration<float>(now - created_at).count();
return elapsed >= duration_seconds;
}
float getProgress() const {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration<float>(now - created_at).count();
return std::min(1.0f, elapsed / duration_seconds);
}
};
/**
* @brief Simple notification manager for user feedback
*
* Usage:
* Notifications::instance().info("Transaction sent!");
* Notifications::instance().error("Connection failed");
*/
class Notifications {
public:
static Notifications& instance() {
static Notifications inst;
return inst;
}
void info(const std::string& message, float duration = -1.0f) {
if (duration < 0.0f) duration = schemaDuration("duration-info", 1.0f);
push(message, NotificationType::Info, duration);
}
void success(const std::string& message, float duration = -1.0f) {
if (duration < 0.0f) duration = schemaDuration("duration-success", 2.5f);
push(message, NotificationType::Success, duration);
}
void warning(const std::string& message, float duration = -1.0f) {
if (duration < 0.0f) duration = schemaDuration("duration-warning", 3.5f);
push(message, NotificationType::Warning, duration);
}
void error(const std::string& message, float duration = -1.0f) {
if (duration < 0.0f) duration = schemaDuration("duration-error", 4.0f);
push(message, NotificationType::Error, duration);
}
void push(const std::string& message, NotificationType type, float duration = 5.0f) {
notifications_.emplace_back(message, type, duration);
// Log errors and warnings (debug-only output)
if (type == NotificationType::Error) {
DEBUG_LOGF("[ERROR] Notification: %s\n", message.c_str());
} else if (type == NotificationType::Warning) {
DEBUG_LOGF("[WARN] Notification: %s\n", message.c_str());
}
// Forward errors and warnings to console callback
if (console_callback_ && (type == NotificationType::Error || type == NotificationType::Warning)) {
const char* prefix = (type == NotificationType::Error) ? "[ERROR] " : "[WARN] ";
console_callback_(prefix + message, type == NotificationType::Error);
}
// Limit max notifications
while (notifications_.size() > max_notifications_) {
notifications_.pop_front();
}
}
/// Set a callback that receives error/warning messages (e.g. console tab)
void setConsoleCallback(std::function<void(const std::string&, bool is_error)> cb) {
console_callback_ = std::move(cb);
}
void render();
/// @brief Check if there are non-expired notifications (needs continuous frames for animation)
bool hasActive() const {
for (const auto& n : notifications_) {
if (!n.isExpired()) return true;
}
return false;
}
void clear() {
notifications_.clear();
}
void setMaxNotifications(size_t max) {
max_notifications_ = max;
}
private:
Notifications() = default;
~Notifications() = default;
Notifications(const Notifications&) = delete;
Notifications& operator=(const Notifications&) = delete;
std::deque<Notification> notifications_;
size_t max_notifications_ = 5;
std::function<void(const std::string&, bool)> console_callback_;
static float schemaDuration(const char* key, float fallback) {
float v = schema::UI().drawElement("components.notifications", key).size;
return v > 0.0f ? v : fallback;
}
};
} // namespace ui
} // namespace dragonx

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -0,0 +1,19 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
namespace dragonx {
class App;
namespace ui {
/**
* @brief Render the Settings page (inline sidebar content, not a modal)
* Consolidates items previously in File/Edit/Wallet/View/Help menus
* into a single scrollable settings page with collapsible sections.
*/
void RenderSettingsPage(App* app);
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,186 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "color_var_resolver.h"
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
namespace dragonx {
namespace ui {
namespace schema {
// ============================================================================
// Palette management
// ============================================================================
void ColorVarResolver::setPalette(const std::unordered_map<std::string, ImU32>& palette) {
palette_ = palette;
}
// ============================================================================
// Public API
// ============================================================================
ImU32 ColorVarResolver::resolve(const std::string& value, ImU32 fallback) const {
if (value.empty()) {
return fallback;
}
// "transparent"
if (value == "transparent") {
return IM_COL32(0, 0, 0, 0);
}
// var(--name) → palette lookup
if (value.size() > 6 && value.substr(0, 4) == "var(") {
// Extract variable name: var(--primary) → --primary
size_t close = value.find(')');
if (close == std::string::npos) {
return fallback;
}
std::string varName = value.substr(4, close - 4);
// Trim whitespace
while (!varName.empty() && varName.front() == ' ') varName.erase(varName.begin());
while (!varName.empty() && varName.back() == ' ') varName.pop_back();
auto it = palette_.find(varName);
if (it != palette_.end()) {
return it->second;
}
return fallback;
}
// #hex
if (value[0] == '#') {
ImU32 out = 0;
if (parseHex(value, out)) {
return out;
}
return fallback;
}
// rgba(r,g,b,a)
if (value.size() > 5 && value.substr(0, 5) == "rgba(") {
ImU32 out = 0;
if (parseRgba(value, out)) {
return out;
}
return fallback;
}
// 0xRRGGBB / 0xRRGGBBAA (legacy format support)
if (value.size() > 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) {
ImU32 out = 0;
if (parseHex(value, out)) {
return out;
}
return fallback;
}
return fallback;
}
bool ColorVarResolver::hasValue(const std::string& value) {
return !value.empty();
}
// ============================================================================
// Static parsers
// ============================================================================
bool ColorVarResolver::parseHex(const std::string& hexStr, ImU32& out) {
if (hexStr.empty()) return false;
std::string hex = hexStr;
// Remove leading # or 0x
if (hex[0] == '#') {
hex = hex.substr(1);
} else if (hex.size() > 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) {
hex = hex.substr(2);
}
// Validate length
if (hex.size() != 6 && hex.size() != 8) {
return false;
}
// Validate hex chars
for (char c : hex) {
if (!std::isxdigit(static_cast<unsigned char>(c))) {
return false;
}
}
unsigned long value = std::strtoul(hex.c_str(), nullptr, 16);
if (hex.size() == 6) {
// #RRGGBB → IM_COL32(R, G, B, 255)
uint8_t r = (value >> 16) & 0xFF;
uint8_t g = (value >> 8) & 0xFF;
uint8_t b = value & 0xFF;
out = IM_COL32(r, g, b, 255);
} else {
// #RRGGBBAA → IM_COL32(R, G, B, A)
uint8_t r = (value >> 24) & 0xFF;
uint8_t g = (value >> 16) & 0xFF;
uint8_t b = (value >> 8) & 0xFF;
uint8_t a = value & 0xFF;
out = IM_COL32(r, g, b, a);
}
return true;
}
bool ColorVarResolver::parseRgba(const std::string& rgba, ImU32& out) {
// Expected: "rgba(183,28,28,0.5)" or "rgba(183, 28, 28, 0.5)"
if (rgba.size() < 11) return false; // minimum: "rgba(0,0,0,0)"
// Find opening paren
size_t open = rgba.find('(');
size_t close = rgba.rfind(')');
if (open == std::string::npos || close == std::string::npos || close <= open) {
return false;
}
std::string inner = rgba.substr(open + 1, close - open - 1);
// Parse 4 comma-separated values
float values[4] = {0, 0, 0, 0};
int count = 0;
const char* p = inner.c_str();
for (int i = 0; i < 4 && *p; ++i) {
// Skip whitespace
while (*p == ' ' || *p == '\t') ++p;
char* end = nullptr;
values[i] = std::strtof(p, &end);
if (end == p) break; // failed to parse
count++;
p = end;
// Skip whitespace and comma
while (*p == ' ' || *p == '\t') ++p;
if (*p == ',') ++p;
}
if (count != 4) return false;
// r,g,b are 0-255 integers, a is 0.0-1.0 float
uint8_t r = static_cast<uint8_t>(values[0] < 0 ? 0 : (values[0] > 255 ? 255 : values[0]));
uint8_t g = static_cast<uint8_t>(values[1] < 0 ? 0 : (values[1] > 255 ? 255 : values[1]));
uint8_t b = static_cast<uint8_t>(values[2] < 0 ? 0 : (values[2] > 255 ? 255 : values[2]));
uint8_t a = static_cast<uint8_t>(values[3] < 0 ? 0 : (values[3] > 1.0f ? 255 : values[3] * 255.0f));
out = IM_COL32(r, g, b, a);
return true;
}
} // namespace schema
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,75 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
#include <string>
#include <unordered_map>
namespace dragonx {
namespace ui {
namespace schema {
/**
* @brief CSS custom property resolver for color values
*
* Resolves color strings in multiple formats:
* - var(--name) → lookup from theme palette
* - #RRGGBB → direct hex (6 digits, alpha=255)
* - #RRGGBBAA → direct hex with alpha (8 digits)
* - rgba(r,g,b,a) → CSS-style with float alpha 0.0-1.0
* - transparent → fully transparent
* - empty string → returns fallback
*/
class ColorVarResolver {
public:
/**
* @brief Set the palette map (called when skin is loaded)
* @param palette Map of "--name" → ImU32 color values
*/
void setPalette(const std::unordered_map<std::string, ImU32>& palette);
/**
* @brief Get the current palette
*/
const std::unordered_map<std::string, ImU32>& palette() const { return palette_; }
/**
* @brief Resolve a color string to an ImU32 value
*
* @param value Color string (var ref, hex, rgba, or "transparent")
* @param fallback Value to return if resolution fails or value is empty
* @return Resolved ImU32 color
*/
ImU32 resolve(const std::string& value, ImU32 fallback = IM_COL32(0,0,0,0)) const;
/**
* @brief Check if a color string is non-empty (has an override)
*/
static bool hasValue(const std::string& value);
/**
* @brief Parse a hex color string (#RRGGBB or #RRGGBBAA)
* @param hex The hex string (with or without # prefix)
* @param[out] out Parsed color
* @return true if parsed successfully
*/
static bool parseHex(const std::string& hex, ImU32& out);
/**
* @brief Parse an rgba() color string
* @param rgba String like "rgba(183,28,28,0.5)"
* @param[out] out Parsed color
* @return true if parsed successfully
*/
static bool parseRgba(const std::string& rgba, ImU32& out);
private:
std::unordered_map<std::string, ImU32> palette_;
};
} // namespace schema
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,294 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "element_styles.h"
#include <toml++/toml.hpp>
namespace dragonx {
namespace ui {
namespace schema {
// ============================================================================
// TOML helper macros — read optional fields, leave sentinel if absent
// ============================================================================
#define READ_FLOAT(t, field, out) do { auto _v = (t)[field].value<double>(); if (_v) (out) = static_cast<float>(*_v); } while(0)
#define READ_INT(t, field, out) do { auto _v = (t)[field].value<int64_t>(); if (_v) (out) = static_cast<int>(*_v); } while(0)
#define READ_STRING(t, field, out) do { auto _v = (t)[field].value<std::string>(); if (_v) (out) = *_v; } while(0)
#define READ_PADDING(t, field, out) do { \
if ((t).contains(field)) { \
if (auto* _arr = (t)[field].as_array(); _arr && _arr->size() >= 2) { \
auto _v0 = (*_arr)[0].value<double>(); \
auto _v1 = (*_arr)[1].value<double>(); \
if (_v0) (out)[0] = static_cast<float>(*_v0); \
if (_v1) (out)[1] = static_cast<float>(*_v1); \
} else { \
auto _v = (t)[field].value<double>(); \
if (_v) (out)[0] = (out)[1] = static_cast<float>(*_v); \
} \
} \
} while(0)
// ============================================================================
// Parse functions — cast void* back to toml::table
// ============================================================================
namespace detail {
void parseElementColors(const void* dataObj, ElementColors& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_STRING(t, "color", out.color);
READ_STRING(t, "background", out.background);
READ_STRING(t, "background-hover", out.backgroundHover);
READ_STRING(t, "background-active", out.backgroundActive);
READ_STRING(t, "border-color", out.borderColor);
}
void parseButtonStyle(const void* dataObj, ButtonStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "width", out.width);
READ_FLOAT(t, "height", out.height);
READ_STRING(t, "font", out.font);
READ_PADDING(t, "padding", out.padding);
READ_FLOAT(t, "opacity", out.opacity);
READ_FLOAT(t, "border-radius", out.borderRadius);
READ_FLOAT(t, "border-width", out.borderWidth);
READ_FLOAT(t, "min-width", out.minWidth);
READ_STRING(t, "align", out.align);
READ_FLOAT(t, "gap", out.gap);
parseElementColors(dataObj, out.colors);
}
void parseInputStyle(const void* dataObj, InputStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "width", out.width);
READ_FLOAT(t, "height", out.height);
READ_INT(t, "lines", out.lines);
READ_STRING(t, "font", out.font);
READ_PADDING(t, "padding", out.padding);
READ_FLOAT(t, "border-radius", out.borderRadius);
READ_FLOAT(t, "border-width", out.borderWidth);
READ_STRING(t, "border-color-focus", out.borderColorFocus);
READ_STRING(t, "placeholder-color", out.placeholderColor);
READ_FLOAT(t, "width-ratio", out.widthRatio);
READ_FLOAT(t, "max-width", out.maxWidth);
parseElementColors(dataObj, out.colors);
}
void parseLabelStyle(const void* dataObj, LabelStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_STRING(t, "font", out.font);
READ_STRING(t, "color", out.color);
READ_FLOAT(t, "opacity", out.opacity);
READ_STRING(t, "align", out.align);
READ_INT(t, "truncate", out.truncate);
READ_FLOAT(t, "position", out.position);
}
void parseTableStyle(const void* dataObj, TableStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "min-height", out.minHeight);
READ_FLOAT(t, "height-ratio", out.heightRatio);
READ_FLOAT(t, "bottom-reserve", out.bottomReserve);
READ_FLOAT(t, "row-height", out.rowHeight);
READ_STRING(t, "header-font", out.headerFont);
READ_STRING(t, "cell-font", out.cellFont);
READ_STRING(t, "border-color", out.borderColor);
READ_STRING(t, "stripe-color", out.stripeColor);
if (auto* cols = t["columns"].as_table()) {
for (auto&& [key, val] : *cols) {
auto* colTable = val.as_table();
if (!colTable) continue;
ColumnStyle col;
READ_FLOAT(*colTable, "width", col.width);
READ_STRING(*colTable, "font", col.font);
READ_STRING(*colTable, "align", col.align);
READ_INT(*colTable, "truncate", col.truncate);
out.columns[std::string(key.str())] = col;
}
}
}
void parseCheckboxStyle(const void* dataObj, CheckboxStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_STRING(t, "font", out.font);
READ_STRING(t, "color", out.color);
READ_STRING(t, "check-color", out.checkColor);
READ_STRING(t, "background", out.background);
}
void parseComboStyle(const void* dataObj, ComboStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "width", out.width);
READ_STRING(t, "font", out.font);
READ_STRING(t, "color", out.color);
READ_STRING(t, "background", out.background);
READ_FLOAT(t, "border-radius", out.borderRadius);
READ_INT(t, "truncate", out.truncate);
}
void parseSliderStyle(const void* dataObj, SliderStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "width", out.width);
READ_STRING(t, "track-color", out.trackColor);
READ_STRING(t, "fill-color", out.fillColor);
READ_STRING(t, "thumb-color", out.thumbColor);
READ_FLOAT(t, "thumb-radius", out.thumbRadius);
}
void parseWindowStyle(const void* dataObj, WindowStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "width", out.width);
READ_FLOAT(t, "height", out.height);
READ_PADDING(t, "padding", out.padding);
READ_FLOAT(t, "border-radius", out.borderRadius);
READ_FLOAT(t, "border-width", out.borderWidth);
READ_STRING(t, "background", out.background);
READ_STRING(t, "border-color", out.borderColor);
READ_STRING(t, "title-font", out.titleFont);
}
void parseSeparatorStyle(const void* dataObj, SeparatorStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_STRING(t, "color", out.color);
READ_FLOAT(t, "thickness", out.thickness);
READ_PADDING(t, "margin", out.margin);
}
void parseDrawElementStyle(const void* dataObj, DrawElementStyle& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_STRING(t, "color", out.color);
READ_STRING(t, "background", out.background);
READ_FLOAT(t, "thickness", out.thickness);
READ_FLOAT(t, "radius", out.radius);
READ_FLOAT(t, "opacity", out.opacity);
READ_FLOAT(t, "size", out.size);
READ_FLOAT(t, "height", out.height);
// Collect any extra color/float properties not in the standard set
static const char* knownKeys[] = {
"color", "background", "thickness", "radius", "opacity", "size", "height",
nullptr
};
for (auto&& [key, val] : t) {
// Skip known keys
bool known = false;
for (const char** k = knownKeys; *k; ++k) {
if (key.str() == *k) { known = true; break; }
}
if (known) continue;
if (val.is_string()) {
out.extraColors[std::string(key.str())] = *val.value<std::string>();
} else if (val.is_integer() || val.is_floating_point()) {
out.extraFloats[std::string(key.str())] = static_cast<float>(val.value<double>().value_or(0.0));
}
}
}
void parseBreakpointConfig(const void* dataObj, BreakpointConfig& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
if (auto* c = t["compact"].as_table()) {
READ_FLOAT(*c, "max-width", out.compact.maxWidth);
READ_FLOAT(*c, "max-height", out.compact.maxHeight);
READ_FLOAT(*c, "min-width", out.compact.minWidth);
READ_FLOAT(*c, "min-height", out.compact.minHeight);
}
if (auto* e = t["expanded"].as_table()) {
READ_FLOAT(*e, "max-width", out.expanded.maxWidth);
READ_FLOAT(*e, "max-height", out.expanded.maxHeight);
READ_FLOAT(*e, "min-width", out.expanded.minWidth);
READ_FLOAT(*e, "min-height", out.expanded.minHeight);
}
}
void parseResponsiveButtonOverride(const void* dataObj, ResponsiveButtonOverride& out) {
const toml::table& t = *static_cast<const toml::table*>(dataObj);
READ_FLOAT(t, "width", out.width);
READ_FLOAT(t, "height", out.height);
READ_STRING(t, "font", out.font);
READ_PADDING(t, "padding", out.padding);
}
} // namespace detail
// ============================================================================
// Merge helpers
// ============================================================================
void mergeButton(ButtonStyle& base, const ButtonStyle& overlay) {
if (overlay.width > 0) base.width = overlay.width;
if (overlay.height > 0) base.height = overlay.height;
if (!overlay.font.empty()) base.font = overlay.font;
if (overlay.padding[0] > 0) { base.padding[0] = overlay.padding[0]; base.padding[1] = overlay.padding[1]; }
if (overlay.opacity >= 0) base.opacity = overlay.opacity;
if (overlay.borderRadius >= 0) base.borderRadius = overlay.borderRadius;
if (overlay.borderWidth >= 0) base.borderWidth = overlay.borderWidth;
if (overlay.minWidth >= 0) base.minWidth = overlay.minWidth;
if (!overlay.align.empty()) base.align = overlay.align;
if (overlay.gap > 0) base.gap = overlay.gap;
// Colors: non-empty string overrides
if (!overlay.colors.color.empty()) base.colors.color = overlay.colors.color;
if (!overlay.colors.background.empty()) base.colors.background = overlay.colors.background;
if (!overlay.colors.backgroundHover.empty()) base.colors.backgroundHover = overlay.colors.backgroundHover;
if (!overlay.colors.backgroundActive.empty()) base.colors.backgroundActive = overlay.colors.backgroundActive;
if (!overlay.colors.borderColor.empty()) base.colors.borderColor = overlay.colors.borderColor;
}
void mergeInput(InputStyle& base, const InputStyle& overlay) {
if (overlay.width != 0) base.width = overlay.width;
if (overlay.height > 0) base.height = overlay.height;
if (overlay.lines > 0) base.lines = overlay.lines;
if (!overlay.font.empty()) base.font = overlay.font;
if (overlay.padding[0] > 0) { base.padding[0] = overlay.padding[0]; base.padding[1] = overlay.padding[1]; }
if (overlay.borderRadius >= 0) base.borderRadius = overlay.borderRadius;
if (overlay.borderWidth >= 0) base.borderWidth = overlay.borderWidth;
if (!overlay.borderColorFocus.empty()) base.borderColorFocus = overlay.borderColorFocus;
if (!overlay.placeholderColor.empty()) base.placeholderColor = overlay.placeholderColor;
if (overlay.widthRatio >= 0) base.widthRatio = overlay.widthRatio;
if (overlay.maxWidth >= 0) base.maxWidth = overlay.maxWidth;
if (!overlay.colors.color.empty()) base.colors.color = overlay.colors.color;
if (!overlay.colors.background.empty()) base.colors.background = overlay.colors.background;
if (!overlay.colors.borderColor.empty()) base.colors.borderColor = overlay.colors.borderColor;
}
void mergeLabel(LabelStyle& base, const LabelStyle& overlay) {
if (!overlay.font.empty()) base.font = overlay.font;
if (!overlay.color.empty()) base.color = overlay.color;
if (overlay.opacity >= 0) base.opacity = overlay.opacity;
if (!overlay.align.empty()) base.align = overlay.align;
if (overlay.truncate > 0) base.truncate = overlay.truncate;
if (overlay.position >= 0) base.position = overlay.position;
}
void mergeWindow(WindowStyle& base, const WindowStyle& overlay) {
if (overlay.width > 0) base.width = overlay.width;
if (overlay.height > 0) base.height = overlay.height;
if (overlay.padding[0] > 0) { base.padding[0] = overlay.padding[0]; base.padding[1] = overlay.padding[1]; }
if (overlay.borderRadius >= 0) base.borderRadius = overlay.borderRadius;
if (overlay.borderWidth >= 0) base.borderWidth = overlay.borderWidth;
if (!overlay.background.empty()) base.background = overlay.background;
if (!overlay.borderColor.empty()) base.borderColor = overlay.borderColor;
if (!overlay.titleFont.empty()) base.titleFont = overlay.titleFont;
}
void applyResponsiveButton(ButtonStyle& base, const ResponsiveButtonOverride& ovr) {
if (ovr.width > 0) base.width = ovr.width;
if (ovr.height > 0) base.height = ovr.height;
if (!ovr.font.empty()) base.font = ovr.font;
if (ovr.padding[0] > 0) { base.padding[0] = ovr.padding[0]; base.padding[1] = ovr.padding[1]; }
}
#undef READ_FLOAT
#undef READ_INT
#undef READ_STRING
#undef READ_PADDING
} // namespace schema
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,273 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
#include <string>
#include <map>
#include <optional>
namespace dragonx {
namespace ui {
namespace schema {
// ============================================================================
// Color properties shared by all styleable elements
// ============================================================================
struct ElementColors {
std::string color; // "var(--on-primary)" | "rgba(...)" | "#hex" | ""
std::string background;
std::string backgroundHover; // null = auto-derive
std::string backgroundActive; // null = auto-derive
std::string borderColor;
};
// ============================================================================
// Element Style Structs
// ============================================================================
/**
* @brief Style for button elements
*
* Sentinel values: 0 or -1 = inherit from globals, "" = inherit
*/
struct ButtonStyle {
float width = 0; // 0 = auto-size
float height = 0; // 0 = auto from font + padding
std::string font; // "" = inherit from globals
float padding[2] = {0, 0}; // [h, v] — 0 = inherit
float opacity = -1; // -1 = inherit, 0.0-1.0
float borderRadius = -1; // -1 = inherit
float borderWidth = -1; // -1 = inherit
float minWidth = -1; // -1 = inherit
std::string align; // "" | "left" | "center" | "right"
ElementColors colors;
// Gap between adjacent buttons (layout helper)
float gap = 0;
};
/**
* @brief Style for text input elements
*/
struct InputStyle {
float width = 0; // 0 = auto, -1 = fill remaining
float height = 0; // 0 = single-line auto
int lines = 0; // 0 = inherit, 1 = single, >1 = multiline
std::string font;
float padding[2] = {0, 0};
float borderRadius = -1;
float borderWidth = -1;
ElementColors colors;
std::string borderColorFocus; // "var(--primary)" for focus ring
std::string placeholderColor;
// Ratio-based width (alternative to fixed)
float widthRatio = -1; // -1 = not set, 0.0-1.0 = fraction of available
float maxWidth = -1; // -1 = no limit
};
/**
* @brief Style for text labels
*/
struct LabelStyle {
std::string font;
std::string color;
float opacity = -1;
std::string align;
int truncate = 0; // 0 = no truncation, >0 = max chars
float position = -1; // SameLine label position (-1 = not set)
};
/**
* @brief Style for table columns
*/
struct ColumnStyle {
float width = -1;
std::string font;
std::string align;
int truncate = 0;
};
/**
* @brief Style for table elements
*/
struct TableStyle {
float minHeight = -1;
float heightRatio = -1; // fraction of available space
float bottomReserve = -1;
float rowHeight = -1;
std::string headerFont;
std::string cellFont;
std::string borderColor;
std::string stripeColor;
std::map<std::string, ColumnStyle> columns;
};
/**
* @brief Style for checkbox elements
*/
struct CheckboxStyle {
std::string font;
std::string color;
std::string checkColor;
std::string background;
};
/**
* @brief Style for combo/dropdown elements
*/
struct ComboStyle {
float width = 0;
std::string font;
std::string color;
std::string background;
float borderRadius = -1;
int truncate = 0;
};
/**
* @brief Style for slider elements
*/
struct SliderStyle {
float width = 0;
std::string trackColor;
std::string fillColor;
std::string thumbColor;
float thumbRadius = -1;
};
/**
* @brief Style for windows and dialogs
*/
struct WindowStyle {
float width = 0;
float height = 0;
float padding[2] = {0, 0};
float borderRadius = -1;
float borderWidth = -1;
std::string background;
std::string borderColor;
std::string titleFont;
};
/**
* @brief Style for separator lines
*/
struct SeparatorStyle {
std::string color;
float thickness = -1;
float margin[2] = {0, 0}; // [top, bottom]
};
/**
* @brief Style for DrawList-driven custom elements (progress rings, sparklines, etc.)
*/
struct DrawElementStyle {
std::string color;
std::string background;
float thickness = -1;
float radius = -1;
float opacity = -1;
float size = -1; // generic size (e.g., QR code size)
float height = -1;
// Additional named properties for flexible DrawList elements
std::map<std::string, std::string> extraColors;
std::map<std::string, float> extraFloats;
/// Look up any extra float by key, returning fallback if absent.
float getFloat(const std::string& key, float fallback = 0.0f) const {
auto it = extraFloats.find(key);
return it != extraFloats.end() ? it->second : fallback;
}
/// Return size if set (>= 0), else fallback.
float sizeOr(float fallback) const { return size >= 0.0f ? size : fallback; }
};
/**
* @brief Style for spacing (layout helper)
*/
struct SpacingStyle {
float size = 0;
};
// ============================================================================
// Responsive overrides
// ============================================================================
/**
* @brief Per-breakpoint property overrides
*
* Only non-sentinel values override the base style. Applied on top of
* the element's base style when the current window size matches the
* breakpoint.
*/
struct ResponsiveButtonOverride {
float width = 0;
float height = 0;
std::string font;
float padding[2] = {0, 0};
};
struct ResponsiveInputOverride {
float width = 0;
float height = 0;
};
// ============================================================================
// Breakpoint definitions
// ============================================================================
struct BreakpointDef {
float maxWidth = -1; // -1 = no constraint
float maxHeight = -1;
float minWidth = -1;
float minHeight = -1;
};
struct BreakpointConfig {
BreakpointDef compact;
BreakpointDef expanded;
};
// ============================================================================
// JSON parsing helpers (implemented in element_styles.cpp)
// ============================================================================
// Forward declare nlohmann::json to avoid header dependency
namespace detail {
// Parse individual style types from a JSON object
// These are called by UISchema during load — not for external use
void parseButtonStyle(const void* jsonObj, ButtonStyle& out);
void parseInputStyle(const void* jsonObj, InputStyle& out);
void parseLabelStyle(const void* jsonObj, LabelStyle& out);
void parseTableStyle(const void* jsonObj, TableStyle& out);
void parseCheckboxStyle(const void* jsonObj, CheckboxStyle& out);
void parseComboStyle(const void* jsonObj, ComboStyle& out);
void parseSliderStyle(const void* jsonObj, SliderStyle& out);
void parseWindowStyle(const void* jsonObj, WindowStyle& out);
void parseSeparatorStyle(const void* jsonObj, SeparatorStyle& out);
void parseDrawElementStyle(const void* jsonObj, DrawElementStyle& out);
void parseBreakpointConfig(const void* jsonObj, BreakpointConfig& out);
void parseElementColors(const void* jsonObj, ElementColors& out);
void parseResponsiveButtonOverride(const void* jsonObj, ResponsiveButtonOverride& out);
}
// ============================================================================
// Merge helpers — apply overlay on top of base (non-sentinel values win)
// ============================================================================
void mergeButton(ButtonStyle& base, const ButtonStyle& overlay);
void mergeInput(InputStyle& base, const InputStyle& overlay);
void mergeLabel(LabelStyle& base, const LabelStyle& overlay);
void mergeWindow(WindowStyle& base, const WindowStyle& overlay);
void applyResponsiveButton(ButtonStyle& base, const ResponsiveButtonOverride& ovr);
} // namespace schema
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,800 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "skin_manager.h"
#include "ui_schema.h"
#include "../../util/platform.h"
#include "../../resources/embedded_resources.h"
#include "../theme.h"
#include "../material/color_theme.h"
#include "../effects/theme_effects.h"
#include "../effects/imgui_acrylic.h"
#include <toml++/toml.hpp>
#include <fstream>
#include <filesystem>
#include <algorithm>
#include <cstdio>
#include "../../util/logger.h"
namespace fs = std::filesystem;
namespace dragonx {
namespace ui {
namespace schema {
// ============================================================================
// Singleton
// ============================================================================
SkinManager& SkinManager::instance() {
static SkinManager s;
return s;
}
// ============================================================================
// Directory helpers
// ============================================================================
std::string SkinManager::getBundledSkinsDirectory() {
// Bundled skins live in res/themes/ next to the executable
fs::path exe_dir = util::getExecutableDirectory();
fs::path themes_dir = exe_dir / "res" / "themes";
if (fs::exists(themes_dir)) {
return themes_dir.string();
}
// Fallback: current working directory
themes_dir = fs::current_path() / "res" / "themes";
if (fs::exists(themes_dir)) {
return themes_dir.string();
}
// No on-disk themes dir found (single-file Windows distribution).
// Extract embedded overlay themes to the config directory.
fs::path configDir = util::Platform::getObsidianDragonDir();
fs::path extractedDir = configDir / "bundled-themes";
int extracted = resources::extractBundledThemes(extractedDir.string());
if (extracted > 0) {
DEBUG_LOGF("[SkinManager] Extracted %d embedded bundled themes to %s\n",
extracted, extractedDir.string().c_str());
}
if (fs::exists(extractedDir)) {
return extractedDir.string();
}
return (exe_dir / "res" / "themes").string();
}
std::string SkinManager::getUserSkinsDirectory() {
// User themes in ObsidianDragon config directory — folder-based
fs::path configDir = util::Platform::getObsidianDragonDir();
return (configDir / "themes").string();
}
// ============================================================================
// Scan for skins
// ============================================================================
void SkinManager::scanDirectory(const std::string& dir, bool bundled) {
if (!fs::exists(dir) || !fs::is_directory(dir)) {
DEBUG_LOGF("[SkinManager] Directory does not exist: %s\n", dir.c_str());
return;
}
DEBUG_LOGF("[SkinManager] Scanning %s directory: %s\n", bundled ? "bundled" : "user", dir.c_str());
if (bundled) {
// Bundled skins: all .toml files in res/themes/ except ui.toml
// (ui.toml is the base theme and is always loaded as "dragonx")
for (const auto& entry : fs::directory_iterator(dir)) {
if (!entry.is_regular_file()) continue;
fs::path p = entry.path();
if (p.extension() != ".toml") continue;
std::string stem = p.stem().string();
// Skip ui.toml - it's the base theme handled separately as "dragonx"
if (stem == "ui") continue;
// Try to parse and extract metadata
toml::table root;
try {
root = toml::parse_file(p.string());
} catch (...) {
DEBUG_LOGF("[SkinManager] Skipping '%s': invalid TOML\n", p.filename().string().c_str());
continue;
}
auto* theme = root["theme"].as_table();
if (!theme) {
DEBUG_LOGF("[SkinManager] Skipping '%s': no [theme] section\n", p.filename().string().c_str());
continue;
}
SkinInfo info;
info.path = p.string();
info.bundled = true;
// ID = filename stem (e.g. "dark" from dark.toml)
info.id = stem;
if (auto name = (*theme)["name"].value<std::string>()) {
info.name = *name;
} else {
info.name = info.id;
}
if (auto author = (*theme)["author"].value<std::string>()) {
info.author = *author;
}
if (auto dark = (*theme)["dark"].value<bool>()) {
info.dark = *dark;
}
// Resolve image paths from theme.images (bundled: res/img/)
fs::path imgDir = p.parent_path().parent_path() / "img";
std::string bgFilename;
std::string logoFilename;
if (auto* images = (*theme)["images"].as_table()) {
if (auto bg = (*images)["background_image"].value<std::string>()) {
bgFilename = *bg;
}
if (auto logo = (*images)["logo"].value<std::string>()) {
logoFilename = *logo;
}
}
if (!bgFilename.empty()) {
fs::path bgPath = imgDir / bgFilename;
if (fs::exists(bgPath)) {
info.backgroundImagePath = bgPath.string();
}
}
if (!logoFilename.empty()) {
fs::path logoImgPath = imgDir / logoFilename;
if (fs::exists(logoImgPath)) {
info.logoPath = logoImgPath.string();
}
}
skins_.push_back(std::move(info));
}
} else {
// User themes: each subfolder must contain a theme.toml
for (const auto& entry : fs::directory_iterator(dir)) {
if (!entry.is_directory()) continue;
fs::path themeDir = entry.path();
fs::path themeToml = themeDir / "theme.toml";
if (!fs::exists(themeToml)) {
DEBUG_LOGF("[SkinManager] Skipping folder '%s': no theme.toml found\n",
themeDir.filename().string().c_str());
continue;
}
DEBUG_LOGF("[SkinManager] Found theme folder: %s (theme.toml exists)\n", themeDir.filename().string().c_str());
// Validate the theme file
auto validation = validateSkinFile(themeToml.string());
// Parse metadata even from invalid themes (so they show in the list)
toml::table root;
try {
root = toml::parse_file(themeToml.string());
} catch (...) {
// Still add as invalid
SkinInfo info;
info.id = themeDir.filename().string();
info.name = info.id;
info.path = themeToml.string();
info.directory = themeDir.string();
info.bundled = false;
info.valid = false;
info.validationError = "Invalid TOML";
skins_.push_back(std::move(info));
continue;
}
SkinInfo info;
info.id = themeDir.filename().string();
info.path = themeToml.string();
info.directory = themeDir.string();
info.bundled = false;
info.valid = validation.valid;
info.validationError = validation.error;
// Extract metadata from theme section
if (auto* theme = root["theme"].as_table()) {
if (auto name = (*theme)["name"].value<std::string>()) {
info.name = *name;
} else {
info.name = info.id;
}
if (auto author = (*theme)["author"].value<std::string>()) {
info.author = *author;
}
if (auto dark = (*theme)["dark"].value<bool>()) {
info.dark = *dark;
}
// Resolve image paths (from TOML)
fs::path imgDir = themeDir / "img";
std::string bgFilename;
std::string logoFilename;
if (auto* images = (*theme)["images"].as_table()) {
if (auto bg = (*images)["background_image"].value<std::string>()) {
bgFilename = *bg;
}
if (auto logo = (*images)["logo"].value<std::string>()) {
logoFilename = *logo;
}
}
// Check if image files exist
if (!bgFilename.empty()) {
fs::path bgPath = imgDir / bgFilename;
if (fs::exists(bgPath)) {
info.backgroundImagePath = bgPath.string();
}
}
if (!logoFilename.empty()) {
fs::path logoImgPath = imgDir / logoFilename;
if (fs::exists(logoImgPath)) {
info.logoPath = logoImgPath.string();
}
}
} else {
info.name = info.id;
}
skins_.push_back(std::move(info));
}
// Also scan for loose .toml files (unified format with [theme.palette])
for (const auto& entry : fs::directory_iterator(dir)) {
if (!entry.is_regular_file()) continue;
fs::path p = entry.path();
if (p.extension() != ".toml") continue;
toml::table root;
try {
root = toml::parse_file(p.string());
} catch (...) {
DEBUG_LOGF("[SkinManager] Skipping '%s': invalid TOML\n", p.filename().string().c_str());
continue;
}
SkinInfo info;
info.path = p.string();
info.id = p.stem().string();
info.bundled = false;
// Check for unified format ([theme] with [theme.palette])
auto* theme = root["theme"].as_table();
if (theme) {
if (auto name = (*theme)["name"].value<std::string>()) {
info.name = *name;
} else {
info.name = info.id;
}
if (auto author = (*theme)["author"].value<std::string>()) {
info.author = *author;
}
if (auto dark = (*theme)["dark"].value<bool>()) {
info.dark = *dark;
}
auto validation = validateSkinFile(p.string());
info.valid = validation.valid;
info.validationError = validation.error;
// Resolve image paths (look in same directory as the .toml file)
fs::path imgDir = p.parent_path();
std::string bgFilename;
std::string logoFilename;
if (auto* images = (*theme)["images"].as_table()) {
if (auto bg = (*images)["background_image"].value<std::string>()) {
bgFilename = *bg;
}
if (auto logo = (*images)["logo"].value<std::string>()) {
logoFilename = *logo;
}
}
if (!bgFilename.empty()) {
fs::path bgPath = imgDir / bgFilename;
if (fs::exists(bgPath)) {
info.backgroundImagePath = bgPath.string();
}
}
if (!logoFilename.empty()) {
fs::path logoImgPath = imgDir / logoFilename;
if (fs::exists(logoImgPath)) {
info.logoPath = logoImgPath.string();
}
}
}
else {
DEBUG_LOGF("[SkinManager] Skipping '%s': unrecognized TOML format\n",
p.filename().string().c_str());
continue;
}
skins_.push_back(std::move(info));
}
}
}
void SkinManager::refresh() {
skins_.clear();
// Scan bundled skins (res/ directory)
scanDirectory(getBundledSkinsDirectory(), true);
// Scan user skins
std::string userDir = getUserSkinsDirectory();
if (fs::exists(userDir)) {
scanDirectory(userDir, false);
}
// Ensure the base "dragonx" theme always appears (it's ui.toml, the main theme).
// Other bundled themes are discovered automatically from res/themes/*.toml.
{
bool found = false;
for (const auto& s : skins_) {
if (s.id == "dragonx") { found = true; break; }
}
if (!found) {
SkinInfo info;
info.id = "dragonx";
info.name = "DragonX";
info.author = "The Hush Developers";
info.dark = true;
info.bundled = true;
info.valid = true;
// Try to set path to ui.toml if it exists
fs::path uiPath = fs::path(getBundledSkinsDirectory()) / "ui.toml";
if (fs::exists(uiPath)) {
info.path = uiPath.string();
}
skins_.push_back(std::move(info));
DEBUG_LOGF("[SkinManager] Injected base theme: dragonx\n");
}
}
// Sort: "dragonx" first, then bundled grouped by mode (dark then light), then user
std::sort(skins_.begin(), skins_.end(), [](const SkinInfo& a, const SkinInfo& b) {
// DragonX always first
if (a.id == "dragonx") return true;
if (b.id == "dragonx") return false;
// Bundled before user
if (a.bundled != b.bundled) return a.bundled;
// Group: dark themes first, then light themes
if (a.dark != b.dark) return a.dark;
// Alphabetical by name within each group
return a.name < b.name;
});
DEBUG_LOGF("[SkinManager] Found %zu skins\n", skins_.size());
}
// ============================================================================
// Find
// ============================================================================
const SkinManager::SkinInfo* SkinManager::findById(const std::string& id) const {
for (const auto& skin : skins_) {
if (skin.id == id) return &skin;
}
return nullptr;
}
// ============================================================================
// Validation
// ============================================================================
SkinManager::ValidationResult SkinManager::validateSkinFile(const std::string& path) {
ValidationResult result;
// 1. Must be valid TOML
toml::table root;
try {
root = toml::parse_file(path);
} catch (const toml::parse_error& e) {
result.error = std::string("Invalid TOML: ") + e.what();
return result;
}
// 3. Must contain "theme" table
auto* theme = root["theme"].as_table();
if (!theme) {
result.error = "Missing or invalid 'theme' section";
return result;
}
// 4. theme.name must be a non-empty string
auto name = (*theme)["name"].value<std::string>();
if (!name || name->empty()) {
result.error = "theme.name must be a non-empty string";
return result;
}
// 5. theme.palette must exist with at least --primary and --background
auto* palette = (*theme)["palette"].as_table();
if (!palette) {
result.error = "Missing theme.palette table";
return result;
}
if (!palette->contains("--primary")) {
result.error = "Palette missing required '--primary' color";
return result;
}
if (!palette->contains("--background")) {
result.error = "Palette missing required '--background' color";
return result;
}
// 6. If globals exists, must be a table
if (root.contains("globals") && !root["globals"].is_table()) {
result.error = "'globals' must be a table";
return result;
}
result.valid = true;
return result;
}
// ============================================================================
// Import
// ============================================================================
bool SkinManager::importSkin(const std::string& sourcePath) {
fs::path srcPath(sourcePath);
std::string userDir = getUserSkinsDirectory();
try {
fs::create_directories(userDir);
} catch (const fs::filesystem_error& e) {
DEBUG_LOGF("[SkinManager] Failed to create themes directory: %s\n", e.what());
return false;
}
if (fs::is_directory(srcPath)) {
// Import a theme folder — copy entire folder into themes/
fs::path themeToml = srcPath / "theme.toml";
if (!fs::exists(themeToml)) {
DEBUG_LOGF("[SkinManager] Import folder has no theme.toml: %s\n", sourcePath.c_str());
return false;
}
auto validation = validateSkinFile(themeToml.string());
if (!validation.valid) {
DEBUG_LOGF("[SkinManager] Import validation failed: %s\n", validation.error.c_str());
return false;
}
fs::path destDir = fs::path(userDir) / srcPath.filename();
try {
fs::copy(srcPath, destDir, fs::copy_options::recursive | fs::copy_options::overwrite_existing);
} catch (const fs::filesystem_error& e) {
DEBUG_LOGF("[SkinManager] Failed to copy theme folder: %s\n", e.what());
return false;
}
DEBUG_LOGF("[SkinManager] Imported theme folder: %s → %s\n", sourcePath.c_str(), destDir.string().c_str());
} else {
// Import a single .toml file — create a folder for it
auto validation = validateSkinFile(sourcePath);
if (!validation.valid) {
DEBUG_LOGF("[SkinManager] Import validation failed: %s\n", validation.error.c_str());
return false;
}
std::string folderName = srcPath.stem().string();
fs::path destDir = fs::path(userDir) / folderName;
try {
fs::create_directories(destDir);
fs::copy_file(srcPath, destDir / "theme.toml", fs::copy_options::overwrite_existing);
} catch (const fs::filesystem_error& e) {
DEBUG_LOGF("[SkinManager] Failed to import skin file: %s\n", e.what());
return false;
}
DEBUG_LOGF("[SkinManager] Imported skin file as folder: %s → %s\n", sourcePath.c_str(), destDir.string().c_str());
}
refresh();
return true;
}
// ============================================================================
// Remove
// ============================================================================
bool SkinManager::removeSkin(const std::string& id) {
const SkinInfo* skin = findById(id);
if (!skin) {
DEBUG_LOGF("[SkinManager] Skin not found: %s\n", id.c_str());
return false;
}
if (skin->bundled) {
DEBUG_LOGF("[SkinManager] Cannot remove bundled skin: %s\n", id.c_str());
return false;
}
try {
if (!skin->directory.empty() && fs::is_directory(skin->directory)) {
// Folder-based theme — remove the entire directory
fs::remove_all(skin->directory);
} else {
// Legacy flat file
fs::remove(skin->path);
}
} catch (const fs::filesystem_error& e) {
DEBUG_LOGF("[SkinManager] Failed to remove skin: %s\n", e.what());
return false;
}
DEBUG_LOGF("[SkinManager] Removed skin: %s\n", id.c_str());
// If we removed the active skin, fall back to default
if (activeSkinId_ == id) {
setActiveSkin("dragonx");
}
refresh();
return true;
}
// ============================================================================
// Activate skin
// ============================================================================
bool SkinManager::setActiveSkin(const std::string& id) {
const SkinInfo* skin = findById(id);
if (!skin) {
DEBUG_LOGF("[SkinManager] Skin not found: %s\n", id.c_str());
return false;
}
if (!skin->valid) {
DEBUG_LOGF("[SkinManager] Skin is invalid: %s (%s)\n", id.c_str(), skin->validationError.c_str());
return false;
}
bool loaded = false;
// For skin files: always reload base layout first, then merge visual
// overlay on top. This ensures overlays only change palette + backdrop
// while inheriting all layout values from ui.toml.
if (!skin->path.empty()) {
auto& schema = UISchema::instance();
std::string basePath = schema.basePath();
if (!basePath.empty() || schema.hasEmbeddedBase()) {
if (!basePath.empty() && basePath == skin->path) {
// Switching back to the base theme: full reload, clear overlay
schema.reloadBase();
schema.reapplyColorsToImGui();
loaded = true;
} else {
// Switching to a non-base skin: reload base then merge overlay
if (schema.reloadBase()) {
if (schema.mergeOverlayFromFile(skin->path)) {
schema.reapplyColorsToImGui();
loaded = true;
}
}
}
}
// Fallback: no base path or embedded data, full load of skin file
if (!loaded && schema.loadFromFile(skin->path)) {
schema.reapplyColorsToImGui();
loaded = true;
}
} else if (!id.empty()) {
// Skin with no path (e.g., "dragonx" on Windows with embedded ui.toml):
// just reload the base to restore the default theme
auto& schema = UISchema::instance();
if (schema.hasEmbeddedBase() || !schema.basePath().empty()) {
schema.reloadBase();
schema.reapplyColorsToImGui();
loaded = true;
}
}
// Fall back to built-in C++ themes (works even without theme files)
if (!loaded) {
if (!SetThemeById(id)) {
DEBUG_LOGF("[SkinManager] Failed to load skin: %s\n", id.c_str());
return false;
}
DEBUG_LOGF("[SkinManager] Loaded via built-in theme fallback: %s\n", id.c_str());
loaded = true;
}
activeSkinId_ = id;
DEBUG_LOGF("[SkinManager] Activated skin: %s (%s)\n", id.c_str(), skin->name.c_str());
// Reload theme visual effects config from the new skin's [effects] section
effects::ThemeEffects::instance().loadFromTheme();
// Resolve image paths from UISchema (which parsed [theme] images from the TOML).
// The UISchema stores relative filenames (e.g. "backgrounds/texture/drgx_bg.png");
// resolve them to absolute paths using the theme's directory structure.
{
// Use a mutable reference to update the SkinInfo
SkinInfo* mutableSkin = nullptr;
for (auto& s : skins_) {
if (s.id == id) { mutableSkin = &s; break; }
}
fs::path imgDir = resolveImgDir(skin);
resolveAndFireCallback(mutableSkin, imgDir);
}
// Force acrylic to re-capture the background with new theme colors/images.
// This must happen AFTER images are reloaded so the next frame renders the
// updated background before capture.
effects::ImGuiAcrylic::InvalidateCapture();
return true;
}
void SkinManager::resolveAndReloadImages(const std::string& skinId, const std::string& tomlPath) {
// Find the skin and update its image paths from the current UISchema values
SkinInfo* skin = nullptr;
for (auto& s : skins_) {
if (s.id == skinId) {
skin = &s;
break;
}
}
fs::path imgDir = resolveImgDir(skin);
resolveAndFireCallback(skin, imgDir);
}
// ---------------------------------------------------------------------------
// Gradient mode
// ---------------------------------------------------------------------------
void SkinManager::setGradientMode(bool enabled) {
if (gradientMode_ == enabled) return;
gradientMode_ = enabled;
// Re-resolve + reload for the currently active skin
const SkinInfo* skin = findById(activeSkinId_);
if (!skin) return;
SkinInfo* mutableSkin = nullptr;
for (auto& s : skins_) {
if (s.id == activeSkinId_) { mutableSkin = &s; break; }
}
fs::path imgDir = resolveImgDir(skin);
resolveAndFireCallback(mutableSkin, imgDir);
effects::ImGuiAcrylic::InvalidateCapture();
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
fs::path SkinManager::resolveImgDir(const SkinInfo* skin) const {
if (!skin) {
return fs::path(getBundledSkinsDirectory()).parent_path() / "img";
}
if (skin->bundled) {
fs::path themeDir = skin->path.empty()
? fs::path(getBundledSkinsDirectory())
: fs::path(skin->path).parent_path();
return themeDir.parent_path() / "img";
}
if (!skin->directory.empty()) {
return fs::path(skin->directory) / "img";
}
if (!skin->path.empty()) {
return fs::path(skin->path).parent_path();
}
return fs::path(getBundledSkinsDirectory()).parent_path() / "img";
}
std::string SkinManager::resolveGradientBg(const std::string& bgFilename,
const fs::path& imgDir,
bool isDark) const {
// Given bgFilename like "backgrounds/texture/drgx_bg.png",
// look for "backgrounds/gradient/gradient_drgx_bg.png".
fs::path bgRel(bgFilename);
std::string stem = bgRel.stem().string(); // "drgx_bg"
std::string ext = bgRel.extension().string(); // ".png"
// Build the gradient candidate: backgrounds/gradient/gradient_<stem><ext>
std::string gradientRel = "backgrounds/gradient/gradient_" + stem + ext;
fs::path gradientPath = imgDir / gradientRel;
if (fs::exists(gradientPath)) {
return gradientPath.string();
}
// Fallback: dark_gradient.png or light_gradient.png
std::string fallbackName = isDark ? "dark_gradient.png" : "light_gradient.png";
std::string fallbackRel = "backgrounds/gradient/" + fallbackName;
fs::path fallbackPath = imgDir / fallbackRel;
if (fs::exists(fallbackPath)) {
return fallbackPath.string();
}
// Last resort: pass the relative gradient filename for embedded lookup (Windows)
return gradientRel;
}
void SkinManager::resolveAndFireCallback(SkinInfo* skin, const fs::path& imgDir) {
auto& schema = UISchema::instance();
std::string bgFilename = schema.backgroundImagePath();
std::string logoFilename = schema.logoImagePath();
std::string resolvedBg;
std::string resolvedLogo;
if (!bgFilename.empty()) {
if (gradientMode_) {
resolvedBg = resolveGradientBg(bgFilename, imgDir, schema.isDarkTheme());
} else {
fs::path bgPath = imgDir / bgFilename;
if (fs::exists(bgPath)) {
resolvedBg = bgPath.string();
} else {
resolvedBg = bgFilename;
}
}
}
if (!logoFilename.empty()) {
fs::path logoPath = imgDir / logoFilename;
if (fs::exists(logoPath)) {
resolvedLogo = logoPath.string();
} else {
resolvedLogo = logoFilename;
}
}
if (skin) {
skin->backgroundImagePath = resolvedBg;
skin->logoPath = resolvedLogo;
}
DEBUG_LOGF("[SkinManager] Resolved images (gradient=%s): bg='%s', logo='%s'\n",
gradientMode_ ? "on" : "off", resolvedBg.c_str(), resolvedLogo.c_str());
if (imageReloadCb_) {
imageReloadCb_(resolvedBg, resolvedLogo);
}
}
} // namespace schema
} // namespace ui
} // namespace dragonx

Some files were not shown because too many files have changed in this diff Show More