forked from quickshell/quickshell
		
	core/lazyloader: add LazyLoader
Also fixes qml incubation in general, which was completely broken, meaning the native qml Loader type should also work now.
This commit is contained in:
		
							parent
							
								
									8d742e315e
								
							
						
					
					
						commit
						518977932d
					
				
					 11 changed files with 499 additions and 0 deletions
				
			
		| 
						 | 
					@ -19,6 +19,8 @@ qt_add_library(quickshell-core STATIC
 | 
				
			||||||
	generation.cpp
 | 
						generation.cpp
 | 
				
			||||||
	scan.cpp
 | 
						scan.cpp
 | 
				
			||||||
	qsintercept.cpp
 | 
						qsintercept.cpp
 | 
				
			||||||
 | 
						incubator.cpp
 | 
				
			||||||
 | 
						lazyloader.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}")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
#include "floatingwindow.hpp"
 | 
					#include "floatingwindow.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
#include <qqmllist.h>
 | 
					#include <qqmllist.h>
 | 
				
			||||||
#include <qquickitem.h>
 | 
					#include <qquickitem.h>
 | 
				
			||||||
#include <qtypes.h>
 | 
					#include <qtypes.h>
 | 
				
			||||||
| 
						 | 
					@ -37,6 +38,8 @@ FloatingWindowInterface::FloatingWindowInterface(QObject* parent)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FloatingWindowInterface::onReload(QObject* oldInstance) {
 | 
					void FloatingWindowInterface::onReload(QObject* oldInstance) {
 | 
				
			||||||
 | 
						QQmlEngine::setContextForObject(this->window, QQmlEngine::contextForObject(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto* old = qobject_cast<FloatingWindowInterface*>(oldInstance);
 | 
						auto* old = qobject_cast<FloatingWindowInterface*>(oldInstance);
 | 
				
			||||||
	this->window->onReload(old != nullptr ? old->window : nullptr);
 | 
						this->window->onReload(old != nullptr ? old->window : nullptr);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,13 +3,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qcontainerfwd.h>
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
#include <qcoreapplication.h>
 | 
					#include <qcoreapplication.h>
 | 
				
			||||||
 | 
					#include <qdebug.h>
 | 
				
			||||||
#include <qfilesystemwatcher.h>
 | 
					#include <qfilesystemwatcher.h>
 | 
				
			||||||
#include <qhash.h>
 | 
					#include <qhash.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qqmlcontext.h>
 | 
					#include <qqmlcontext.h>
 | 
				
			||||||
#include <qqmlengine.h>
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
 | 
					#include <qqmlincubator.h>
 | 
				
			||||||
#include <qtimer.h>
 | 
					#include <qtimer.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "incubator.hpp"
 | 
				
			||||||
#include "plugin.hpp"
 | 
					#include "plugin.hpp"
 | 
				
			||||||
#include "qsintercept.hpp"
 | 
					#include "qsintercept.hpp"
 | 
				
			||||||
#include "reload.hpp"
 | 
					#include "reload.hpp"
 | 
				
			||||||
| 
						 | 
					@ -23,6 +28,7 @@ EngineGeneration::EngineGeneration(QmlScanner scanner)
 | 
				
			||||||
	g_generations.insert(&this->engine, this);
 | 
						g_generations.insert(&this->engine, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->engine.setNetworkAccessManagerFactory(&this->interceptNetFactory);
 | 
						this->engine.setNetworkAccessManagerFactory(&this->interceptNetFactory);
 | 
				
			||||||
 | 
						this->engine.setIncubationController(&this->delayedIncubationController);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EngineGeneration::~EngineGeneration() {
 | 
					EngineGeneration::~EngineGeneration() {
 | 
				
			||||||
| 
						 | 
					@ -31,6 +37,13 @@ EngineGeneration::~EngineGeneration() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void EngineGeneration::onReload(EngineGeneration* old) {
 | 
					void EngineGeneration::onReload(EngineGeneration* old) {
 | 
				
			||||||
 | 
						if (old != nullptr) {
 | 
				
			||||||
 | 
							// if the old generation holds the window incubation controller as the
 | 
				
			||||||
 | 
							// new generation acquires it then incubators will hang intermittently
 | 
				
			||||||
 | 
							old->incubationControllers.clear();
 | 
				
			||||||
 | 
							old->engine.setIncubationController(&old->delayedIncubationController);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto* app = QCoreApplication::instance();
 | 
						auto* app = QCoreApplication::instance();
 | 
				
			||||||
	QObject::connect(&this->engine, &QQmlEngine::quit, app, &QCoreApplication::quit);
 | 
						QObject::connect(&this->engine, &QQmlEngine::quit, app, &QCoreApplication::quit);
 | 
				
			||||||
	QObject::connect(&this->engine, &QQmlEngine::exit, app, &QCoreApplication::exit);
 | 
						QObject::connect(&this->engine, &QQmlEngine::exit, app, &QCoreApplication::exit);
 | 
				
			||||||
| 
						 | 
					@ -75,6 +88,51 @@ void EngineGeneration::setWatchingFiles(bool watching) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) {
 | 
				
			||||||
 | 
						auto* obj = dynamic_cast<QObject*>(controller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We only want controllers that we can swap out if destroyed.
 | 
				
			||||||
 | 
						// This happens if the window owning the active controller dies.
 | 
				
			||||||
 | 
						if (obj == nullptr) {
 | 
				
			||||||
 | 
							qCDebug(logIncubator) << "Could not register incubation controller as it is not a QObject"
 | 
				
			||||||
 | 
							                      << controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->incubationControllers.push_back(controller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    obj,
 | 
				
			||||||
 | 
						    &QObject::destroyed,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &EngineGeneration::incubationControllerDestroyed
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logIncubator) << "Registered incubation controller" << controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->engine.incubationController() == &this->delayedIncubationController) {
 | 
				
			||||||
 | 
							this->assignIncubationController();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void EngineGeneration::incubationControllerDestroyed() {
 | 
				
			||||||
 | 
						qCDebug(logIncubator) << "Active incubation controller destroyed, deregistering";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->incubationControllers.removeAll(dynamic_cast<QQmlIncubationController*>(this->sender()));
 | 
				
			||||||
 | 
						this->assignIncubationController();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void EngineGeneration::assignIncubationController() {
 | 
				
			||||||
 | 
						auto* controller = this->incubationControllers.first();
 | 
				
			||||||
 | 
						if (controller == nullptr) controller = &this->delayedIncubationController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logIncubator) << "Assigning incubation controller to engine:" << controller
 | 
				
			||||||
 | 
						                      << "fallback:" << (controller == &this->delayedIncubationController);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->engine.setIncubationController(controller);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) {
 | 
					EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) {
 | 
				
			||||||
	while (object != nullptr) {
 | 
						while (object != nullptr) {
 | 
				
			||||||
		auto* context = QQmlEngine::contextForObject(object);
 | 
							auto* context = QQmlEngine::contextForObject(object);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,12 @@
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
#include <qfilesystemwatcher.h>
 | 
					#include <qfilesystemwatcher.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlincubator.h>
 | 
				
			||||||
#include <qtclasshelpermacros.h>
 | 
					#include <qtclasshelpermacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "incubator.hpp"
 | 
				
			||||||
#include "qsintercept.hpp"
 | 
					#include "qsintercept.hpp"
 | 
				
			||||||
#include "scan.hpp"
 | 
					#include "scan.hpp"
 | 
				
			||||||
#include "shell.hpp"
 | 
					#include "shell.hpp"
 | 
				
			||||||
| 
						 | 
					@ -21,6 +24,8 @@ public:
 | 
				
			||||||
	void onReload(EngineGeneration* old);
 | 
						void onReload(EngineGeneration* old);
 | 
				
			||||||
	void setWatchingFiles(bool watching);
 | 
						void setWatchingFiles(bool watching);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void registerIncubationController(QQmlIncubationController* controller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static EngineGeneration* findObjectGeneration(QObject* object);
 | 
						static EngineGeneration* findObjectGeneration(QObject* object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QmlScanner scanner;
 | 
						QmlScanner scanner;
 | 
				
			||||||
| 
						 | 
					@ -29,7 +34,15 @@ public:
 | 
				
			||||||
	ShellRoot* root = nullptr;
 | 
						ShellRoot* root = nullptr;
 | 
				
			||||||
	SingletonRegistry singletonRegistry;
 | 
						SingletonRegistry singletonRegistry;
 | 
				
			||||||
	QFileSystemWatcher* watcher = nullptr;
 | 
						QFileSystemWatcher* watcher = nullptr;
 | 
				
			||||||
 | 
						DelayedQmlIncubationController delayedIncubationController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void filesChanged();
 | 
						void filesChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void incubationControllerDestroyed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						void assignIncubationController();
 | 
				
			||||||
 | 
						QVector<QQmlIncubationController*> incubationControllers;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/core/incubator.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/core/incubator.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					#include "incubator.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qqmlincubator.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logIncubator, "quickshell.incubator", QtWarningMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) {
 | 
				
			||||||
 | 
						switch (status) {
 | 
				
			||||||
 | 
						case QQmlIncubator::Ready: emit this->completed(); break;
 | 
				
			||||||
 | 
						case QQmlIncubator::Error: emit this->failed(); break;
 | 
				
			||||||
 | 
						default: break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/core/incubator.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/core/incubator.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,30 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlincubator.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_DECLARE_LOGGING_CATEGORY(logIncubator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QsQmlIncubator
 | 
				
			||||||
 | 
					    : public QObject
 | 
				
			||||||
 | 
					    , public QQmlIncubator {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit QsQmlIncubator(QsQmlIncubator::IncubationMode mode, QObject* parent = nullptr)
 | 
				
			||||||
 | 
						    : QObject(parent)
 | 
				
			||||||
 | 
						    , QQmlIncubator(mode) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void statusChanged(QQmlIncubator::Status status) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void completed();
 | 
				
			||||||
 | 
						void failed();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DelayedQmlIncubationController: public QQmlIncubationController {
 | 
				
			||||||
 | 
						// Do nothing.
 | 
				
			||||||
 | 
						// This ensures lazy loaders don't start blocking before onReload creates windows.
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										202
									
								
								src/core/lazyloader.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/core/lazyloader.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,202 @@
 | 
				
			||||||
 | 
					#include "lazyloader.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlcomponent.h>
 | 
				
			||||||
 | 
					#include <qqmlcontext.h>
 | 
				
			||||||
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
 | 
					#include <qqmlincubator.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "incubator.hpp"
 | 
				
			||||||
 | 
					#include "reload.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::onReload(QObject* oldInstance) {
 | 
				
			||||||
 | 
						auto* old = qobject_cast<LazyLoader*>(oldInstance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->incubateIfReady(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (old != nullptr && old->mItem != nullptr && this->incubator != nullptr) {
 | 
				
			||||||
 | 
							this->incubator->forceCompletion();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mItem != nullptr) {
 | 
				
			||||||
 | 
							if (auto* reloadable = qobject_cast<Reloadable*>(this->mItem)) {
 | 
				
			||||||
 | 
								reloadable->onReload(old == nullptr ? nullptr : old->mItem);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								Reloadable::reloadRecursive(this->mItem, old);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->postReload = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* LazyLoader::item() {
 | 
				
			||||||
 | 
						if (this->isLoading()) this->setActive(true);
 | 
				
			||||||
 | 
						return this->mItem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::setItem(QObject* item) {
 | 
				
			||||||
 | 
						if (item == this->mItem) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mItem != nullptr) {
 | 
				
			||||||
 | 
							this->mItem->deleteLater();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mItem = item;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (item != nullptr) {
 | 
				
			||||||
 | 
							item->setParent(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this->postReload) {
 | 
				
			||||||
 | 
								if (auto* reloadable = qobject_cast<Reloadable*>(this->mItem)) {
 | 
				
			||||||
 | 
									reloadable->onReload(nullptr);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									Reloadable::reloadRecursive(this->mItem, nullptr);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->targetActive = this->isActive();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->itemChanged();
 | 
				
			||||||
 | 
						emit this->activeChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LazyLoader::isLoading() const { return this->incubator != nullptr; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::setLoading(bool loading) {
 | 
				
			||||||
 | 
						if (loading == this->targetLoading || this->isActive()) return;
 | 
				
			||||||
 | 
						this->targetLoading = loading;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (loading) {
 | 
				
			||||||
 | 
							this->incubateIfReady();
 | 
				
			||||||
 | 
						} else if (this->mItem != nullptr) {
 | 
				
			||||||
 | 
							this->mItem->deleteLater();
 | 
				
			||||||
 | 
							this->mItem = nullptr;
 | 
				
			||||||
 | 
						} else if (this->incubator != nullptr) {
 | 
				
			||||||
 | 
							delete this->incubator;
 | 
				
			||||||
 | 
							this->incubator = nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LazyLoader::isActive() const { return this->mItem != nullptr; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::setActive(bool active) {
 | 
				
			||||||
 | 
						if (active == this->targetActive) return;
 | 
				
			||||||
 | 
						this->targetActive = active;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (active) {
 | 
				
			||||||
 | 
							if (this->isLoading()) {
 | 
				
			||||||
 | 
								this->incubator->forceCompletion();
 | 
				
			||||||
 | 
							} else if (!this->isActive()) {
 | 
				
			||||||
 | 
								this->incubateIfReady();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (this->isActive()) {
 | 
				
			||||||
 | 
							this->setItem(nullptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QQmlComponent* LazyLoader::component() const {
 | 
				
			||||||
 | 
						return this->cleanupComponent ? nullptr : this->mComponent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::setComponent(QQmlComponent* component) {
 | 
				
			||||||
 | 
						if (this->cleanupComponent) this->setSource(nullptr);
 | 
				
			||||||
 | 
						if (component == this->mComponent) return;
 | 
				
			||||||
 | 
						this->cleanupComponent = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mComponent != nullptr) {
 | 
				
			||||||
 | 
							QObject::disconnect(this->mComponent, nullptr, this, nullptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mComponent = component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (component != nullptr) {
 | 
				
			||||||
 | 
							QObject::connect(
 | 
				
			||||||
 | 
							    this->mComponent,
 | 
				
			||||||
 | 
							    &QObject::destroyed,
 | 
				
			||||||
 | 
							    this,
 | 
				
			||||||
 | 
							    &LazyLoader::onComponentDestroyed
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->componentChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::onComponentDestroyed() {
 | 
				
			||||||
 | 
						this->mComponent = nullptr;
 | 
				
			||||||
 | 
						// todo: figure out what happens to the incubator
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString LazyLoader::source() const { return this->mSource; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::setSource(QString source) {
 | 
				
			||||||
 | 
						if (!this->cleanupComponent) this->setComponent(nullptr);
 | 
				
			||||||
 | 
						if (source == this->mSource) return;
 | 
				
			||||||
 | 
						this->cleanupComponent = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mSource = std::move(source);
 | 
				
			||||||
 | 
						delete this->mComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->mSource.isEmpty()) {
 | 
				
			||||||
 | 
							auto* context = QQmlEngine::contextForObject(this);
 | 
				
			||||||
 | 
							this->mComponent = new QQmlComponent(
 | 
				
			||||||
 | 
							    context == nullptr ? nullptr : context->engine(),
 | 
				
			||||||
 | 
							    context == nullptr ? this->mSource : context->resolvedUrl(this->mSource)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this->mComponent->isError()) {
 | 
				
			||||||
 | 
								qWarning() << this->mComponent->errorString().toStdString().c_str();
 | 
				
			||||||
 | 
								delete this->mComponent;
 | 
				
			||||||
 | 
								this->mComponent = nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->mComponent = nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->sourceChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::incubateIfReady(bool overrideReloadCheck) {
 | 
				
			||||||
 | 
						if (!(this->postReload || overrideReloadCheck) || !(this->targetLoading || this->targetActive)
 | 
				
			||||||
 | 
						    || this->mComponent == nullptr || this->incubator != nullptr)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->incubator = new QsQmlIncubator(
 | 
				
			||||||
 | 
						    this->targetActive ? QQmlIncubator::Synchronous : QQmlIncubator::Asynchronous,
 | 
				
			||||||
 | 
						    this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						QObject::connect(this->incubator, &QsQmlIncubator::completed, this, &LazyLoader::onIncubationCompleted);
 | 
				
			||||||
 | 
						QObject::connect(this->incubator, &QsQmlIncubator::failed, this, &LazyLoader::onIncubationFailed);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->loadingChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mComponent->create(*this->incubator, QQmlEngine::contextForObject(this->mComponent));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::onIncubationCompleted() {
 | 
				
			||||||
 | 
						this->setItem(this->incubator->object());
 | 
				
			||||||
 | 
						delete this->incubator;
 | 
				
			||||||
 | 
						this->incubator = nullptr;
 | 
				
			||||||
 | 
						this->targetLoading = false;
 | 
				
			||||||
 | 
						emit this->loadingChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LazyLoader::onIncubationFailed() {
 | 
				
			||||||
 | 
						qWarning() << "Failed to create LazyLoader component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto& error: this->incubator->errors()) {
 | 
				
			||||||
 | 
							qWarning() << error;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delete this->incubator;
 | 
				
			||||||
 | 
						this->targetLoading = false;
 | 
				
			||||||
 | 
						emit this->loadingChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										163
									
								
								src/core/lazyloader.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/core/lazyloader.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,163 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QtQml/qqmlcomponent.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlincubator.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "incubator.hpp"
 | 
				
			||||||
 | 
					#include "reload.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Asynchronous component loader.
 | 
				
			||||||
 | 
					/// The LazyLoader can be used to prepare components that don't need to be
 | 
				
			||||||
 | 
					/// created immediately, such as windows that aren't visible until triggered
 | 
				
			||||||
 | 
					/// by another action. It works on creating the component in the gaps between
 | 
				
			||||||
 | 
					/// frame rendering to prevent blocking the interface thread.
 | 
				
			||||||
 | 
					/// It can also be used to preserve memory by loading components only
 | 
				
			||||||
 | 
					/// when you need them and unloading them afterward.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Note that when reloading the UI due to changes, lazy loaders will always
 | 
				
			||||||
 | 
					/// load synchronously so windows can be reused.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// #### Example
 | 
				
			||||||
 | 
					/// The following example creates a PopupWindow asynchronously as the bar loads.
 | 
				
			||||||
 | 
					/// This means the bar can be shown onscreen before the popup is ready, however
 | 
				
			||||||
 | 
					/// trying to show the popup before it has finished loading in the background
 | 
				
			||||||
 | 
					/// will cause the UI thread to block.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ```qml
 | 
				
			||||||
 | 
					/// import QtQuick
 | 
				
			||||||
 | 
					/// import QtQuick.Controls
 | 
				
			||||||
 | 
					/// import Quickshell
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ShellRoot {
 | 
				
			||||||
 | 
					///   PanelWindow {
 | 
				
			||||||
 | 
					///     id: window
 | 
				
			||||||
 | 
					///     height: 50
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///     anchors {
 | 
				
			||||||
 | 
					///       bottom: true
 | 
				
			||||||
 | 
					///       left: true
 | 
				
			||||||
 | 
					///       right: true
 | 
				
			||||||
 | 
					///     }
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///     LazyLoader {
 | 
				
			||||||
 | 
					///       id: popupLoader
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///       // start loading immediately
 | 
				
			||||||
 | 
					///       loading: true
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///       // this window will be loaded in the background during spare
 | 
				
			||||||
 | 
					///       // frame time unless active is set to true, where it will be
 | 
				
			||||||
 | 
					///       // loaded in the foreground
 | 
				
			||||||
 | 
					///       PopupWindow {
 | 
				
			||||||
 | 
					///         // position the popup above the button
 | 
				
			||||||
 | 
					///         parentWindow: window
 | 
				
			||||||
 | 
					///         relativeX: window.width / 2 - width / 2
 | 
				
			||||||
 | 
					///         relativeY: -height
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///         // some heavy component here
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///         width: 200
 | 
				
			||||||
 | 
					///         height: 200
 | 
				
			||||||
 | 
					///       }
 | 
				
			||||||
 | 
					///     }
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///     Button {
 | 
				
			||||||
 | 
					///       anchors.centerIn: parent
 | 
				
			||||||
 | 
					///       text: "show popup"
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///       // accessing popupLoader.item will force the loader to
 | 
				
			||||||
 | 
					///       // finish loading on the UI thread if it isn't finished yet.
 | 
				
			||||||
 | 
					///       onClicked: popupLoader.item.visible = !popupLoader.item.visible
 | 
				
			||||||
 | 
					///     }
 | 
				
			||||||
 | 
					///   }
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// > [!WARNING] Components that internally load other components must explicitly
 | 
				
			||||||
 | 
					/// > support asynchronous loading to avoid blocking.
 | 
				
			||||||
 | 
					/// >
 | 
				
			||||||
 | 
					/// > Notably, [Variants](../variants) does not corrently support asynchronous
 | 
				
			||||||
 | 
					/// > loading, meaning using it inside a LazyLoader will block similarly to not
 | 
				
			||||||
 | 
					/// > having a loader to start with.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// > [!WARNING] LazyLoaders do not start loading before the first window is created,
 | 
				
			||||||
 | 
					/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
 | 
				
			||||||
 | 
					class LazyLoader: public Reloadable {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// The fully loaded item if the loader is `loading` or `active`, or `null`
 | 
				
			||||||
 | 
						/// if neither `loading` or `active`.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Note that the item is owned by the LazyLoader, and destroying the LazyLoader
 | 
				
			||||||
 | 
						/// will destroy the item.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!WARNING] If you access the `item` of a loader that is currently loading,
 | 
				
			||||||
 | 
						/// > it will block as if you had set `active` to true immediately beforehand.
 | 
				
			||||||
 | 
						/// >
 | 
				
			||||||
 | 
						/// > You can instead set `loading` and listen to the `activeChanged` signal to
 | 
				
			||||||
 | 
						/// > ensure loading happens asynchronously.
 | 
				
			||||||
 | 
						Q_PROPERTY(QObject* item READ item NOTIFY itemChanged);
 | 
				
			||||||
 | 
						/// If the loader is actively loading.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// If the component is not loaded, setting this property to true will start
 | 
				
			||||||
 | 
						/// loading it asynchronously. If the component is already loaded, setting
 | 
				
			||||||
 | 
						/// this property has no effect.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool loading READ isLoading WRITE setLoading NOTIFY loadingChanged);
 | 
				
			||||||
 | 
						/// If the component is fully loaded.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Setting this property to `true` will force the component to load to completion,
 | 
				
			||||||
 | 
						/// blocking the UI, and setting it to `false` will destroy the component, requiring
 | 
				
			||||||
 | 
						/// it to be loaded again.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged);
 | 
				
			||||||
 | 
						/// The component to load. Mutually exclusive to `source`.
 | 
				
			||||||
 | 
						Q_PROPERTY(QQmlComponent* component READ component WRITE setComponent NOTIFY componentChanged);
 | 
				
			||||||
 | 
						/// The URI to load the component from. Mutually exclusive to `component`.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged);
 | 
				
			||||||
 | 
						Q_CLASSINFO("DefaultProperty", "component");
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						void onReload(QObject* oldInstance) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isActive() const;
 | 
				
			||||||
 | 
						void setActive(bool active);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isLoading() const;
 | 
				
			||||||
 | 
						void setLoading(bool loading);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QObject* item();
 | 
				
			||||||
 | 
						void setItem(QObject* item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QQmlComponent* component() const;
 | 
				
			||||||
 | 
						void setComponent(QQmlComponent* component);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString source() const;
 | 
				
			||||||
 | 
						void setSource(QString source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void activeChanged();
 | 
				
			||||||
 | 
						void loadingChanged();
 | 
				
			||||||
 | 
						void itemChanged();
 | 
				
			||||||
 | 
						void sourceChanged();
 | 
				
			||||||
 | 
						void componentChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onIncubationCompleted();
 | 
				
			||||||
 | 
						void onIncubationFailed();
 | 
				
			||||||
 | 
						void onComponentDestroyed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						void incubateIfReady(bool overrideReloadCheck = false);
 | 
				
			||||||
 | 
						void waitForObjectCreation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool postReload = false;
 | 
				
			||||||
 | 
						bool targetLoading = false;
 | 
				
			||||||
 | 
						bool targetActive = false;
 | 
				
			||||||
 | 
						QObject* mItem = nullptr;
 | 
				
			||||||
 | 
						QString mSource;
 | 
				
			||||||
 | 
						QQmlComponent* mComponent = nullptr;
 | 
				
			||||||
 | 
						QsQmlIncubator* incubator = nullptr;
 | 
				
			||||||
 | 
						bool cleanupComponent = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -14,5 +14,6 @@ headers = [
 | 
				
			||||||
	"floatingwindow.hpp",
 | 
						"floatingwindow.hpp",
 | 
				
			||||||
	"popupwindow.hpp",
 | 
						"popupwindow.hpp",
 | 
				
			||||||
	"singleton.hpp",
 | 
						"singleton.hpp",
 | 
				
			||||||
 | 
						"lazyloader.hpp",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
-----
 | 
					-----
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qnamespace.h>
 | 
					#include <qnamespace.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlcontext.h>
 | 
				
			||||||
#include <qqmlengine.h>
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
#include <qqmllist.h>
 | 
					#include <qqmllist.h>
 | 
				
			||||||
#include <qquickitem.h>
 | 
					#include <qquickitem.h>
 | 
				
			||||||
| 
						 | 
					@ -11,6 +12,7 @@
 | 
				
			||||||
#include <qtypes.h>
 | 
					#include <qtypes.h>
 | 
				
			||||||
#include <qwindow.h>
 | 
					#include <qwindow.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "generation.hpp"
 | 
				
			||||||
#include "qmlscreen.hpp"
 | 
					#include "qmlscreen.hpp"
 | 
				
			||||||
#include "region.hpp"
 | 
					#include "region.hpp"
 | 
				
			||||||
#include "reload.hpp"
 | 
					#include "reload.hpp"
 | 
				
			||||||
| 
						 | 
					@ -33,6 +35,13 @@ ProxyWindowBase::~ProxyWindowBase() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ProxyWindowBase::onReload(QObject* oldInstance) {
 | 
					void ProxyWindowBase::onReload(QObject* oldInstance) {
 | 
				
			||||||
	this->window = this->createWindow(oldInstance);
 | 
						this->window = this->createWindow(oldInstance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
 | 
				
			||||||
 | 
							// All windows have effectively the same incubation controller so it dosen't matter
 | 
				
			||||||
 | 
							// which window it belongs to. We do want to replace the delay one though.
 | 
				
			||||||
 | 
							generation->registerIncubationController(this->window->incubationController());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->setupWindow();
 | 
						this->setupWindow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Reloadable::reloadRecursive(this->mContentItem, oldInstance);
 | 
						Reloadable::reloadRecursive(this->mContentItem, oldInstance);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,6 +182,8 @@ WaylandPanelInterface::WaylandPanelInterface(QObject* parent)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void WaylandPanelInterface::onReload(QObject* oldInstance) {
 | 
					void WaylandPanelInterface::onReload(QObject* oldInstance) {
 | 
				
			||||||
 | 
						QQmlEngine::setContextForObject(this->layer, QQmlEngine::contextForObject(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto* old = qobject_cast<WaylandPanelInterface*>(oldInstance);
 | 
						auto* old = qobject_cast<WaylandPanelInterface*>(oldInstance);
 | 
				
			||||||
	this->layer->onReload(old != nullptr ? old->layer : nullptr);
 | 
						this->layer->onReload(old != nullptr ? old->layer : nullptr);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue