feat: CJK font rendering, force quit confirmation, settings i18n
- Rebuild CJK font subset (1421 glyphs) and convert CFF→TTF for stb_truetype compatibility, fixing Chinese/Japanese/Korean rendering - Add force quit confirmation dialog with cancel/confirm actions - Show force quit tooltip immediately on hover (no delay) - Translate hardcoded English strings in settings dropdowns (auto-lock timeouts, slider "Off" labels) - Fix mojibake en-dashes in 7 translation JSON files - Add helper scripts: build_cjk_subset, convert_cjk_to_ttf, check_font_coverage, fix_mojibake
This commit is contained in:
42
src/app.cpp
42
src/app.cpp
@@ -2535,12 +2535,50 @@ void App::renderShutdownScreen()
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.75f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.1f, 0.1f, 1.0f));
|
||||
if (ImGui::Button(forceLabel, btnSize)) {
|
||||
DEBUG_LOGF("Force quit requested by user after %.0fs\n", shutdown_timer_);
|
||||
shutdown_complete_ = true;
|
||||
force_quit_confirm_ = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", TR("force_quit_warning"));
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
|
||||
// Force Quit confirmation popup
|
||||
if (force_quit_confirm_) {
|
||||
ImGui::OpenPopup("##ForceQuitConfirm");
|
||||
force_quit_confirm_ = false;
|
||||
}
|
||||
ImVec2 popupCenter(wp.x + vp_size.x * 0.5f, wp.y + vp_size.y * 0.5f);
|
||||
ImGui::SetNextWindowPos(popupCenter, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
if (ImGui::BeginPopupModal("##ForceQuitConfirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) {
|
||||
ImGui::PushFont(Type().subtitle1());
|
||||
ImGui::TextUnformatted(TR("force_quit_confirm_title"));
|
||||
ImGui::PopFont();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted(TR("force_quit_confirm_msg"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = 120.0f;
|
||||
float totalW = btnW * 2 + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - totalW) * 0.5f);
|
||||
|
||||
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.15f, 0.15f, 0.9f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.75f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.1f, 0.1f, 1.0f));
|
||||
if (ImGui::Button(TR("force_quit_yes"), ImVec2(btnW, 0))) {
|
||||
DEBUG_LOGF("Force quit confirmed by user after %.0fs\n", shutdown_timer_);
|
||||
shutdown_complete_ = true;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
|
||||
@@ -372,6 +372,7 @@ private:
|
||||
std::string shutdown_status_;
|
||||
std::thread shutdown_thread_;
|
||||
float shutdown_timer_ = 0.0f;
|
||||
bool force_quit_confirm_ = false;
|
||||
std::chrono::steady_clock::time_point shutdown_start_time_;
|
||||
|
||||
// Daemon restart (e.g. after changing debug log categories)
|
||||
|
||||
@@ -12,4 +12,4 @@ 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");
|
||||
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.otf");
|
||||
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.ttf");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -300,7 +300,14 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
|
||||
cjkCfg.GlyphRanges = cjkRanges;
|
||||
snprintf(cjkCfg.Name, sizeof(cjkCfg.Name), "NotoSansCJK %.0fpx (merge)", size);
|
||||
|
||||
io.Fonts->AddFontFromMemoryTTF(cjkCopy, g_noto_cjk_subset_size, size, &cjkCfg);
|
||||
ImFont* mergeResult = io.Fonts->AddFontFromMemoryTTF(cjkCopy, g_noto_cjk_subset_size, size, &cjkCfg);
|
||||
if (mergeResult) {
|
||||
DEBUG_LOGF("Typography: Merged CJK (%u bytes) into %s OK\n",
|
||||
g_noto_cjk_subset_size, name);
|
||||
} else {
|
||||
DEBUG_LOGF("Typography: WARNING — CJK merge FAILED for %s (size=%u)\n",
|
||||
name, g_noto_cjk_subset_size);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOGF("Typography: Failed to load %s\n", name);
|
||||
|
||||
@@ -713,7 +713,7 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
char blur_fmt[16];
|
||||
if (sp_blur_amount < 0.01f)
|
||||
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
|
||||
snprintf(blur_fmt, sizeof(blur_fmt), "%s", TR("slider_off"));
|
||||
else
|
||||
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
|
||||
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
|
||||
@@ -735,7 +735,7 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
char noise_fmt[16];
|
||||
if (sp_noise_opacity < 0.01f)
|
||||
snprintf(noise_fmt, sizeof(noise_fmt), "Off");
|
||||
snprintf(noise_fmt, sizeof(noise_fmt), "%s", TR("slider_off"));
|
||||
else
|
||||
snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", sp_noise_opacity * 100.0f);
|
||||
if (ImGui::SliderFloat("##NoiseOpacity", &sp_noise_opacity, 0.0f, 1.0f, noise_fmt,
|
||||
@@ -1001,7 +1001,7 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
char blur_fmt[16];
|
||||
if (sp_blur_amount < 0.01f)
|
||||
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
|
||||
snprintf(blur_fmt, sizeof(blur_fmt), "%s", TR("slider_off"));
|
||||
else
|
||||
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
|
||||
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
|
||||
@@ -1019,7 +1019,7 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
char noise_fmt[16];
|
||||
if (sp_noise_opacity < 0.01f)
|
||||
snprintf(noise_fmt, sizeof(noise_fmt), "Off");
|
||||
snprintf(noise_fmt, sizeof(noise_fmt), "%s", TR("slider_off"));
|
||||
else
|
||||
snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", sp_noise_opacity * 100.0f);
|
||||
if (ImGui::SliderFloat("##NoiseOpacity", &sp_noise_opacity, 0.0f, 1.0f, noise_fmt,
|
||||
@@ -1586,7 +1586,7 @@ void RenderSettingsPage(App* app) {
|
||||
float comboW = S.drawElement("components.settings-page", "security-combo-width").sizeOr(120.0f);
|
||||
|
||||
int timeout = app->settings()->getAutoLockTimeout();
|
||||
const char* timeoutLabels[] = { "Off", "1 min", "5 min", "15 min", "30 min", "1 hour" };
|
||||
const char* timeoutLabels[] = { TR("timeout_off"), TR("timeout_1min"), TR("timeout_5min"), TR("timeout_15min"), TR("timeout_30min"), TR("timeout_1hour") };
|
||||
int timeoutValues[] = { 0, 60, 300, 900, 1800, 3600 };
|
||||
int selTimeout = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
|
||||
@@ -261,6 +261,10 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["pin_not_set"] = "PIN not set. Use passphrase to unlock.";
|
||||
strings_["restarting_after_encryption"] = "Restarting daemon after encryption...";
|
||||
strings_["force_quit"] = "Force Quit";
|
||||
strings_["force_quit_warning"] = "This will immediately kill the daemon without a clean shutdown. May require a blockchain resync.";
|
||||
strings_["force_quit_confirm_title"] = "Force Quit?";
|
||||
strings_["force_quit_confirm_msg"] = "This will immediately kill the daemon without a clean shutdown.\nThis may corrupt the blockchain index and require a resync.";
|
||||
strings_["force_quit_yes"] = "Force Quit";
|
||||
strings_["reduce_motion"] = "Reduce Motion";
|
||||
strings_["tt_reduce_motion"] = "Disable animated transitions and balance lerp for accessibility";
|
||||
strings_["ago"] = "ago";
|
||||
@@ -281,6 +285,13 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["settings_block_explorer_urls"] = "Block Explorer URLs";
|
||||
strings_["settings_configure_explorer"] = "Configure external block explorer links";
|
||||
strings_["settings_auto_lock"] = "AUTO-LOCK";
|
||||
strings_["timeout_off"] = "Off";
|
||||
strings_["timeout_1min"] = "1 min";
|
||||
strings_["timeout_5min"] = "5 min";
|
||||
strings_["timeout_15min"] = "15 min";
|
||||
strings_["timeout_30min"] = "30 min";
|
||||
strings_["timeout_1hour"] = "1 hour";
|
||||
strings_["slider_off"] = "Off";
|
||||
strings_["settings_wallet_file_size"] = "Wallet file size: %s";
|
||||
strings_["settings_wallet_location"] = "Wallet location: %s";
|
||||
strings_["settings_rpc_note"] = "Note: Connection settings are usually auto-detected from DRAGONX.conf";
|
||||
|
||||
Reference in New Issue
Block a user