Make a group animation dirty when a member changed

Some properties of one of the member animations in a group
animation can be changed after the group animation initialized.
1. If the group animation is already proceeding, they will
affect the next loop.
2. If the group animation started but does not proceed, they
will affect the current loop

Fixes: QTBUG-110589
Fixes: QTBUG-61282
Fixes: QTBUG-95840
Pick-to: 6.6 6.5
Change-Id: I018105bdd75dd5bd7c24e9126853cd79c8f0123b
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Inho Lee 2023-04-21 09:44:59 +02:00
parent 9831b38fc5
commit 0e69268b49
5 changed files with 328 additions and 1 deletions

View File

@ -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 *>(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<QObject> QQuickPropertyAction::targets()
@ -1722,6 +1751,36 @@ void QQuickAnimationGroupPrivate::removeLast_animation(QQmlListProperty<QQuickAb
q->d_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();
}
/*!

View File

@ -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<QQuickAbstractAnimation> *list, QQuickAbstractAnimation *role);
static QQuickAbstractAnimation *at_animation(QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype index);
@ -227,6 +228,10 @@ public:
QQuickAbstractAnimation *role);
static void removeLast_animation(QQmlListProperty<QQuickAbstractAnimation> *list);
QList<QQuickAbstractAnimation *> animations;
void restartFromCurrentLoop();
void animationCurrentLoopChanged(QAbstractAnimationJob *job) override;
bool animationDirty: 1;
};
class Q_QUICK_PRIVATE_EXPORT QQuickPropertyAnimationPrivate : public QQuickAbstractAnimationPrivate

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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<QObject> obj(c.create());
auto *root = qobject_cast<QQuickRectangle*>(obj.data());
QVERIFY2(root, qPrintable(c.errorString()));
QQuickSequentialAnimation *seqAnim0 = root->findChild<QQuickSequentialAnimation*>("seqAnim0");
QVERIFY(seqAnim0);
QQuickRectangle *target0 = root->findChild<QQuickRectangle*>("target0");
QVERIFY(target0);
QQuickParallelAnimation *parAnim0 = root->findChild<QQuickParallelAnimation*>("parAnim0");
QVERIFY(parAnim0);
QQuickRectangle *target1 = root->findChild<QQuickRectangle*>("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<bool>());
QTRY_VERIFY(target1->property("onFinishedCalled").value<bool>());
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<QObject> obj(c.create());
auto *root = qobject_cast<QQuickRectangle*>(obj.data());
QVERIFY2(root, qPrintable(c.errorString()));
QQuickSequentialAnimation *seqAnim0 = root->findChild<QQuickSequentialAnimation*>("seqAnim0");
QVERIFY(seqAnim0);
QQuickRectangle *target0 = root->findChild<QQuickRectangle*>("target0");
QVERIFY(target0);
QQuickParallelAnimation *parAnim0 = root->findChild<QQuickParallelAnimation*>("parAnim0");
QVERIFY(parAnim0);
QQuickRectangle *target1 = root->findChild<QQuickRectangle*>("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<bool>());
QTRY_VERIFY(target1->property("onFinishedCalled").value<bool>());
QTRY_COMPARE(target0->x(), 140);
QTRY_COMPARE(target1->x(), 140);
}
QTEST_MAIN(tst_qquickanimations)
#include "tst_qquickanimations.moc"