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
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;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue