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.
This commit is contained in:
parent
d6ed717c39
commit
1da43be6c0
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ProxyShellWindow*>(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;
|
||||
|
|
|
@ -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<QObject> 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;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ description = "Core QuickShell types"
|
|||
headers = [
|
||||
"qmlglobal.hpp",
|
||||
"qmlscreen.hpp",
|
||||
"scavenge.hpp",
|
||||
"reload.hpp",
|
||||
"shell.hpp",
|
||||
"variants.hpp",
|
||||
"proxywindow.hpp",
|
||||
|
|
|
@ -17,17 +17,29 @@ ProxyWindowBase::~ProxyWindowBase() {
|
|||
}
|
||||
}
|
||||
|
||||
void ProxyWindowBase::earlyInit(QObject* old) {
|
||||
auto* oldpw = qobject_cast<ProxyWindowBase*>(old);
|
||||
void ProxyWindowBase::onReload(QObject* oldInstance) {
|
||||
auto* old = qobject_cast<ProxyWindowBase*>(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<QObject> ProxyWindowBase::data() {
|
|||
);
|
||||
}
|
||||
|
||||
QQmlListProperty<QObject> ProxyWindowBase::dataBacker(QQmlListProperty<QObject>* prop) {
|
||||
auto* that = static_cast<ProxyWindowBase*>(prop->object); // NOLINT
|
||||
return that->window->property("data").value<QQmlListProperty<QObject>>();
|
||||
QQmlListProperty<QObject> ProxyWindowBase::dataBacker() {
|
||||
return this->window->property("data").value<QQmlListProperty<QObject>>();
|
||||
}
|
||||
|
||||
void ProxyWindowBase::dataAppend(QQmlListProperty<QObject>* prop, QObject* obj) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.append(&backer, obj);
|
||||
auto* self = static_cast<ProxyWindowBase*>(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<QObject>* prop) {
|
||||
auto backer = dataBacker(prop);
|
||||
return backer.count(&backer);
|
||||
auto* self = static_cast<ProxyWindowBase*>(prop->object); // NOLINT
|
||||
|
||||
if (self->window == nullptr) {
|
||||
return self->pendingChildren.count();
|
||||
} else {
|
||||
auto backer = self->dataBacker();
|
||||
return backer.count(&backer);
|
||||
}
|
||||
}
|
||||
|
||||
QObject* ProxyWindowBase::dataAt(QQmlListProperty<QObject>* prop, qsizetype i) {
|
||||
auto backer = dataBacker(prop);
|
||||
return backer.at(&backer, i);
|
||||
auto* self = static_cast<ProxyWindowBase*>(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<QObject>* prop) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.clear(&backer);
|
||||
auto* self = static_cast<ProxyWindowBase*>(prop->object); // NOLINT
|
||||
|
||||
if (self->window == nullptr) {
|
||||
self->pendingChildren.clear();
|
||||
} else {
|
||||
auto backer = self->dataBacker();
|
||||
backer.clear(&backer);
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyWindowBase::dataReplace(QQmlListProperty<QObject>* prop, qsizetype i, QObject* obj) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.replace(&backer, i, obj);
|
||||
auto* self = static_cast<ProxyWindowBase*>(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<QObject>* prop) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.removeLast(&backer);
|
||||
}
|
||||
auto* self = static_cast<ProxyWindowBase*>(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);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qevent.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
|
@ -11,7 +12,7 @@
|
|||
#include <qtypes.h>
|
||||
|
||||
#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<QObject> 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<QObject> 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<QObject> dataBacker(QQmlListProperty<QObject>* prop);
|
||||
void updateMask();
|
||||
QQmlListProperty<QObject> dataBacker();
|
||||
|
||||
static void dataAppend(QQmlListProperty<QObject>* prop, QObject* obj);
|
||||
static qsizetype dataCount(QQmlListProperty<QObject>* prop);
|
||||
static QObject* dataAt(QQmlListProperty<QObject>* prop, qsizetype i);
|
||||
|
@ -154,7 +163,7 @@ private:
|
|||
static void dataReplace(QQmlListProperty<QObject>* prop, qsizetype i, QObject* obj);
|
||||
static void dataRemoveLast(QQmlListProperty<QObject>* prop);
|
||||
|
||||
PendingRegion* mMask = nullptr;
|
||||
QVector<QObject*> 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;
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
77
src/cpp/reload.cpp
Normal file
77
src/cpp/reload.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "reload.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
|
||||
void ReloadPropagator::onReload(QObject* oldInstance) {
|
||||
auto* old = qobject_cast<ReloadPropagator*>(oldInstance);
|
||||
|
||||
for (auto i = 0; i < this->mChildren.length(); i++) {
|
||||
auto* newChild = qobject_cast<Reloadable*>(this->mChildren.at(i));
|
||||
if (newChild != nullptr) {
|
||||
auto* oldChild = old == nullptr || old->mChildren.length() <= i
|
||||
? nullptr
|
||||
: qobject_cast<Reloadable*>(old->mChildren.at(i));
|
||||
newChild->onReload(oldChild);
|
||||
} else {
|
||||
Reloadable::reloadRecursive(newChild, oldInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<QObject> ReloadPropagator::data() {
|
||||
return QQmlListProperty<QObject>(
|
||||
this,
|
||||
nullptr,
|
||||
&ReloadPropagator::appendComponent,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void ReloadPropagator::appendComponent(QQmlListProperty<QObject>* list, QObject* obj) {
|
||||
auto* self = static_cast<ReloadPropagator*>(list->object); // NOLINT
|
||||
obj->setParent(self);
|
||||
self->mChildren.append(obj);
|
||||
}
|
||||
|
||||
void Reloadable::reloadRecursive(QObject* newObj, QObject* oldRoot) {
|
||||
auto* reloadable = qobject_cast<Reloadable*>(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<Reloadable*>(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;
|
||||
}
|
107
src/cpp/reload.hpp
Normal file
107
src/cpp/reload.hpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
///! 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<QObject> children READ data);
|
||||
Q_CLASSINFO("DefaultProperty", "children");
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit ReloadPropagator(QObject* parent = nullptr): Reloadable(parent) {}
|
||||
|
||||
void onReload(QObject* oldInstance) override;
|
||||
|
||||
QQmlListProperty<QObject> data();
|
||||
|
||||
private:
|
||||
static void appendComponent(QQmlListProperty<QObject>* list, QObject* obj);
|
||||
|
||||
QList<QObject*> mChildren;
|
||||
};
|
|
@ -9,7 +9,6 @@
|
|||
#include <qqmlengine.h>
|
||||
#include <qurl.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -5,18 +5,15 @@
|
|||
#include <qtmetamacros.h>
|
||||
#include <qurl.h>
|
||||
|
||||
#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:
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
#include "scavenge.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmllist.h>
|
||||
|
||||
// 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<Scavengeable*>(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<ScavengeableScope*>(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<QObject> ScavengeableScope::data() {
|
||||
return QQmlListProperty<QObject>(
|
||||
this,
|
||||
nullptr,
|
||||
&ScavengeableScope::appendComponent,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void ScavengeableScope::appendComponent(QQmlListProperty<QObject>* list, QObject* component) {
|
||||
auto* self = static_cast<ScavengeableScope*>(list->object); // NOLINT
|
||||
component->setParent(self);
|
||||
self->mData.append(component);
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
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<QObject> 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<QObject> data();
|
||||
|
||||
private:
|
||||
static void appendComponent(QQmlListProperty<QObject>* list, QObject* component);
|
||||
|
||||
// track only the children assigned to `data` in order
|
||||
QList<QObject*> mData;
|
||||
QList<QObject*> scavengeableData;
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
#include <qqmlengine.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -5,50 +5,47 @@
|
|||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlengine.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
#include "reload.hpp"
|
||||
|
||||
void Variants::earlyInit(QObject* old) {
|
||||
auto* oldv = qobject_cast<Variants*>(old);
|
||||
if (oldv != nullptr) {
|
||||
this->scavengeableInstances = std::move(oldv->instances);
|
||||
}
|
||||
}
|
||||
void Variants::onReload(QObject* oldInstance) {
|
||||
auto* old = qobject_cast<Variants*>(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<Reloadable*>(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);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
#include "reload.hpp"
|
||||
|
||||
// extremely inefficient map
|
||||
template <typename K, typename V>
|
||||
|
@ -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<QVariantMap, QObject*> instances;
|
||||
|
||||
// pointers may die post componentComplete.
|
||||
AwfulMap<QVariantMap, QObject*> scavengeableInstances;
|
||||
QVariantMap* activeScavengeVariant = nullptr;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue