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