feat(debug): full UI screenshot sweep — captures modals/dialogs/flows/states
The existing sweep only captures tabs. This adds a "Full UI sweep" (DEBUG OPTIONS) that also drives every normally-hidden surface into view and captures it under every skin, so all the UI a normal sweep misses can be reviewed at once. - The capture unit becomes a "surface" (SweepTarget: a base tab, optionally with a setup()/teardown() that forces a modal / multi-step flow / state overlay on top). Tabs are surfaces with a null setup. The existing tab sweep is folded into the same state machine (one code path). All sweep logic + the catalog + demo data move into a new src/app_sweep.cpp. - Runs OFFLINE against injected demo data (installDemoWalletData snapshots then restores the real state_ — WalletState isn't copy-assignable, so a targeted field snapshot is used) and fires NO live op: capture_mode_ guards the seed-backup RPC, auto-lock, and the migration pumps; the async-firing dialogs are neutralized by pre-setting their state (fetch_started_ + demo phrase; seed_migration_step_ set directly). Verified: 0 RPC/secret/send calls. - Catalog (v1): tabs + the bool-flag modals (import/export key, backup, seed-backup, about, settings) + the 6 migrate-to-seed steps + 4 wizard steps + lock/warmup/not-ready overlays + the send-confirm popup (via a new ui::SweepShowSendConfirm debug hook, since its state is send_tab-static). - Overlay/blur surfaces settle 8 frames (blur backdrop freeze); setup re-runs each frame (OpenPopup popups must re-fire). updateScreenshotSweep now runs before the first-run-wizard early-return in render() so the sweep advances while a wizard step is shown. main.cpp drops vsync during a sweep so it isn't throttled to the compositor's unfocused rate. Output: <config>/screenshots-full/<surface>/<skin>.png + index.md. - DRAGONX_FULL_SWEEP=1 runs the sweep headlessly then quits (CI / verification). Verified end-to-end under WSLg: 288 PNGs (9 skins x 32 surfaces), all 1200x720, [Sweep] done, clean quit, no daemon. Byte-identity across runs holds only for static skins on data-free surfaces — the rest vary by animated theme effects / live price / relative timestamps (expected for a review tool, not a bug). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -759,109 +759,7 @@ void App::setCurrentPage(ui::NavPage page)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Debug screenshot sweep ──────────────────────────────────────────────────
|
||||
// Cycle every skin x every build-enabled tab, saving one PNG per (skin,tab). Driven from
|
||||
// App::render() (updateScreenshotSweep pins the page + settles); main.cpp reads the framebuffer
|
||||
// on the settled frame and calls onScreenshotCaptured() to advance.
|
||||
|
||||
// Filesystem-safe one-word page name (tracePageName returns "Overview tab" etc.).
|
||||
static const char* sweepPageName(ui::NavPage page)
|
||||
{
|
||||
switch (page) {
|
||||
case ui::NavPage::Overview: return "overview";
|
||||
case ui::NavPage::Send: return "send";
|
||||
case ui::NavPage::Receive: return "receive";
|
||||
case ui::NavPage::History: return "history";
|
||||
case ui::NavPage::Contacts: return "contacts";
|
||||
case ui::NavPage::Chat: return "chat";
|
||||
case ui::NavPage::Mining: return "mining";
|
||||
case ui::NavPage::Market: return "market";
|
||||
case ui::NavPage::Console: return "console";
|
||||
case ui::NavPage::Peers: return "network";
|
||||
case ui::NavPage::Explorer: return "explorer";
|
||||
case ui::NavPage::Settings: return "settings";
|
||||
default: return "page";
|
||||
}
|
||||
}
|
||||
|
||||
std::string App::screenshotDir() const
|
||||
{
|
||||
return (std::filesystem::path(util::Platform::getObsidianDragonDir()) / "screenshots").string();
|
||||
}
|
||||
|
||||
void App::startScreenshotSweep()
|
||||
{
|
||||
if (screenshot_sweep_active_) return;
|
||||
|
||||
sweep_skins_.clear();
|
||||
for (const auto& s : ui::schema::SkinManager::instance().available())
|
||||
if (s.valid) sweep_skins_.push_back(s.id);
|
||||
sweep_pages_.clear();
|
||||
for (int p = 0; p < static_cast<int>(ui::NavPage::Count_); ++p) {
|
||||
ui::NavPage pg = static_cast<ui::NavPage>(p);
|
||||
if (isPageEnabledForBuild(pg)) sweep_pages_.push_back(pg);
|
||||
}
|
||||
if (sweep_skins_.empty() || sweep_pages_.empty()) return;
|
||||
|
||||
// Fixed output folder (organized into per-tab subfolders below); subsequent sweeps overwrite the
|
||||
// existing PNGs in place rather than creating a new folder each time.
|
||||
sweep_dir_ = screenshotDir();
|
||||
std::error_code ec; std::filesystem::create_directories(sweep_dir_, ec);
|
||||
|
||||
sweep_saved_skin_ = ui::schema::SkinManager::instance().activeSkinId();
|
||||
sweep_saved_page_ = current_page_;
|
||||
sweep_skin_idx_ = 0; sweep_page_idx_ = 0;
|
||||
screenshot_sweep_active_ = true;
|
||||
sweep_capture_this_frame_ = false;
|
||||
|
||||
ui::schema::SkinManager::instance().setActiveSkin(sweep_skins_[0]);
|
||||
applySweepTarget();
|
||||
ui::Notifications::instance().info("Screenshot sweep running…");
|
||||
DEBUG_LOGF("[Sweep] -> %s (%d skins x %d pages)\n",
|
||||
sweep_dir_.c_str(), (int)sweep_skins_.size(), (int)sweep_pages_.size());
|
||||
}
|
||||
|
||||
void App::applySweepTarget()
|
||||
{
|
||||
current_page_ = sweep_pages_[sweep_page_idx_];
|
||||
page_alpha_ = 1.0f; // skip the page-switch fade so the shot isn't captured mid-animation
|
||||
// Organized as <screenshots>/<tab>/<skin>.png — one subfolder per tab, one PNG per theme,
|
||||
// overwritten in place on the next sweep. (writePng in main.cpp creates the parent subfolder.)
|
||||
sweep_current_path_ = (std::filesystem::path(sweep_dir_) / sweepPageName(current_page_)
|
||||
/ (sweep_skins_[sweep_skin_idx_] + ".png")).string();
|
||||
sweep_settle_frames_ = 4; // let the new skin (TOML reload / acrylic recapture) + page settle
|
||||
sweep_capture_this_frame_ = false;
|
||||
}
|
||||
|
||||
void App::updateScreenshotSweep()
|
||||
{
|
||||
if (!screenshot_sweep_active_) return;
|
||||
current_page_ = sweep_pages_[sweep_page_idx_]; // keep the page pinned each frame
|
||||
page_alpha_ = 1.0f;
|
||||
if (sweep_settle_frames_ > 0) { sweep_settle_frames_--; sweep_capture_this_frame_ = false; }
|
||||
else sweep_capture_this_frame_ = true; // settled — main.cpp captures this frame
|
||||
}
|
||||
|
||||
void App::onScreenshotCaptured()
|
||||
{
|
||||
sweep_capture_this_frame_ = false;
|
||||
if (!screenshot_sweep_active_) return;
|
||||
if (++sweep_page_idx_ >= static_cast<int>(sweep_pages_.size())) {
|
||||
sweep_page_idx_ = 0;
|
||||
if (++sweep_skin_idx_ >= static_cast<int>(sweep_skins_.size())) {
|
||||
// Done — restore the original skin + page.
|
||||
ui::schema::SkinManager::instance().setActiveSkin(sweep_saved_skin_);
|
||||
current_page_ = sweep_saved_page_;
|
||||
page_alpha_ = 1.0f;
|
||||
screenshot_sweep_active_ = false;
|
||||
ui::Notifications::instance().success("Screenshots saved to " + sweep_dir_);
|
||||
DEBUG_LOGF("[Sweep] done -> %s\n", sweep_dir_.c_str());
|
||||
return;
|
||||
}
|
||||
ui::schema::SkinManager::instance().setActiveSkin(sweep_skins_[sweep_skin_idx_]);
|
||||
}
|
||||
applySweepTarget();
|
||||
}
|
||||
// Screenshot sweep (tab-only + full-UI) + its demo data live in src/app_sweep.cpp.
|
||||
|
||||
bool App::shouldRefreshTransactions() const
|
||||
{
|
||||
@@ -2914,6 +2812,7 @@ void App::exportSeedPhrase(std::function<void(bool, bool, const std::string&, co
|
||||
// learns whether a mnemonic exists). Legacy (non-mnemonic) wallets are not nagged.
|
||||
void App::maybeRemindSeedBackup()
|
||||
{
|
||||
if (capture_mode_) return; // no live ops during a UI sweep
|
||||
if (lite_wallet_) return; // lite has its own seed UX
|
||||
if (!settings_ || settings_->getSeedBackupReminded()) return;
|
||||
if (!state_.connected || !state_.encryption_state_known) return;
|
||||
@@ -3063,6 +2962,7 @@ void App::beginSweepToSeedWallet()
|
||||
// remainder is left behind (a sweep too big for one transaction).
|
||||
void App::pollSweepStatus()
|
||||
{
|
||||
if (capture_mode_) return; // no live ops during a UI sweep
|
||||
if (!rpc_ || !worker_ || !state_.connected) return;
|
||||
if (seed_migration_confirm_in_flight_ || seed_migration_sweep_txid_.empty()) return;
|
||||
seed_migration_confirm_in_flight_ = true;
|
||||
@@ -3172,6 +3072,7 @@ void App::beginAdoptSeedWallet()
|
||||
|
||||
void App::pumpSeedMigration()
|
||||
{
|
||||
if (capture_mode_) return; // no live ops during a UI sweep (steps are set directly)
|
||||
// --- Adopt handoff (Phase 2, background daemon swap) ---
|
||||
{
|
||||
bool done = false, ok = false; std::string err;
|
||||
|
||||
Reference in New Issue
Block a user