Merge scene work for 5.1.2

The ambient scene grows up: the hidden-tab bug that rained +1s out of an
empty sky is fixed at both ends, the wipeout is a skid rather than a
stunt, clearing a field says WIN, and each tab gets its own amount of
scene — History runs, Receive sits, Send is money going past.

Claude-Session: https://claude.ai/code/session_01VLQ5DsgKv23osGANoFT3zF
This commit is contained in:
jwinterm
2026-09-03 15:12:18 -04:00
5 changed files with 865 additions and 90 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent)
// wowlet: ambient scene as a short banner under the address table. A layout
// item, never an overlay over the table.
auto *scene = new SceneWidget(this, SceneWidget::Dawn);
auto *scene = new SceneWidget(this, SceneWidget::Dawn, SceneWidget::Sit);
scene->setMinimumHeight(110);
scene->setMaximumHeight(130);
ui->verticalLayout->addWidget(scene);
+10 -3
View File
@@ -29,10 +29,17 @@ SendWidget::SendWidget(Wallet *wallet, QWidget *parent)
// wowlet: the form only needs the top of the tab — give the rest of it to
// the ambient scene. A layout item under the form, never an overlay: the
// send form itself stays untouched.
auto *scene = new SceneWidget(this, SceneWidget::Night);
//
// Centred in the space below the form rather than stacked straight under
// it: butted up against the Clear/Send row the scene reads as part of the
// form, and a band of night sky touching a button looks like a mistake.
auto *scene = new SceneWidget(this, SceneWidget::Night, SceneWidget::Money);
scene->setMinimumHeight(160);
scene->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->verticalLayoutOuter->addWidget(scene, 1);
scene->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
ui->verticalLayoutOuter->addSpacing(14); // never touches the Send row
ui->verticalLayoutOuter->addStretch(1);
ui->verticalLayoutOuter->addWidget(scene);
ui->verticalLayoutOuter->addStretch(1);
QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)";
QRegularExpression rx;
+797 -84
View File
File diff suppressed because it is too large Load Diff
+27 -1
View File
@@ -4,18 +4,44 @@
#include "SceneWidget.h"
#include <QQmlContext>
#include <QQuickItem>
#include "utils/config.h"
SceneWidget::SceneWidget(QWidget *parent, Variant variant)
SceneWidget::SceneWidget(QWidget *parent, Variant variant, Mode mode)
: QQuickWidget(parent)
, m_backend(new SceneBackend(this))
{
m_backend->setReducedMotion(conf()->get(Config::reducedMotion).toBool());
m_backend->setVariant(static_cast<int>(variant));
m_backend->setMode(mode == Sit ? QStringLiteral("sit")
: mode == Money ? QStringLiteral("money")
: QStringLiteral("run"));
this->rootContext()->setContextProperty("sceneCtx", m_backend);
this->setResizeMode(QQuickWidget::SizeRootObjectToView);
this->setSource(QUrl("qrc:/qml/scene.qml"));
this->callScene("suspend");
}
void SceneWidget::showEvent(QShowEvent *event) {
QQuickWidget::showEvent(event);
this->callScene("restart");
}
void SceneWidget::hideEvent(QHideEvent *event) {
this->callScene("suspend");
QQuickWidget::hideEvent(event);
}
void SceneWidget::resizeEvent(QResizeEvent *event) {
QQuickWidget::resizeEvent(event);
if (this->isVisible())
this->callScene("restart");
}
void SceneWidget::callScene(const char *method) {
if (auto *root = this->rootObject())
QMetaObject::invokeMethod(root, method);
}
void SceneWidget::refreshReducedMotion() {
+30 -1
View File
@@ -5,6 +5,7 @@
#define WOWLET_SCENEWIDGET_H
#include <QObject>
#include <QString>
#include <QQuickWidget>
// Context object exposed to scene.qml as `sceneCtx`. Carries the reduced-motion
@@ -14,18 +15,23 @@ class SceneBackend : public QObject {
Q_OBJECT
Q_PROPERTY(bool reducedMotion READ reducedMotion NOTIFY reducedMotionChanged)
Q_PROPERTY(int variant READ variant NOTIFY variantChanged)
Q_PROPERTY(QString mode READ mode NOTIFY modeChanged)
public:
explicit SceneBackend(QObject *parent = nullptr) : QObject(parent) {}
bool reducedMotion() const { return m_reducedMotion; }
void setReducedMotion(bool r) { if (m_reducedMotion != r) { m_reducedMotion = r; emit reducedMotionChanged(); } }
int variant() const { return m_variant; }
void setVariant(int v) { if (m_variant != v) { m_variant = v; emit variantChanged(); } }
QString mode() const { return m_mode; }
void setMode(const QString &m) { if (m_mode != m) { m_mode = m; emit modeChanged(); } }
signals:
void reducedMotionChanged();
void variantChanged();
void modeChanged();
private:
bool m_reducedMotion = false;
int m_variant = 0;
QString m_mode = QStringLiteral("run");
};
// wowlet: reusable ambient-scene surface. Hosts scene.qml and reads the
@@ -38,9 +44,32 @@ public:
// same field. Must match the `skies` table in scene.qml.
enum Variant { Dusk = 0, Dawn = 1, Night = 2 };
explicit SceneWidget(QWidget *parent = nullptr, Variant variant = Dusk);
// How much scene a surface wants. History has room for the whole run;
// Receive gets a doge sitting in the field; Send gets money blowing past and
// nothing else, so the form it sits under is not competing with a dog.
enum Mode { Run, Sit, Money };
explicit SceneWidget(QWidget *parent = nullptr, Variant variant = Dusk,
Mode mode = Run);
void refreshReducedMotion();
protected:
// A scene on a hidden tab is parked rather than left running: an unshown
// QQuickWidget is 0px wide, and a lap run against a 0px field collects every
// coin at once and leaves its litter (+1s, barks) to surface on the real lap
// when the tab is finally shown. Parking also keeps a background tab from
// burning CPU on animation nobody is looking at.
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
// Belt and braces: a scene that is on screen and has just been given a real
// size is running, whether or not a show event was the thing that got it
// there. `restart` only sets a flag, so re-arming an already-running scene
// costs nothing and does not interrupt its lap.
void resizeEvent(QResizeEvent *event) override;
private:
void callScene(const char *method);
SceneBackend *m_backend;
};