fix(ui): DPI-scale hand-drawn overlays + freeze sweep demo state
Hand-drawn absolute geometry (dl->AddText offsets, SetNextWindowSize, explicit ImVec2 button widths, SameLine strides) is immune to the app's DPI machinery (font-atlas rebuild + ScaleAllSizes), so several overlays rendered native-size (tiny) on HiDPI / Windows 150% displays. Multiply the raw logical-px geometry by Layout::dpiScale(): - seed_display: content-aware, DPI-scaled seed-grid column stride (a raw 130px stride let words collide with the next column's index at 150%) - lock screen: card/logo/input/unlock-button + all vertical offsets - migrate-to-seed + seed-backup dialogs: button widths - send-confirm: confirm/cancel button widths - about dialog: value-column x, edition inset, link/close button widths - chat conversation list: row height + padding (fixes the preview clip) Also freeze demo state during the full UI sweep: guard App::update() on capture_mode_ so a running daemon's refresh can't clobber the injected demo state (it blanked Send/Receive/History with a CDB error mid-sweep). Verified on Windows 4K@150% and locally at font_scale=1.5 (same dpiScale()==1.5 code path). Adds a DPI-scaling convention note to CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -106,5 +106,6 @@ The version has a **single source of truth**: `project(... VERSION 1.2.0 ...)` p
|
||||
- **C++17.** Match the surrounding code's style per file.
|
||||
- **Icons:** use the Material Design icon font defines (`ICON_MD_*`); never raw Unicode glyphs.
|
||||
- **UI layout values** belong in `res/themes/ui.toml`, read via `schema::UI()` — do not hardcode pixel sizes/offsets in code.
|
||||
- **DPI / display scaling:** `schema::UI()` returns **logical (raw) px**; `Layout::dpiScale()` (= OS DPI × the in-app font-scale) is the single scale factor. The `Layout::k*()` helpers and `BeginOverlayDialog` already fold it in, and ImGui auto-layout scales via the DPI-rebuilt font atlas + `ScaleAllSizes` — so tabs/standard widgets scale for free. But **hand-drawn absolute geometry** (`dl->AddText` at manual `cy += 24.0f` offsets, `SetNextWindowSize`, explicit `ImVec2(width,0)` button sizes, `SameLine(x)` column strides) is immune to all of that and must be multiplied by `ui::Layout::dpiScale()` yourself, or it renders native-size (tiny) on a HiDPI display. Font metrics (`font->LegacySize`, `CalcTextSize`) are already scaled — don't double-scale those. Verify at scale with a full sweep run at `font_scale: 1.5` (same `dpiScale()==1.5` code path as OS 150%).
|
||||
- **i18n:** user-facing strings are translated via `src/util/i18n`; the English source of truth is the `strings_[...]` map in `src/util/i18n.cpp`, and the per-language translations live as the **source of truth** in `res/lang/` (`de`, `es`, `fr`, `ja`, `ko`, `pt`, `ru`, `zh`). **Edit those JSONs directly and additively** — write with `json.dump(..., indent=4, sort_keys=True, ensure_ascii=False)`; never bulk-regenerate/overwrite a whole file (that path silently dropped ~285 keys/language before). `scripts/add_missing_translations.py` back-fills only the keys missing from a JSON (non-destructive), and `scripts/build_cjk_subset.py` rebuilds the CJK subset font (`res/fonts/NotoSansCJK-Subset.ttf`) after new CJK glyphs are added.
|
||||
- **Commits:** the history uses Conventional Commits (`feat(scope): …`, `fix(scope): …`). PRs target `master`.
|
||||
|
||||
52
src/app.cpp
52
src/app.cpp
@@ -627,7 +627,13 @@ void App::update()
|
||||
{
|
||||
PERF_SCOPE("Update.Total");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
|
||||
// Full UI screenshot sweep: demo state is injected once and must stay frozen. Skip every live
|
||||
// op (refresh/connect/pumps) so a real daemon can't clobber it — on Windows a running node's
|
||||
// refresh set connected=false (CDB error) and blanked the Send/Receive/History tabs mid-sweep.
|
||||
// capture_mode_ is only set for the offline full sweep, so the live tab-only sweep is unaffected.
|
||||
if (capture_mode_) return;
|
||||
|
||||
// Track user interaction for auto-lock
|
||||
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0 ||
|
||||
io.MouseClicked[0] || io.MouseClicked[1] ||
|
||||
@@ -3006,6 +3012,9 @@ void App::renderSeedBackupDialog()
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, TR("seed_backup_title"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
// Button widths are logical px; BeginOverlayDialog scales the card by dpiScale() so scale these
|
||||
// to match (raw widths render small/left-packed at higher DPI / font scale).
|
||||
const float dp = ui::Layout::dpiScale();
|
||||
|
||||
if (state_.isLocked()) {
|
||||
ImGui::TextWrapped("%s", TR("seed_backup_locked"));
|
||||
@@ -3027,11 +3036,11 @@ void App::renderSeedBackupDialog()
|
||||
ImGui::Spacing();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton(TR("seed_backup_copy"), ImVec2(120, 0))) {
|
||||
if (ui::material::StyledButton(TR("seed_backup_copy"), ImVec2(120 * dp, 0))) {
|
||||
copySecretToClipboard(seed_backup_phrase_);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(150, 0))) {
|
||||
if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(150 * dp, 0))) {
|
||||
std::string path = util::Platform::getConfigDir() + "/dragonx-seed-backup.txt";
|
||||
std::string content = seed_backup_phrase_ + "\n";
|
||||
const bool ok = util::Platform::writeFileAtomically(path, content,
|
||||
@@ -3041,7 +3050,7 @@ void App::renderSeedBackupDialog()
|
||||
: std::string(TR("seed_backup_save_failed"))) + path;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("seed_backup_close"), ImVec2(120, 0))) {
|
||||
if (ui::material::StyledButton(TR("seed_backup_close"), ImVec2(120 * dp, 0))) {
|
||||
closeAndWipe();
|
||||
ui::material::EndOverlayDialog();
|
||||
return;
|
||||
@@ -3054,7 +3063,7 @@ void App::renderSeedBackupDialog()
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton(TR("seed_backup_close"), ImVec2(120, 0))) {
|
||||
if (ui::material::StyledButton(TR("seed_backup_close"), ImVec2(120 * dp, 0))) {
|
||||
closeAndWipe();
|
||||
}
|
||||
ui::material::EndOverlayDialog();
|
||||
@@ -3085,6 +3094,9 @@ void App::renderSeedMigrationDialog()
|
||||
if (!show_seed_migration_ && busy) show_seed_migration_ = true;
|
||||
|
||||
const ImVec4 kMedium = ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium());
|
||||
// BeginOverlayDialog scales the card by dpiScale(); the button widths below are authored in
|
||||
// logical px and must be scaled the same way or they render small/left-packed at higher DPI.
|
||||
const float dp = ui::Layout::dpiScale();
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, TR("mig_title"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
@@ -3093,10 +3105,10 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::TextWrapped("%s", TR("mig_intro"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton(TR("mig_create"), ImVec2(180, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_create"), ImVec2(180 * dp, 0)))
|
||||
beginCreateSeedWallet();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("cancel"), ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("cancel"), ImVec2(120 * dp, 0)))
|
||||
close();
|
||||
break;
|
||||
|
||||
@@ -3117,10 +3129,10 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::TextColored(kMedium, "%s", TR("mig_receive_addr"));
|
||||
ImGui::TextWrapped("%s", seed_migration_dest_.c_str());
|
||||
ImGui::Spacing();
|
||||
if (ui::material::StyledButton(TR("mig_copy_seed"), ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_copy_seed"), ImVec2(120 * dp, 0)))
|
||||
copySecretToClipboard(seed_migration_seed_);
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(130, 0))) {
|
||||
if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(130 * dp, 0))) {
|
||||
std::string path = util::Platform::getConfigDir() + "/dragonx-seed-wallet-backup.txt";
|
||||
std::string content = seed_migration_seed_ + "\nAddress: " + seed_migration_dest_ + "\n";
|
||||
const bool ok = util::Platform::writeFileAtomically(path, content, /*restrict=*/true);
|
||||
@@ -3138,14 +3150,14 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Separator();
|
||||
if (!seed_migration_backed_up_) ImGui::BeginDisabled();
|
||||
if (ui::material::StyledButton(TR("mig_continue_sweep"), ImVec2(170, 0))) {
|
||||
if (ui::material::StyledButton(TR("mig_continue_sweep"), ImVec2(170 * dp, 0))) {
|
||||
wipeSeed(); // the sweep/adopt steps only use the address, never the seed itself
|
||||
seed_migration_step_ = SeedMigrationStep::Sweep;
|
||||
refreshSeedMigrationBalance();
|
||||
}
|
||||
if (!seed_migration_backed_up_) ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("mig_discard"), ImVec2(160, 0))) {
|
||||
if (ui::material::StyledButton(TR("mig_discard"), ImVec2(160 * dp, 0))) {
|
||||
// Throw away the isolated new wallet + clear the pending state.
|
||||
if (!seed_migration_temp_dir_.empty()) {
|
||||
std::error_code ec;
|
||||
@@ -3181,15 +3193,15 @@ void App::renderSeedMigrationDialog()
|
||||
{
|
||||
const bool canSweep = !state_.isLocked() && seed_migration_balance_ > 0.0;
|
||||
if (!canSweep) ImGui::BeginDisabled();
|
||||
if (ui::material::StyledButton(TR("mig_sweep_all"), ImVec2(170, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_sweep_all"), ImVec2(170 * dp, 0)))
|
||||
beginSweepToSeedWallet();
|
||||
if (!canSweep) ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("refresh"), ImVec2(110, 0)))
|
||||
if (ui::material::StyledButton(TR("refresh"), ImVec2(110 * dp, 0)))
|
||||
refreshSeedMigrationBalance();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("mig_later"), ImVec2(100, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_later"), ImVec2(100 * dp, 0)))
|
||||
close(); // pending state persists; resumes here next time
|
||||
break;
|
||||
}
|
||||
@@ -3219,22 +3231,22 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (confirmed && legacyEmpty) {
|
||||
if (ui::material::StyledButton(TR("mig_adopt"), ImVec2(180, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_adopt"), ImVec2(180 * dp, 0)))
|
||||
beginAdoptSeedWallet();
|
||||
} else if (confirmed && !legacyEmpty) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
|
||||
ImGui::TextWrapped("%s", TR("mig_remainder"));
|
||||
ImGui::PopStyleColor();
|
||||
if (ui::material::StyledButton(TR("mig_sweep_remaining"), ImVec2(180, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0)))
|
||||
beginSweepToSeedWallet();
|
||||
} else {
|
||||
ImGui::TextColored(kMedium, "%s", TR("mig_waiting_mine"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("refresh"), ImVec2(110, 0)))
|
||||
if (ui::material::StyledButton(TR("refresh"), ImVec2(110 * dp, 0)))
|
||||
pollSweepStatus();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("mig_later"), ImVec2(100, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_later"), ImVec2(100 * dp, 0)))
|
||||
close(); // pending state + txid persist; resumes here next time
|
||||
break;
|
||||
}
|
||||
@@ -3260,7 +3272,7 @@ void App::renderSeedMigrationDialog()
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton(TR("mig_done_btn"), ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_done_btn"), ImVec2(120 * dp, 0)))
|
||||
close();
|
||||
break;
|
||||
|
||||
@@ -3270,7 +3282,7 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton(TR("close"), ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0)))
|
||||
close();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -824,12 +824,19 @@ void App::renderLockScreen() {
|
||||
MarkBlurOverlayDrawn(nullptr); // suppress foreground theme-effect bleed + re-capture on unlock
|
||||
}
|
||||
|
||||
// Card
|
||||
// Card. The lock screen is fully hand-drawn (dl->AddText at manual y offsets), so — unlike
|
||||
// BeginOverlayDialog cards — nothing scales automatically. Multiply every geometry literal
|
||||
// (card size, logo, input/button widths, vertical gaps) by dpiScale() so the card grows with
|
||||
// OS DPI / font scale instead of stranding a native-size card in a large window. The font
|
||||
// metrics (LegacySize / CalcTextSize) are already DPI-scaled via the atlas, so leave those.
|
||||
const float dp = ui::Layout::dpiScale();
|
||||
const auto& S = ui::schema::UI();
|
||||
float cardW = S.drawElement("screens.lock-screen", "card").getFloat("width", 400.0f);
|
||||
float cardH = S.drawElement("screens.lock-screen", "card").height;
|
||||
if (cardW <= 0) cardW = 400.0f;
|
||||
if (cardH <= 0) cardH = 320.0f;
|
||||
cardW *= dp;
|
||||
cardH *= dp;
|
||||
|
||||
float cardX = winPos.x + (winSize.x - cardW) * 0.5f;
|
||||
float cardY = winPos.y + (winSize.y - cardH) * 0.5f;
|
||||
@@ -845,17 +852,17 @@ void App::renderLockScreen() {
|
||||
cardGlass.borderWidth = 1.0f;
|
||||
DrawGlassPanel(dl, cardMin, cardMax, cardGlass);
|
||||
|
||||
float cy = cardY + 24.0f;
|
||||
float cy = cardY + 24.0f * dp;
|
||||
|
||||
// Logo
|
||||
float logoSize = S.drawElement("screens.lock-screen", "logo").sizeOr(64.0f);
|
||||
float logoSize = S.drawElement("screens.lock-screen", "logo").sizeOr(64.0f) * dp;
|
||||
if (logo_tex_ != 0) {
|
||||
float aspect = (logo_h_ > 0) ? (float)logo_w_ / (float)logo_h_ : 1.0f;
|
||||
float logoW = logoSize * aspect;
|
||||
float logoX = cardX + (cardW - logoW) * 0.5f;
|
||||
dl->AddImage(logo_tex_, ImVec2(logoX, cy), ImVec2(logoX + logoW, cy + logoSize));
|
||||
}
|
||||
cy += logoSize + 16.0f;
|
||||
cy += logoSize + 16.0f * dp;
|
||||
|
||||
// Title
|
||||
ImFont* titleFont = S.resolveFont(S.label("screens.lock-screen", "title").font);
|
||||
@@ -869,7 +876,7 @@ void App::renderLockScreen() {
|
||||
ImVec2 ts = titleFont->CalcTextSizeA(titleFont->LegacySize, FLT_MAX, 0, title);
|
||||
dl->AddText(titleFont, titleFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ts.x) * 0.5f, cy), textCol, title);
|
||||
cy += ts.y + 20.0f;
|
||||
cy += ts.y + 20.0f * dp;
|
||||
}
|
||||
|
||||
// Lockout timer
|
||||
@@ -882,7 +889,7 @@ void App::renderLockScreen() {
|
||||
ImVec2 ms = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ms.x) * 0.5f, cy), ui::material::Warning(), msg);
|
||||
cy += captionFont->LegacySize + 12.0f;
|
||||
cy += captionFont->LegacySize + 12.0f * dp;
|
||||
}
|
||||
|
||||
// Check if PIN vault is available
|
||||
@@ -907,7 +914,7 @@ void App::renderLockScreen() {
|
||||
ImVec2(startX, cy), IM_COL32(255,255,255,120), modeIcon);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(startX + iconSize.x, textY), IM_COL32(255,255,255,120), modeText);
|
||||
cy += std::max(iconSize.y, textSize.y) + 8.0f;
|
||||
cy += std::max(iconSize.y, textSize.y) + 8.0f * dp;
|
||||
|
||||
// Switch link
|
||||
ImVec2 sls = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, switchLabel);
|
||||
@@ -924,7 +931,7 @@ void App::renderLockScreen() {
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(switchX, cy), ui::material::Primary(), switchLabel);
|
||||
ImGui::PopStyleColor();
|
||||
cy += captionFont->LegacySize + 12.0f;
|
||||
cy += captionFont->LegacySize + 12.0f * dp;
|
||||
} else {
|
||||
// No PIN vault — don't show toggle, force passphrase mode
|
||||
lock_use_pin_ = false;
|
||||
@@ -933,6 +940,7 @@ void App::renderLockScreen() {
|
||||
// Input field
|
||||
float inputW = S.drawElement("screens.lock-screen", "input").getFloat("width", 320.0f);
|
||||
if (inputW <= 0) inputW = 320.0f;
|
||||
inputW *= dp;
|
||||
float inputX = cardX + (cardW - inputW) * 0.5f;
|
||||
|
||||
bool canSubmit = lock_lockout_timer_ <= 0.0f && !lock_unlock_in_progress_;
|
||||
@@ -942,7 +950,7 @@ void App::renderLockScreen() {
|
||||
// PIN input
|
||||
ImGui::SetCursorScreenPos(ImVec2(inputX, cy));
|
||||
ImGui::PushItemWidth(inputW);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
|
||||
ImGuiInputTextFlags pinFlags = ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal;
|
||||
if (canSubmit) pinFlags |= ImGuiInputTextFlags_EnterReturnsTrue;
|
||||
submitted = ImGui::InputText("##lock_pin", lock_pin_buf_,
|
||||
@@ -953,7 +961,7 @@ void App::renderLockScreen() {
|
||||
// Passphrase input (original)
|
||||
ImGui::SetCursorScreenPos(ImVec2(inputX, cy));
|
||||
ImGui::PushItemWidth(inputW);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
|
||||
ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags_Password;
|
||||
if (canSubmit) inputFlags |= ImGuiInputTextFlags_EnterReturnsTrue;
|
||||
submitted = ImGui::InputText("##lock_pass", lock_passphrase_buf_,
|
||||
@@ -965,9 +973,9 @@ void App::renderLockScreen() {
|
||||
// default frame is subtle on the glass card, so this shows the field is active and ready to type.
|
||||
if (ImGui::IsItemActive() || ImGui::IsItemFocused()) {
|
||||
dl->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
|
||||
ui::material::Primary(), 6.0f, 0, 2.0f);
|
||||
ui::material::Primary(), 6.0f * dp, 0, 2.0f * dp);
|
||||
}
|
||||
cy += 40.0f + 12.0f;
|
||||
cy += (40.0f + 12.0f) * dp;
|
||||
|
||||
// Focus the input when the lock screen first appears.
|
||||
// IsWindowAppearing() does not work here because the lock screen is
|
||||
@@ -985,13 +993,13 @@ void App::renderLockScreen() {
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - es.x) * 0.5f, cy), ui::material::Error(),
|
||||
lock_error_msg_.c_str());
|
||||
cy += captionFont->LegacySize + 8.0f;
|
||||
cy += captionFont->LegacySize + 8.0f * dp;
|
||||
}
|
||||
|
||||
// "Unlocking..." feedback while worker thread is running
|
||||
// Always reserve the vertical space so the button doesn't shift.
|
||||
{
|
||||
float rowH = captionFont->LegacySize + 8.0f;
|
||||
float rowH = captionFont->LegacySize + 8.0f * dp;
|
||||
if (lock_unlock_in_progress_) {
|
||||
// Animated spinner dots
|
||||
char msg[64];
|
||||
@@ -1009,13 +1017,15 @@ void App::renderLockScreen() {
|
||||
float unlockH = S.drawElement("screens.lock-screen", "unlock-button").height;
|
||||
if (unlockW <= 0) unlockW = 320.0f;
|
||||
if (unlockH <= 0) unlockH = 44.0f;
|
||||
unlockW *= dp;
|
||||
unlockH *= dp;
|
||||
float unlockX = cardX + (cardW - unlockW) * 0.5f;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(unlockX, cy));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Primary()));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||
ImGui::BeginDisabled(!canSubmit);
|
||||
bool btnClicked = ui::material::TactileButton("Unlock", ImVec2(unlockW, unlockH));
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -24,7 +24,16 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
auto closeBtn = S.button("dialogs.about", "close-button");
|
||||
auto versionLbl = S.label("dialogs.about", "version-label");
|
||||
auto editionLbl = S.label("dialogs.about", "edition-label");
|
||||
|
||||
|
||||
// Schema positions/widths are logical px. BeginOverlayDialog scales the card by dpiScale(), so
|
||||
// scale the value-column x, the edition inset, and the button widths to match — raw values left
|
||||
// the value column jammed against the labels and the buttons left-packed at higher DPI.
|
||||
const float dp = Layout::dpiScale();
|
||||
const float valX = versionLbl.position * dp;
|
||||
const float edX = editionLbl.position * dp;
|
||||
const float linkW = linkBtn.width * dp;
|
||||
const float closeW = closeBtn.width * dp;
|
||||
|
||||
if (!material::BeginOverlayDialog(TR("about_title"), p_open, win.width, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
@@ -39,7 +48,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - editionLbl.position);
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - edX);
|
||||
ImGui::TextDisabled("%s", TR("about_edition"));
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -48,24 +57,24 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
|
||||
// Version info
|
||||
ImGui::Text("%s", TR("about_version"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text("%s", DRAGONX_VERSION);
|
||||
|
||||
ImGui::Text("%s", TR("about_imgui"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text("%s", IMGUI_VERSION);
|
||||
|
||||
ImGui::Text("%s", TR("about_build_date"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text("%s %s", __DATE__, __TIME__);
|
||||
|
||||
#ifdef DRAGONX_DEBUG
|
||||
ImGui::Text("%s", TR("about_build_type"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("about_debug"));
|
||||
#else
|
||||
ImGui::Text("%s", TR("about_build_type"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text("%s", TR("about_release"));
|
||||
#endif
|
||||
|
||||
@@ -76,20 +85,20 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("%s", TR("about_daemon"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("connected"));
|
||||
|
||||
const auto& state = app->getWalletState();
|
||||
ImGui::Text("%s", TR("about_chain"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text("ObsidianDragon");
|
||||
|
||||
ImGui::Text("%s", TR("about_block_height"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text("%d", state.sync.blocks);
|
||||
|
||||
ImGui::Text("%s", TR("about_connections"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::SameLine(valX);
|
||||
ImGui::Text(TR("about_peers_count"), state.peers.size());
|
||||
}
|
||||
|
||||
@@ -121,22 +130,22 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Links
|
||||
if (material::StyledButton(TR("about_website"), ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
if (material::StyledButton(TR("about_website"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
||||
util::Platform::openUrl("https://dragonx.is");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton(TR("about_github"), ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
if (material::StyledButton(TR("about_github"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
||||
util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton(TR("about_block_explorer"), ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
if (material::StyledButton(TR("about_block_explorer"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
||||
util::Platform::openUrl("https://explorer.dragonx.is");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Close button
|
||||
float button_width = closeBtn.width;
|
||||
float button_width = closeW;
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) * 0.5f);
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
*p_open = false;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "../../util/i18n.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../material/type.h"
|
||||
#include "../layout.h" // Layout::dpiScale()
|
||||
#include "imgui.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -134,8 +135,11 @@ void RenderChatTab(App* app)
|
||||
|
||||
const ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
const float listW = std::clamp(avail.x * 0.32f, 220.0f, 360.0f);
|
||||
const float pad = 10.0f;
|
||||
const float rowH = 52.0f;
|
||||
// Row geometry is logical px — scale by dpiScale() so rows/padding grow with the (DPI-scaled)
|
||||
// fonts. Left raw, at higher DPI the row was too short for the enlarged text and the preview's
|
||||
// right margin (rowW - pad) shrank to ~zero, clipping the last glyph mid-word.
|
||||
const float pad = 10.0f * Layout::dpiScale();
|
||||
const float rowH = 52.0f * Layout::dpiScale();
|
||||
|
||||
ImFont* nameFont = material::Type().body2();
|
||||
ImFont* metaFont = material::Type().caption();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "../layout.h" // Layout::dpiScale()
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
@@ -31,14 +32,26 @@ inline std::vector<std::string> SplitSeedWords(const std::string& phrase)
|
||||
}
|
||||
|
||||
// Render the words as a numbered 4-column grid (matches the lite welcome wizard layout).
|
||||
// The column stride is sized to the widest "NN. word" cell and scaled by dpiScale(): a raw px
|
||||
// stride collided with the next column's index once the font grew at higher DPI / font scale
|
||||
// (e.g. at 150% "13. elevator" ran straight into "14."). Sizing to content keeps a gap at any
|
||||
// scale and for any word length.
|
||||
inline void RenderSeedWordGrid(const std::vector<std::string>& words, float colSpacingPx = 130.0f)
|
||||
{
|
||||
const float dp = Layout::dpiScale();
|
||||
float colStride = colSpacingPx * dp;
|
||||
for (size_t i = 0; i < words.size(); ++i) {
|
||||
char cell[96];
|
||||
std::snprintf(cell, sizeof(cell), "%2zu. %s", i + 1, words[i].c_str());
|
||||
const float need = ImGui::CalcTextSize(cell).x + 20.0f * dp; // cell width + inter-column gap
|
||||
if (need > colStride) colStride = need;
|
||||
}
|
||||
for (size_t i = 0; i < words.size(); ++i) {
|
||||
char cell[96];
|
||||
std::snprintf(cell, sizeof(cell), "%2zu. %s", i + 1, words[i].c_str());
|
||||
ImGui::TextUnformatted(cell);
|
||||
if ((i % 4) != 3 && i + 1 < words.size())
|
||||
ImGui::SameLine(((i % 4) + 1) * colSpacingPx);
|
||||
ImGui::SameLine(((i % 4) + 1) * colStride);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -821,7 +821,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
if (s_sending) {
|
||||
Type().text(TypeStyle::Body2, TR("sending"));
|
||||
} else {
|
||||
if (TactileButton(TR("confirm_and_send"), ImVec2(S.button("tabs.send", "confirm-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "confirm-button").font))) {
|
||||
if (TactileButton(TR("confirm_and_send"), ImVec2(S.button("tabs.send", "confirm-button").width * Layout::dpiScale(), std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "confirm-button").font))) {
|
||||
// Re-validate against LIVE state — the confirm dialog persists across frames, so the
|
||||
// balance could have dropped or sync (re)started (or the fee bumped total over available)
|
||||
// since Review. Don't broadcast a now-invalid transaction.
|
||||
@@ -874,7 +874,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("cancel"), ImVec2(S.button("tabs.send", "cancel-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "cancel-button").font))) {
|
||||
if (TactileButton(TR("cancel"), ImVec2(S.button("tabs.send", "cancel-button").width * Layout::dpiScale(), std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "cancel-button").font))) {
|
||||
s_show_confirm = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user