diff options
author | Ulf Hermann <[email protected]> | 2025-06-24 12:11:20 +0200 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2025-07-02 08:37:28 +0000 |
commit | bc586861768ba411be89ec099b796c3c3387b3d6 (patch) | |
tree | 0413b9ed70fe0f14b03ca1833f12db7f25e71b45 | |
parent | a8b64c41f168d986b1404c78ebd4624105f4f855 (diff) |
QtQuick: Optimize path element handling
The path elements need to implement removeLast and replace. Otherwise
such operations are extremely slow. Furthermore, we can add nullptr to
the path list. Don't try to connect or disconnect those.
Pick-to: 6.10
Task-number: QTBUG-137554
Change-Id: I15352861d62f1b716954a482444fe71dc4518ea3
Reviewed-by: Fabian Kosmale <[email protected]>
-rw-r--r-- | src/quick/util/qquickpath.cpp | 86 | ||||
-rw-r--r-- | src/quick/util/qquickpath_p.h | 3 | ||||
-rw-r--r-- | src/quick/util/qquickpath_p_p.h | 53 | ||||
-rw-r--r-- | tests/auto/quick/qquickpath/data/splicePathList.qml | 54 | ||||
-rw-r--r-- | tests/auto/quick/qquickpath/tst_qquickpath.cpp | 14 |
5 files changed, 189 insertions, 21 deletions
diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index 3b69a4384b..0ce18f5951 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -16,27 +16,53 @@ QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcPath, "qt.quick.shapes.path") -void QQuickPathPrivate::appendPathElement(QQuickPathElement *pathElement) +void QQuickPathPrivate::enablePathElement(QQuickPathElement *pathElement) { Q_Q(QQuickPath); - _pathElements.append(pathElement); - if (componentComplete) { - QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement); - if (curve) - _pathCurves.append(curve); - else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement)) - _pathTexts.append(text); - else { - QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement); - if (attribute && !_attributes.contains(attribute->name())) - _attributes.append(attribute->name()); - } - - q->processPath(); + if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement)) { + _pathCurves.append(curve); + } else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement)) { + _pathTexts.append(text); + } else { + QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement); + if (attribute && !_attributes.contains(attribute->name())) + _attributes.append(attribute->name()); + } + // There may be multiple entries of the same value + if (!_pathElements.contains(pathElement)) q->connect(pathElement, SIGNAL(changed()), q, SLOT(processPath())); +} + +void QQuickPathPrivate::disablePathElement(QQuickPathElement *pathElement) +{ + Q_Q(QQuickPath); + + if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement)) { + _pathCurves.removeOne(curve); + } else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement)) { + _pathTexts.removeOne(text); + } else if (QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement)) { + const QString name = attribute->name(); + bool found = false; + + // TODO: This is rather expensive. Why do the attributes have to be unique? + for (QQuickPathElement *other : std::as_const(_pathElements)) { + QQuickPathAttribute *otherAttribute = qobject_cast<QQuickPathAttribute *>(other); + if (otherAttribute && otherAttribute->name() == name) { + found = true; + break; + } + } + + if (!found) + _attributes.removeOne(name); } + + // There may be multiple entries of the same value + if (!_pathElements.contains(pathElement)) + q->disconnect(pathElement, SIGNAL(changed()), q, SLOT(processPath())); } /*! @@ -256,7 +282,9 @@ QQmlListProperty<QQuickPathElement> QQuickPath::pathElements() pathElements_append, pathElements_count, pathElements_at, - pathElements_clear); + pathElements_clear, + pathElements_replace, + pathElements_removeLast); } static QQuickPathPrivate *privatePath(QObject *object) @@ -300,6 +328,18 @@ void QQuickPath::pathElements_clear(QQmlListProperty<QQuickPathElement> *propert emit path->changed(); } +void QQuickPath::pathElements_replace( + QQmlListProperty<QQuickPathElement> *property, qsizetype position, + QQuickPathElement *pathElement) +{ + privatePath(property->object)->replacePathElement(position, pathElement); +} + +void QQuickPath::pathElements_removeLast(QQmlListProperty<QQuickPathElement> *property) +{ + privatePath(property->object)->removeLastPathElement(); +} + void QQuickPath::interpolate(int idx, const QString &name, qreal value) { Q_D(QQuickPath); @@ -577,16 +617,20 @@ void QQuickPath::disconnectPathElements() { Q_D(const QQuickPath); - for (QQuickPathElement *pathElement : d->_pathElements) - disconnect(pathElement, SIGNAL(changed()), this, SLOT(processPath())); + for (QQuickPathElement *pathElement : d->_pathElements) { + if (pathElement) + disconnect(pathElement, SIGNAL(changed()), this, SLOT(processPath())); + } } void QQuickPath::connectPathElements() { Q_D(const QQuickPath); - for (QQuickPathElement *pathElement : d->_pathElements) - connect(pathElement, SIGNAL(changed()), this, SLOT(processPath())); + for (QQuickPathElement *pathElement : d->_pathElements) { + if (pathElement) + connect(pathElement, SIGNAL(changed()), this, SLOT(processPath())); + } } void QQuickPath::gatherAttributes() @@ -595,6 +639,8 @@ void QQuickPath::gatherAttributes() QSet<QString> attributes; + Q_ASSERT(d->_pathCurves.isEmpty()); + // First gather up all the attributes for (QQuickPathElement *pathElement : std::as_const(d->_pathElements)) { if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement)) diff --git a/src/quick/util/qquickpath_p.h b/src/quick/util/qquickpath_p.h index f4086925ab..3b32712325 100644 --- a/src/quick/util/qquickpath_p.h +++ b/src/quick/util/qquickpath_p.h @@ -677,6 +677,9 @@ protected: static void pathElements_append(QQmlListProperty<QQuickPathElement> *, QQuickPathElement *); static qsizetype pathElements_count(QQmlListProperty<QQuickPathElement> *); static void pathElements_clear(QQmlListProperty<QQuickPathElement> *); + static void pathElements_replace( + QQmlListProperty<QQuickPathElement> *, qsizetype, QQuickPathElement *); + static void pathElements_removeLast(QQmlListProperty<QQuickPathElement> *); private Q_SLOTS: void processPath(); diff --git a/src/quick/util/qquickpath_p_p.h b/src/quick/util/qquickpath_p_p.h index c19e719924..8f13d403fc 100644 --- a/src/quick/util/qquickpath_p_p.h +++ b/src/quick/util/qquickpath_p_p.h @@ -41,7 +41,54 @@ public: , processPending(false), asynchronous(false), useCustomPath(false) {} - void appendPathElement(QQuickPathElement *pathElement); + void appendPathElement(QQuickPathElement *pathElement) + { + _pathElements.append(pathElement); + if (pathElement && componentComplete) { + enablePathElement(pathElement); + q_func()->processPath(); + } + } + + void removeLastPathElement() + { + QQuickPathElement *pathElement = _pathElements.takeLast(); + if (pathElement && componentComplete) { + disablePathElement(pathElement); + q_func()->processPath(); + } + } + + void replacePathElement(qsizetype position, QQuickPathElement *pathElement) + { + if (position >= _pathElements.length()) + _pathElements.resize(position + 1, nullptr); + + if (!componentComplete) { + _pathElements[position] = pathElement; + return; + } + + bool changed = false; + + QQuickPathElement *oldElement = _pathElements.at(position); + if (oldElement == pathElement) + return; + + if (oldElement) { + disablePathElement(oldElement); + changed = true; + } + + _pathElements[position] = pathElement; + if (pathElement) { + enablePathElement(pathElement); + changed = true; + } + + if (changed) + q_func()->processPath(); + } QPainterPath _path; QList<QQuickPathElement*> _pathElements; @@ -62,6 +109,10 @@ public: bool processPending : 1; bool asynchronous : 1; bool useCustomPath : 1; + +private: + void enablePathElement(QQuickPathElement *pathElement); + void disablePathElement(QQuickPathElement *pathElement); }; QT_END_NAMESPACE diff --git a/tests/auto/quick/qquickpath/data/splicePathList.qml b/tests/auto/quick/qquickpath/data/splicePathList.qml new file mode 100644 index 0000000000..8e9dfba708 --- /dev/null +++ b/tests/auto/quick/qquickpath/data/splicePathList.qml @@ -0,0 +1,54 @@ +import QtQuick + +Item { + id: oldShape + + Path { + id: oldPath + + startX: -100 + startY: 100 + + PathCubic { + x: 1000 + y: 200 + control1X: 1 + control1Y: 2 + control2X: 3 + control2Y: 4 + } + } + + Instantiator { + id: oldPathCubics + + delegate: + PathCubic { + x: 1000 + y: 200 + control1X: 1 + control1Y: 2 + control2X: 3 + control2Y: 4 + } + + onObjectAdded: (index, object) => { + oldPath.pathElements.splice(index, 0, object) + gc() + } + onObjectRemoved: (index, object) => { + oldPath.pathElements.splice(index, 1) + } + } + + Timer { + Component.onCompleted: start() + interval: 2 + repeat: true + property bool v: false + onTriggered: { + oldPathCubics.model = v ? 0 : 100 + v = !v + } + } +} diff --git a/tests/auto/quick/qquickpath/tst_qquickpath.cpp b/tests/auto/quick/qquickpath/tst_qquickpath.cpp index 3c0c706990..a7646ae658 100644 --- a/tests/auto/quick/qquickpath/tst_qquickpath.cpp +++ b/tests/auto/quick/qquickpath/tst_qquickpath.cpp @@ -29,6 +29,7 @@ private slots: void appendRemove(); void asynchronous(); void cornerProperties(); + void splicePathList(); private: void arc(QSizeF scale); @@ -550,6 +551,19 @@ void tst_QuickPath::cornerProperties() QCOMPARE(pathRectangle.bottomRightRadius(), 0.0); } +void tst_QuickPath::splicePathList() +{ + // We don't want to see warnings about connecting to nullptr, and we don't want to crash. + // And we don't want to hit the test timeout. + QTest::failOnWarning(); + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("splicePathList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} + QTEST_MAIN(tst_QuickPath) #include "tst_qquickpath.moc" |