forked from quickshell/quickshell
core/transformwatcher: add TransformWatcher
This commit is contained in:
parent
fd5b73adbb
commit
a06af243ad
|
@ -12,6 +12,7 @@ Checks: >
|
||||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||||
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||||
|
-cppcoreguidelines-avoid-goto,
|
||||||
google-build-using-namespace.
|
google-build-using-namespace.
|
||||||
google-explicit-constructor,
|
google-explicit-constructor,
|
||||||
google-global-names-in-headers,
|
google-global-names-in-headers,
|
||||||
|
|
2
docs
2
docs
|
@ -1 +1 @@
|
||||||
Subproject commit adb293de95f09ed7aa80b7bea615e4e2c9203be3
|
Subproject commit 7c6cdbcd7b172a354669c0d2cb64bd1fbe1ca75f
|
2
examples
2
examples
|
@ -1 +1 @@
|
||||||
Subproject commit 57078cd364cbceebff431ad136986234d7a538bc
|
Subproject commit b9e744b50673304dfddb68f3da2a2e906d028b96
|
|
@ -23,6 +23,7 @@ qt_add_library(quickshell-core STATIC
|
||||||
lazyloader.cpp
|
lazyloader.cpp
|
||||||
easingcurve.cpp
|
easingcurve.cpp
|
||||||
iconimageprovider.cpp
|
iconimageprovider.cpp
|
||||||
|
transformwatcher.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
||||||
|
|
|
@ -16,5 +16,6 @@ headers = [
|
||||||
"singleton.hpp",
|
"singleton.hpp",
|
||||||
"lazyloader.hpp",
|
"lazyloader.hpp",
|
||||||
"easingcurve.hpp",
|
"easingcurve.hpp",
|
||||||
|
"transformwatcher.hpp",
|
||||||
]
|
]
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -5,3 +5,4 @@ function (qs_test name)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
qs_test(popupwindow popupwindow.cpp)
|
qs_test(popupwindow popupwindow.cpp)
|
||||||
|
qs_test(transformwatcher transformwatcher.cpp)
|
||||||
|
|
105
src/core/test/transformwatcher.cpp
Normal file
105
src/core/test/transformwatcher.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include "transformwatcher.hpp"
|
||||||
|
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qquickitem.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
|
#include <qtest.h>
|
||||||
|
#include <qtestcase.h>
|
||||||
|
|
||||||
|
#include "../transformwatcher.hpp"
|
||||||
|
|
||||||
|
void TestTransformWatcher::aParentOfB() { // NOLINT
|
||||||
|
auto a = QQuickItem();
|
||||||
|
a.setObjectName("a");
|
||||||
|
auto b = QQuickItem();
|
||||||
|
b.setObjectName("b");
|
||||||
|
b.setParentItem(&a);
|
||||||
|
|
||||||
|
auto watcher = TransformWatcher();
|
||||||
|
watcher.setA(&a);
|
||||||
|
watcher.setB(&b);
|
||||||
|
|
||||||
|
QCOMPARE(watcher.parentChain, {&a});
|
||||||
|
QCOMPARE(watcher.childChain, {&b});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTransformWatcher::bParentOfA() { // NOLINT
|
||||||
|
auto a = QQuickItem();
|
||||||
|
a.setObjectName("a");
|
||||||
|
auto b = QQuickItem();
|
||||||
|
b.setObjectName("b");
|
||||||
|
a.setParentItem(&b);
|
||||||
|
|
||||||
|
auto watcher = TransformWatcher();
|
||||||
|
watcher.setA(&a);
|
||||||
|
watcher.setB(&b);
|
||||||
|
|
||||||
|
QCOMPARE(watcher.parentChain, (QList {&a, &b}));
|
||||||
|
QCOMPARE(watcher.childChain, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// a
|
||||||
|
// p1 b
|
||||||
|
// p2 c1
|
||||||
|
// p3
|
||||||
|
void TestTransformWatcher::aParentChainB() { // NOLINT
|
||||||
|
auto a = QQuickItem();
|
||||||
|
a.setObjectName("a");
|
||||||
|
auto b = QQuickItem();
|
||||||
|
b.setObjectName("b");
|
||||||
|
|
||||||
|
auto p1 = QQuickItem();
|
||||||
|
p1.setObjectName("p1");
|
||||||
|
auto p2 = QQuickItem();
|
||||||
|
p2.setObjectName("p2");
|
||||||
|
auto p3 = QQuickItem();
|
||||||
|
p3.setObjectName("p3");
|
||||||
|
auto c1 = QQuickItem();
|
||||||
|
c1.setObjectName("c1");
|
||||||
|
|
||||||
|
a.setParentItem(&p1);
|
||||||
|
p1.setParentItem(&p2);
|
||||||
|
p2.setParentItem(&p3);
|
||||||
|
|
||||||
|
b.setParentItem(&c1);
|
||||||
|
c1.setParentItem(&p3);
|
||||||
|
|
||||||
|
auto watcher = TransformWatcher();
|
||||||
|
watcher.setA(&a);
|
||||||
|
watcher.setB(&b);
|
||||||
|
|
||||||
|
QCOMPARE(watcher.parentChain, (QList {&a, &p1, &p2, &p3}));
|
||||||
|
QCOMPARE(watcher.childChain, (QList {&b, &c1}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTransformWatcher::multiWindow() { // NOLINT
|
||||||
|
auto a = QQuickItem();
|
||||||
|
a.setObjectName("a");
|
||||||
|
auto b = QQuickItem();
|
||||||
|
b.setObjectName("b");
|
||||||
|
|
||||||
|
auto p = QQuickItem();
|
||||||
|
p.setObjectName("p");
|
||||||
|
auto c = QQuickItem();
|
||||||
|
c.setObjectName("c");
|
||||||
|
|
||||||
|
a.setParentItem(&p);
|
||||||
|
b.setParentItem(&c);
|
||||||
|
|
||||||
|
auto aW = QQuickWindow();
|
||||||
|
auto bW = QQuickWindow();
|
||||||
|
|
||||||
|
p.setParentItem(aW.contentItem());
|
||||||
|
c.setParentItem(bW.contentItem());
|
||||||
|
|
||||||
|
auto watcher = TransformWatcher();
|
||||||
|
watcher.setA(&a);
|
||||||
|
watcher.setB(&b);
|
||||||
|
|
||||||
|
QCOMPARE(watcher.parentChain, (QList {&a, &p, aW.contentItem()}));
|
||||||
|
QCOMPARE(watcher.childChain, (QList {&b, &c, bW.contentItem()}));
|
||||||
|
QCOMPARE(watcher.parentWindow, &aW);
|
||||||
|
QCOMPARE(watcher.childWindow, &bW);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(TestTransformWatcher);
|
14
src/core/test/transformwatcher.hpp
Normal file
14
src/core/test/transformwatcher.hpp
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
class TestTransformWatcher: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void aParentOfB();
|
||||||
|
void bParentOfA();
|
||||||
|
void aParentChainB();
|
||||||
|
void multiWindow();
|
||||||
|
};
|
136
src/core/transformwatcher.cpp
Normal file
136
src/core/transformwatcher.cpp
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
#include "transformwatcher.hpp"
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdebug.h>
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qquickitem.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
|
|
||||||
|
void TransformWatcher::resolveChains(QQuickItem* a, QQuickItem* b, QQuickItem* commonParent) {
|
||||||
|
if (a == nullptr || b == nullptr) return;
|
||||||
|
|
||||||
|
auto aChain = QVector<QQuickItem*>();
|
||||||
|
auto bChain = QVector<QQuickItem*>();
|
||||||
|
|
||||||
|
auto* aParent = a;
|
||||||
|
auto* bParent = b;
|
||||||
|
|
||||||
|
// resolve the parent chain of b. if a is in the chain break early
|
||||||
|
while (bParent != nullptr) {
|
||||||
|
bChain.push_back(bParent);
|
||||||
|
|
||||||
|
if (bParent == a) {
|
||||||
|
aChain.push_back(a);
|
||||||
|
goto chainResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bParent == commonParent) break;
|
||||||
|
bParent = bParent->parentItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve the parent chain of a, breaking as soon as b is found
|
||||||
|
while (aParent != nullptr) {
|
||||||
|
aChain.push_back(aParent);
|
||||||
|
|
||||||
|
for (auto bParent = bChain.begin(); bParent != bChain.end(); bParent++) {
|
||||||
|
if (*bParent == aParent) {
|
||||||
|
bParent++;
|
||||||
|
bChain.erase(bParent, bChain.end());
|
||||||
|
goto chainResolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aParent == commonParent) break;
|
||||||
|
aParent = aParent->parentItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commonParent != nullptr && aParent == commonParent) {
|
||||||
|
qWarning() << this << "failed to find a common parent between" << a << "and" << b
|
||||||
|
<< "due to incorrectly set commonParent" << commonParent;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chainResolved:
|
||||||
|
|
||||||
|
this->parentChain = aChain;
|
||||||
|
if (bChain.last() == aChain.last()) bChain.removeLast();
|
||||||
|
this->childChain = bChain;
|
||||||
|
|
||||||
|
if (a->window() != b->window()) {
|
||||||
|
this->parentWindow = a->window();
|
||||||
|
this->childWindow = b->window();
|
||||||
|
} else {
|
||||||
|
this->parentWindow = nullptr;
|
||||||
|
this->childWindow = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformWatcher::resolveChains() {
|
||||||
|
this->resolveChains(this->mA, this->mB, this->mCommonParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformWatcher::linkItem(QQuickItem* item) const {
|
||||||
|
QObject::connect(item, &QQuickItem::xChanged, this, &TransformWatcher::transformChanged);
|
||||||
|
QObject::connect(item, &QQuickItem::yChanged, this, &TransformWatcher::transformChanged);
|
||||||
|
QObject::connect(item, &QQuickItem::widthChanged, this, &TransformWatcher::transformChanged);
|
||||||
|
QObject::connect(item, &QQuickItem::heightChanged, this, &TransformWatcher::transformChanged);
|
||||||
|
QObject::connect(item, &QQuickItem::scaleChanged, this, &TransformWatcher::transformChanged);
|
||||||
|
QObject::connect(item, &QQuickItem::rotationChanged, this, &TransformWatcher::transformChanged);
|
||||||
|
|
||||||
|
QObject::connect(item, &QQuickItem::parentChanged, this, &TransformWatcher::recalcChains);
|
||||||
|
QObject::connect(item, &QQuickItem::windowChanged, this, &TransformWatcher::recalcChains);
|
||||||
|
QObject::connect(item, &QObject::destroyed, this, &TransformWatcher::recalcChains);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformWatcher::linkChains() {
|
||||||
|
for (auto* item: this->parentChain) {
|
||||||
|
this->linkItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* item: this->childChain) {
|
||||||
|
this->linkItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformWatcher::unlinkChains() {
|
||||||
|
for (auto* item: this->parentChain) {
|
||||||
|
QObject::disconnect(item, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* item: this->childChain) {
|
||||||
|
QObject::disconnect(item, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformWatcher::recalcChains() {
|
||||||
|
this->unlinkChains();
|
||||||
|
this->resolveChains();
|
||||||
|
this->linkChains();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem* TransformWatcher::a() const { return this->mA; }
|
||||||
|
|
||||||
|
void TransformWatcher::setA(QQuickItem* a) {
|
||||||
|
if (this->mA == a) return;
|
||||||
|
this->mA = a;
|
||||||
|
this->recalcChains();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem* TransformWatcher::b() const { return this->mB; }
|
||||||
|
|
||||||
|
void TransformWatcher::setB(QQuickItem* b) {
|
||||||
|
if (this->mB == b) return;
|
||||||
|
this->mB = b;
|
||||||
|
this->recalcChains();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem* TransformWatcher::commonParent() const { return this->mCommonParent; }
|
||||||
|
|
||||||
|
void TransformWatcher::setCommonParent(QQuickItem* commonParent) {
|
||||||
|
if (this->mCommonParent == commonParent) return;
|
||||||
|
this->mCommonParent = commonParent;
|
||||||
|
this->resolveChains();
|
||||||
|
}
|
84
src/core/transformwatcher.hpp
Normal file
84
src/core/transformwatcher.hpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qquickitem.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#ifdef QS_TEST
|
||||||
|
class TestTransformWatcher;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///! Monitor of all geometry changes between two objects.
|
||||||
|
/// The TransformWatcher monitors all properties that affect the geometry
|
||||||
|
/// of two `Item`s relative to eachother.
|
||||||
|
///
|
||||||
|
/// > [!INFO] The algorithm responsible for determining the relationship
|
||||||
|
/// > between `a` and `b` is biased towards `a` being a parent of `b`,
|
||||||
|
/// > or `a` being closer to the common parent of `a` and `b` than `b`.
|
||||||
|
class TransformWatcher: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
// clang-format off
|
||||||
|
Q_PROPERTY(QQuickItem* a READ a WRITE setA NOTIFY aChanged);
|
||||||
|
Q_PROPERTY(QQuickItem* b READ b WRITE setB NOTIFY bChanged);
|
||||||
|
/// Known common parent of both `a` and `b`. Defaults to `null`.
|
||||||
|
///
|
||||||
|
/// This property can be used to optimize the algorithm that figures out
|
||||||
|
/// the relationship between `a` and `b`. Setting it to something that is not
|
||||||
|
/// a common parent of both `a` and `b` will prevent the path from being determined
|
||||||
|
/// correctly, and setting it to `null` will disable the optimization.
|
||||||
|
Q_PROPERTY(QQuickItem* commonParent READ commonParent WRITE setCommonParent NOTIFY commonParentChanged);
|
||||||
|
/// This property is updated whenever the geometry of any item in the path from `a` to `b` changes.
|
||||||
|
///
|
||||||
|
/// Its value is undefined, and is intended to trigger an expression update.
|
||||||
|
Q_PROPERTY(QObject* transform READ transform NOTIFY transformChanged);
|
||||||
|
// clang-format on
|
||||||
|
QML_ELEMENT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TransformWatcher(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
[[nodiscard]] QQuickItem* a() const;
|
||||||
|
void setA(QQuickItem* a);
|
||||||
|
|
||||||
|
[[nodiscard]] QQuickItem* b() const;
|
||||||
|
void setB(QQuickItem* b);
|
||||||
|
|
||||||
|
[[nodiscard]] QQuickItem* commonParent() const;
|
||||||
|
void setCommonParent(QQuickItem* commonParent);
|
||||||
|
|
||||||
|
[[nodiscard]] QObject* transform() const { return nullptr; } // NOLINT
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void transformChanged();
|
||||||
|
|
||||||
|
void aChanged();
|
||||||
|
void bChanged();
|
||||||
|
void commonParentChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void recalcChains();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void resolveChains(QQuickItem* a, QQuickItem* b, QQuickItem* commonParent);
|
||||||
|
void resolveChains();
|
||||||
|
void linkItem(QQuickItem* item) const;
|
||||||
|
void linkChains();
|
||||||
|
void unlinkChains();
|
||||||
|
|
||||||
|
QQuickItem* mA = nullptr;
|
||||||
|
QQuickItem* mB = nullptr;
|
||||||
|
QQuickItem* mCommonParent = nullptr;
|
||||||
|
|
||||||
|
// a -> traverse parent chain -> parent window -> global scope -> child window -> traverse child chain -> b
|
||||||
|
QList<QQuickItem*> parentChain;
|
||||||
|
QList<QQuickItem*> childChain;
|
||||||
|
QQuickWindow* parentWindow = nullptr;
|
||||||
|
QQuickWindow* childWindow = nullptr;
|
||||||
|
|
||||||
|
#ifdef QS_TEST
|
||||||
|
friend class TestTransformWatcher;
|
||||||
|
#endif
|
||||||
|
};
|
Loading…
Reference in a new issue