aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
+ }
+ }
+}