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:
353
src/ui/theme_loader.cpp
Normal file
353
src/ui/theme_loader.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "theme_loader.h"
|
||||
#include "schema/color_var_resolver.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
// ============================================================================
|
||||
// Color Parsing
|
||||
// ============================================================================
|
||||
|
||||
bool ThemeLoader::parseHexColor(const std::string& hexStr, ImU32& outColor)
|
||||
{
|
||||
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 (6 for RGB, 8 for RGBA)
|
||||
if (hex.size() != 6 && hex.size() != 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate hex characters
|
||||
for (char c : hex) {
|
||||
if (!std::isxdigit(static_cast<unsigned char>(c))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse hex value
|
||||
unsigned long value = std::strtoul(hex.c_str(), nullptr, 16);
|
||||
|
||||
if (hex.size() == 6) {
|
||||
// RGB format: 0xRRGGBB -> IM_COL32(R, G, B, 255)
|
||||
uint8_t r = (value >> 16) & 0xFF;
|
||||
uint8_t g = (value >> 8) & 0xFF;
|
||||
uint8_t b = value & 0xFF;
|
||||
outColor = IM_COL32(r, g, b, 255);
|
||||
} else {
|
||||
// RGBA format: 0xRRGGBBAA -> 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;
|
||||
outColor = IM_COL32(r, g, b, a);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ThemeLoader::colorToHexString(ImU32 color, bool includeAlpha)
|
||||
{
|
||||
uint8_t r = (color >> IM_COL32_R_SHIFT) & 0xFF;
|
||||
uint8_t g = (color >> IM_COL32_G_SHIFT) & 0xFF;
|
||||
uint8_t b = (color >> IM_COL32_B_SHIFT) & 0xFF;
|
||||
uint8_t a = (color >> IM_COL32_A_SHIFT) & 0xFF;
|
||||
|
||||
char buf[16];
|
||||
if (includeAlpha || a != 255) {
|
||||
snprintf(buf, sizeof(buf), "0x%02X%02X%02X%02X", r, g, b, a);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "0x%02X%02X%02X", r, g, b);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Multi-format Color Parsing
|
||||
// ============================================================================
|
||||
|
||||
bool ThemeLoader::parseColorString(const std::string& str, ImU32& outColor)
|
||||
{
|
||||
if (str.empty()) return false;
|
||||
|
||||
// Try #hex / 0xhex first (handles both formats)
|
||||
if (parseHexColor(str, outColor)) return true;
|
||||
|
||||
// Try rgba(r,g,b,a)
|
||||
if (schema::ColorVarResolver::parseRgba(str, outColor)) return true;
|
||||
|
||||
// "transparent"
|
||||
if (str == "transparent") {
|
||||
outColor = IM_COL32(0, 0, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Luminance Calculation
|
||||
// ============================================================================
|
||||
|
||||
float ThemeLoader::getLuminance(ImU32 color)
|
||||
{
|
||||
// Extract RGB components (0-255)
|
||||
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;
|
||||
|
||||
// Convert to linear RGB (sRGB gamma correction)
|
||||
auto linearize = [](float c) {
|
||||
return c <= 0.03928f ? c / 12.92f : std::pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
|
||||
r = linearize(r);
|
||||
g = linearize(g);
|
||||
b = linearize(b);
|
||||
|
||||
// Calculate relative luminance (ITU-R BT.709)
|
||||
return 0.2126f * r + 0.7152f * g + 0.0722f * b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Default Color Computation
|
||||
// ============================================================================
|
||||
|
||||
void ThemeLoader::computeDefaults(material::ColorTheme& theme, bool isDark)
|
||||
{
|
||||
using namespace material;
|
||||
|
||||
// Helper to check if a color is "unset" (black with no alpha)
|
||||
auto isUnset = [](ImU32 c) { return c == 0; };
|
||||
|
||||
if (isDark) {
|
||||
// Dark theme defaults
|
||||
if (isUnset(theme.primaryVariant)) theme.primaryVariant = Darken(theme.primary, 0.2f);
|
||||
if (isUnset(theme.primaryLight)) theme.primaryLight = Lighten(theme.primary, 0.3f);
|
||||
|
||||
if (isUnset(theme.secondary)) theme.secondary = Hex(0x03DAC6);
|
||||
if (isUnset(theme.secondaryVariant)) theme.secondaryVariant = Darken(theme.secondary, 0.2f);
|
||||
if (isUnset(theme.secondaryLight)) theme.secondaryLight = Lighten(theme.secondary, 0.3f);
|
||||
|
||||
if (isUnset(theme.surface)) theme.surface = Lighten(theme.background, 0.05f);
|
||||
if (isUnset(theme.surfaceVariant)) theme.surfaceVariant = Lighten(theme.background, 0.10f);
|
||||
|
||||
if (isUnset(theme.onPrimary)) theme.onPrimary = Hex(0xFFFFFF);
|
||||
if (isUnset(theme.onSecondary)) theme.onSecondary = Hex(0x000000);
|
||||
if (isUnset(theme.onBackground)) theme.onBackground = Hex(0xFFFFFF);
|
||||
if (isUnset(theme.onSurface)) theme.onSurface = Hex(0xFFFFFF);
|
||||
if (isUnset(theme.onSurfaceMedium)) theme.onSurfaceMedium = HexA(0xFFFFFF, 179);
|
||||
if (isUnset(theme.onSurfaceDisabled)) theme.onSurfaceDisabled = HexA(0xFFFFFF, 97);
|
||||
|
||||
if (isUnset(theme.error)) theme.error = Hex(0xCF6679);
|
||||
if (isUnset(theme.onError)) theme.onError = Hex(0x000000);
|
||||
|
||||
if (isUnset(theme.success)) theme.success = Hex(0x81C784);
|
||||
if (isUnset(theme.onSuccess)) theme.onSuccess = Hex(0x000000);
|
||||
if (isUnset(theme.warning)) theme.warning = Hex(0xFFB74D);
|
||||
if (isUnset(theme.onWarning)) theme.onWarning = Hex(0x000000);
|
||||
|
||||
if (isUnset(theme.stateHover)) theme.stateHover = HexA(0xFFFFFF, 10);
|
||||
if (isUnset(theme.stateFocus)) theme.stateFocus = HexA(0xFFFFFF, 31);
|
||||
if (isUnset(theme.statePressed)) theme.statePressed = HexA(0xFFFFFF, 25);
|
||||
if (isUnset(theme.stateSelected)) theme.stateSelected = HexA(0xFFFFFF, 20);
|
||||
if (isUnset(theme.stateDragged)) theme.stateDragged = HexA(0xFFFFFF, 20);
|
||||
|
||||
if (isUnset(theme.divider)) theme.divider = HexA(0xFFFFFF, 31);
|
||||
if (isUnset(theme.outline)) theme.outline = HexA(0xFFFFFF, 31);
|
||||
if (isUnset(theme.scrim)) theme.scrim = HexA(0x000000, 128);
|
||||
} else {
|
||||
// Light theme defaults
|
||||
if (isUnset(theme.primaryVariant)) theme.primaryVariant = Darken(theme.primary, 0.15f);
|
||||
if (isUnset(theme.primaryLight)) theme.primaryLight = Lighten(theme.primary, 0.4f);
|
||||
|
||||
if (isUnset(theme.secondary)) theme.secondary = Hex(0x03DAC6);
|
||||
if (isUnset(theme.secondaryVariant)) theme.secondaryVariant = Darken(theme.secondary, 0.15f);
|
||||
if (isUnset(theme.secondaryLight)) theme.secondaryLight = Lighten(theme.secondary, 0.4f);
|
||||
|
||||
if (isUnset(theme.surface)) theme.surface = Hex(0xFFFFFF);
|
||||
if (isUnset(theme.surfaceVariant)) theme.surfaceVariant = Darken(theme.background, 0.02f);
|
||||
|
||||
if (isUnset(theme.onPrimary)) theme.onPrimary = Hex(0xFFFFFF);
|
||||
if (isUnset(theme.onSecondary)) theme.onSecondary = Hex(0x000000);
|
||||
if (isUnset(theme.onBackground)) theme.onBackground = Hex(0x000000);
|
||||
if (isUnset(theme.onSurface)) theme.onSurface = Hex(0x000000);
|
||||
if (isUnset(theme.onSurfaceMedium)) theme.onSurfaceMedium = HexA(0x000000, 179);
|
||||
if (isUnset(theme.onSurfaceDisabled)) theme.onSurfaceDisabled = HexA(0x000000, 97);
|
||||
|
||||
if (isUnset(theme.error)) theme.error = Hex(0xB00020);
|
||||
if (isUnset(theme.onError)) theme.onError = Hex(0xFFFFFF);
|
||||
|
||||
if (isUnset(theme.success)) theme.success = Hex(0x4CAF50);
|
||||
if (isUnset(theme.onSuccess)) theme.onSuccess = Hex(0xFFFFFF);
|
||||
if (isUnset(theme.warning)) theme.warning = Hex(0xFF9800);
|
||||
if (isUnset(theme.onWarning)) theme.onWarning = Hex(0x000000);
|
||||
|
||||
if (isUnset(theme.stateHover)) theme.stateHover = HexA(0x000000, 10);
|
||||
if (isUnset(theme.stateFocus)) theme.stateFocus = HexA(0x000000, 31);
|
||||
if (isUnset(theme.statePressed)) theme.statePressed = HexA(0x000000, 25);
|
||||
if (isUnset(theme.stateSelected)) theme.stateSelected = HexA(0x000000, 20);
|
||||
if (isUnset(theme.stateDragged)) theme.stateDragged = HexA(0x000000, 20);
|
||||
|
||||
if (isUnset(theme.divider)) theme.divider = HexA(0x000000, 31);
|
||||
if (isUnset(theme.outline)) theme.outline = HexA(0x000000, 31);
|
||||
if (isUnset(theme.scrim)) theme.scrim = HexA(0x000000, 128);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Acrylic Theme Derivation
|
||||
// ============================================================================
|
||||
|
||||
AcrylicTheme ThemeLoader::deriveAcrylicTheme(const material::ColorTheme& theme)
|
||||
{
|
||||
AcrylicTheme acrylic;
|
||||
|
||||
// Extract primary color components for tinting
|
||||
float primaryR = ((theme.primary >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
|
||||
float primaryG = ((theme.primary >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
|
||||
float primaryB = ((theme.primary >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
|
||||
|
||||
// Extract background color components
|
||||
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;
|
||||
|
||||
bool isDark = getLuminance(theme.background) < 0.5f;
|
||||
|
||||
if (isDark) {
|
||||
// Dark theme: tint towards primary color
|
||||
|
||||
// Sidebar: Strong primary tint
|
||||
acrylic.sidebar.tintColor = ImVec4(
|
||||
bgR * 0.5f + primaryR * 0.5f,
|
||||
bgG * 0.5f + primaryG * 0.5f,
|
||||
bgB * 0.5f + primaryB * 0.5f,
|
||||
1.0f
|
||||
);
|
||||
acrylic.sidebar.tintOpacity = 0.85f;
|
||||
acrylic.sidebar.luminosityOpacity = 0.4f;
|
||||
acrylic.sidebar.blurRadius = 40.0f;
|
||||
acrylic.sidebar.noiseOpacity = 0.02f;
|
||||
acrylic.sidebar.fallbackColor = ImVec4(bgR * 0.7f, bgG * 0.7f, bgB * 0.7f, 1.0f);
|
||||
acrylic.sidebar.enabled = true;
|
||||
|
||||
// Popups: Subtle dark with slight primary tint
|
||||
acrylic.popup.tintColor = ImVec4(
|
||||
bgR * 0.8f + primaryR * 0.2f,
|
||||
bgG * 0.8f + primaryG * 0.2f,
|
||||
bgB * 0.8f + primaryB * 0.2f,
|
||||
1.0f
|
||||
);
|
||||
acrylic.popup.tintOpacity = 0.80f;
|
||||
acrylic.popup.luminosityOpacity = 0.5f;
|
||||
acrylic.popup.blurRadius = 30.0f;
|
||||
acrylic.popup.noiseOpacity = 0.02f;
|
||||
acrylic.popup.fallbackColor = ImVec4(bgR, bgG, bgB, 0.98f);
|
||||
acrylic.popup.enabled = true;
|
||||
|
||||
// Cards: Very subtle primary tint
|
||||
acrylic.card.tintColor = ImVec4(
|
||||
bgR * 0.9f + primaryR * 0.1f,
|
||||
bgG * 0.9f + primaryG * 0.1f,
|
||||
bgB * 0.9f + primaryB * 0.1f,
|
||||
1.0f
|
||||
);
|
||||
acrylic.card.tintOpacity = 0.65f;
|
||||
acrylic.card.luminosityOpacity = 0.6f;
|
||||
acrylic.card.blurRadius = 20.0f;
|
||||
acrylic.card.noiseOpacity = 0.015f;
|
||||
acrylic.card.fallbackColor = ImVec4(bgR + 0.05f, bgG + 0.05f, bgB + 0.05f, 1.0f);
|
||||
acrylic.card.enabled = true;
|
||||
|
||||
// Context menus: Dark and crisp
|
||||
acrylic.menu.tintColor = ImVec4(bgR, bgG, bgB, 1.0f);
|
||||
acrylic.menu.tintOpacity = 0.88f;
|
||||
acrylic.menu.luminosityOpacity = 0.45f;
|
||||
acrylic.menu.blurRadius = 25.0f;
|
||||
acrylic.menu.noiseOpacity = 0.02f;
|
||||
acrylic.menu.fallbackColor = ImVec4(bgR, bgG, bgB, 0.98f);
|
||||
acrylic.menu.enabled = true;
|
||||
|
||||
// Tooltips: Very transparent
|
||||
acrylic.tooltip.tintColor = ImVec4(bgR, bgG, bgB, 1.0f);
|
||||
acrylic.tooltip.tintOpacity = 0.75f;
|
||||
acrylic.tooltip.luminosityOpacity = 0.5f;
|
||||
acrylic.tooltip.blurRadius = 15.0f;
|
||||
acrylic.tooltip.noiseOpacity = 0.01f;
|
||||
acrylic.tooltip.fallbackColor = ImVec4(bgR + 0.02f, bgG + 0.02f, bgB + 0.02f, 0.95f);
|
||||
acrylic.tooltip.enabled = true;
|
||||
} else {
|
||||
// Light theme
|
||||
|
||||
// Sidebar: Light with slight primary tint
|
||||
acrylic.sidebar.tintColor = ImVec4(
|
||||
bgR * 0.9f + primaryR * 0.1f,
|
||||
bgG * 0.9f + primaryG * 0.1f,
|
||||
bgB * 0.9f + primaryB * 0.1f,
|
||||
1.0f
|
||||
);
|
||||
acrylic.sidebar.tintOpacity = 0.82f;
|
||||
acrylic.sidebar.luminosityOpacity = 0.7f;
|
||||
acrylic.sidebar.blurRadius = 30.0f;
|
||||
acrylic.sidebar.noiseOpacity = 0.015f;
|
||||
acrylic.sidebar.fallbackColor = ImVec4(bgR, bgG, bgB, 1.0f);
|
||||
acrylic.sidebar.enabled = true;
|
||||
|
||||
// Popups
|
||||
acrylic.popup.tintColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
acrylic.popup.tintOpacity = 0.85f;
|
||||
acrylic.popup.luminosityOpacity = 0.75f;
|
||||
acrylic.popup.blurRadius = 25.0f;
|
||||
acrylic.popup.noiseOpacity = 0.015f;
|
||||
acrylic.popup.fallbackColor = ImVec4(bgR, bgG, bgB, 0.98f);
|
||||
acrylic.popup.enabled = true;
|
||||
|
||||
// Cards
|
||||
acrylic.card.tintColor = ImVec4(bgR, bgG, bgB, 1.0f);
|
||||
acrylic.card.tintOpacity = 0.70f;
|
||||
acrylic.card.luminosityOpacity = 0.75f;
|
||||
acrylic.card.blurRadius = 18.0f;
|
||||
acrylic.card.noiseOpacity = 0.01f;
|
||||
acrylic.card.fallbackColor = ImVec4(bgR, bgG, bgB, 1.0f);
|
||||
acrylic.card.enabled = true;
|
||||
|
||||
// Menus
|
||||
acrylic.menu.tintColor = ImVec4(bgR, bgG, bgB, 1.0f);
|
||||
acrylic.menu.tintOpacity = 0.88f;
|
||||
acrylic.menu.luminosityOpacity = 0.7f;
|
||||
acrylic.menu.blurRadius = 22.0f;
|
||||
acrylic.menu.noiseOpacity = 0.015f;
|
||||
acrylic.menu.fallbackColor = ImVec4(bgR, bgG, bgB, 0.98f);
|
||||
acrylic.menu.enabled = true;
|
||||
|
||||
// Tooltips
|
||||
acrylic.tooltip.tintColor = ImVec4(bgR - 0.02f, bgG - 0.02f, bgB - 0.02f, 1.0f);
|
||||
acrylic.tooltip.tintOpacity = 0.80f;
|
||||
acrylic.tooltip.luminosityOpacity = 0.7f;
|
||||
acrylic.tooltip.blurRadius = 12.0f;
|
||||
acrylic.tooltip.noiseOpacity = 0.01f;
|
||||
acrylic.tooltip.fallbackColor = ImVec4(bgR - 0.02f, bgG - 0.02f, bgB - 0.02f, 0.95f);
|
||||
acrylic.tooltip.enabled = true;
|
||||
}
|
||||
|
||||
return acrylic;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user