feat: ambient silliness across the app (reduced-motion aware)

- Lock screen: full ambient scene above the unlock form (the idle 'screensaver'
  — wallet auto-locks here) instead of a thin banner.
- History ('home'): full scene as a top banner.
- Send/Receive: IdleDoge — a small click-through sitting doge in the corner,
  the 'most quiet' idle for task screens.
- Mining keeps its own scene.
All gated by Config::reducedMotion (scenes freeze, doge shows a still frame).
This commit is contained in:
jwinterm
2026-08-31 11:10:25 -04:00
parent df4711d1c1
commit 6942cbb459
6 changed files with 91 additions and 5 deletions
+8
View File
@@ -14,6 +14,7 @@
#include "utils/config.h" #include "utils/config.h"
#include "utils/Icons.h" #include "utils/Icons.h"
#include "WebsocketNotifier.h" #include "WebsocketNotifier.h"
#include "widgets/SceneWidget.h"
HistoryWidget::HistoryWidget(Wallet *wallet, QWidget *parent) HistoryWidget::HistoryWidget(Wallet *wallet, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@@ -24,6 +25,13 @@ HistoryWidget::HistoryWidget(Wallet *wallet, QWidget *parent)
, m_model(wallet->historyModel()) , m_model(wallet->historyModel())
{ {
ui->setupUi(this); ui->setupUi(this);
// wowlet: History is "home" — the full ambient scene as a top banner.
auto *scene = new SceneWidget(this);
scene->setMinimumHeight(120);
scene->setMaximumHeight(150);
ui->verticalLayout->insertWidget(0, scene);
m_contextMenu->addMenu(m_copyMenu); m_contextMenu->addMenu(m_copyMenu);
m_contextMenu->addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails); m_contextMenu->addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails);
m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
+3
View File
@@ -12,6 +12,7 @@
#include "utils/config.h" #include "utils/config.h"
#include "utils/Icons.h" #include "utils/Icons.h"
#include "utils/Utils.h" #include "utils/Utils.h"
#include "widgets/IdleDoge.h"
ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent) ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@@ -20,6 +21,8 @@ ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
new IdleDoge(this); // wowlet: whisper-quiet corner critter (freezes when reduced-motion)
m_model = m_wallet->subaddressModel(); m_model = m_wallet->subaddressModel();
m_proxyModel = new SubaddressProxyModel(this, m_wallet->subaddress()); m_proxyModel = new SubaddressProxyModel(this, m_wallet->subaddress());
m_proxyModel->setSourceModel(m_model); m_proxyModel->setSourceModel(m_model);
+3
View File
@@ -11,6 +11,7 @@
#include "Icons.h" #include "Icons.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
#include "widgets/IdleDoge.h"
#if defined(WITH_SCANNER) #if defined(WITH_SCANNER)
#include "wizard/offline_tx_signing/OfflineTxSigningWizard.h" #include "wizard/offline_tx_signing/OfflineTxSigningWizard.h"
@@ -25,6 +26,8 @@ SendWidget::SendWidget(Wallet *wallet, QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
new IdleDoge(this); // wowlet: whisper-quiet corner critter (freezes when reduced-motion)
QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)"; QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)";
QRegularExpression rx; QRegularExpression rx;
rx.setPattern(amount_rx); rx.setPattern(amount_rx);
+46
View File
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "IdleDoge.h"
#include <QEvent>
#include <QMovie>
#include "utils/config.h"
IdleDoge::IdleDoge(QWidget *parent, int height)
: QLabel(parent)
, m_movie(new QMovie(":/assets/images/dog_sitting.gif", QByteArray(), this))
{
m_movie->jumpToFrame(0);
const QSize frame = m_movie->currentImage().size();
if (frame.height() > 0)
m_movie->setScaledSize(QSize(frame.width() * height / frame.height(), height));
this->setMovie(m_movie);
this->setAttribute(Qt::WA_TransparentForMouseEvents); // never block the form
this->adjustSize();
if (conf()->get(Config::reducedMotion).toBool())
m_movie->jumpToFrame(0); // still frame
else
m_movie->start();
if (parent) {
parent->installEventFilter(this);
this->reposition();
this->raise();
}
}
void IdleDoge::reposition() {
if (auto *p = parentWidget())
this->move(p->width() - this->width() - 14, p->height() - this->height() - 14);
}
bool IdleDoge::eventFilter(QObject *o, QEvent *e) {
if (o == parentWidget() && (e->type() == QEvent::Resize || e->type() == QEvent::Show)) {
this->reposition();
this->raise();
}
return QLabel::eventFilter(o, e);
}
+25
View File
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef WOWLET_IDLEDOGE_H
#define WOWLET_IDLEDOGE_H
#include <QLabel>
class QMovie;
// wowlet: a small sitting doge that floats, click-through, in the bottom-right
// corner of its parent — the "most quiet" ambient idle for task screens (Send,
// Receive). Freezes to a still frame when "Prefer reduced motion" is set.
class IdleDoge : public QLabel {
Q_OBJECT
public:
explicit IdleDoge(QWidget *parent, int height = 44);
protected:
bool eventFilter(QObject *o, QEvent *e) override;
private:
void reposition();
QMovie *m_movie;
};
#endif //WOWLET_IDLEDOGE_H
+6 -5
View File
@@ -19,12 +19,13 @@ WalletUnlockWidget::WalletUnlockWidget(QWidget *parent, Wallet *wallet)
ui->setupUi(this); ui->setupUi(this);
this->reset(); this->reset();
// wowlet: ambient scene banner above the unlock form (freezes itself into a // wowlet: the lock screen shows the full ambient scene above the unlock form
// still when "Prefer reduced motion" is set). // (this is the idle "screensaver" — the wallet auto-locks here on inactivity).
// It freezes into a still when "Prefer reduced motion" is set.
auto *scene = new SceneWidget(this); auto *scene = new SceneWidget(this);
scene->setMinimumHeight(120); scene->setMinimumHeight(200);
scene->setMaximumHeight(140); scene->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->verticalLayout->insertWidget(0, scene); ui->verticalLayout->insertWidget(0, scene, 1);
ui->buttonBox->button(QDialogButtonBox::Ok)->setAutoDefault(true); ui->buttonBox->button(QDialogButtonBox::Ok)->setAutoDefault(true);