Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qlottievectorimagecontroller_p.h"
#include <QtQml/private/qqmlbuiltins_p.h>
#include <QtQml/qqmlproperty.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/private/qquickanimation_p.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
/*!
\qmltype LottieVectorImageController
\inqmlmodule Qt.labs.lottieqt
\inherits QtObject
\since 6.12
\brief Provides animation control for a VectorImage that is displaying a Lottie file.
VectorImage can display and play Lottie animations, but by itself it provides only rudimentary
functionality for controlling the playback. LottieVectorImageController extends VectorImage
with more powerful API for querying and controlling the animation.
LottieVectorImageController's \l target property specifies the VectorImage to be controlled. It
is set by default if the LottieVectorImageController is created as a child or property of a
VectorImage. When the VectorImage has loaded a Lottie file, and its \l {VectorImage::status}
{status} is \c Ready, the LottieVectorImageController's properties can be read and written, and
its functions called, to control the playback.
Taken together with VectorImage, LottieVectorImageController mimics the API of the existing
LottieAnimation item, so they are typically interchangeable with little effort. (The \c
getDuration() function is not provided, since it can be trivially replaced by a simple
expression using the endFrame, startFrame and optionally frameRate properties.)
\sa VectorImage, {qtlottieviewer Example}
*/
QLottieVectorImageController::QLottieVectorImageController(QObject *parent)
: QObject(parent), QQmlParserStatus()
{
}
/*!
\internal
*/
void QLottieVectorImageController::classBegin()
{
}
/*!
\internal
*/
void QLottieVectorImageController::componentComplete()
{
if (!m_target) {
if (auto *p = qobject_cast<QQuickVectorImage *>(parent()))
setTarget(p);
}
}
/*!
\qmlproperty VectorImage LottieVectorImageController::target
This property specifies the VectorImage to be controlled.
If the LottieVectorImageController is created as a child or property of a VectorImage, that
will be the default target. Otherwise, the target must be set explicitly.
*/
QQuickVectorImage *QLottieVectorImageController::target() const
{
return m_target;
}
void QLottieVectorImageController::setTarget(QQuickVectorImage *newTarget)
{
if (m_target == newTarget)
return;
if (m_target)
disconnect(m_target, &QQuickVectorImage::generatedItemChanged, this, &QLottieVectorImageController::onGeneratedItemChanged);
m_target = newTarget;
if (m_target)
connect(m_target, &QQuickVectorImage::generatedItemChanged, this, &QLottieVectorImageController::onGeneratedItemChanged);
onGeneratedItemChanged();
emit targetChanged();
}
void QLottieVectorImageController::onGeneratedItemChanged()
{
clearMarkerPlayback();
if (m_generatedItem)
QObject::disconnect(m_generatedItem, nullptr, this, nullptr);
const qreal oldStartFrame = startFrame();
const qreal oldEndFrame = endFrame();
const qreal oldFrameRate = frameRate();
const qreal oldCurrentFrame = currentFrame();
const QStringList oldMarkers = markers();
m_generatedItem = m_target ? m_target->generatedItem() : nullptr;
m_animFrameRate = frameRate();
m_markers.clear();
if (m_generatedItem) {
const QQmlProperty prop(m_generatedItem, QStringLiteral("frameCounter"));
if (prop.isValid())
prop.connectNotifySignal(this, SIGNAL(currentFrameChanged()));
const QVariantMap markersMap = m_generatedItem->property("markers").toMap();
for (auto it = markersMap.cbegin(); it != markersMap.cend(); ++it) {
const QVariantList data = it.value().toList();
m_markers.append({ it.key(), data.value(0).toInt(), data.value(1).toInt() });
}
std::sort(m_markers.begin(), m_markers.end(),
[](const QLottieRoot::Marker &a, const QLottieRoot::Marker &b) {
return a.frame < b.frame;
});
}
if (startFrame() != oldStartFrame)
emit startFrameChanged();
if (endFrame() != oldEndFrame)
emit endFrameChanged();
if (m_animFrameRate != oldFrameRate)
emit frameRateChanged();
if (currentFrame() != oldCurrentFrame)
emit currentFrameChanged();
if (markers() != oldMarkers)
emit markersChanged();
}
/*!
\qmlproperty real LottieVectorImageController::frameRate
This property holds the animation playback speed in frames per second.
When the vector image is loaded, this property is set to the frame rate value specified in the
source file.
After loading, frameRate can be modified to change the playback speed. Assigning an empty
value to it will reset it to the source file value.
*/
qreal QLottieVectorImageController::frameRate() const
{
return m_generatedItem ? m_generatedItem->property("frameRate").toReal() : 30;
}
void QLottieVectorImageController::setFrameRate(qreal newFrameRate)
{
QQuickItem *item = m_generatedItem;
if (item && item->property("frameRate").toReal() != newFrameRate) {
const qreal currentFrame = item->property("frameCounter").toReal();
item->setProperty("frameRate", QVariant(newFrameRate));
auto *anim = item->findChild<QQuickNumberAnimation *>("_qt_frameCounterAnimation", Qt::FindDirectChildrenOnly);
if (anim && m_target) {
// Force the NumberAnimation to change speed now, then restore pause state & frame
anim->restart();
anim->setPaused(m_target->animations()->paused());
gotoFrame(currentFrame);
}
emit frameRateChanged();
}
}
void QLottieVectorImageController::resetFrameRate()
{
setFrameRate(m_animFrameRate);
}
/*!
\qmlproperty real LottieVectorImageController::startFrame
\readonly
The animation's starting frame number ("in-frame"), as specified in the source file.
*/
qreal QLottieVectorImageController::startFrame() const
{
return m_generatedItem ? m_generatedItem->property("startFrame").toReal() : 0;
}
/*!
\qmlproperty real LottieVectorImageController::endFrame
\readonly
The animation's ending frame number ("out-frame"), as specified in the source file.
*/
qreal QLottieVectorImageController::endFrame() const
{
return m_generatedItem ? m_generatedItem->property("endFrame").toReal() : 0;
}
/*!
\qmlproperty real LottieVectorImageController::currentFrame
\readonly
The number of the currently displayed animation frame.
*/
qreal QLottieVectorImageController::currentFrame() const
{
return m_generatedItem ? m_generatedItem->property("frameCounter").toReal() : 0;
}
/*!
\qmlmethod void LottieVectorImageController::play()
Starts (un-pauses) the playback.
*/
void QLottieVectorImageController::play()
{
if (m_target)
m_target->animations()->setPaused(false);
}
/*!
\qmlmethod void LottieVectorImageController::pause()
Pauses the playback.
*/
void QLottieVectorImageController::pause()
{
if (m_target)
m_target->animations()->setPaused(true);
}
/*!
\qmlmethod void LottieVectorImageController::togglePause()
Toggles the \l target VectorImage between playing and paused modes.
*/
void QLottieVectorImageController::togglePause()
{
if (m_target) {
const bool isPaused = m_target->animations()->paused();
m_target->animations()->setPaused(!isPaused);
}
}
/*!
\qmlmethod void LottieVectorImageController::start()
Restarts the playback from the startFrame.
*/
void QLottieVectorImageController::start()
{
gotoAndPlay(startFrame());
}
/*!
\qmlmethod void LottieVectorImageController::stop()
Resets the playback to the startFrame and pauses it.
*/
void QLottieVectorImageController::stop()
{
gotoAndStop(startFrame());
}
/*!
\qmlmethod void LottieVectorImageController::gotoAndPlay(real frame)
Moves the playback to Lottie frame number \a frame and plays it.
\a frame will be clamped between startFrame and endFrame.
*/
void QLottieVectorImageController::gotoAndPlay(qreal frame)
{
clearMarkerPlayback();
gotoFrame(frame);
play();
}
/*!
\qmlmethod void LottieVectorImageController::gotoAndStop(real frame)
Moves the playback to Lottie frame number \a frame and pauses it.
\a frame will be clamped between startFrame and endFrame.
*/
void QLottieVectorImageController::gotoAndStop(qreal frame)
{
clearMarkerPlayback();
pause();
gotoFrame(frame);
}
/*!
\qmlproperty list<string> LottieVectorImageController::markers
\readonly
\since 6.13
Names of the markers defined in the Lottie animation file. The list
is available after the target VectorImage has loaded and is ready.
Markers can be used with \l gotoAndPlay() and \l gotoAndStop() to
jump to a named position in the animation. If a marker has a non-zero
duration, \l gotoAndPlay() will stop playback automatically at the end
of the marked segment.
*/
QStringList QLottieVectorImageController::markers() const
{
QStringList names;
names.reserve(m_markers.size());
for (const QLottieRoot::Marker &marker : m_markers)
names.append(marker.name);
return names;
}
/*!
\qmlmethod bool LottieVectorImageController::gotoAndPlay(string markerName)
\since 6.13
Moves the playback to the frame that has a marker with the given \a markerName and plays it.
If the marker has a non-zero duration, playback stops automatically at the end of the segment.
Returns \c true if the marker was found, \c false otherwise.
*/
bool QLottieVectorImageController::gotoAndPlay(const QString &markerName)
{
const auto it = std::find_if(
m_markers.cbegin(), m_markers.cend(),
[&markerName](const QLottieRoot::Marker &m) { return m.name == markerName; });
if (it == m_markers.cend())
return false;
clearMarkerPlayback();
if (it->duration > 0) {
m_markerEndFrame = it->frame + it->duration;
m_markerEndConnection =
connect(this, &QLottieVectorImageController::currentFrameChanged, this, [this]() {
if (currentFrame() >= m_markerEndFrame) {
clearMarkerPlayback();
pause();
emit finished();
}
});
}
gotoFrame(it->frame);
play();
return true;
}
/*!
\qmlmethod bool LottieVectorImageController::gotoAndStop(string markerName)
\since 6.13
Moves the playback to the frame that has a marker with the given \a markerName and pauses it.
Returns \c true if the marker was found, \c false otherwise.
*/
bool QLottieVectorImageController::gotoAndStop(const QString &markerName)
{
const auto it = std::find_if(
m_markers.cbegin(), m_markers.cend(),
[&markerName](const QLottieRoot::Marker &m) { return m.name == markerName; });
if (it == m_markers.cend())
return false;
gotoAndStop(it->frame);
return true;
}
void QLottieVectorImageController::clearMarkerPlayback()
{
m_markerEndFrame = -1;
QObject::disconnect(m_markerEndConnection);
}
void QLottieVectorImageController::gotoFrame(qreal frame)
{
if (m_generatedItem) {
auto *anim = m_generatedItem->findChild<QQuickNumberAnimation *>("_qt_frameCounterAnimation",
Qt::FindDirectChildrenOnly);
if (anim) {
const qreal from = anim->from();
const qreal to = anim->to();
const qreal rawProgress = (frame - from) / (to - from);
if (qIsFinite(rawProgress)) {
const qreal progress = qBound(qreal(0), rawProgress, qreal(1));
anim->setCurrentTime(qRound(progress * anim->duration()));
}
}
}
}
QT_END_NAMESPACE