diff options
author | Mitch Curtis <mitch.curtis@qt.io> | 2018-03-07 14:57:29 +0100 |
---|---|---|
committer | Mitch Curtis <mitch.curtis@qt.io> | 2018-03-09 09:11:31 +0000 |
commit | 31e64606f253663f8ee2032b94521afd1804ab11 (patch) | |
tree | 92484cb64b16db769cf0cd37470245e670fd22ae | |
parent | 5446be855becab2499611f2bc2280b74160f2e75 (diff) |
Animation: add finished() signal
Currently, Animation has a stopped() signal. This is executed even if
the animation is stopped manually. To react to an animation finishing
naturally, you currently have to do the following:
onRunningChanged: if (!running) doStuff()
This patch adds a dedicated signal:
onFinished: doStuff()
[ChangeLog][QtQuick][Animation] Added finished() signal to
Animation as a convenient way to react to an animation finishing
naturally.
Change-Id: I8765d3e8e2b7bf7ef66a6acb69feafb43e9619d3
Reviewed-by: J-P Nurmi <jpnurmi@qt.io>
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r-- | src/quick/util/qquickanimation.cpp | 22 | ||||
-rw-r--r-- | src/quick/util/qquickanimation_p.h | 1 | ||||
-rw-r--r-- | src/quick/util/qquickutilmodule.cpp | 3 | ||||
-rw-r--r-- | tests/auto/quick/qquickanimations/data/finished.qml | 95 | ||||
-rw-r--r-- | tests/auto/quick/qquickanimations/tst_qquickanimations.cpp | 74 |
5 files changed, 195 insertions, 0 deletions
diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index a89f237499..9bbf70b3a4 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -234,6 +234,27 @@ QQmlProperty QQuickAbstractAnimationPrivate::createProperty(QObject *obj, const The corresponding handler is \c onStopped. */ +/*! + \qmlsignal QtQuick::Animation::finished() + \since 5.12 + + This signal is emitted when the animation has finished naturally. + + It is not emitted when \l running is set to \c false, nor for animations whose + \l loops property is set to \c Animation.Infinite. + + In addition, it is only emitted for top-level, standalone animations. It + will not be emitted for animations in a Behavior or Transition, or + animations that are part of an animation group. + + If \l alwaysRunToEnd is true, this signal will not be emitted until the + animation has completed its current iteration. + + The corresponding handler is \c onFinished. + + \sa stopped(), started(), running +*/ + void QQuickAbstractAnimation::setRunning(bool r) { Q_D(QQuickAbstractAnimation); @@ -656,6 +677,7 @@ void QQuickAbstractAnimationPrivate::animationFinished(QAbstractAnimationJob*) if (loopCount != 1) animationInstance->setLoopCount(loopCount); } + emit q->finished(); } QQuickAbstractAnimation::ThreadingModel QQuickAbstractAnimation::threadingModel() const diff --git a/src/quick/util/qquickanimation_p.h b/src/quick/util/qquickanimation_p.h index 2293f2597f..746cb938bd 100644 --- a/src/quick/util/qquickanimation_p.h +++ b/src/quick/util/qquickanimation_p.h @@ -126,6 +126,7 @@ Q_SIGNALS: void pausedChanged(bool); void alwaysRunToEndChanged(bool); void loopCountChanged(int); + Q_REVISION(12) void finished(); public Q_SLOTS: void restart(); diff --git a/src/quick/util/qquickutilmodule.cpp b/src/quick/util/qquickutilmodule.cpp index b2e5b84cf4..31d4d4c437 100644 --- a/src/quick/util/qquickutilmodule.cpp +++ b/src/quick/util/qquickutilmodule.cpp @@ -132,4 +132,7 @@ void QQuickUtilModule::defineModule() qmlRegisterType<QQuickShortcut,9>("QtQuick", 2, 9, "Shortcut"); #endif + + qmlRegisterUncreatableType<QQuickAbstractAnimation, 12>("QtQuick", 2, 12, "Animation", + QQuickAbstractAnimation::tr("Animation is an abstract class")); } diff --git a/tests/auto/quick/qquickanimations/data/finished.qml b/tests/auto/quick/qquickanimations/data/finished.qml new file mode 100644 index 0000000000..a18b321501 --- /dev/null +++ b/tests/auto/quick/qquickanimations/data/finished.qml @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 + +Item { + id: root + width: 400 + height: 400 + + property bool finishedUsableInQml: false + + property alias simpleTopLevelAnimation: simpleTopLevelAnimation + property real foo: 0 + + property alias transitionRect: transitionRect + property alias transition: transition + property alias animationWithinTransition: animationWithinTransition + + property real bar: 0 + property alias animationWithinBehavior: animationWithinBehavior + property alias behavior: behavior + + NumberAnimation { + id: simpleTopLevelAnimation + target: root + property: "foo" + from: 0 + to: 1 + duration: 10 + + onFinished: finishedUsableInQml = true + } + + Rectangle { + id: transitionRect + color: "green" + width: 50 + height: 50 + anchors.centerIn: parent + + states: State { + name: "go" + } + transitions: Transition { + id: transition + to: "go" + SequentialAnimation { + NumberAnimation { + id: animationWithinTransition + duration: 10 + property: "foo" + from: 1 + to: 2 + } + } + } + } + + Behavior on bar { + id: behavior + NumberAnimation { + id: animationWithinBehavior + duration: 10 + property: "bar" + from: 0 + to: 1 + } + } +} diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index de86bb16db..3cfe03a376 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -105,6 +105,7 @@ private slots: void pathSvgAnimation(); void pathLineUnspecifiedXYBug(); void unsetAnimatorProxyJobWindow(); + void finished(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ @@ -1612,6 +1613,79 @@ void tst_qquickanimations::unsetAnimatorProxyJobWindow() QCOMPARE(proxy.job().data(), job); } +void tst_qquickanimations::finished() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("finished.qml")); + QScopedPointer<QObject> root(component.create()); + QVERIFY(root); + + // Test that finished() is emitted for a simple top-level animation. + // (Each test is in its own block so that we can reuse the nice signal names :)) + { + QQuickAbstractAnimation *simpleTopLevelAnimation + = root->property("simpleTopLevelAnimation").value<QQuickAbstractAnimation*>(); + QVERIFY(simpleTopLevelAnimation); + + QSignalSpy stoppedSpy(simpleTopLevelAnimation, SIGNAL(stopped())); + QVERIFY(stoppedSpy.isValid()); + + QSignalSpy finishedSpy(simpleTopLevelAnimation, SIGNAL(finished())); + QVERIFY(finishedSpy.isValid()); + + QVERIFY(simpleTopLevelAnimation->setProperty("running", QVariant(true))); + QTRY_COMPARE(stoppedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 1); + + // Test that the signal is properly revisioned and hence accessible from QML. + QCOMPARE(root->property("finishedUsableInQml").toBool(), true); + } + + // Test that finished() is not emitted for animations within a Transition. + { + QObject *transition = root->property("transition").value<QObject*>(); + QVERIFY(transition); + + QSignalSpy runningChangedSpy(transition, SIGNAL(runningChanged())); + QVERIFY(runningChangedSpy.isValid()); + + QQuickAbstractAnimation *animationWithinTransition + = root->property("animationWithinTransition").value<QQuickAbstractAnimation*>(); + QVERIFY(animationWithinTransition); + + QSignalSpy stoppedSpy(animationWithinTransition, SIGNAL(stopped())); + QVERIFY(stoppedSpy.isValid()); + + QSignalSpy finishedSpy(animationWithinTransition, SIGNAL(finished())); + QVERIFY(finishedSpy.isValid()); + + QObject *transitionRect = root->property("transitionRect").value<QObject*>(); + QVERIFY(transitionRect); + QVERIFY(transitionRect->setProperty("state", QVariant(QLatin1String("go")))); + QTRY_COMPARE(runningChangedSpy.count(), 1); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + } + + // Test that finished() is not emitted for animations within a Behavior. + { + QQuickAbstractAnimation *animationWithinBehavior + = root->property("animationWithinBehavior").value<QQuickAbstractAnimation*>(); + QVERIFY(animationWithinBehavior); + + QSignalSpy stoppedSpy(animationWithinBehavior, SIGNAL(stopped())); + QVERIFY(stoppedSpy.isValid()); + + QSignalSpy finishedSpy(animationWithinBehavior, SIGNAL(finished())); + QVERIFY(finishedSpy.isValid()); + + QVERIFY(root->setProperty("bar", QVariant(1.0))); + QTRY_COMPARE(root->property("bar").toReal(), 1.0); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + } +} + QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc" |