forked from quickshell/quickshell
		
	core/transformwatcher: add TransformWatcher
This commit is contained in:
		
							parent
							
								
									fd5b73adbb
								
							
						
					
					
						commit
						a06af243ad
					
				
					 10 changed files with 345 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue