diff options
-rw-r--r-- | src/quick/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/quick/util/qquickframeanimation.cpp | 498 | ||||
-rw-r--r-- | src/quick/util/qquickframeanimation_p.h | 121 | ||||
-rw-r--r-- | tests/auto/quick/qquickanimations/data/frameAnimation.qml | 47 | ||||
-rw-r--r-- | tests/auto/quick/qquickanimations/tst_qquickanimations.cpp | 69 | ||||
-rw-r--r-- | tests/manual/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/manual/frameanimation/CMakeLists.txt | 21 | ||||
-rw-r--r-- | tests/manual/frameanimation/main.cpp | 61 | ||||
-rw-r--r-- | tests/manual/frameanimation/main.qml | 281 |
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 + } + } +} |