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-avoid-const-or-ref-data-members,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
google-build-using-namespace.
|
||||
google-explicit-constructor,
|
||||
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
|
||||
easingcurve.cpp
|
||||
iconimageprovider.cpp
|
||||
transformwatcher.cpp
|
||||
)
|
||||
|
||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
||||
|
|
|
@ -16,5 +16,6 @@ headers = [
|
|||
"singleton.hpp",
|
||||
"lazyloader.hpp",
|
||||
"easingcurve.hpp",
|
||||
"transformwatcher.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
|
@ -5,3 +5,4 @@ function (qs_test name)
|
|||
endfunction()
|
||||
|
||||
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