From 1da43be6c013badada1cea577e86b8fe7441eae1 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Fri, 16 Feb 2024 06:38:20 -0800 Subject: [PATCH] feat: completely redesign hot reloader The hot reloader previously attempted to figure out which parent a component would attach to as it loaded. This was fairly error prone as it was heuristic based and didn't work as soon as you split definitions into multiple QML files. The new hot reloader functions by first completely building the widget tree, then applying the old tree to the first tree and pulling out usable values. Proxy windows now wait to appear until being reloaded. Additionally added support for `reloadableId` to help match a Reloadable to its value in the previous widget tree. --- .clang-tidy | 1 + CMakeLists.txt | 2 +- src/cpp/layershell.cpp | 175 +++++++++++++++++++++----------------- src/cpp/layershell.hpp | 35 +++----- src/cpp/module.md | 2 +- src/cpp/proxywindow.cpp | 184 +++++++++++++++++++++++++++++----------- src/cpp/proxywindow.hpp | 73 ++++++++-------- src/cpp/qmlglobal.hpp | 33 +------ src/cpp/reload.cpp | 77 +++++++++++++++++ src/cpp/reload.hpp | 107 +++++++++++++++++++++++ src/cpp/rootwrapper.cpp | 7 +- src/cpp/rootwrapper.hpp | 5 +- src/cpp/scavenge.cpp | 87 ------------------- src/cpp/scavenge.hpp | 79 ----------------- src/cpp/shell.hpp | 6 +- src/cpp/variants.cpp | 73 ++++++++-------- src/cpp/variants.hpp | 14 ++- 17 files changed, 518 insertions(+), 442 deletions(-) create mode 100644 src/cpp/reload.cpp create mode 100644 src/cpp/reload.hpp delete mode 100644 src/cpp/scavenge.cpp delete mode 100644 src/cpp/scavenge.hpp diff --git a/.clang-tidy b/.clang-tidy index b53ebf35..6642fa76 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -34,6 +34,7 @@ Checks: > -readability-uppercase-literal-suffix, -readability-braces-around-statements, -readability-redundant-access-specifiers, + -readability-else-after-return, tidyfox-*, CheckOptions: performance-for-range-copy.WarnOnAllAutoCopies: true diff --git a/CMakeLists.txt b/CMakeLists.txt index db9fe596..590b4c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ qt_add_executable(quickshell src/cpp/variants.cpp src/cpp/rootwrapper.cpp src/cpp/proxywindow.cpp - src/cpp/scavenge.cpp + src/cpp/reload.cpp src/cpp/rootwrapper.cpp src/cpp/qmlglobal.cpp src/cpp/qmlscreen.cpp diff --git a/src/cpp/layershell.cpp b/src/cpp/layershell.cpp index ed752eaa..922f7427 100644 --- a/src/cpp/layershell.cpp +++ b/src/cpp/layershell.cpp @@ -14,18 +14,11 @@ #include "proxywindow.hpp" #include "qmlscreen.hpp" -void ProxyShellWindow::earlyInit(QObject* old) { - this->ProxyWindowBase::earlyInit(old); - +void ProxyShellWindow::setupWindow() { QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyShellWindow::screenChanged); - this->shellWindow = LayerShellQt::Window::get(this->window); - // dont want to steal focus unless actually configured to - this->shellWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityNone); - - // this dosent reset if its unset - this->shellWindow->setExclusiveZone(0); + this->ProxyWindowBase::setupWindow(); // clang-format off QObject::connect(this->shellWindow, &LayerShellQt::Window::anchorsChanged, this, &ProxyShellWindow::anchorsChanged); @@ -38,26 +31,16 @@ void ProxyShellWindow::earlyInit(QObject* old) { QObject::connect(this, &ProxyShellWindow::anchorsChanged, this, &ProxyShellWindow::updateExclusionZone); QObject::connect(this, &ProxyShellWindow::marginsChanged, this, &ProxyShellWindow::updateExclusionZone); // clang-format on -} -void ProxyShellWindow::componentComplete() { - this->complete = true; + this->window->setScreen(this->mScreen); + this->setAnchors(this->mAnchors); + this->setMargins(this->mMargins); + this->setExclusionMode(this->mExclusionMode); // also sets exclusion zone + this->setLayer(this->mLayer); + this->shellWindow->setScope(this->mScope); + this->setKeyboardFocus(this->mKeyboardFocus); - // The default anchor settings are a hazard because they cover the entire screen. - // We opt for 0 anchors by default to avoid blocking user input. - this->setAnchors(this->stagingAnchors); - this->updateExclusionZone(); - - // Make sure we signal changes from anchors, but only if this is a reload. - // If we do it on first load then it sends an extra change at 0px. - if (this->stagingAnchors.mLeft && this->stagingAnchors.mRight && this->width() != 0) - this->widthChanged(this->width()); - if (this->stagingAnchors.mTop && this->stagingAnchors.mBottom && this->height() != 0) - this->heightChanged(this->height()); - - this->window->setVisible(this->stagingVisible); - - this->ProxyWindowBase::componentComplete(); + this->connected = true; } QQuickWindow* ProxyShellWindow::disownWindow() { @@ -65,54 +48,56 @@ QQuickWindow* ProxyShellWindow::disownWindow() { return this->ProxyWindowBase::disownWindow(); } -void ProxyShellWindow::setVisible(bool visible) { - if (!this->complete) this->stagingVisible = visible; - else this->ProxyWindowBase::setVisible(visible); -} - -bool ProxyShellWindow::isVisible() { - return this->complete ? this->ProxyWindowBase::isVisible() : this->stagingVisible; -} - void ProxyShellWindow::setWidth(qint32 width) { - this->requestedWidth = width; + this->mWidth = width; // only update the actual size if not blocked by anchors auto anchors = this->anchors(); - if (this->complete && (!anchors.mLeft || !anchors.mRight)) this->ProxyWindowBase::setWidth(width); -} - -qint32 ProxyShellWindow::width() { - return this->complete ? this->ProxyWindowBase::width() : this->requestedWidth; + if (!anchors.mLeft || !anchors.mRight) this->ProxyWindowBase::setWidth(width); } void ProxyShellWindow::setHeight(qint32 height) { - this->requestedHeight = height; + this->mHeight = height; // only update the actual size if not blocked by anchors auto anchors = this->anchors(); - if (this->complete && (!anchors.mTop || !anchors.mBottom)) - this->ProxyWindowBase::setHeight(height); -} - -qint32 ProxyShellWindow::height() { - return this->complete ? this->ProxyWindowBase::height() : this->requestedHeight; + if (!anchors.mTop || !anchors.mBottom) this->ProxyWindowBase::setHeight(height); } void ProxyShellWindow::setScreen(QuickShellScreenInfo* screen) { - this->window->setScreen(screen->screen); + if (this->mScreen != nullptr) { + QObject::disconnect(this->mScreen, nullptr, this, nullptr); + } + + auto* qscreen = screen == nullptr ? nullptr : screen->screen; + if (qscreen != nullptr) { + QObject::connect(qscreen, &QObject::destroyed, this, &ProxyShellWindow::onScreenDestroyed); + } + + if (this->window == nullptr) this->mScreen = qscreen; + else this->window->setScreen(qscreen); } +void ProxyShellWindow::onScreenDestroyed() { this->mScreen = nullptr; } + QuickShellScreenInfo* ProxyShellWindow::screen() const { + QScreen* qscreen = nullptr; + + if (this->window == nullptr) { + if (this->mScreen != nullptr) qscreen = this->mScreen; + } else { + qscreen = this->window->screen(); + } + return new QuickShellScreenInfo( const_cast(this), // NOLINT - this->window->screen() + qscreen ); } void ProxyShellWindow::setAnchors(Anchors anchors) { - if (!this->complete) { - this->stagingAnchors = anchors; + if (this->window == nullptr) { + this->mAnchors = anchors; return; } @@ -122,14 +107,14 @@ void ProxyShellWindow::setAnchors(Anchors anchors) { if (anchors.mTop) lsAnchors |= LayerShellQt::Window::AnchorTop; if (anchors.mBottom) lsAnchors |= LayerShellQt::Window::AnchorBottom; - if (!anchors.mLeft || !anchors.mRight) this->ProxyWindowBase::setWidth(this->requestedWidth); - if (!anchors.mTop || !anchors.mBottom) this->ProxyWindowBase::setHeight(this->requestedHeight); + if (!anchors.mLeft || !anchors.mRight) this->ProxyWindowBase::setWidth(this->mWidth); + if (!anchors.mTop || !anchors.mBottom) this->ProxyWindowBase::setHeight(this->mHeight); this->shellWindow->setAnchors(lsAnchors); } Anchors ProxyShellWindow::anchors() const { - if (!this->complete) return this->stagingAnchors; + if (this->window == nullptr) return this->mAnchors; auto lsAnchors = this->shellWindow->anchors(); @@ -144,40 +129,49 @@ Anchors ProxyShellWindow::anchors() const { void ProxyShellWindow::setExclusiveZone(qint32 zone) { if (zone < 0) zone = 0; - if (zone == this->requestedExclusionZone) return; - this->requestedExclusionZone = zone; + if (this->connected && zone == this->mExclusionZone) return; + this->mExclusionZone = zone; - if (this->exclusionMode() == ExclusionMode::Normal) { + if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Normal) { this->shellWindow->setExclusiveZone(zone); emit this->exclusionZoneChanged(); } } -qint32 ProxyShellWindow::exclusiveZone() const { return this->shellWindow->exclusionZone(); } +qint32 ProxyShellWindow::exclusiveZone() const { + if (this->window == nullptr) return this->mExclusionZone; + else return this->shellWindow->exclusionZone(); +} ExclusionMode::Enum ProxyShellWindow::exclusionMode() const { return this->mExclusionMode; } void ProxyShellWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) { - if (exclusionMode == this->mExclusionMode) return; + if (this->connected && exclusionMode == this->mExclusionMode) return; this->mExclusionMode = exclusionMode; - if (exclusionMode == ExclusionMode::Normal) { - this->shellWindow->setExclusiveZone(this->requestedExclusionZone); - emit this->exclusionZoneChanged(); - } else if (exclusionMode == ExclusionMode::Ignore) { - this->shellWindow->setExclusiveZone(-1); - emit this->exclusionZoneChanged(); - } else { - this->updateExclusionZone(); + if (this->window != nullptr) { + if (exclusionMode == ExclusionMode::Normal) { + this->shellWindow->setExclusiveZone(this->mExclusionZone); + emit this->exclusionZoneChanged(); + } else if (exclusionMode == ExclusionMode::Ignore) { + this->shellWindow->setExclusiveZone(-1); + emit this->exclusionZoneChanged(); + } else { + this->updateExclusionZone(); + } } } void ProxyShellWindow::setMargins(Margins margins) { - auto lsMargins = QMargins(margins.mLeft, margins.mTop, margins.mRight, margins.mBottom); - this->shellWindow->setMargins(lsMargins); + if (this->window == nullptr) this->mMargins = margins; + else { + auto lsMargins = QMargins(margins.mLeft, margins.mTop, margins.mRight, margins.mBottom); + this->shellWindow->setMargins(lsMargins); + } } Margins ProxyShellWindow::margins() const { + if (this->window == nullptr) return this->mMargins; auto lsMargins = this->shellWindow->margins(); auto margins = Margins(); @@ -190,6 +184,11 @@ Margins ProxyShellWindow::margins() const { } void ProxyShellWindow::setLayer(Layer::Enum layer) { + if (this->window == nullptr) { + this->mLayer = layer; + return; + } + auto lsLayer = LayerShellQt::Window::LayerBackground; // clang-format off @@ -205,6 +204,8 @@ void ProxyShellWindow::setLayer(Layer::Enum layer) { } Layer::Enum ProxyShellWindow::layer() const { + if (this->window == nullptr) return this->mLayer; + auto layer = Layer::Top; auto lsLayer = this->shellWindow->layer(); @@ -220,11 +221,22 @@ Layer::Enum ProxyShellWindow::layer() const { return layer; } -void ProxyShellWindow::setScope(const QString& scope) { this->shellWindow->setScope(scope); } +void ProxyShellWindow::setScope(const QString& scope) { + if (this->window == nullptr) this->mScope = scope; + else this->shellWindow->setScope(scope); +} -QString ProxyShellWindow::scope() const { return this->shellWindow->scope(); } +QString ProxyShellWindow::scope() const { + if (this->window == nullptr) return this->mScope; + else return this->shellWindow->scope(); +} void ProxyShellWindow::setKeyboardFocus(KeyboardFocus::Enum focus) { + if (this->window == nullptr) { + this->mKeyboardFocus = focus; + return; + } + auto lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; // clang-format off @@ -239,6 +251,8 @@ void ProxyShellWindow::setKeyboardFocus(KeyboardFocus::Enum focus) { } KeyboardFocus::Enum ProxyShellWindow::keyboardFocus() const { + if (this->window == nullptr) return this->mKeyboardFocus; + auto focus = KeyboardFocus::None; auto lsFocus = this->shellWindow->keyboardInteractivity(); @@ -254,6 +268,11 @@ KeyboardFocus::Enum ProxyShellWindow::keyboardFocus() const { } void ProxyShellWindow::setScreenConfiguration(ScreenConfiguration::Enum configuration) { + if (this->window == nullptr) { + this->mScreenConfiguration = configuration; + return; + } + auto lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; // clang-format off @@ -267,6 +286,8 @@ void ProxyShellWindow::setScreenConfiguration(ScreenConfiguration::Enum configur } ScreenConfiguration::Enum ProxyShellWindow::screenConfiguration() const { + if (this->window == nullptr) return this->mScreenConfiguration; + auto configuration = ScreenConfiguration::Window; auto lsConfiguration = this->shellWindow->screenConfiguration(); @@ -280,14 +301,8 @@ ScreenConfiguration::Enum ProxyShellWindow::screenConfiguration() const { return configuration; } -void ProxyShellWindow::setCloseOnDismissed(bool close) { - this->shellWindow->setCloseOnDismissed(close); -} - -bool ProxyShellWindow::closeOnDismissed() const { return this->shellWindow->closeOnDismissed(); } - void ProxyShellWindow::updateExclusionZone() { - if (this->exclusionMode() == ExclusionMode::Auto) { + if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Auto) { auto anchors = this->anchors(); auto zone = -1; diff --git a/src/cpp/layershell.hpp b/src/cpp/layershell.hpp index a7f53c56..597e75d2 100644 --- a/src/cpp/layershell.hpp +++ b/src/cpp/layershell.hpp @@ -162,27 +162,18 @@ class ProxyShellWindow: public ProxyWindowBase { /// The degree of keyboard focus taken. Defaults to `KeyboardFocus.None`. Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged); Q_PROPERTY(ScreenConfiguration::Enum screenConfiguration READ screenConfiguration WRITE setScreenConfiguration); - Q_PROPERTY(bool closeOnDismissed READ closeOnDismissed WRITE setCloseOnDismissed); QML_ELEMENT; // clang-format on -protected: - void earlyInit(QObject* old) override; - public: - void componentComplete() override; + void setupWindow() override; QQuickWindow* disownWindow() override; QQmlListProperty data(); - void setVisible(bool visible) override; - bool isVisible() override; - void setWidth(qint32 width) override; - qint32 width() override; void setHeight(qint32 height) override; - qint32 height() override; void setScreen(QuickShellScreenInfo* screen); [[nodiscard]] QuickShellScreenInfo* screen() const; @@ -211,9 +202,6 @@ public: void setScreenConfiguration(ScreenConfiguration::Enum configuration); [[nodiscard]] ScreenConfiguration::Enum screenConfiguration() const; - void setCloseOnDismissed(bool close); - [[nodiscard]] bool closeOnDismissed() const; - signals: void screenChanged(); void anchorsChanged(); @@ -225,20 +213,19 @@ signals: private slots: void updateExclusionZone(); + void onScreenDestroyed(); private: LayerShellQt::Window* shellWindow = nullptr; - bool anchorsInitialized = false; + QScreen* mScreen = nullptr; ExclusionMode::Enum mExclusionMode = ExclusionMode::Normal; - qint32 requestedExclusionZone = 0; + qint32 mExclusionZone = 0; + Anchors mAnchors; + Margins mMargins; + Layer::Enum mLayer = Layer::Top; + QString mScope; + KeyboardFocus::Enum mKeyboardFocus = KeyboardFocus::None; + ScreenConfiguration::Enum mScreenConfiguration = ScreenConfiguration::Window; - // needed to ensure size dosent fuck up when changing layershell attachments - // along with setWidth and setHeight overrides - qint32 requestedWidth = 100; - qint32 requestedHeight = 100; - - // width/height must be set before anchors, so we have to track anchors and apply them late - bool complete = false; - bool stagingVisible = false; - Anchors stagingAnchors; + bool connected = false; }; diff --git a/src/cpp/module.md b/src/cpp/module.md index c24bc8b4..86faae2a 100644 --- a/src/cpp/module.md +++ b/src/cpp/module.md @@ -3,7 +3,7 @@ description = "Core QuickShell types" headers = [ "qmlglobal.hpp", "qmlscreen.hpp", - "scavenge.hpp", + "reload.hpp", "shell.hpp", "variants.hpp", "proxywindow.hpp", diff --git a/src/cpp/proxywindow.cpp b/src/cpp/proxywindow.cpp index 96f1928f..6e17a55c 100644 --- a/src/cpp/proxywindow.cpp +++ b/src/cpp/proxywindow.cpp @@ -17,17 +17,29 @@ ProxyWindowBase::~ProxyWindowBase() { } } -void ProxyWindowBase::earlyInit(QObject* old) { - auto* oldpw = qobject_cast(old); +void ProxyWindowBase::onReload(QObject* oldInstance) { + auto* old = qobject_cast(oldInstance); - if (oldpw == nullptr || oldpw->window == nullptr) { + if (old == nullptr || old->window == nullptr) { this->window = new QQuickWindow(); } else { - this->window = oldpw->disownWindow(); + this->window = old->disownWindow(); } - this->window->setMask(QRegion()); + this->setupWindow(); + auto backer = this->dataBacker(); + for (auto* child: this->pendingChildren) { + backer.append(&backer, child); + } + + this->pendingChildren.clear(); + + emit this->windowConnected(); + this->window->setVisible(this->mVisible); +} + +void ProxyWindowBase::setupWindow() { // clang-format off QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged); QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyWindowBase::widthChanged); @@ -38,6 +50,11 @@ void ProxyWindowBase::earlyInit(QObject* old) { QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onMaskChanged); QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onMaskChanged); // clang-format on + + this->setWidth(this->mWidth); + this->setHeight(this->mHeight); + this->setColor(this->mColor); + this->updateMask(); } QQuickWindow* ProxyWindowBase::disownWindow() { @@ -52,20 +69,57 @@ QQuickWindow* ProxyWindowBase::disownWindow() { return window; } -QQuickWindow* ProxyWindowBase::backingWindow() { return this->window; } -QQuickItem* ProxyWindowBase::item() { return this->window->contentItem(); } +QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; } -// NOLINTNEXTLINE -#define PROXYPROP(type, get, set) \ - type ProxyWindowBase::get() { return this->window->get(); } \ - void ProxyWindowBase::set(type value) { this->window->set(value); } +bool ProxyWindowBase::isVisible() const { + if (this->window == nullptr) return this->mVisible; + else return this->window->isVisible(); +} -PROXYPROP(bool, isVisible, setVisible); -PROXYPROP(qint32, width, setWidth); -PROXYPROP(qint32, height, setHeight); -PROXYPROP(QColor, color, setColor); +void ProxyWindowBase::setVisible(bool visible) { + if (this->window == nullptr) { + this->mVisible = visible; + emit this->visibleChanged(); + } else this->window->setVisible(visible); +} -PendingRegion* ProxyWindowBase::mask() { return this->mMask; } +qint32 ProxyWindowBase::width() const { + if (this->window == nullptr) return this->mWidth; + else return this->window->width(); +} + +void ProxyWindowBase::setWidth(qint32 width) { + if (this->window == nullptr) { + this->mWidth = width; + emit this->widthChanged(); + } else this->window->setWidth(width); +} + +qint32 ProxyWindowBase::height() const { + if (this->window == nullptr) return this->mHeight; + else return this->window->height(); +} + +void ProxyWindowBase::setHeight(qint32 height) { + if (this->window == nullptr) { + this->mHeight = height; + emit this->heightChanged(); + } else this->window->setHeight(height); +} + +QColor ProxyWindowBase::color() const { + if (this->window == nullptr) return this->mColor; + else return this->window->color(); +} + +void ProxyWindowBase::setColor(QColor color) { + if (this->window == nullptr) { + this->mColor = color; + emit this->colorChanged(); + } else this->window->setColor(color); +} + +PendingRegion* ProxyWindowBase::mask() const { return this->mMask; } void ProxyWindowBase::setMask(PendingRegion* mask) { if (this->mMask != nullptr) { @@ -81,6 +135,10 @@ void ProxyWindowBase::setMask(PendingRegion* mask) { } void ProxyWindowBase::onMaskChanged() { + if (this->window != nullptr) this->updateMask(); +} + +void ProxyWindowBase::updateMask() { QRegion mask; if (this->mMask != nullptr) { // if left as the default, dont combine it with the whole window area, leave it as is. @@ -114,59 +172,87 @@ QQmlListProperty ProxyWindowBase::data() { ); } -QQmlListProperty ProxyWindowBase::dataBacker(QQmlListProperty* prop) { - auto* that = static_cast(prop->object); // NOLINT - return that->window->property("data").value>(); +QQmlListProperty ProxyWindowBase::dataBacker() { + return this->window->property("data").value>(); } void ProxyWindowBase::dataAppend(QQmlListProperty* prop, QObject* obj) { - auto backer = dataBacker(prop); - backer.append(&backer, obj); + auto* self = static_cast(prop->object); // NOLINT + + if (self->window == nullptr) { + if (obj != nullptr) { + obj->setParent(self); + self->pendingChildren.append(obj); + } + } else { + auto backer = self->dataBacker(); + backer.append(&backer, obj); + } } qsizetype ProxyWindowBase::dataCount(QQmlListProperty* prop) { - auto backer = dataBacker(prop); - return backer.count(&backer); + auto* self = static_cast(prop->object); // NOLINT + + if (self->window == nullptr) { + return self->pendingChildren.count(); + } else { + auto backer = self->dataBacker(); + return backer.count(&backer); + } } QObject* ProxyWindowBase::dataAt(QQmlListProperty* prop, qsizetype i) { - auto backer = dataBacker(prop); - return backer.at(&backer, i); + auto* self = static_cast(prop->object); // NOLINT + + if (self->window == nullptr) { + return self->pendingChildren.at(i); + } else { + auto backer = self->dataBacker(); + return backer.at(&backer, i); + } } void ProxyWindowBase::dataClear(QQmlListProperty* prop) { - auto backer = dataBacker(prop); - backer.clear(&backer); + auto* self = static_cast(prop->object); // NOLINT + + if (self->window == nullptr) { + self->pendingChildren.clear(); + } else { + auto backer = self->dataBacker(); + backer.clear(&backer); + } } void ProxyWindowBase::dataReplace(QQmlListProperty* prop, qsizetype i, QObject* obj) { - auto backer = dataBacker(prop); - backer.replace(&backer, i, obj); + auto* self = static_cast(prop->object); // NOLINT + + if (self->window == nullptr) { + if (obj != nullptr) { + obj->setParent(self); + self->pendingChildren.replace(i, obj); + } + } else { + auto backer = self->dataBacker(); + backer.replace(&backer, i, obj); + } } void ProxyWindowBase::dataRemoveLast(QQmlListProperty* prop) { - auto backer = dataBacker(prop); - backer.removeLast(&backer); -} + auto* self = static_cast(prop->object); // NOLINT -void ProxyFloatingWindow::earlyInit(QObject* old) { - this->ProxyWindowBase::earlyInit(old); - this->geometryLocked = this->window->isVisible(); -} - -void ProxyFloatingWindow::componentComplete() { - this->ProxyWindowBase::componentComplete(); - this->geometryLocked = true; -} - -void ProxyFloatingWindow::setWidth(qint32 value) { - if (!this->geometryLocked) { - this->ProxyWindowBase::setWidth(value); + if (self->window == nullptr) { + self->pendingChildren.removeLast(); + } else { + auto backer = self->dataBacker(); + backer.removeLast(&backer); } } -void ProxyFloatingWindow::setHeight(qint32 value) { - if (!this->geometryLocked) { - this->ProxyWindowBase::setHeight(value); - } +void ProxyFloatingWindow::setWidth(qint32 width) { + if (this->window == nullptr || !this->window->isVisible()) this->ProxyWindowBase::setWidth(width); +} + +void ProxyFloatingWindow::setHeight(qint32 height) { + if (this->window == nullptr || !this->window->isVisible()) + this->ProxyWindowBase::setHeight(height); } diff --git a/src/cpp/proxywindow.hpp b/src/cpp/proxywindow.hpp index 27ab57b1..b1f94434 100644 --- a/src/cpp/proxywindow.hpp +++ b/src/cpp/proxywindow.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -11,7 +12,7 @@ #include #include "region.hpp" -#include "scavenge.hpp" +#include "reload.hpp" // Proxy to an actual window exposing a limited property set with the ability to // transfer it to a new window. @@ -19,7 +20,7 @@ // // NOTE: setting an `id` in qml will point to the proxy window and not the real window so things // like anchors must use `item`. -class ProxyWindowBase: public Scavenger { +class ProxyWindowBase: public Reloadable { Q_OBJECT; /// The QtQuick window backing this window. /// @@ -29,8 +30,6 @@ class ProxyWindowBase: public Scavenger { /// > /// > Use **only** if you know what you are doing. Q_PROPERTY(QQuickWindow* _backingWindow READ backingWindow); - /// The content item of the window. - Q_PROPERTY(QQuickItem* item READ item CONSTANT); /// The visibility of the window. /// /// > [!INFO] Windows are not visible by default so you will need to set this to make the window @@ -99,12 +98,8 @@ class ProxyWindowBase: public Scavenger { Q_PROPERTY(QQmlListProperty data READ data); Q_CLASSINFO("DefaultProperty", "data"); -protected: - void earlyInit(QObject* old) override; - QQuickWindow* window = nullptr; - public: - explicit ProxyWindowBase(QObject* parent = nullptr): Scavenger(parent) {} + explicit ProxyWindowBase(QObject* parent = nullptr): Reloadable(parent) {} ~ProxyWindowBase() override; ProxyWindowBase(ProxyWindowBase&) = delete; @@ -112,41 +107,55 @@ public: void operator=(ProxyWindowBase&) = delete; void operator=(ProxyWindowBase&&) = delete; + void onReload(QObject* oldInstance) override; + + virtual void setupWindow(); + // Disown the backing window and delete all its children. virtual QQuickWindow* disownWindow(); - QQuickWindow* backingWindow(); - QQuickItem* item(); + [[nodiscard]] QQuickWindow* backingWindow() const; - virtual bool isVisible(); - virtual void setVisible(bool value); + [[nodiscard]] virtual bool isVisible() const; + virtual void setVisible(bool visible); - virtual qint32 width(); - virtual void setWidth(qint32 value); + [[nodiscard]] virtual qint32 width() const; + virtual void setWidth(qint32 width); - virtual qint32 height(); - virtual void setHeight(qint32 value); + [[nodiscard]] virtual qint32 height() const; + virtual void setHeight(qint32 height); - QColor color(); - void setColor(QColor value); + [[nodiscard]] QColor color() const; + void setColor(QColor color); - PendingRegion* mask(); + [[nodiscard]] PendingRegion* mask() const; void setMask(PendingRegion* mask); QQmlListProperty data(); signals: - void visibleChanged(bool visible); - void widthChanged(qint32 width); - void heightChanged(qint32 width); - void colorChanged(QColor color); + void windowConnected(); + void visibleChanged(); + void widthChanged(); + void heightChanged(); + void colorChanged(); void maskChanged(); private slots: void onMaskChanged(); +protected: + bool mVisible = false; + qint32 mWidth = 100; + qint32 mHeight = 100; + QColor mColor; + PendingRegion* mMask = nullptr; + QQuickWindow* window = nullptr; + private: - static QQmlListProperty dataBacker(QQmlListProperty* prop); + void updateMask(); + QQmlListProperty dataBacker(); + static void dataAppend(QQmlListProperty* prop, QObject* obj); static qsizetype dataCount(QQmlListProperty* prop); static QObject* dataAt(QQmlListProperty* prop, qsizetype i); @@ -154,7 +163,7 @@ private: static void dataReplace(QQmlListProperty* prop, qsizetype i, QObject* obj); static void dataRemoveLast(QQmlListProperty* prop); - PendingRegion* mMask = nullptr; + QVector pendingChildren; }; // qt attempts to resize the window but fails because wayland @@ -164,12 +173,8 @@ class ProxyFloatingWindow: public ProxyWindowBase { QML_ELEMENT; public: - void earlyInit(QObject* old) override; - void componentComplete() override; - - void setWidth(qint32 value) override; - void setHeight(qint32 value) override; - -private: - bool geometryLocked = false; + // Setting geometry while the window is visible makes the content item shrink but not the window + // which is awful so we disable it for floating windows. + void setWidth(qint32 width) override; + void setHeight(qint32 height) override; }; diff --git a/src/cpp/qmlglobal.hpp b/src/cpp/qmlglobal.hpp index 03009d53..f0e964a2 100644 --- a/src/cpp/qmlglobal.hpp +++ b/src/cpp/qmlglobal.hpp @@ -45,36 +45,9 @@ public: /// `hard` - perform a hard reload. If this is false, QuickShell will attempt to reuse windows /// that already exist. If true windows will be recreated. /// - /// > [!INFO] QuickShell can only reuse windows that are in a hierarchy of elements known - /// > internally as `Scavengeable`. These types are [ShellRoot] and [Variants]. - /// > - /// > ```qml - /// > // this will reuse the window on reload - /// > ShellRoot { - /// > Varaints { - /// > ProxyShellWindow { - /// > // ... - /// > } - /// > - /// > // ... - /// > } - /// > } - /// > - /// > // this will NOT reuse the window on reload, - /// > // and will destroy the old one / create a new one every time - /// > ShellRoot { - /// > AnyNonScavengeableType { - /// > ProxyShellWindow { - /// > // ... - /// > } - /// > - /// > // ... - /// > } - /// > } - /// > ``` - /// > - /// > [ShellRoot]: ../shellroot - /// > [Variants]: ../variants + /// See [Reloadable] for more information on what can be reloaded and how. + /// + /// [Reloadable]: ../reloadable Q_INVOKABLE void reload(bool hard); signals: diff --git a/src/cpp/reload.cpp b/src/cpp/reload.cpp new file mode 100644 index 00000000..187fc7a9 --- /dev/null +++ b/src/cpp/reload.cpp @@ -0,0 +1,77 @@ +#include "reload.hpp" + +#include +#include +#include + +void ReloadPropagator::onReload(QObject* oldInstance) { + auto* old = qobject_cast(oldInstance); + + for (auto i = 0; i < this->mChildren.length(); i++) { + auto* newChild = qobject_cast(this->mChildren.at(i)); + if (newChild != nullptr) { + auto* oldChild = old == nullptr || old->mChildren.length() <= i + ? nullptr + : qobject_cast(old->mChildren.at(i)); + newChild->onReload(oldChild); + } else { + Reloadable::reloadRecursive(newChild, oldInstance); + } + } +} + +QQmlListProperty ReloadPropagator::data() { + return QQmlListProperty( + this, + nullptr, + &ReloadPropagator::appendComponent, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + ); +} + +void ReloadPropagator::appendComponent(QQmlListProperty* list, QObject* obj) { + auto* self = static_cast(list->object); // NOLINT + obj->setParent(self); + self->mChildren.append(obj); +} + +void Reloadable::reloadRecursive(QObject* newObj, QObject* oldRoot) { + auto* reloadable = qobject_cast(newObj); + if (reloadable != nullptr) { + QObject* oldInstance = nullptr; + if (oldRoot != nullptr && !reloadable->mReloadableId.isEmpty()) { + oldInstance = Reloadable::getChildByReloadId(oldRoot, reloadable->mReloadableId); + } + + // pass handling to the child's onReload, which should call back into reloadRecursive, + // with its oldInstance becoming the new oldRoot. + reloadable->onReload(oldInstance); + } else if (newObj != nullptr) { + Reloadable::reloadChildrenRecursive(newObj, oldRoot); + } +} + +void Reloadable::reloadChildrenRecursive(QObject* newRoot, QObject* oldRoot) { + for (auto* child: newRoot->children()) { + Reloadable::reloadRecursive(child, oldRoot); + } +} + +QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId) { + for (auto* child: parent->children()) { + auto* reloadable = qobject_cast(child); + if (reloadable != nullptr) { + if (reloadable->mReloadableId == reloadId) return reloadable; + // if not then don't check its children as thats a seperate reload scope. + } else { + auto* reloadable = Reloadable::getChildByReloadId(child, reloadId); + if (reloadable != nullptr) return reloadable; + } + } + + return nullptr; +} diff --git a/src/cpp/reload.hpp b/src/cpp/reload.hpp new file mode 100644 index 00000000..fc701849 --- /dev/null +++ b/src/cpp/reload.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +///! The base class of all types that can be reloaded. +/// Reloadables will attempt to take specific state from previous config revisions if possible. +/// Some examples are `ProxyShellWindow` and `ProxyFloatingWindow` which will attempt to find the +/// windows assigned to them in the previous configuration. +class Reloadable: public QObject, public QQmlParserStatus { + Q_OBJECT; + Q_INTERFACES(QQmlParserStatus); + /// An additional identifier that can be used to try to match a reloadable object to its + /// previous state. + /// + /// Simply keeping a stable identifier across config versions (saves) is + /// enough to help the reloader figure out which object in the old revision corrosponds to + /// this object in the current revision, and facilitate smoother reloading. + /// + /// Note that identifiers are scoped, and will try to do the right thing in context. + /// For example if you have a `Variants` wrapping an object with an identified element inside, + /// a scope is created at the variant level. + /// + /// ```qml + /// Variants { + /// // multiple variants of the same object tree + /// variants: [ { foo: 1 }, { foo: 2 } ] + /// + /// // any non `Reloadable` object + /// QtObject { + /// ProxyFloatingWindow { + /// // this ProxyFloatingWindow will now be matched to the same one in the previous + /// // widget tree for its variant. "myFloatingWindow" refers to both the variant in + /// // `foo: 1` and `foo: 2` for each tree. + /// reloadableId: "myFloatingWindow" + /// + /// // ... + /// } + /// } + /// } + /// ``` + Q_PROPERTY(QString reloadableId MEMBER mReloadableId); + QML_ELEMENT; + QML_UNCREATABLE( + "Reloadable is the base class of reloadable types and cannot be created on its own." + ); + +public: + explicit Reloadable(QObject* parent = nullptr): QObject(parent) {} + + /// called unconditionally in the reload phase, with nullptr if no source could be determined + virtual void onReload(QObject* oldInstance) = 0; + + // TODO: onReload runs after initialization for reloadable objects created late + void classBegin() override {} + void componentComplete() override {} + + // Reload objects in the parent->child graph recursively. + static void reloadRecursive(QObject* newObj, QObject* oldRoot); + // Same as above but does not reload the passed object, only its children. + static void reloadChildrenRecursive(QObject* newRoot, QObject* oldRoot); + + QString mReloadableId; + +private: + static QObject* getChildByReloadId(QObject* parent, const QString& reloadId); +}; + +///! Basic type that propagates reloads to child items in order. +/// Convenience type equivalent to setting `reloadableId` on properties in a +/// QtObject instance. +/// +/// Note that this does not work for visible `Item`s (all widgets). +/// +/// ```qml +/// ShellRoot { +/// Variants { +/// variants: ... +/// +/// ReloadPropagator { +/// // everything in here behaves the same as if it was defined +/// // directly in `Variants` reload-wise. +/// } +/// } +/// } +class ReloadPropagator: public Reloadable { + Q_OBJECT; + Q_PROPERTY(QQmlListProperty children READ data); + Q_CLASSINFO("DefaultProperty", "children"); + QML_ELEMENT; + +public: + explicit ReloadPropagator(QObject* parent = nullptr): Reloadable(parent) {} + + void onReload(QObject* oldInstance) override; + + QQmlListProperty data(); + +private: + static void appendComponent(QQmlListProperty* list, QObject* obj); + + QList mChildren; +}; diff --git a/src/cpp/rootwrapper.cpp b/src/cpp/rootwrapper.cpp index 886e2aef..17dd5491 100644 --- a/src/cpp/rootwrapper.cpp +++ b/src/cpp/rootwrapper.cpp @@ -9,7 +9,6 @@ #include #include -#include "scavenge.hpp" #include "shell.hpp" #include "watcher.hpp" @@ -23,8 +22,6 @@ RootWrapper::RootWrapper(QString rootPath): } } -QObject* RootWrapper::scavengeTargetFor(QObject* /* child */) { return this->root; } - void RootWrapper::reloadGraph(bool hard) { if (this->root != nullptr) { this->engine.clearComponentCache(); @@ -32,9 +29,7 @@ void RootWrapper::reloadGraph(bool hard) { auto component = QQmlComponent(&this->engine, QUrl::fromLocalFile(this->rootPath)); - SCAVENGE_PARENT = hard ? nullptr : this; auto* obj = component.beginCreate(this->engine.rootContext()); - SCAVENGE_PARENT = nullptr; if (obj == nullptr) { qWarning() << component.errorString().toStdString().c_str(); @@ -51,6 +46,8 @@ void RootWrapper::reloadGraph(bool hard) { component.completeCreate(); + newRoot->onReload(hard ? nullptr : this->root); + if (this->root != nullptr) { this->root->deleteLater(); this->root = nullptr; diff --git a/src/cpp/rootwrapper.hpp b/src/cpp/rootwrapper.hpp index 2f463042..534a7669 100644 --- a/src/cpp/rootwrapper.hpp +++ b/src/cpp/rootwrapper.hpp @@ -5,18 +5,15 @@ #include #include -#include "scavenge.hpp" #include "shell.hpp" #include "watcher.hpp" -class RootWrapper: public QObject, virtual public Scavengeable { +class RootWrapper: public QObject { Q_OBJECT; public: explicit RootWrapper(QString rootPath); - QObject* scavengeTargetFor(QObject* child) override; - void reloadGraph(bool hard); private slots: diff --git a/src/cpp/scavenge.cpp b/src/cpp/scavenge.cpp deleted file mode 100644 index 2ad77694..00000000 --- a/src/cpp/scavenge.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "scavenge.hpp" -#include - -#include -#include -#include -#include -#include -#include - -// FIXME: there are core problems with SCAVENGE_PARENT due to the qml engine liking to set parents really late. -// this should instead be handled by proxying all property values until a possible target is ready or definitely not coming. -// The parent should probably be stable in componentComplete() but should be tested. - -QObject* SCAVENGE_PARENT = nullptr; // NOLINT - -void Scavenger::classBegin() { - // prayers - if (this->parent() == nullptr) { - this->setParent(SCAVENGE_PARENT); - SCAVENGE_PARENT = nullptr; - } - - auto* parent = dynamic_cast(this->parent()); - - QObject* old = nullptr; - if (parent != nullptr) { - old = parent->scavengeTargetFor(this); - } - - this->earlyInit(old); -} - -QObject* createComponentScavengeable( - QObject& parent, - QQmlComponent& component, - QVariantMap& initialProperties -) { - SCAVENGE_PARENT = &parent; - auto* instance = component.beginCreate(QQmlEngine::contextForObject(&parent)); - SCAVENGE_PARENT = nullptr; - if (instance == nullptr) return nullptr; - if (instance->parent() != nullptr) instance->setParent(&parent); - component.setInitialProperties(instance, initialProperties); - component.completeCreate(); - - if (instance == nullptr) { - qWarning() << component.errorString().toStdString().c_str(); - } - - return instance; -} - -void ScavengeableScope::earlyInit(QObject* old) { - auto* oldshell = qobject_cast(old); - - if (oldshell != nullptr) { - this->scavengeableData = std::move(oldshell->mData); - } -} - -QObject* ScavengeableScope::scavengeTargetFor(QObject* /* child */) { - if (this->scavengeableData.length() > this->mData.length()) { - return this->scavengeableData[this->mData.length()]; - } - - return nullptr; -} - -QQmlListProperty ScavengeableScope::data() { - return QQmlListProperty( - this, - nullptr, - &ScavengeableScope::appendComponent, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr - ); -} - -void ScavengeableScope::appendComponent(QQmlListProperty* list, QObject* component) { - auto* self = static_cast(list->object); // NOLINT - component->setParent(self); - self->mData.append(component); -} diff --git a/src/cpp/scavenge.hpp b/src/cpp/scavenge.hpp deleted file mode 100644 index 6e9e6d0c..00000000 --- a/src/cpp/scavenge.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -extern QObject* SCAVENGE_PARENT; // NOLINT - -class Scavenger: public QObject, public QQmlParserStatus { - Q_OBJECT; - Q_INTERFACES(QQmlParserStatus); - -public: - explicit Scavenger(QObject* parent = nullptr): QObject(parent) {} - ~Scavenger() override = default; - - Scavenger(Scavenger&) = delete; - Scavenger(Scavenger&&) = delete; - void operator=(Scavenger&) = delete; - void operator=(Scavenger&&) = delete; - - void classBegin() override; - void componentComplete() override {} - -protected: - // do early init, sometimes with a scavengeable target - virtual void earlyInit(QObject* old) = 0; -}; - -class Scavengeable { -public: - explicit Scavengeable() = default; - virtual ~Scavengeable() = default; - - Scavengeable(Scavengeable&) = delete; - Scavengeable(Scavengeable&&) = delete; - void operator=(Scavengeable&) = delete; - void operator=(Scavengeable&&) = delete; - - // return an old object that might have salvageable resources - virtual QObject* scavengeTargetFor(QObject* child) = 0; -}; - -QObject* createComponentScavengeable( - QObject& parent, - QQmlComponent& component, - QVariantMap& initialProperties -); - -///! Reloader connection scope -/// Attempts to maintain scavengeable connections. -/// This is mostly useful to split a scavengeable component slot (e.g. `Variants`) -/// into multiple slots. -/// -/// If you don't know what that means you probably don't need it. -class ScavengeableScope: public Scavenger, virtual public Scavengeable { - Q_OBJECT; - Q_PROPERTY(QQmlListProperty data READ data); - Q_CLASSINFO("DefaultProperty", "data"); - QML_ELEMENT; - -public: - explicit ScavengeableScope(QObject* parent = nullptr): Scavenger(parent) {} - - void earlyInit(QObject* old) override; - QObject* scavengeTargetFor(QObject* child) override; - - QQmlListProperty data(); - -private: - static void appendComponent(QQmlListProperty* list, QObject* component); - - // track only the children assigned to `data` in order - QList mData; - QList scavengeableData; -}; diff --git a/src/cpp/shell.hpp b/src/cpp/shell.hpp index cc2862ce..318be183 100644 --- a/src/cpp/shell.hpp +++ b/src/cpp/shell.hpp @@ -5,7 +5,7 @@ #include #include -#include "scavenge.hpp" +#include "reload.hpp" class ShellConfig { Q_GADGET; @@ -16,7 +16,7 @@ public: }; ///! Root config element -class ShellRoot: public ScavengeableScope { +class ShellRoot: public ReloadPropagator { Q_OBJECT; /// If `config.watchFiles` is true the configuration will be reloaded whenever it changes. /// Defaults to true. @@ -24,7 +24,7 @@ class ShellRoot: public ScavengeableScope { QML_ELEMENT; public: - explicit ShellRoot(QObject* parent = nullptr): ScavengeableScope(parent) {} + explicit ShellRoot(QObject* parent = nullptr): ReloadPropagator(parent) {} void setConfig(ShellConfig config); [[nodiscard]] ShellConfig config() const; diff --git a/src/cpp/variants.cpp b/src/cpp/variants.cpp index a02740fb..f984f6d7 100644 --- a/src/cpp/variants.cpp +++ b/src/cpp/variants.cpp @@ -5,50 +5,47 @@ #include #include #include +#include -#include "scavenge.hpp" +#include "reload.hpp" -void Variants::earlyInit(QObject* old) { - auto* oldv = qobject_cast(old); - if (oldv != nullptr) { - this->scavengeableInstances = std::move(oldv->instances); - } -} +void Variants::onReload(QObject* oldInstance) { + auto* old = qobject_cast(oldInstance); -QObject* Variants::scavengeTargetFor(QObject* /* child */) { - // Attempt to find the set that most closely matches the current one. - // This is biased to the order of the scavenge list which should help in - // case of conflicts as long as variants have not been reordered. + for (auto& [variant, instanceObj]: this->instances.values) { + QObject* oldInstance = nullptr; + if (old != nullptr) { + auto& values = old->instances.values; - if (this->activeScavengeVariant != nullptr) { - auto& values = this->scavengeableInstances.values; - if (values.empty()) return nullptr; - - int matchcount = 0; - int matchi = 0; - int i = 0; - for (auto& [valueSet, _]: values) { - int count = 0; - for (auto& [k, v]: this->activeScavengeVariant->toStdMap()) { - if (valueSet.contains(k) && valueSet.value(k) == v) { - count++; + int matchcount = 0; + int matchi = 0; + int i = 0; + for (auto& [valueSet, _]: values) { + int count = 0; + for (auto& [k, v]: variant.toStdMap()) { + if (valueSet.contains(k) && valueSet.value(k) == v) { + count++; + } } + + if (count > matchcount) { + matchcount = count; + matchi = i; + } + + i++; } - if (count > matchcount) { - matchcount = count; - matchi = i; + if (matchcount > 0) { + oldInstance = values.takeAt(matchi).second; } - - i++; } - if (matchcount > 0) { - return values.takeAt(matchi).second; - } + auto* instance = qobject_cast(instanceObj); + + if (instance != nullptr) instance->onReload(oldInstance); + else Reloadable::reloadChildrenRecursive(instanceObj, oldInstance); } - - return nullptr; } void Variants::setVariants(QVariantList variants) { @@ -57,7 +54,7 @@ void Variants::setVariants(QVariantList variants) { } void Variants::componentComplete() { - this->Scavenger::componentComplete(); + this->Reloadable::componentComplete(); this->updateVariants(); } @@ -96,14 +93,18 @@ void Variants::updateVariants() { continue; // we dont need to recreate this one } - this->activeScavengeVariant = &variant; - auto* instance = createComponentScavengeable(*this, *this->mComponent, variant); + auto* instance = this->mComponent->createWithInitialProperties( + variant, + QQmlEngine::contextForObject(this) + ); if (instance == nullptr) { + qWarning() << this->mComponent->errorString().toStdString().c_str(); qWarning() << "failed to create variant with object" << variant; continue; } + instance->setParent(this); this->instances.insert(variant, instance); } diff --git a/src/cpp/variants.hpp b/src/cpp/variants.hpp index 9526538b..6b07663e 100644 --- a/src/cpp/variants.hpp +++ b/src/cpp/variants.hpp @@ -7,8 +7,9 @@ #include #include #include +#include -#include "scavenge.hpp" +#include "reload.hpp" // extremely inefficient map template @@ -28,7 +29,7 @@ public: /// screen. /// /// [QuickShell.screens]: ../quickshell#prop.screens -class Variants: public Scavenger, virtual public Scavengeable { +class Variants: public Reloadable { Q_OBJECT; /// The component to create instances of Q_PROPERTY(QQmlComponent* component MEMBER mComponent); @@ -39,10 +40,9 @@ class Variants: public Scavenger, virtual public Scavengeable { QML_ELEMENT; public: - explicit Variants(QObject* parent = nullptr): Scavenger(parent) {} + explicit Variants(QObject* parent = nullptr): Reloadable(parent) {} - void earlyInit(QObject* old) override; - QObject* scavengeTargetFor(QObject* child) override; + void onReload(QObject* oldInstance) override; void componentComplete() override; @@ -53,8 +53,4 @@ private: QQmlComponent* mComponent = nullptr; QVariantList mVariants; AwfulMap instances; - - // pointers may die post componentComplete. - AwfulMap scavengeableInstances; - QVariantMap* activeScavengeVariant = nullptr; };