diff options
| author | Eirik Aavitsland <eirik.aavitsland@qt.io> | 2025-11-11 16:07:42 +0100 |
|---|---|---|
| committer | Eirik Aavitsland <eirik.aavitsland@qt.io> | 2025-11-26 09:49:39 +0100 |
| commit | cc506e0c96357b835e15e9ef94e41acd64a3d9db (patch) | |
| tree | 7a240b5aa5ba63e3be79388c3b382940cb63b025 /src | |
| parent | df3b945c2e430120ddc7fd17e4f0fefd5d7cc2f8 (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.h | 2 | ||||
| -rw-r--r-- | src/quickvectorimage/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qquickgenerator.cpp | 4 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qquicknodeinfo_p.h | 2 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qquickqmlgenerator.cpp | 32 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qsvgvisitorimpl.cpp | 8 | ||||
| -rw-r--r-- | src/quickvectorimage/helpers/qquickpathinterpolated.cpp | 127 | ||||
| -rw-r--r-- | src/quickvectorimage/helpers/qquickpathinterpolated_p.h | 54 |
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 |
