aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaj Grönholm <kaj.gronholm@qt.io>2022-05-02 10:03:05 +0300
committerKaj Grönholm <kaj.gronholm@qt.io>2022-05-25 14:51:20 +0300
commitbcec02f8699b0b815306a5303137a938420b64aa (patch)
tree5d12250dc117d2bfe1c46b4eeb9093b629deb2e5
parentf9892cf5b8cac056f24712c4839740cefb917089 (diff)
Implement FrameAnimation Quick element
FrameAnimation is a helper synchronized to animation frame updates which can be used for custom animations and similar needs. Compared to QML Timer element, it doesn't allow setting the interval or repeat properties. Instead, it is triggered on animation updates and provides useful properties like frame number and frameTime & smoothFrameTime for fps-independent animation steps. Also it is directly synchronized with QAbstractAnimationJob instead of going though an extra Qt event loop like the QML Timer. Contains autotest and manual test. Task-number: QTBUG-102641 Change-Id: I5c72992462aba651b6fe8f2846baac3346799c56 Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
-rw-r--r--src/quick/CMakeLists.txt1
-rw-r--r--src/quick/util/qquickframeanimation.cpp498
-rw-r--r--src/quick/util/qquickframeanimation_p.h121
-rw-r--r--tests/auto/quick/qquickanimations/data/frameAnimation.qml47
-rw-r--r--tests/auto/quick/qquickanimations/tst_qquickanimations.cpp69
-rw-r--r--tests/manual/CMakeLists.txt1
-rw-r--r--tests/manual/frameanimation/CMakeLists.txt21
-rw-r--r--tests/manual/frameanimation/main.cpp61
-rw-r--r--tests/manual/frameanimation/main.qml281
9 files changed, 1100 insertions, 0 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index 2ae5264235..20f7ec5beb 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -203,6 +203,7 @@ qt_internal_add_qml_module(Quick
util/qquicktransitionmanager_p_p.h
util/qquickvalidator.cpp util/qquickvalidator_p.h
util/qquickvaluetypes.cpp util/qquickvaluetypes_p.h
+ util/qquickframeanimation.cpp util/qquickframeanimation_p.h
DEFINES
QT_NO_FOREACH
QT_NO_INTEGER_EVENT_COORDINATES
diff --git a/src/quick/util/qquickframeanimation.cpp b/src/quick/util/qquickframeanimation.cpp
new file mode 100644
index 0000000000..d93afea0c2
--- /dev/null
+++ b/src/quick/util/qquickframeanimation.cpp
@@ -0,0 +1,498 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickframeanimation_p.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qelapsedtimer.h>
+#include "private/qabstractanimationjob_p.h"
+#include <private/qobject_p.h>
+#include <qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+class QFrameAnimationJob : public QAbstractAnimationJob
+{
+ int duration() const override {
+ return 1;
+ }
+};
+
+class QQuickFrameAnimationPrivate : public QObjectPrivate, public QAnimationJobChangeListener
+{
+ Q_DECLARE_PUBLIC(QQuickFrameAnimation)
+public:
+ QQuickFrameAnimationPrivate() {}
+
+ void animationCurrentLoopChanged(QAbstractAnimationJob *) override {
+ maybeTick();
+ }
+
+ void maybeTick()
+ {
+ Q_Q(QQuickFrameAnimation);
+ if (!running || paused)
+ return;
+
+ qint64 elapsedTimeNs = elapsedTimer.nsecsElapsed();
+ qint64 frameTimeNs = elapsedTimeNs - prevElapsedTimeNs;
+ if (prevFrameTimeNs != frameTimeNs) {
+ frameTime = qreal(frameTimeNs) / 1000000000.0;
+ Q_EMIT q->frameTimeChanged();
+ }
+
+ const qreal f = 0.1;
+ qreal newSmoothFrameTime = f * frameTime + (1.0 - f) * smoothFrameTime;
+ if (!qFuzzyCompare(newSmoothFrameTime, smoothFrameTime)) {
+ smoothFrameTime = newSmoothFrameTime;
+ Q_EMIT q->smoothFrameTimeChanged();
+ }
+
+ q->setElapsedTime(elapsedTime + frameTime);
+
+ const int frame = (firstTick && currentFrame > 0) ? 0 : currentFrame + 1;
+ q->setCurrentFrame(frame);
+
+ prevElapsedTimeNs = elapsedTimeNs;
+ prevFrameTimeNs = frameTimeNs;
+ firstTick = false;
+
+ Q_EMIT q->triggered();
+ }
+
+ // Handle the running/pausing state updates.
+ void updateState()
+ {
+ if (!componentComplete)
+ return;
+
+ if (running && !paused) {
+ if (firstTick) {
+ elapsedTime = 0;
+ elapsedTimer.start();
+ }
+ prevElapsedTimeNs = elapsedTimer.nsecsElapsed();
+ frameJob.start();
+ } else {
+ frameJob.stop();
+ }
+ }
+
+private:
+ QFrameAnimationJob frameJob;
+ QElapsedTimer elapsedTimer;
+ int currentFrame = 0;
+ qreal frameTime = 0.0;
+ qreal smoothFrameTime = 0.0;
+ qreal elapsedTime = 0.0;
+ qint64 prevFrameTimeNs = 0;
+ qint64 prevElapsedTimeNs = 0;
+ bool running = false;
+ bool paused = false;
+ bool componentComplete = false;
+ bool firstTick = true;
+};
+
+/*!
+ \qmltype FrameAnimation
+ \instantiates QQuickFrameAnimation
+ \inqmlmodule QtQuick
+ \ingroup qtquick-interceptors
+ \since 6.4
+ \brief Triggers a handler at every animation frame update.
+
+ A FrameAnimation can be used to trigger an action every time animations
+ have progressed and an animation frame has been rendered. See the documentation
+ about the \l{qtquick-visualcanvas-scenegraph.html}{Scene Graph} for in-depth
+ information about the threaded and basic render loops.
+
+ For general animations, prefer using \c NumberAnimation and other \c Animation
+ elements as those provide declarative way to describe the animations.
+
+ FrameAnimation on the other hand should be used for custom imperative animations
+ and in use-cases like these:
+ \list
+ \li When you need to run some code on every frame update. Or e.g. every other frame,
+ maybe using progressive rendering.
+ \li When the speed / target is changing during the animation, normal QML animations
+ can be too limiting.
+ \li When more accurate frame update time is needed, e.g. for fps counter.
+ \endlist
+
+ Compared to \c Timer which allows to set the \c interval time, FrameAnimation runs
+ always in synchronization with the animation updates. If you have used \c Timer
+ with a short interval for custom animations like below, please consider switching
+ to use FrameAnimation instead for smoother animations.
+ \code
+ // BAD
+ Timer {
+ interval: 16
+ repeat: true
+ running: true
+ onTriggered: {
+ // Animate something
+ }
+ }
+
+ // GOOD
+ FrameAnimation {
+ running: true
+ onTriggered: {
+ // Animate something
+ }
+ }
+ \endcode
+*/
+QQuickFrameAnimation::QQuickFrameAnimation(QObject *parent)
+ : QObject(*(new QQuickFrameAnimationPrivate), parent)
+{
+ Q_D(QQuickFrameAnimation);
+ d->frameJob.addAnimationChangeListener(d, QAbstractAnimationJob::CurrentLoop);
+ d->frameJob.setLoopCount(-1);
+}
+
+/*!
+ \qmlsignal QtQuick::FrameAnimation::triggered()
+
+ This signal is emitted when the FrameAnimation has progressed to a new frame.
+*/
+
+/*!
+ \qmlproperty bool QtQuick::FrameAnimation::running
+
+ If set to true, starts the frame animation; otherwise stops it.
+
+ \a running defaults to false.
+
+ \sa stop(), start(), restart()
+*/
+bool QQuickFrameAnimation::isRunning() const
+{
+ Q_D(const QQuickFrameAnimation);
+ return d->running;
+}
+
+void QQuickFrameAnimation::setRunning(bool running)
+{
+ Q_D(QQuickFrameAnimation);
+ if (d->running != running) {
+ d->running = running;
+ d->firstTick = true;
+ Q_EMIT runningChanged();
+ d->updateState();
+ }
+}
+
+/*!
+ \qmlproperty bool QtQuick::FrameAnimation::paused
+
+ If set to true, pauses the frame animation; otherwise resumes it.
+
+ \a paused defaults to false.
+
+ \sa pause(), resume()
+*/
+bool QQuickFrameAnimation::isPaused() const
+{
+ Q_D(const QQuickFrameAnimation);
+ return d->paused;
+}
+
+void QQuickFrameAnimation::setPaused(bool paused)
+{
+ Q_D(QQuickFrameAnimation);
+ if (d->paused != paused) {
+ d->paused = paused;
+ Q_EMIT pausedChanged();
+ d->updateState();
+ }
+}
+
+/*!
+ \qmlproperty int QtQuick::FrameAnimation::currentFrame
+ \readonly
+
+ This property holds the number of frame updates since the start.
+ When the frame animation is restarted, currentFrame starts from \c 0.
+
+ The following example shows how to react on frame updates.
+
+ \code
+ FrameAnimation {
+ running: true
+ onTriggered: {
+ // Run code on every frame update.
+ }
+ }
+ \endcode
+
+ This property can also be used for rendering only every nth frame. Consider an
+ advanced usage where the UI contains two heavy elements and to reach smooth 60fps
+ overall frame rate, you decide to render these heavy elements at 30fps, first one
+ on every even frames and second one on every odd frames:
+
+ \code
+ FrameAnimation {
+ running: true
+ onTriggered: {
+ if (currentFrame % 2 == 0)
+ updateUIElement1();
+ else
+ updateUIElement2();
+ }
+ }
+ \endcode
+
+ By default, \c frame is 0.
+*/
+int QQuickFrameAnimation::currentFrame() const
+{
+ Q_D(const QQuickFrameAnimation);
+ return d->currentFrame;
+}
+
+/*!
+ \qmlproperty qreal QtQuick::FrameAnimation::frameTime
+ \readonly
+
+ This property holds the time (in seconds) since the previous frame update.
+
+ The following example shows how to use frameTime to animate item with
+ varying speed, adjusting to screen refresh rates and possible fps drops.
+
+ \code
+ Rectangle {
+ id: rect
+ property real speed: 360
+ width: 100
+ height: 100
+ color: "red"
+ }
+
+ FrameAnimation {
+ id: frameAnimation
+ running: true
+ onTriggered: {
+ // Rotate the item speed-degrees / second.
+ rect.rotation = rect.speed * frameTime
+ }
+ }
+ \endcode
+
+ By default, \c frameTime is 0.
+*/
+qreal QQuickFrameAnimation::frameTime() const
+{
+ Q_D(const QQuickFrameAnimation);
+ return d->frameTime;
+}
+
+/*!
+ \qmlproperty qreal QtQuick::FrameAnimation::smoothFrameTime
+ \readonly
+
+ This property holds the smoothed time (in seconds) since the previous frame update.
+
+ The following example shows how to use smoothFrameTime to show average fps.
+
+ \code
+ Text {
+ text: "fps: " + frameAnimation.fps.toFixed(0)
+ }
+
+ FrameAnimation {
+ id: frameAnimation
+ property real fps: smoothFrameTime > 0 ? (1.0 / smoothFrameTime) : 0
+ running: true
+ }
+ \endcode
+
+ By default, \c smoothFrameTime is 0.
+*/
+qreal QQuickFrameAnimation::smoothFrameTime() const
+{
+ Q_D(const QQuickFrameAnimation);
+ return d->smoothFrameTime;
+}
+
+/*!
+ \qmlproperty qreal QtQuick::FrameAnimation::elapsedTime
+ \readonly
+
+ This property holds the time (in seconds) since the previous start.
+
+ By default, \c elapsedTime is 0.
+*/
+qreal QQuickFrameAnimation::elapsedTime() const
+{
+ Q_D(const QQuickFrameAnimation);
+ return d->elapsedTime;
+}
+
+/*!
+ \qmlmethod QtQuick::FrameAnimation::start()
+ \brief Starts the frame animation
+
+ If the frame animation is already running, calling this method has no effect. The
+ \c running property will be true following a call to \c start().
+*/
+void QQuickFrameAnimation::start()
+{
+ setRunning(true);
+}
+
+/*!
+ \qmlmethod QtQuick::FrameAnimation::stop()
+ \brief Stops the frame animation
+
+ If the frame animation is not running, calling this method has no effect. Both the \c running and
+ \c paused properties will be false following a call to \c stop().
+*/
+void QQuickFrameAnimation::stop()
+{
+ setRunning(false);
+ setPaused(false);
+}
+
+/*!
+ \qmlmethod QtQuick::FrameAnimation::restart()
+ \brief Restarts the frame animation
+
+ If the FrameAnimation is not running it will be started, otherwise it will be
+ stopped, reset to initial state and started. The \c running property
+ will be true following a call to \c restart().
+*/
+void QQuickFrameAnimation::restart()
+{
+ stop();
+ start();
+}
+
+/*!
+ \qmlmethod QtQuick::FrameAnimation::pause()
+ \brief Pauses the frame animation
+
+ If the frame animation is already paused or not \c running, calling this method has no effect.
+ The \c paused property will be true following a call to \c pause().
+*/
+void QQuickFrameAnimation::pause()
+{
+ setPaused(true);
+}
+
+/*!
+ \qmlmethod QtQuick::FrameAnimation::resume()
+ \brief Resumes a paused frame animation
+
+ If the frame animation is not paused or not \c running, calling this method has no effect.
+ The \c paused property will be false following a call to \c resume().
+*/
+void QQuickFrameAnimation::resume()
+{
+ setPaused(false);
+}
+
+/*!
+ \qmlmethod QtQuick::FrameAnimation::reset()
+ \brief Resets the frame animation properties
+
+ Calling this method resets the \c frame and \c elapsedTime to their initial
+ values (0). This method has no effect on \c running or \c paused properties
+ and can be called while they are true or false.
+
+ The difference between calling \c reset() and \c restart() is that \c reset()
+ will always initialize the properties while \c restart() initializes them only
+ at the next frame update which doesn't happen e.g. if \c restart() is
+ immediately followed by \c pause().
+*/
+void QQuickFrameAnimation::reset()
+{
+ Q_D(QQuickFrameAnimation);
+ setElapsedTime(0);
+ setCurrentFrame(0);
+ d->prevElapsedTimeNs = 0;
+ d->elapsedTimer.start();
+}
+
+/*!
+ \internal
+ */
+void QQuickFrameAnimation::classBegin()
+{
+ Q_D(QQuickFrameAnimation);
+ d->componentComplete = false;
+}
+
+/*!
+ \internal
+ */
+void QQuickFrameAnimation::componentComplete()
+{
+ Q_D(QQuickFrameAnimation);
+ d->componentComplete = true;
+ d->updateState();
+}
+
+/*!
+ \internal
+ */
+void QQuickFrameAnimation::setCurrentFrame(int frame)
+{
+ Q_D(QQuickFrameAnimation);
+ if (d->currentFrame != frame) {
+ d->currentFrame = frame;
+ Q_EMIT currentFrameChanged();
+ }
+}
+
+/*!
+ \internal
+ */
+void QQuickFrameAnimation::setElapsedTime(qreal elapsedTime)
+{
+ Q_D(QQuickFrameAnimation);
+ if (!qFuzzyCompare(d->elapsedTime, elapsedTime)) {
+ d->elapsedTime = elapsedTime;
+ Q_EMIT elapsedTimeChanged();
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquickframeanimation_p.cpp"
diff --git a/src/quick/util/qquickframeanimation_p.h b/src/quick/util/qquickframeanimation_p.h
new file mode 100644
index 0000000000..c92e5b681f
--- /dev/null
+++ b/src/quick/util/qquickframeanimation_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKFRAMEANIMATION_H
+#define QQUICKFRAMEANIMATION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qqml.h>
+#include <QtCore/qobject.h>
+#include <private/qtqmlglobal_p.h>
+#include <private/qtquickglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickFrameAnimationPrivate;
+class Q_QUICK_PRIVATE_EXPORT QQuickFrameAnimation : public QObject, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QQuickFrameAnimation)
+ Q_INTERFACES(QQmlParserStatus)
+ Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
+ Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged)
+ Q_PROPERTY(int currentFrame READ currentFrame NOTIFY currentFrameChanged)
+ Q_PROPERTY(qreal frameTime READ frameTime NOTIFY frameTimeChanged)
+ Q_PROPERTY(qreal smoothFrameTime READ smoothFrameTime NOTIFY smoothFrameTimeChanged)
+ Q_PROPERTY(qreal elapsedTime READ elapsedTime NOTIFY elapsedTimeChanged)
+ QML_NAMED_ELEMENT(FrameAnimation)
+ QML_ADDED_IN_VERSION(6, 4)
+
+public:
+ QQuickFrameAnimation(QObject *parent = nullptr);
+
+ bool isRunning() const;
+ void setRunning(bool running);
+
+ bool isPaused() const;
+ void setPaused(bool paused);
+
+ int currentFrame() const;
+ qreal frameTime() const;
+ qreal smoothFrameTime() const;
+ qreal elapsedTime() const;
+
+protected:
+ void classBegin() override;
+ void componentComplete() override;
+
+public Q_SLOTS:
+ void start();
+ void stop();
+ void restart();
+ void pause();
+ void resume();
+ void reset();
+
+Q_SIGNALS:
+ void triggered();
+ void runningChanged();
+ void pausedChanged();
+ void currentFrameChanged();
+ void frameTimeChanged();
+ void smoothFrameTimeChanged();
+ void elapsedTimeChanged();
+
+private:
+ void setCurrentFrame(int frame);
+ void setElapsedTime(qreal elapsedTime);
+
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickFrameAnimation)
+
+#endif
diff --git a/tests/auto/quick/qquickanimations/data/frameAnimation.qml b/tests/auto/quick/qquickanimations/data/frameAnimation.qml
new file mode 100644
index 0000000000..3c09db832f
--- /dev/null
+++ b/tests/auto/quick/qquickanimations/data/frameAnimation.qml
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 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
+
+Rectangle {
+ id: root
+ width: 200
+ height: 200
+ visible: true
+
+ property alias frameAnimation: frameAnimation
+
+ FrameAnimation {
+ id: frameAnimation
+ onTriggered: {
+ // Pause when we reach the frame 10
+ if (currentFrame === 10)
+ pause();
+ }
+ }
+}
diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp
index 72e0da91cb..58f9b9139e 100644
--- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp
+++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp
@@ -41,6 +41,7 @@
#include <QtQuick/private/qquickpathinterpolator_p.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquicklistview_p.h>
+#include <QtQuick/private/qquickframeanimation_p.h>
#include <QEasingCurve>
#include <limits.h>
@@ -120,6 +121,8 @@ private slots:
void changePropertiesDuringAnimation_data();
void changePropertiesDuringAnimation();
void infiniteLoopsWithoutFrom();
+ void frameAnimation1();
+ void frameAnimation2();
};
#define QTIMED_COMPARE(lhs, rhs) do { \
@@ -2169,6 +2172,72 @@ void tst_qquickanimations::infiniteLoopsWithoutFrom()
animation->stop();
}
+void tst_qquickanimations::frameAnimation1()
+{
+ QQuickFrameAnimation frameAnimation;
+ QVERIFY(!frameAnimation.isRunning());
+ QVERIFY(!frameAnimation.isPaused());
+ QCOMPARE(frameAnimation.currentFrame(), 0);
+ QCOMPARE(frameAnimation.frameTime(), 0);
+ QCOMPARE(frameAnimation.smoothFrameTime(), 0);
+ QCOMPARE(frameAnimation.elapsedTime(), 0);
+
+ frameAnimation.start();
+ QVERIFY(frameAnimation.isRunning());
+ QVERIFY(!frameAnimation.isPaused());
+ frameAnimation.pause();
+ QVERIFY(frameAnimation.isRunning());
+ QVERIFY(frameAnimation.isPaused());
+ frameAnimation.resume();
+ QVERIFY(frameAnimation.isRunning());
+ QVERIFY(!frameAnimation.isPaused());
+ frameAnimation.stop();
+ QVERIFY(!frameAnimation.isRunning());
+ QVERIFY(!frameAnimation.isPaused());
+ frameAnimation.restart();
+ QVERIFY(frameAnimation.isRunning());
+ QVERIFY(!frameAnimation.isPaused());
+}
+
+void tst_qquickanimations::frameAnimation2()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("frameAnimation.qml"));
+ QScopedPointer<QObject> obj(c.create());
+ auto *root = qobject_cast<QQuickRectangle*>(obj.data());
+ QVERIFY(root);
+
+ QQuickFrameAnimation *frameAnimation = root->findChild<QQuickFrameAnimation*>();
+ QVERIFY(frameAnimation);
+ QSignalSpy spy(frameAnimation, SIGNAL(triggered()));
+
+ // Start the animation and wait at least 1 frame
+ frameAnimation->start();
+ QVERIFY(frameAnimation->isRunning());
+ QVERIFY(spy.wait(500));
+ QVERIFY(frameAnimation->currentFrame() > 0);
+ QVERIFY(frameAnimation->frameTime() > 0);
+ QVERIFY(frameAnimation->smoothFrameTime() > 0);
+ QVERIFY(frameAnimation->elapsedTime() > 0);
+
+ // Stopping and reseting should return currentFrame back to 0
+ frameAnimation->stop();
+ frameAnimation->reset();
+ QCOMPARE(frameAnimation->currentFrame(), 0);
+
+ // Start and wait so the animatation runs into frame 10 and pauses
+ frameAnimation->start();
+ QTest::qWait(500);
+ QVERIFY(frameAnimation->isPaused());
+ QCOMPARE(frameAnimation->currentFrame(), 10);
+
+ // Then resume the animation
+ frameAnimation->resume();
+ QVERIFY(!frameAnimation->isPaused());
+ QVERIFY(spy.wait(500));
+ QVERIFY(frameAnimation->currentFrame() > 10);
+}
+
QTEST_MAIN(tst_qquickanimations)
#include "tst_qquickanimations.moc"
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index 386e72d153..e5d4450f8c 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -13,3 +13,4 @@ add_subdirectory(touch)
# add_subdirectory(v4) # TODO: port if needed
add_subdirectory(quickcontrols2)
add_subdirectory(quickdialogs)
+add_subdirectory(frameanimation)
diff --git a/tests/manual/frameanimation/CMakeLists.txt b/tests/manual/frameanimation/CMakeLists.txt
new file mode 100644
index 0000000000..f632ec2079
--- /dev/null
+++ b/tests/manual/frameanimation/CMakeLists.txt
@@ -0,0 +1,21 @@
+qt_internal_add_manual_test(frameanimation
+ GUI
+ SOURCES
+ main.cpp
+ PUBLIC_LIBRARIES
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+)
+
+# Resources:
+set(qml_resource_files
+ "main.qml"
+)
+
+qt_internal_add_resource(frameanimation "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
diff --git a/tests/manual/frameanimation/main.cpp b/tests/manual/frameanimation/main.cpp
new file mode 100644
index 0000000000..01a454f396
--- /dev/null
+++ b/tests/manual/frameanimation/main.cpp
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ return app.exec();
+}
diff --git a/tests/manual/frameanimation/main.qml b/tests/manual/frameanimation/main.qml
new file mode 100644
index 0000000000..a784e3f3e7
--- /dev/null
+++ b/tests/manual/frameanimation/main.qml
@@ -0,0 +1,281 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+Window {
+ width: 640
+ height: 480
+ visible: true
+ title: qsTr("FrameAnimation Tester")
+
+ component AnimatedComponent : Item {
+
+ property alias text: textItem.text
+
+ width: 60
+ height: 60
+ Rectangle {
+ anchors.fill: parent
+ color: "#b0b0b0"
+ border.width: 1
+ border.color: "#404040"
+ }
+ Text {
+ id: textItem
+ anchors.centerIn: parent
+ font.bold: true
+ font.pixelSize: 14
+ color: "#202020"
+ }
+ }
+
+ FrameAnimation {
+ id: frameAnimation
+ onTriggered: {
+ var rotation = rotatingRect1.rotation;
+ // How long (in seconds) a full rotation takes
+ var rotationDuration = 4.0;
+ rotation += (360 / rotationDuration) * frameTime;
+ rotatingRect1.rotation = rotation;
+ if (currentFrame % 2 == 0)
+ rotatingRect2.rotation = rotation;
+ if (currentFrame % 2 == 1)
+ rotatingRect3.rotation = rotation;
+ if (currentFrame % 3 == 0)
+ rotatingRect4.rotation = rotation;
+ if (currentFrame % 10 == 0)
+ rotatingRect5.rotation = rotation;
+ if (currentFrame % 10 == 4)
+ rotatingRect6.rotation = rotation;
+ }
+ }
+
+ Row {
+ id: buttonsRow
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 10
+ Button {
+ width: 100
+ checkable: true
+ checked: frameAnimation.running
+ text: "running"
+ onCheckedChanged: {
+ frameAnimation.running = checked;
+ }
+ }
+ Button {
+ width: 100
+ checkable: true
+ checked: frameAnimation.paused
+ text: "paused"
+ onCheckedChanged: {
+ frameAnimation.paused = checked;
+ }
+ }
+ }
+
+ Row {
+ id: buttonsRow2
+ anchors.top: buttonsRow.bottom
+ anchors.topMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 10
+ Button {
+ width: 80
+ text: "start()"
+ onClicked: {
+ frameAnimation.start();
+ }
+ }
+ Button {
+ width: 80
+ text: "stop()"
+ onClicked: {
+ frameAnimation.stop();
+ }
+ }
+ Button {
+ width: 80
+ text: "restart()"
+ onClicked: {
+ frameAnimation.restart();
+ }
+ }
+ Button {
+ width: 80
+ text: "pause()"
+ onClicked: {
+ frameAnimation.pause();
+ }
+ }
+ Button {
+ width: 80
+ text: "resume()"
+ onClicked: {
+ frameAnimation.resume();
+ }
+ }
+ Button {
+ width: 80
+ text: "reset()"
+ onClicked: {
+ frameAnimation.reset();
+ }
+ }
+ }
+
+ Column {
+ id: statusTexts
+ anchors.top: buttonsRow2.bottom
+ anchors.topMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 10
+ Text {
+ text: "FRAME: <b>" + frameAnimation.currentFrame + "</b>"
+ font.pixelSize: 16
+ }
+ Text {
+ text: "FRAME TIME: <b>" + frameAnimation.frameTime.toFixed(4) + "</b>"
+ font.pixelSize: 16
+ }
+ Text {
+ text: "SMOOTH FRAME TIME: <b>" + frameAnimation.smoothFrameTime.toFixed(4) + "</b>"
+ font.pixelSize: 16
+ }
+ Text {
+ text: "ELAPSED TIME: <b>" + frameAnimation.elapsedTime.toFixed(4) + "</b>"
+ font.pixelSize: 16
+ }
+ }
+
+ Text {
+ id: infoText
+ anchors.top: statusTexts.bottom
+ anchors.topMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "Animations with different update slot / refresh times"
+ font.pixelSize: 16
+ }
+
+ Row {
+ id: rotatingRects
+ anchors.top: infoText.bottom
+ anchors.topMargin: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 20
+ AnimatedComponent {
+ id: rotatingRect1
+ text: "1/1"
+ }
+ AnimatedComponent {
+ id: rotatingRect2
+ text: "1/2"
+ }
+ AnimatedComponent {
+ id: rotatingRect3
+ text: "2/2"
+ }
+ AnimatedComponent {
+ id: rotatingRect4
+ text: "1/3"
+ }
+ AnimatedComponent {
+ id: rotatingRect5
+ text: "1/10"
+ }
+ AnimatedComponent {
+ id: rotatingRect6
+ text: "5/10"
+ }
+ }
+
+ Row {
+ id: movingRects
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 10
+ spacing: 20
+ height: 120
+ AnimatedComponent {
+ id: movingRect1
+ text: "1/1"
+ y: Math.sin(frameAnimation.elapsedTime) * 50
+ }
+ AnimatedComponent {
+ id: movingRect2
+ text: "1/2"
+ y: (frameAnimation.currentFrame % 2 == 0) ? Math.sin(frameAnimation.elapsedTime) * 50 : y
+ }
+ AnimatedComponent {
+ id: movingRect3
+ text: "2/2"
+ y: (frameAnimation.currentFrame % 2 == 1) ? Math.sin(frameAnimation.elapsedTime) * 50 : y
+ }
+ AnimatedComponent {
+ id: movingRect4
+ text: "1/3"
+ y: (frameAnimation.currentFrame % 3 == 0) ? Math.sin(frameAnimation.elapsedTime) * 50 : y
+ }
+ AnimatedComponent {
+ id: movingRect5
+ text: "1/10"
+ y: (frameAnimation.currentFrame % 10 == 0) ? Math.sin(frameAnimation.elapsedTime) * 50 : y
+ }
+ AnimatedComponent {
+ id: movingRect6
+ text: "5/10"
+ y: (frameAnimation.currentFrame % 10 == 4) ? Math.sin(frameAnimation.elapsedTime) * 50 : y
+ }
+ }
+}