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