diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index dcc7e2effc..113fcf50f0 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -165,6 +165,27 @@ QQmlProperty QQuickAbstractAnimationPrivate::createProperty(QObject *obj, const return prop; } +void QQuickAbstractAnimationPrivate::animationGroupDirty() +{ + Q_ASSERT(group != nullptr); + if (!componentComplete) + return; + + auto *animGroupPriv = static_cast(QQuickAnimationGroupPrivate::get(group)); + if (animGroupPriv->running && !animGroupPriv->animationDirty) { + animGroupPriv->animationDirty = true; + + if (group->currentTime() == 0) { + // restart if the animation didn't proceed yet. + animGroupPriv->restartFromCurrentLoop(); + } + } + + // check the animationGroup is one of another animationGroup members + if (animGroupPriv->group) + animGroupPriv->animationGroupDirty(); +} + /*! \qmlsignal QtQuick::Animation::started() @@ -696,6 +717,8 @@ void QQuickPauseAnimation::setDuration(int duration) return; d->duration = duration; emit durationChanged(duration); + if (d->group) + d->animationGroupDirty(); } QAbstractAnimationJob* QQuickPauseAnimation::transition(QQuickStateActions &actions, @@ -1076,6 +1099,8 @@ void QQuickPropertyAction::setTargetObject(QObject *o) return; d->target = o; emit targetChanged(); + if (d->group) + d->animationGroupDirty(); } QString QQuickPropertyAction::property() const @@ -1091,6 +1116,8 @@ void QQuickPropertyAction::setProperty(const QString &n) return; d->propertyName = n; emit propertyChanged(); + if (d->group) + d->animationGroupDirty(); } /*! @@ -1121,6 +1148,8 @@ void QQuickPropertyAction::setProperties(const QString &p) return; d->properties = p; emit propertiesChanged(p); + if (d->group) + d->animationGroupDirty(); } QQmlListProperty QQuickPropertyAction::targets() @@ -1722,6 +1751,36 @@ void QQuickAnimationGroupPrivate::removeLast_animation(QQmlListPropertyd_func()->animations.last()->setGroup(nullptr); } +void QQuickAnimationGroupPrivate::restartFromCurrentLoop() +{ + Q_Q(QQuickAnimationGroup); + if (!animationDirty) + return; + + animationDirty = false; + + Q_ASSERT(animationInstance); + const int currentLoop = animationInstance->currentLoop(); + + QSignalBlocker signalBlocker(q); + q->stop(); + q->start(); + + Q_ASSERT(animationInstance); + // Restarting adjusts animationInstance's loopCount + // Since we just want to start it from this loop, + // it will be restored again. + if (loopCount != -1) + animationInstance->setLoopCount(loopCount - currentLoop); +} + +void QQuickAnimationGroupPrivate::animationCurrentLoopChanged(QAbstractAnimationJob *) +{ + if (!animationDirty) + return; + restartFromCurrentLoop(); +} + QQuickAnimationGroup::~QQuickAnimationGroup() { Q_D(QQuickAnimationGroup); @@ -2135,6 +2194,8 @@ void QQuickPropertyAnimation::setDuration(int duration) if (d->componentComplete && d->running) d->ourPropertiesDirty = true; emit durationChanged(duration); + if (d->group) + d->animationGroupDirty(); } /*! @@ -2164,6 +2225,8 @@ void QQuickPropertyAnimation::setFrom(const QVariant &f) if (d->componentComplete && d->running) d->ourPropertiesDirty = true; emit fromChanged(); + if (d->group) + d->animationGroupDirty(); } /*! @@ -2193,6 +2256,8 @@ void QQuickPropertyAnimation::setTo(const QVariant &t) if (d->componentComplete && d->running) d->ourPropertiesDirty = true; emit toChanged(); + if (d->group) + d->animationGroupDirty(); } /*! @@ -2425,6 +2490,8 @@ void QQuickPropertyAnimation::setEasing(const QEasingCurve &e) if (d->componentComplete && d->running) d->ourPropertiesDirty = true; emit easingChanged(e); + if (d->group) + d->animationGroupDirty(); } QObject *QQuickPropertyAnimation::target() const @@ -2440,6 +2507,8 @@ void QQuickPropertyAnimation::setTargetObject(QObject *o) return; d->target = o; emit targetChanged(); + if (d->group) + d->animationGroupDirty(); } QString QQuickPropertyAnimation::property() const @@ -2455,6 +2524,8 @@ void QQuickPropertyAnimation::setProperty(const QString &n) return; d->propertyName = n; emit propertyChanged(); + if (d->group) + d->animationGroupDirty(); } QString QQuickPropertyAnimation::properties() const @@ -2471,6 +2542,8 @@ void QQuickPropertyAnimation::setProperties(const QString &prop) d->properties = prop; emit propertiesChanged(prop); + if (d->group) + d->animationGroupDirty(); } /*! diff --git a/src/quick/util/qquickanimation_p_p.h b/src/quick/util/qquickanimation_p_p.h index a12a35f070..2321b8fceb 100644 --- a/src/quick/util/qquickanimation_p_p.h +++ b/src/quick/util/qquickanimation_p_p.h @@ -164,6 +164,7 @@ public: QAbstractAnimationJob* animationInstance; static QQmlProperty createProperty(QObject *obj, const QString &str, QObject *infoObj, QString *errorMessage = nullptr); + void animationGroupDirty(); }; class QQuickPauseAnimationPrivate : public QQuickAbstractAnimationPrivate @@ -217,7 +218,7 @@ class QQuickAnimationGroupPrivate : public QQuickAbstractAnimationPrivate Q_DECLARE_PUBLIC(QQuickAnimationGroup) public: QQuickAnimationGroupPrivate() - : QQuickAbstractAnimationPrivate() {} + : QQuickAbstractAnimationPrivate(), animationDirty(false) {} static void append_animation(QQmlListProperty *list, QQuickAbstractAnimation *role); static QQuickAbstractAnimation *at_animation(QQmlListProperty *list, qsizetype index); @@ -227,6 +228,10 @@ public: QQuickAbstractAnimation *role); static void removeLast_animation(QQmlListProperty *list); QList animations; + + void restartFromCurrentLoop(); + void animationCurrentLoopChanged(QAbstractAnimationJob *job) override; + bool animationDirty: 1; }; class Q_QUICK_PRIVATE_EXPORT QQuickPropertyAnimationPrivate : public QQuickAbstractAnimationPrivate diff --git a/tests/auto/quick/qquickanimations/data/restartAnimationGroupWhenDirty.qml b/tests/auto/quick/qquickanimations/data/restartAnimationGroupWhenDirty.qml new file mode 100644 index 0000000000..aec1cec432 --- /dev/null +++ b/tests/auto/quick/qquickanimations/data/restartAnimationGroupWhenDirty.qml @@ -0,0 +1,92 @@ +import QtQuick + +Rectangle { + width: 300 + height: 300 + + // test SequentialAnimation + Rectangle { + id: line0 + y: 100 + width: parent.width + height: 2 + color: "blue" + } + Rectangle { + id: target0 + objectName: "target0" + y: 100 + anchors.verticalCenter: line0.verticalCenter + height: line0.height * 5 + width: height + color: "red" + radius: height/2 + + property bool onFinishedCalled : false; + + SequentialAnimation { + id: seqAnim0 + objectName: "seqAnim0" + loops: 2 + running: true + NumberAnimation { + id: anim0 + target: target0 + property: "x" + from: 0 + to: 50 + duration: 500 + } + Component.onCompleted: anim0.to = 290 + onFinished: target0.onFinishedCalled = true + } + } + + // test ParallelAnimation + Rectangle { + id: line1 + y: 200 + width: parent.width + height: 2 + color: "blue" + } + Rectangle { + id: target1 + objectName: "target1" + anchors.verticalCenter: line1.verticalCenter + height: line1.height * 5 + width: height + color: "yellow" + radius: height/2 + + property bool onFinishedCalled : false; + + ParallelAnimation { + id: parAnim0 + objectName: "parAnim0" + loops: 2 + running: true + NumberAnimation { + id: anim1 + target: target1 + property: "x" + from: 0 + to: 50 + duration: 500 + } + Component.onCompleted: anim1.to = 290 + onFinished: target1.onFinishedCalled = true + } + } + + Timer { + interval: 400 + running: true + onTriggered: { + seqAnim0.pause() + parAnim0.pause() + anim0.to = 140 + anim1.to = 140 + } + } +} diff --git a/tests/auto/quick/qquickanimations/data/restartNestedAnimationGroupWhenDirty.qml b/tests/auto/quick/qquickanimations/data/restartNestedAnimationGroupWhenDirty.qml new file mode 100644 index 0000000000..da0de96448 --- /dev/null +++ b/tests/auto/quick/qquickanimations/data/restartNestedAnimationGroupWhenDirty.qml @@ -0,0 +1,96 @@ +import QtQuick + +Rectangle { + width: 300 + height: 300 + + // test ParallelAnimation in SequentialAnimation + Rectangle { + id: line0 + y: 100 + width: parent.width + height: 2 + color: "blue" + } + Rectangle { + id: target0 + objectName: "target0" + y: 100 + anchors.verticalCenter: line0.verticalCenter + height: line0.height * 5 + width: height + color: "red" + radius: height/2 + + property bool onFinishedCalled : false; + + SequentialAnimation { + id: seqAnim0 + objectName: "seqAnim0" + loops: 2 + running: true + ParallelAnimation { + NumberAnimation { + id: anim0 + target: target0 + property: "x" + from: 0 + to: 50 + duration: 500 + } + } + Component.onCompleted: anim0.to = 290 + onFinished: target0.onFinishedCalled = true + } + } + + // test SequentialAnimation in ParallelAnimation + Rectangle { + id: line1 + y: 200 + width: parent.width + height: 2 + color: "blue" + } + Rectangle { + id: target1 + objectName: "target1" + anchors.verticalCenter: line1.verticalCenter + height: line1.height * 5 + width: height + color: "yellow" + radius: height/2 + + property bool onFinishedCalled : false; + + ParallelAnimation { + id: parAnim0 + objectName: "parAnim0" + loops: 2 + running: true + SequentialAnimation { + NumberAnimation { + id: anim1 + target: target1 + property: "x" + from: 0 + to: 50 + duration: 500 + } + } + Component.onCompleted: anim1.to = 290 + onFinished: target1.onFinishedCalled = true + } + } + + Timer { + interval: 400 + running: true + onTriggered: { + seqAnim0.pause() + parAnim0.pause() + anim0.to = 140 + anim1.to = 140 + } + } +} diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index f7a84ca979..623df8f505 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -98,6 +98,8 @@ private slots: void infiniteLoopsWithoutFrom(); void frameAnimation1(); void frameAnimation2(); + void restartAnimationGroupWhenDirty(); + void restartNestedAnimationGroupWhenDirty(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ @@ -2212,6 +2214,65 @@ void tst_qquickanimations::frameAnimation2() QVERIFY(frameAnimation->currentFrame() > 3); } +//QTBUG-110589 +void tst_qquickanimations::restartAnimationGroupWhenDirty() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("restartAnimationGroupWhenDirty.qml")); + QScopedPointer obj(c.create()); + auto *root = qobject_cast(obj.data()); + QVERIFY2(root, qPrintable(c.errorString())); + + QQuickSequentialAnimation *seqAnim0 = root->findChild("seqAnim0"); + QVERIFY(seqAnim0); + QQuickRectangle *target0 = root->findChild("target0"); + QVERIFY(target0); + QQuickParallelAnimation *parAnim0 = root->findChild("parAnim0"); + QVERIFY(parAnim0); + QQuickRectangle *target1 = root->findChild("target1"); + QVERIFY(target1); + + QTRY_VERIFY(seqAnim0->isPaused()); + QTRY_VERIFY(parAnim0->isPaused()); + QTRY_VERIFY(target0->x() > 140); + QTRY_VERIFY(target1->x() > 140); + seqAnim0->resume(); + parAnim0->resume(); + QTRY_VERIFY(target0->property("onFinishedCalled").value()); + QTRY_VERIFY(target1->property("onFinishedCalled").value()); + QTRY_COMPARE(target0->x(), 140); + QTRY_COMPARE(target1->x(), 140); +} + +//QTBUG-95840 +void tst_qquickanimations::restartNestedAnimationGroupWhenDirty() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("restartNestedAnimationGroupWhenDirty.qml")); + QScopedPointer obj(c.create()); + auto *root = qobject_cast(obj.data()); + QVERIFY2(root, qPrintable(c.errorString())); + + QQuickSequentialAnimation *seqAnim0 = root->findChild("seqAnim0"); + QVERIFY(seqAnim0); + QQuickRectangle *target0 = root->findChild("target0"); + QVERIFY(target0); + QQuickParallelAnimation *parAnim0 = root->findChild("parAnim0"); + QVERIFY(parAnim0); + QQuickRectangle *target1 = root->findChild("target1"); + QVERIFY(target1); + + QTRY_VERIFY(seqAnim0->isPaused()); + QTRY_VERIFY(parAnim0->isPaused()); + QTRY_VERIFY(target0->x() > 140); + QTRY_VERIFY(target1->x() > 140); + seqAnim0->resume(); + parAnim0->resume(); + QTRY_VERIFY(target0->property("onFinishedCalled").value()); + QTRY_VERIFY(target1->property("onFinishedCalled").value()); + QTRY_COMPARE(target0->x(), 140); + QTRY_COMPARE(target1->x(), 140); +} QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc"