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
|
@ -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…
Reference in a new issue