aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickframeanimation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/util/qquickframeanimation.cpp')
-rw-r--r--src/quick/util/qquickframeanimation.cpp463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/quick/util/qquickframeanimation.cpp b/src/quick/util/qquickframeanimation.cpp
new file mode 100644
index 0000000000..bbdd0e79e0
--- /dev/null
+++ b/src/quick/util/qquickframeanimation.cpp
@@ -0,0 +1,463 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#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: 90
+ width: 100
+ height: 100
+ color: "red"
+ anchors.centerIn: parent
+ }
+
+ 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"