Files
ObsidianDragon/src/ui/theme_loader.cpp
DanS 3aee55b49c 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)
2026-02-27 00:26:01 -06:00

354 lines
14 KiB
C++

// 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