fix(wallets): cap card at viewport + flex list so many wallets never clip
The wallets dialog hand-computed a content-sized card height. With many wallets on a small / HiDPI (150%) window the computed height could exceed the viewport, and the framework clips a too-tall card — footer and all. Cap the card at 0.86 of the viewport height. When capped, the wallet list flexes to the space left above the create/scan/footer controls and scrolls internally, so those controls stay pinned and visible; uncapped (the common case, few wallets) nothing changes and there's no spurious scrollbar. Add a modal-wallets-many sweep surface (8 wallets, self-cleaning teardown) to exercise the capped-scroll path. Verified at 100% + 150%. This is the one real fixed-height clip risk from the modal audit; the other 44 modals use auto-height (grow-to-fit, capped at a viewport ratio) and were confirmed non-clipping — e.g. the 24-word seed-backup grid renders fully at 150% in a 720px window. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -354,6 +354,38 @@ void App::buildSweepCatalog()
|
||||
ui::WalletsDialog::show(&a);
|
||||
},
|
||||
[](App&) { ui::WalletsDialog::hide(); });
|
||||
|
||||
// Many wallets — exercises the viewport-cap path: the card can't fit all rows, so the list
|
||||
// must scroll internally while the create/scan/footer controls stay pinned (esp. at 150%).
|
||||
// Teardown removes the extra files it dropped so the plain modal-wallets surface stays a
|
||||
// clean 3-wallet reference (both surfaces share the one throwaway datadir).
|
||||
static const char* kManyExtra[] = {"wallet-cold.dat", "wallet-trading.dat", "wallet-mining.dat",
|
||||
"wallet-donations.dat", "wallet-2023.dat", "wallet-payroll.dat"};
|
||||
add("modal-wallets-many", ui::NavPage::Settings,
|
||||
[](App& a) {
|
||||
std::error_code ec;
|
||||
const std::string dd = util::Platform::getDragonXDataDir();
|
||||
fs::create_directories(dd, ec);
|
||||
const char* names[] = {"wallet.dat", "wallet-savings.dat", "wallet-cold.dat",
|
||||
"wallet-trading.dat", "wallet-mining.dat", "wallet-donations.dat",
|
||||
"wallet-2023.dat", "wallet-payroll.dat"};
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (!fs::exists(dd + "/" + names[i], ec))
|
||||
std::ofstream(dd + "/" + names[i], std::ios::binary) << std::string(64000 + i * 4096, '\0');
|
||||
data::WalletIndexEntry e; e.fileName = names[i]; e.displayName = names[i];
|
||||
e.cachedBalance = 5.0 * (i + 1); e.cachedAddressCount = 2 + i;
|
||||
e.lastOpenedEpoch = 1720000000 - i * 86400; e.syncedHere = true;
|
||||
a.walletIndex().upsert(e);
|
||||
}
|
||||
if (a.settings()) a.settings()->setActiveWalletFile("wallet.dat");
|
||||
ui::WalletsDialog::show(&a);
|
||||
},
|
||||
[](App& a) {
|
||||
ui::WalletsDialog::hide();
|
||||
std::error_code ec;
|
||||
const std::string dd = util::Platform::getDragonXDataDir();
|
||||
for (const char* n : kManyExtra) fs::remove(dd + "/" + n, ec);
|
||||
});
|
||||
}
|
||||
|
||||
// First-run wizard — one per meaningful phase (full-node only; blocks all other UI while shown).
|
||||
|
||||
@@ -81,7 +81,14 @@ public:
|
||||
const float headH = Type().h6()->LegacySize + Layout::spacingXs() // framework h6 title
|
||||
+ Type().caption()->LegacySize + Layout::spacingSm(); // intro + gap
|
||||
const float padV = 48.0f; // content-child padding (top+bottom) + margin
|
||||
const float cardH = (headH + listH + belowH + padV) / dp;
|
||||
// Size to content, but cap at a viewport fraction: with many wallets on a small / HiDPI
|
||||
// screen the content-sized card could exceed the window (the framework would clip a too-tall
|
||||
// card, footer and all). When capped, the wallet list flexes + scrolls and the controls
|
||||
// below it stay pinned. In the common case (few wallets) nothing changes.
|
||||
const float desiredH = headH + listH + belowH + padV; // physical
|
||||
const float vpH = ImGui::GetMainViewport()->Size.y;
|
||||
const bool capped = desiredH > vpH * 0.86f;
|
||||
const float cardH = std::min(desiredH, vpH * 0.86f) / dp;
|
||||
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("wallets_title");
|
||||
@@ -98,8 +105,13 @@ public:
|
||||
|
||||
// ---- Wallet cards (Material-style rows; no table) -------------------------------------
|
||||
const float listW = ImGui::GetContentRegionAvail().x;
|
||||
// Flex only when the card was viewport-capped: give the list the space left above the
|
||||
// create/scan/footer controls so it scrolls internally and those stay visible. Uncapped,
|
||||
// it equals the content height exactly (no spurious scrollbar — the common case).
|
||||
float listHFit = listH;
|
||||
if (capped) listHFit = std::max(walRowH, ImGui::GetContentRegionAvail().y - belowH);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
ImGui::BeginChild("##walletList", ImVec2(listW, listH), false);
|
||||
ImGui::BeginChild("##walletList", ImVec2(listW, listHFit), false);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // inter-card gaps are explicit
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
|
||||
Reference in New Issue
Block a user