feat: Full UI internationalization, pool hashrate stats, and layout caching
- Replace all hardcoded English strings with TR() translation keys across every tab, dialog, and component (~20 UI files) - Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with complete translations (~37k lines added) - Improve i18n loader with exe-relative path fallback and English base fallback for missing keys - Add pool-side hashrate polling via pool stats API in xmrig_manager - Introduce Layout::beginFrame() per-frame caching and refresh balance layout config only on schema generation change - Offload daemon output parsing to worker thread - Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
This commit is contained in:
@@ -351,18 +351,24 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
|
||||
|
||||
// Noise grain overlay — drawn OVER the surface overlay so card
|
||||
// opacity doesn't hide it. Gives cards a tactile paper feel.
|
||||
// Noise tint is cached per-generation, opacity checked each panel.
|
||||
{
|
||||
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));
|
||||
// Tint base color changes only on theme reload; opacity slider may change per-frame
|
||||
static uint32_t s_noiseGen = 0;
|
||||
static uint8_t s_baseAlpha = 0;
|
||||
if (curGen != s_noiseGen) {
|
||||
s_noiseGen = curGen;
|
||||
s_baseAlpha = (s_glassNoiseTint >> IM_COL32_A_SHIFT) & 0xFF;
|
||||
}
|
||||
uint8_t scaledAlpha = static_cast<uint8_t>(std::min(255.0f, s_baseAlpha * 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();
|
||||
ImVec2 noiseMin(pMin.x + inset, pMin.y + inset);
|
||||
ImVec2 noiseMax(pMax.x - inset, pMax.y - inset);
|
||||
// Image rect matches clip bounds exactly — no PushClipRect needed
|
||||
dragonx::util::DrawTiledNoiseRect(dl, noiseMin, noiseMax, noiseTint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,18 +381,20 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
|
||||
// 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.hasAnyPanelEffect()) {
|
||||
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);
|
||||
}
|
||||
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,
|
||||
@@ -749,8 +757,10 @@ inline void ApplySmoothScroll(float speed = 12.0f)
|
||||
s.current = actualY;
|
||||
}
|
||||
|
||||
// Capture mouse wheel when hovered
|
||||
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
|
||||
// Capture mouse wheel when hovered, but not when a popup (combo dropdown
|
||||
// etc.) is open — let the popup handle its own scrolling exclusively.
|
||||
bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
|
||||
if (!popupOpen && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
|
||||
float wheel = ImGui::GetIO().MouseWheel;
|
||||
if (wheel != 0.0f) {
|
||||
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
|
||||
|
||||
@@ -246,11 +246,14 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
|
||||
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.)
|
||||
// Cyrillic (for Russian), Greek, 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.)
|
||||
0x0370, 0x03FF, // Greek and Coptic
|
||||
0x0400, 0x04FF, // Cyrillic (Russian, Ukrainian, etc.)
|
||||
0x0500, 0x052F, // Cyrillic Supplement
|
||||
0x2000, 0x206F, // General Punctuation (em dash, ellipsis, etc.)
|
||||
0x2190, 0x21FF, // Arrows (includes ⇄ U+21C4, ↻ U+21BB)
|
||||
0x2200, 0x22FF, // Mathematical Operators (includes ≈ U+2248)
|
||||
@@ -269,6 +272,36 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
|
||||
|
||||
if (font) {
|
||||
DEBUG_LOGF("Typography: Loaded %s (%.0fpx) as '%s'\n", name, size, cfg.Name);
|
||||
|
||||
// Merge CJK fallback glyphs (Chinese/Japanese/Korean) from subset font
|
||||
if (g_noto_cjk_subset_size > 0) {
|
||||
void* cjkCopy = IM_ALLOC(g_noto_cjk_subset_size);
|
||||
memcpy(cjkCopy, g_noto_cjk_subset_data, g_noto_cjk_subset_size);
|
||||
|
||||
ImFontConfig cjkCfg;
|
||||
cjkCfg.FontDataOwnedByAtlas = true;
|
||||
cjkCfg.MergeMode = true; // merge into the font we just loaded
|
||||
cjkCfg.OversampleH = 1;
|
||||
cjkCfg.OversampleV = 1;
|
||||
cjkCfg.PixelSnapH = true;
|
||||
cjkCfg.GlyphMinAdvanceX = 0;
|
||||
// CJK Unified Ideographs + Hiragana + Katakana + Hangul + fullwidth punctuation
|
||||
static const ImWchar cjkRanges[] = {
|
||||
0x2E80, 0x2FDF, // CJK Radicals
|
||||
0x3000, 0x30FF, // CJK Symbols, Hiragana, Katakana
|
||||
0x3100, 0x312F, // Bopomofo
|
||||
0x31F0, 0x31FF, // Katakana Extensions
|
||||
0x3400, 0x4DBF, // CJK Extension A
|
||||
0x4E00, 0x9FFF, // CJK Unified Ideographs
|
||||
0xAC00, 0xD7AF, // Hangul Syllables
|
||||
0xFF00, 0xFFEF, // Fullwidth Forms
|
||||
0,
|
||||
};
|
||||
cjkCfg.GlyphRanges = cjkRanges;
|
||||
snprintf(cjkCfg.Name, sizeof(cjkCfg.Name), "NotoSansCJK %.0fpx (merge)", size);
|
||||
|
||||
io.Fonts->AddFontFromMemoryTTF(cjkCopy, g_noto_cjk_subset_size, size, &cjkCfg);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOGF("Typography: Failed to load %s\n", name);
|
||||
IM_FREE(fontDataCopy);
|
||||
|
||||
Reference in New Issue
Block a user