aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEirik Aavitsland <eirik.aavitsland@qt.io>2025-11-11 16:07:42 +0100
committerEirik Aavitsland <eirik.aavitsland@qt.io>2025-11-26 09:49:39 +0100
commitcc506e0c96357b835e15e9ef94e41acd64a3d9db (patch)
tree7a240b5aa5ba63e3be79388c3b382940cb63b025 /src
parentdf3b945c2e430120ddc7fd17e4f0fefd5d7cc2f8 (diff)
qml generator: add functionality for animated (morphing) paths
Add a new Path item to the Helpers module: pathInterpolated, that holds a list of paths (specified as svg path texts) and has a settable interpolation factor property. Based on the value factor, the resulting path will be an interpolation between path #n and path#n+1, where n is the integer part of the factor. The fractional part determines the interpolation weight between the two. Replace the static QPainterPath in PathNodeInfo with an animated property holding QPainterPath values. During generation, if the property is found to be animated, a PathInterpolated item is generated instead of a static PathSvg item. Change-Id: Ic061005e135cbde1bd88ab1ac7c9e7840f55c232 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/quick/util/qquicksvgparser_p.h2
-rw-r--r--src/quickvectorimage/CMakeLists.txt1
-rw-r--r--src/quickvectorimage/generator/qquickgenerator.cpp4
-rw-r--r--src/quickvectorimage/generator/qquicknodeinfo_p.h2
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator.cpp32
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl.cpp8
-rw-r--r--src/quickvectorimage/helpers/qquickpathinterpolated.cpp127
-rw-r--r--src/quickvectorimage/helpers/qquickpathinterpolated_p.h54
8 files changed, 220 insertions, 10 deletions
diff --git a/src/quick/util/qquicksvgparser_p.h b/src/quick/util/qquicksvgparser_p.h
index 3af0d182ae..37bb713cf3 100644
--- a/src/quick/util/qquicksvgparser_p.h
+++ b/src/quick/util/qquicksvgparser_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
namespace QQuickSvgParser
{
- bool parsePathDataFast(const QString &dataStr, QPainterPath &path);
+ Q_QUICK_EXPORT bool parsePathDataFast(const QString &dataStr, QPainterPath &path);
Q_QUICK_EXPORT void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation,
int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx,
qreal cury);
diff --git a/src/quickvectorimage/CMakeLists.txt b/src/quickvectorimage/CMakeLists.txt
index fef0fc8834..ba2a671fdd 100644
--- a/src/quickvectorimage/CMakeLists.txt
+++ b/src/quickvectorimage/CMakeLists.txt
@@ -50,6 +50,7 @@ qt_internal_add_qml_module(QuickVectorImageHelpers
helpers/qquickcoloropacityanimation_p.h helpers/qquickcoloropacityanimation.cpp
helpers/qquicktransformgroup_p.h helpers/qquicktransformgroup.cpp
helpers/qquickitemspy_p.h helpers/qquickitemspy.cpp
+ helpers/qquickpathinterpolated_p.h helpers/qquickpathinterpolated.cpp
LIBRARIES
Qt::QuickPrivate
Qt::QuickVectorImageGeneratorPrivate
diff --git a/src/quickvectorimage/generator/qquickgenerator.cpp b/src/quickvectorimage/generator/qquickgenerator.cpp
index 582611fbe5..fc5c5bb190 100644
--- a/src/quickvectorimage/generator/qquickgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickgenerator.cpp
@@ -47,11 +47,11 @@ bool QQuickGenerator::generate()
void QQuickGenerator::optimizePaths(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
{
- QPainterPath pathCopy = info.painterPath;
+ QPainterPath pathCopy = info.path.defaultValue().value<QPainterPath>();
pathCopy.setFillRule(info.fillRule);
const QRectF &boundingRect = overrideBoundingRect.isNull() ? pathCopy.boundingRect() : overrideBoundingRect;
- if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::OptimizePaths)) {
+ if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::OptimizePaths) && !info.path.isAnimated()) {
QQuadPath strokePath = QQuadPath::fromPainterPath(pathCopy);
bool fillPathNeededClose;
QQuadPath fillPath = strokePath.subPathsClosed(&fillPathNeededClose);
diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h
index d42ba72d6e..54fdc5bf3c 100644
--- a/src/quickvectorimage/generator/qquicknodeinfo_p.h
+++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h
@@ -100,7 +100,7 @@ struct PathTrimInfo
struct PathNodeInfo : NodeInfo
{
- QPainterPath painterPath;
+ QQuickAnimatedProperty path = QQuickAnimatedProperty(QVariant::fromValue(QPainterPath{}));
Qt::FillRule fillRule = Qt::FillRule::WindingFill;
QQuickAnimatedProperty fillColor = QQuickAnimatedProperty(QVariant::fromValue(QColor{}));
QQuickAnimatedProperty fillOpacity = QQuickAnimatedProperty(QVariant::fromValue(qreal(1.0)));
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
index 7f821aa12f..b9fd394a74 100644
--- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
@@ -745,12 +745,40 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte
if (!hintStr.isEmpty())
stream() << hintStr;
- QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
- stream() << "PathSvg { path: \"" << svgPathString << "\" }";
+ QQuickAnimatedProperty pathFactor(QVariant::fromValue(0));
+ QString pathId = shapePathId + "_ip"_L1;
+ if (!info.path.isAnimated()) {
+ QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
+ stream() << "PathSvg { path: \"" << svgPathString << "\" }";
+ } else {
+ stream() << "PathInterpolated {";
+ m_indentLevel++;
+ stream() << "id: " << pathId;
+ stream() << "svgPaths: [";
+ m_indentLevel++;
+ QQuickAnimatedProperty::PropertyAnimation pathFactorAnim = info.path.animation(0);
+ auto &frames = pathFactorAnim.frames;
+ int pathIdx = 0;
+ for (auto it = frames.begin(); it != frames.end(); ++it) {
+ QString svg = QQuickVectorImageGenerator::Utils::toSvgString(it->value<QPainterPath>());
+ stream() << "\"" << svg << "\"";
+ if (pathIdx < frames.size() - 1)
+ stream(SameLine) << ",";
+ *it = QVariant::fromValue(pathIdx++);
+ }
+ pathFactor.addAnimation(pathFactorAnim);
+ m_indentLevel--;
+ stream() << "]";
+ m_indentLevel--;
+ stream() << "}";
+ }
m_indentLevel--;
stream() << "}";
+ if (pathFactor.isAnimated())
+ generatePropertyAnimation(pathFactor, pathId, "factor"_L1);
+
if (info.trim.enabled) {
generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(".trim"), QStringLiteral("start"));
generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(".trim"), QStringLiteral("end"));
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
index 516ee387d9..bc1dd63a36 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
@@ -853,7 +853,7 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
info.fillColor.setDefaultValue(styleResolver->currentFillColor());
}
- info.painterPath = p;
+ info.path.setDefaultValue(QVariant::fromValue(p));
const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
QPen pen;
@@ -886,7 +886,7 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
strokeInfo.grad = *strokeGradient;
QPainterPathStroker stroker(pen);
- strokeInfo.painterPath = stroker.createStroke(p);
+ strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(p)));
m_generator->generatePath(strokeInfo, boundingRect);
}
};
@@ -1590,7 +1590,7 @@ void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &p
const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
- info.painterPath = path;
+ info.path.setDefaultValue(QVariant::fromValue(path));
info.fillColor.setDefaultValue(styleResolver->currentFillColor());
if (strokeGradient == nullptr) {
info.strokeStyle = StrokeStyle::fromPen(styleResolver->currentStroke());
@@ -1611,7 +1611,7 @@ void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &p
strokeInfo.grad = *strokeGradient;
QPainterPathStroker stroker(styleResolver->currentStroke());
- strokeInfo.painterPath = stroker.createStroke(path);
+ strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(path)));
m_generator->generatePath(strokeInfo);
}
diff --git a/src/quickvectorimage/helpers/qquickpathinterpolated.cpp b/src/quickvectorimage/helpers/qquickpathinterpolated.cpp
new file mode 100644
index 0000000000..1a2a06edb1
--- /dev/null
+++ b/src/quickvectorimage/helpers/qquickpathinterpolated.cpp
@@ -0,0 +1,127 @@
+// Copyright (C) 2025 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 "qquickpathinterpolated_p.h"
+#include <private/qquicksvgparser_p.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_STATIC_LOGGING_CATEGORY(lcPath, "qt.quick.shapes.path")
+
+/*!
+ \qmltype PathInterpolated
+ \nativetype QQuickPathInterpolated
+ \inqmlmodule QtQuick.VectorImage.Helpers
+ \brief Defines a path as an interpolated value between two of the paths in a list.
+ \since 6.11
+
+ This item provides a simple way to specify an interpolated path. This is useful for displaying
+ animated paths, where one path is gradually morphed into the next.
+
+ The interpolation end points are specified in the \l svgPaths list property, using the same
+ syntax as the PathSvg item. Based on the \l factor property, the resulting path will be an
+ interpolation between path \e n and \e n+1 in the list, where \e n is the integer part of the
+ factor. The fractional part determines the interpolation weight between the two.
+
+ \sa Path, PathSvg
+*/
+
+QQuickPathInterpolated::QQuickPathInterpolated(QObject *parent) : QQuickCurve{ parent }
+{
+ connect(this, &QQuickPathInterpolated::factorChanged, this, &QQuickPathElement::changed);
+ connect(this, &QQuickPathInterpolated::svgPathsChanged, this, &QQuickPathElement::changed);
+}
+
+/*!
+ \qmlproperty real QtQuick.VectorImage.Helpers::PathInterpolated::factor
+
+ This property holds the interpolation factor. The effective value is clamped to \e{[0,
+ svgPaths.size - 1]}.
+*/
+
+qreal QQuickPathInterpolated::factor() const
+{
+ return m_factor;
+}
+
+void QQuickPathInterpolated::setFactor(qreal newFactor)
+{
+ if (qFuzzyCompare(m_factor, newFactor) || !qIsFinite(newFactor))
+ return;
+ m_factor = newFactor;
+ emit factorChanged();
+}
+
+/*!
+ \qmlproperty stringlist QtQuick.VectorImage.Helpers::PathInterpolated::svgPaths
+
+ This property holds a list of paths, specified as SVG text strings in the manner of \l PathSvg.
+
+ The generation of an interpolated value between two of the paths in the list depends on them
+ having the same number and types of path elements. The resulting path has the same elements,
+ with coordinates linearly interpolated between the two source paths.
+*/
+
+QStringList QQuickPathInterpolated::svgPaths() const
+{
+ return m_svgPaths;
+}
+
+void QQuickPathInterpolated::setSvgPaths(const QStringList &newSvgPaths)
+{
+ if (m_svgPaths == newSvgPaths)
+ return;
+ m_svgPaths = newSvgPaths;
+ m_dirty = true;
+ emit svgPathsChanged();
+}
+
+void QQuickPathInterpolated::addToPath(QPainterPath &path, const QQuickPathData &)
+{
+ const qsizetype pathCount = m_svgPaths.size();
+ if (m_dirty) {
+ m_paths.clear();
+ m_paths.resize(pathCount);
+ for (qsizetype i = 0; i < pathCount; i++) {
+ if (!QQuickSvgParser::parsePathDataFast(m_svgPaths.at(i), m_paths[i]))
+ qCDebug(lcPath) << "Syntax error in svg path no." << i;
+ }
+ m_dirty = false;
+ }
+ Q_ASSERT(m_paths.size() == pathCount);
+
+ if (m_paths.isEmpty())
+ return;
+
+ QPainterPath res;
+ qreal factorIntValue = 0;
+ const qreal f = std::modf(m_factor, &factorIntValue);
+ const qsizetype pathIdx = qsizetype(qBound(qreal(0), factorIntValue, qreal(pathCount - 1)));
+
+ if (m_paths.size() == 1 || (pathIdx == 0 && f <= 0)) {
+ res = m_paths.first();
+ } else {
+ res = m_paths.at(pathIdx);
+ if ((pathIdx < pathCount - 1) && f > 0) {
+ const QPainterPath &p0 = m_paths.at(pathIdx);
+ const QPainterPath &p1 = m_paths.at(pathIdx + 1);
+ if (p0.elementCount() == p1.elementCount()) {
+ for (qsizetype i = 0; i < p0.elementCount(); i++) {
+ QPainterPath::Element e0 = p0.elementAt(i);
+ QPainterPath::Element e1 = p1.elementAt(i);
+ if (e0.type != e1.type) {
+ qCDebug(lcPath) << "Differing elements in svg path no." << i;
+ break;
+ }
+ QPointF cp = QPointF(e0) + f * (QPointF(e1) - QPointF(e0));
+ res.setElementPositionAt(i, cp.x(), cp.y());
+ }
+ } else {
+ qCDebug(lcPath) << "Differing element count in svg path no." << pathIdx + 1;
+ }
+ }
+ }
+ path.addPath(res);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorimage/helpers/qquickpathinterpolated_p.h b/src/quickvectorimage/helpers/qquickpathinterpolated_p.h
new file mode 100644
index 0000000000..7a24b9c8b4
--- /dev/null
+++ b/src/quickvectorimage/helpers/qquickpathinterpolated_p.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2025 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
+
+#ifndef QQUICKPATHINTERPOLATED_P_H
+#define QQUICKPATHINTERPOLATED_P_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 <QStringList>
+#include <QPainterPath>
+#include <private/qquickpath_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickPathInterpolated : public QQuickCurve
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal factor READ factor WRITE setFactor NOTIFY factorChanged)
+ Q_PROPERTY(QStringList svgPaths READ svgPaths WRITE setSvgPaths NOTIFY svgPathsChanged)
+
+ QML_NAMED_ELEMENT(PathInterpolated)
+ QML_ADDED_IN_VERSION(6, 11)
+public:
+ explicit QQuickPathInterpolated(QObject *parent = nullptr);
+ qreal factor() const;
+ void setFactor(qreal newFactor);
+ QStringList svgPaths() const;
+ void setSvgPaths(const QStringList &newSvgPaths);
+
+ void addToPath(QPainterPath &path, const QQuickPathData &) override;
+
+Q_SIGNALS:
+ void factorChanged();
+ void svgPathsChanged();
+
+private:
+ QStringList m_svgPaths;
+ QList<QPainterPath> m_paths;
+ qreal m_factor = 0;
+ bool m_dirty = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKPATHINTERPOLATED_P_H