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:
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
|
||||
Reference in New Issue
Block a user