diff --git a/src/app_sweep.cpp b/src/app_sweep.cpp index 8c81ca3..0182343 100644 --- a/src/app_sweep.cpp +++ b/src/app_sweep.cpp @@ -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). diff --git a/src/ui/windows/wallets_dialog.h b/src/ui/windows/wallets_dialog.h index d6b79ec..118c62e 100644 --- a/src/ui/windows/wallets_dialog.h +++ b/src/ui/windows/wallets_dialog.h @@ -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();