Skip to content

Commit ffda6bf

Browse files
Merge #7210: feat: modernize launch splash
3d45e61 qt: cache translated splash phase labels (UdjinM6) 80fa525 qt: simplify splash fallback progress state (UdjinM6) b0013af qt: show real wallet rescan progress on startup (UdjinM6) 6a056f4 feat(qt): add phase-based progress bar to splash screen (pasta) d8f33d2 feat(qt): modernize splash screen with rounded corners and refined layout (pasta) Pull request description: ## Issue being fixed or feature implemented - Modernize the splash screen visual design and add a progress bar so users have feedback on initialization progress instead of only a static status message. ### Old <img width="426" height="506" alt="Screenshot 2026-03-11 at 23 24 23" src="https://github.com/user-attachments/assets/d028ad86-499c-445f-9a50-031cae2b54cb" /> ### New https://github.com/user-attachments/assets/aebe03dd-5484-445b-b4e4-0ffb7e57c26e ## What was done? **Commit 1: Visual modernization** - Replaced the flat rectangular splash with a modern card-style design featuring rounded corners and a translucent background - Redesigned layout: smaller centered logo, refined typography (28pt title, 12pt version), subtle text opacity - Replaced the rectangular network badge with a pill-shaped rounded badge - Added canvas padding around the card for compositor-based visual separation - Scoped `QPainter` lifetime to release the pixmap before timer/signal setup **Commit 2: Phase-based progress bar** - Added a thin progress bar at the bottom of the splash card that tracks init phases - Built a phase weight table mapping init messages to progress ranges (e.g., "Loading block index…" = 3–75%) - Phases with `ShowProgress` callbacks (block verification, replaying, rescanning) interpolate linearly within their range - Phases without sub-progress use exponential time-based fill (slow curve for long phases like rescan) - "Verifying wallet(s)…" and "Loading wallet…" are batched as a single bar (verify = 0–20%, load = 20–100%) - "Rescanning…" resets the bar and uses a very slow exponential curve (tau=120s) appropriate for operations that can take hours - Snaps to 100% on "Done loading" since the splash is destroyed immediately after - Phase lookup translates keys via `_()` at runtime so matching works in all locales - `messageColor` is `const` and captured by value in signal lambdas for thread-safe cross-thread callbacks - Animation timer deferred until first message arrives to avoid idle repaints ## How Has This Been Tested? - Manual testing on macOS: verified splash screen appearance, progress bar animation through full startup sequence including wallet verification and loading - Verified progress bar behavior with `--rescan` flag (bar resets and uses slow curve) - Confirmed "Done loading" snaps bar to 100% before splash dismissal - Verified no regressions in testnet/devnet badge rendering ## Breaking Changes None. Visual-only changes to the splash screen with no impact on consensus, RPC, or wallet behavior. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ Top commit has no ACKs. Tree-SHA512: d29656d57435f1ba166b0db458097d594a687775b47520abfa0bd221850feed3dc32cfcdb13941038c30e4b4a71cd4d2954fcc8f00814d749b7ffa4a9cd8a6b7
2 parents 22526ae + 3d45e61 commit ffda6bf

7 files changed

Lines changed: 338 additions & 83 deletions

File tree

src/interfaces/wallet.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ class WalletLoader : public ChainClient
407407
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
408408
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;
409409

410+
//! Register handler for wallet-loading messages. This callback is triggered
411+
//! after a wallet object exists and can emit progress, but before startup
412+
//! load work like AttachChain()/rescan necessarily completes.
413+
virtual std::unique_ptr<Handler> handleLoadWalletLoading(LoadWalletFn fn) = 0;
414+
410415
//! Return pointer to internal context, useful for testing.
411416
virtual wallet::WalletContext* context() { return nullptr; }
412417
};

src/qt/splashscreen.cpp

Lines changed: 286 additions & 80 deletions
Large diffs are not rendered by default.

src/qt/splashscreen.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef BITCOIN_QT_SPLASHSCREEN_H
66
#define BITCOIN_QT_SPLASHSCREEN_H
77

8+
#include <QElapsedTimer>
9+
#include <QTimer>
810
#include <QWidget>
911

1012
#include <memory>
@@ -40,8 +42,11 @@ public Q_SLOTS:
4042
/** Show message and progress */
4143
void showMessage(const QString &message, int alignment, const QColor &color);
4244

43-
/** Handle wallet load notifications. */
44-
void handleLoadWallet();
45+
/** Set progress bar value (-1 = no sub-progress, 0-100 = phase sub-progress) */
46+
void setProgress(int value);
47+
48+
/** Handle early wallet-loading notifications so startup progress can be observed. */
49+
void handleLoadingWallet();
4550

4651
protected:
4752
bool eventFilter(QObject * obj, QEvent * ev) override;
@@ -53,18 +58,33 @@ public Q_SLOTS:
5358
void unsubscribeFromCoreSignals();
5459
/** Initiate shutdown */
5560
void shutdown();
61+
/** Calculate overall progress (0.0-1.0) based on current phase and sub-progress */
62+
qreal calcOverallProgress() const;
5663

5764
QPixmap pixmap;
65+
/** Cached on GUI thread at construction for thread-safe use in cross-thread callbacks.
66+
* Const after construction — no synchronization needed. */
67+
const QColor messageColor;
5868
QString curMessage;
5969
QColor curColor;
6070
int curAlignment{0};
71+
int curProgress{-1};
72+
73+
// Phase-based progress tracking
74+
qreal phaseStart{0.0}; // Overall progress at start of current phase
75+
qreal phaseEnd{0.0}; // Overall progress at end of current phase
76+
QElapsedTimer phaseTimer; // Time since current phase started
77+
const struct PhaseInfo* m_current_phase{nullptr}; // Current phase (defined in splashscreen.cpp)
78+
QString m_current_phase_message; // Message that triggered current phase
79+
qreal displayProgress{0.0}; // Smoothly animated display value (0.0-1.0)
80+
QTimer animTimer;
6181

6282
interfaces::Node* m_node = nullptr;
6383
bool m_shutdown = false;
6484
std::unique_ptr<interfaces::Handler> m_handler_init_message;
6585
std::unique_ptr<interfaces::Handler> m_handler_show_progress;
6686
std::unique_ptr<interfaces::Handler> m_handler_init_wallet;
67-
std::unique_ptr<interfaces::Handler> m_handler_load_wallet;
87+
std::unique_ptr<interfaces::Handler> m_handler_loading_wallet;
6888
std::list<std::unique_ptr<interfaces::Wallet>> m_connected_wallets;
6989
std::list<std::unique_ptr<interfaces::Handler>> m_connected_wallet_handlers;
7090
};

src/wallet/context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct WalletContext {
4646
// this could introduce inconsistent lock ordering and cause deadlocks.
4747
Mutex wallets_mutex;
4848
std::vector<std::shared_ptr<CWallet>> wallets GUARDED_BY(wallets_mutex);
49+
std::list<LoadWalletFn> wallet_loading_fns GUARDED_BY(wallets_mutex);
4950
std::list<LoadWalletFn> wallet_load_fns GUARDED_BY(wallets_mutex);
5051
interfaces::CoinJoin::Loader* coinjoin_loader{nullptr};
5152
// Some Dash RPCs rely on WalletContext yet access NodeContext members

src/wallet/interfaces.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,10 @@ class WalletLoaderImpl : public WalletLoader
810810
{
811811
return HandleLoadWallet(m_context, std::move(fn));
812812
}
813+
std::unique_ptr<Handler> handleLoadWalletLoading(LoadWalletFn fn) override
814+
{
815+
return HandleLoadWalletLoading(m_context, std::move(fn));
816+
}
813817
WalletContext* context() override { return &m_context; }
814818

815819
WalletContext m_context;

src/wallet/wallet.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,21 @@ std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, Lo
193193
return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_load_fns.erase(it); });
194194
}
195195

196+
std::unique_ptr<interfaces::Handler> HandleLoadWalletLoading(WalletContext& context, LoadWalletFn load_wallet)
197+
{
198+
LOCK(context.wallets_mutex);
199+
auto it = context.wallet_loading_fns.emplace(context.wallet_loading_fns.end(), std::move(load_wallet));
200+
return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_loading_fns.erase(it); });
201+
}
202+
203+
void NotifyWalletLoading(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
204+
{
205+
LOCK(context.wallets_mutex);
206+
for (auto& load_wallet : context.wallet_loading_fns) {
207+
load_wallet(interfaces::MakeWallet(context, wallet));
208+
}
209+
}
210+
196211
void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
197212
{
198213
LOCK(context.wallets_mutex);
@@ -3282,6 +3297,8 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
32823297
// Try to top up keypool. No-op if the wallet is locked.
32833298
walletInstance->TopUpKeyPool();
32843299

3300+
NotifyWalletLoading(context, walletInstance);
3301+
32853302
if (chain && !AttachChain(walletInstance, *chain, error, warnings)) {
32863303
walletInstance->m_chain_notifications_handler.reset(); // Reset this pointer so that the wallet will actually be unloaded
32873304
return nullptr;

src/wallet/wallet.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& na
7676
std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
7777
std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
7878
std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
79+
std::unique_ptr<interfaces::Handler> HandleLoadWalletLoading(WalletContext& context, LoadWalletFn load_wallet);
7980
std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, LoadWalletFn load_wallet);
81+
void NotifyWalletLoading(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
8082
void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
8183
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
8284

0 commit comments

Comments
 (0)