forked from quickshell/quickshell
refactor(wayland): seperate cmake files for modularity
This commit is contained in:
parent
5bbd0333ef
commit
c6dde9ca9d
28 changed files with 32 additions and 33 deletions
31
src/core/CMakeLists.txt
Normal file
31
src/core/CMakeLists.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
qt_add_executable(quickshell
|
||||
main.cpp
|
||||
shell.cpp
|
||||
variants.cpp
|
||||
rootwrapper.cpp
|
||||
proxywindow.cpp
|
||||
reload.cpp
|
||||
rootwrapper.cpp
|
||||
qmlglobal.cpp
|
||||
qmlscreen.cpp
|
||||
watcher.cpp
|
||||
region.cpp
|
||||
persistentprops.cpp
|
||||
shellwindow.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell URI QuickShell)
|
||||
|
||||
# qml type registration requires this
|
||||
target_include_directories(quickshell PRIVATE src/core)
|
||||
|
||||
target_link_libraries(quickshell PRIVATE Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2)
|
||||
|
||||
if (LAYERSHELL)
|
||||
find_package(LayerShellQt REQUIRED)
|
||||
|
||||
target_link_libraries(quickshell PRIVATE LayerShellQtInterface)
|
||||
target_compile_definitions(quickshell PRIVATE CONF_LAYERSHELL)
|
||||
|
||||
target_sources(quickshell PRIVATE layershell.cpp)
|
||||
endif()
|
300
src/core/layershell.cpp
Normal file
300
src/core/layershell.cpp
Normal file
|
@ -0,0 +1,300 @@
|
|||
#include "layershell.hpp"
|
||||
|
||||
#include <LayerShellQt/window.h>
|
||||
#include <qmargins.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qscreen.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "proxywindow.hpp"
|
||||
#include "shellwindow.hpp"
|
||||
|
||||
WaylandShellWindow::WaylandShellWindow(QObject* parent):
|
||||
ProxyShellWindow(parent), mWayland(new WaylandShellWindowExtensions(this)) {}
|
||||
|
||||
void WaylandShellWindow::setupWindow() {
|
||||
this->shellWindow = LayerShellQt::Window::get(this->window);
|
||||
|
||||
this->ProxyShellWindow::setupWindow();
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(this->shellWindow, &LayerShellQt::Window::anchorsChanged, this, &ProxyShellWindow::anchorsChanged);
|
||||
QObject::connect(this->shellWindow, &LayerShellQt::Window::marginsChanged, this, &ProxyShellWindow::marginsChanged);
|
||||
|
||||
QObject::connect(
|
||||
this->shellWindow, &LayerShellQt::Window::layerChanged,
|
||||
this->mWayland, &WaylandShellWindowExtensions::layerChanged
|
||||
);
|
||||
QObject::connect(
|
||||
this->shellWindow, &LayerShellQt::Window::keyboardInteractivityChanged,
|
||||
this->mWayland, &WaylandShellWindowExtensions::keyboardFocusChanged
|
||||
);
|
||||
|
||||
QObject::connect(this->window, &QWindow::widthChanged, this, &WaylandShellWindow::updateExclusionZone);
|
||||
QObject::connect(this->window, &QWindow::heightChanged, this, &WaylandShellWindow::updateExclusionZone);
|
||||
QObject::connect(this, &ProxyShellWindow::anchorsChanged, this, &WaylandShellWindow::updateExclusionZone);
|
||||
QObject::connect(this, &ProxyShellWindow::marginsChanged, this, &WaylandShellWindow::updateExclusionZone);
|
||||
// clang-format on
|
||||
|
||||
this->setAnchors(this->mAnchors);
|
||||
this->setMargins(this->mMargins);
|
||||
this->setExclusionMode(this->mExclusionMode); // also sets exclusion zone
|
||||
this->mWayland->setLayer(this->mLayer);
|
||||
this->shellWindow->setScope(this->mScope);
|
||||
this->mWayland->setKeyboardFocus(this->mKeyboardFocus);
|
||||
|
||||
this->connected = true;
|
||||
}
|
||||
|
||||
QQuickWindow* WaylandShellWindow::disownWindow() {
|
||||
QObject::disconnect(this->shellWindow, nullptr, this, nullptr);
|
||||
return this->ProxyWindowBase::disownWindow();
|
||||
}
|
||||
|
||||
void WaylandShellWindow::setWidth(qint32 width) {
|
||||
this->mWidth = width;
|
||||
|
||||
// only update the actual size if not blocked by anchors
|
||||
auto anchors = this->anchors();
|
||||
if (!anchors.mLeft || !anchors.mRight) this->ProxyShellWindow::setWidth(width);
|
||||
}
|
||||
|
||||
void WaylandShellWindow::setHeight(qint32 height) {
|
||||
this->mHeight = height;
|
||||
|
||||
// only update the actual size if not blocked by anchors
|
||||
auto anchors = this->anchors();
|
||||
if (!anchors.mTop || !anchors.mBottom) this->ProxyShellWindow::setHeight(height);
|
||||
}
|
||||
|
||||
void WaylandShellWindow::setAnchors(Anchors anchors) {
|
||||
if (this->window == nullptr) {
|
||||
this->mAnchors = anchors;
|
||||
return;
|
||||
}
|
||||
|
||||
auto lsAnchors = LayerShellQt::Window::Anchors();
|
||||
if (anchors.mLeft) lsAnchors |= LayerShellQt::Window::AnchorLeft;
|
||||
if (anchors.mRight) lsAnchors |= LayerShellQt::Window::AnchorRight;
|
||||
if (anchors.mTop) lsAnchors |= LayerShellQt::Window::AnchorTop;
|
||||
if (anchors.mBottom) lsAnchors |= LayerShellQt::Window::AnchorBottom;
|
||||
|
||||
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 WaylandShellWindow::anchors() const {
|
||||
if (this->window == nullptr) return this->mAnchors;
|
||||
|
||||
auto lsAnchors = this->shellWindow->anchors();
|
||||
|
||||
Anchors anchors;
|
||||
anchors.mLeft = lsAnchors.testFlag(LayerShellQt::Window::AnchorLeft);
|
||||
anchors.mRight = lsAnchors.testFlag(LayerShellQt::Window::AnchorRight);
|
||||
anchors.mTop = lsAnchors.testFlag(LayerShellQt::Window::AnchorTop);
|
||||
anchors.mBottom = lsAnchors.testFlag(LayerShellQt::Window::AnchorBottom);
|
||||
|
||||
return anchors;
|
||||
}
|
||||
|
||||
void WaylandShellWindow::setExclusiveZone(qint32 zone) {
|
||||
if (zone < 0) zone = 0;
|
||||
if (this->connected && zone == this->mExclusionZone) return;
|
||||
this->mExclusionZone = zone;
|
||||
|
||||
if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Normal) {
|
||||
this->shellWindow->setExclusiveZone(zone);
|
||||
emit this->exclusionZoneChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qint32 WaylandShellWindow::exclusiveZone() const {
|
||||
if (this->window == nullptr) return this->mExclusionZone;
|
||||
else return this->shellWindow->exclusionZone();
|
||||
}
|
||||
|
||||
ExclusionMode::Enum WaylandShellWindow::exclusionMode() const { return this->mExclusionMode; }
|
||||
|
||||
void WaylandShellWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) {
|
||||
if (this->connected && exclusionMode == this->mExclusionMode) return;
|
||||
this->mExclusionMode = exclusionMode;
|
||||
|
||||
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 WaylandShellWindow::setMargins(Margins margins) {
|
||||
if (this->window == nullptr) this->mMargins = margins;
|
||||
else {
|
||||
auto lsMargins = QMargins(margins.mLeft, margins.mTop, margins.mRight, margins.mBottom);
|
||||
this->shellWindow->setMargins(lsMargins);
|
||||
}
|
||||
}
|
||||
|
||||
Margins WaylandShellWindow::margins() const {
|
||||
if (this->window == nullptr) return this->mMargins;
|
||||
auto lsMargins = this->shellWindow->margins();
|
||||
|
||||
auto margins = Margins();
|
||||
margins.mLeft = lsMargins.left();
|
||||
margins.mRight = lsMargins.right();
|
||||
margins.mTop = lsMargins.top();
|
||||
margins.mBottom = lsMargins.bottom();
|
||||
|
||||
return margins;
|
||||
}
|
||||
|
||||
void WaylandShellWindowExtensions::setLayer(Layer::Enum layer) {
|
||||
if (this->window->window == nullptr) {
|
||||
this->window->mLayer = layer;
|
||||
return;
|
||||
}
|
||||
|
||||
auto lsLayer = LayerShellQt::Window::LayerBackground;
|
||||
|
||||
// clang-format off
|
||||
switch (layer) {
|
||||
case Layer::Background: lsLayer = LayerShellQt::Window::LayerBackground; break;
|
||||
case Layer::Bottom: lsLayer = LayerShellQt::Window::LayerBottom; break;
|
||||
case Layer::Top: lsLayer = LayerShellQt::Window::LayerTop; break;
|
||||
case Layer::Overlay: lsLayer = LayerShellQt::Window::LayerOverlay; break;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
this->window->shellWindow->setLayer(lsLayer);
|
||||
}
|
||||
|
||||
Layer::Enum WaylandShellWindowExtensions::layer() const {
|
||||
if (this->window->window == nullptr) return this->window->mLayer;
|
||||
|
||||
auto layer = Layer::Top;
|
||||
auto lsLayer = this->window->shellWindow->layer();
|
||||
|
||||
// clang-format off
|
||||
switch (lsLayer) {
|
||||
case LayerShellQt::Window::LayerBackground: layer = Layer::Background; break;
|
||||
case LayerShellQt::Window::LayerBottom: layer = Layer::Bottom; break;
|
||||
case LayerShellQt::Window::LayerTop: layer = Layer::Top; break;
|
||||
case LayerShellQt::Window::LayerOverlay: layer = Layer::Overlay; break;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
void WaylandShellWindowExtensions::setScope(const QString& scope) {
|
||||
if (this->window->window == nullptr) this->window->mScope = scope;
|
||||
else this->window->shellWindow->setScope(scope);
|
||||
}
|
||||
|
||||
QString WaylandShellWindowExtensions::scope() const {
|
||||
if (this->window->window == nullptr) return this->window->mScope;
|
||||
else return this->window->shellWindow->scope();
|
||||
}
|
||||
|
||||
void WaylandShellWindowExtensions::setKeyboardFocus(KeyboardFocus::Enum focus) {
|
||||
if (this->window->window == nullptr) {
|
||||
this->window->mKeyboardFocus = focus;
|
||||
return;
|
||||
}
|
||||
|
||||
auto lsFocus = LayerShellQt::Window::KeyboardInteractivityNone;
|
||||
|
||||
// clang-format off
|
||||
switch (focus) {
|
||||
case KeyboardFocus::None: lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; break;
|
||||
case KeyboardFocus::Exclusive: lsFocus = LayerShellQt::Window::KeyboardInteractivityExclusive; break;
|
||||
case KeyboardFocus::OnDemand: lsFocus = LayerShellQt::Window::KeyboardInteractivityOnDemand; break;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
this->window->shellWindow->setKeyboardInteractivity(lsFocus);
|
||||
}
|
||||
|
||||
KeyboardFocus::Enum WaylandShellWindowExtensions::keyboardFocus() const {
|
||||
if (this->window->window == nullptr) return this->window->mKeyboardFocus;
|
||||
|
||||
auto focus = KeyboardFocus::None;
|
||||
auto lsFocus = this->window->shellWindow->keyboardInteractivity();
|
||||
|
||||
// clang-format off
|
||||
switch (lsFocus) {
|
||||
case LayerShellQt::Window::KeyboardInteractivityNone: focus = KeyboardFocus::None; break;
|
||||
case LayerShellQt::Window::KeyboardInteractivityExclusive: focus = KeyboardFocus::Exclusive; break;
|
||||
case LayerShellQt::Window::KeyboardInteractivityOnDemand: focus = KeyboardFocus::OnDemand; break;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
return focus;
|
||||
}
|
||||
|
||||
void WaylandShellWindowExtensions::setScreenConfiguration(ScreenConfiguration::Enum configuration) {
|
||||
if (this->window->window == nullptr) {
|
||||
this->window->mScreenConfiguration = configuration;
|
||||
return;
|
||||
}
|
||||
|
||||
auto lsConfiguration = LayerShellQt::Window::ScreenFromQWindow;
|
||||
|
||||
// clang-format off
|
||||
switch (configuration) {
|
||||
case ScreenConfiguration::Window: lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; break;
|
||||
case ScreenConfiguration::Compositor: lsConfiguration = LayerShellQt::Window::ScreenFromCompositor; break;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
this->window->shellWindow->setScreenConfiguration(lsConfiguration);
|
||||
}
|
||||
|
||||
ScreenConfiguration::Enum WaylandShellWindowExtensions::screenConfiguration() const {
|
||||
if (this->window->window == nullptr) return this->window->mScreenConfiguration;
|
||||
|
||||
auto configuration = ScreenConfiguration::Window;
|
||||
auto lsConfiguration = this->window->shellWindow->screenConfiguration();
|
||||
|
||||
// clang-format off
|
||||
switch (lsConfiguration) {
|
||||
case LayerShellQt::Window::ScreenFromQWindow: configuration = ScreenConfiguration::Window; break;
|
||||
case LayerShellQt::Window::ScreenFromCompositor: configuration = ScreenConfiguration::Compositor; break;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
void WaylandShellWindow::updateExclusionZone() {
|
||||
if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Auto) {
|
||||
auto anchors = this->anchors();
|
||||
|
||||
auto zone = -1;
|
||||
|
||||
if (anchors.mTop && anchors.mBottom) {
|
||||
if (anchors.mLeft) zone = this->width() + this->margins().mLeft;
|
||||
else if (anchors.mRight) zone = this->width() + this->margins().mRight;
|
||||
} else if (anchors.mLeft && anchors.mRight) {
|
||||
if (anchors.mTop) zone = this->height() + this->margins().mTop;
|
||||
else if (anchors.mBottom) zone = this->height() + this->margins().mBottom;
|
||||
}
|
||||
|
||||
if (zone != -1) {
|
||||
this->shellWindow->setExclusiveZone(zone);
|
||||
emit this->exclusionZoneChanged();
|
||||
}
|
||||
}
|
||||
}
|
144
src/core/layershell.hpp
Normal file
144
src/core/layershell.hpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
#include <LayerShellQt/window.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "shellwindow.hpp"
|
||||
|
||||
namespace Layer { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
Background = 0,
|
||||
Bottom = 1,
|
||||
Top = 2,
|
||||
Overlay = 3,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace Layer
|
||||
|
||||
/// Type of keyboard focus that will be accepted by a [ShellWindow]
|
||||
///
|
||||
/// [ShellWindow]: ../shellwindow
|
||||
namespace KeyboardFocus { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
/// No keyboard input will be accepted.
|
||||
None = 0,
|
||||
/// Exclusive access to the keyboard, locking out all other windows.
|
||||
Exclusive = 1,
|
||||
/// Access to the keyboard as determined by the operating system.
|
||||
///
|
||||
/// > [!WARNING] On some systems, `OnDemand` may cause the shell window to
|
||||
/// > retain focus over another window unexpectedly.
|
||||
/// > You should try `None` if you experience issues.
|
||||
OnDemand = 2,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace KeyboardFocus
|
||||
|
||||
namespace ScreenConfiguration { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
Window = 0,
|
||||
Compositor = 1,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace ScreenConfiguration
|
||||
|
||||
class WaylandShellWindowExtensions;
|
||||
|
||||
class WaylandShellWindow: public ProxyShellWindow {
|
||||
Q_OBJECT;
|
||||
Q_PROPERTY(WaylandShellWindowExtensions* wayland MEMBER mWayland CONSTANT);
|
||||
QML_NAMED_ELEMENT(ShellWindow);
|
||||
|
||||
public:
|
||||
explicit WaylandShellWindow(QObject* parent = nullptr);
|
||||
|
||||
WaylandShellWindowExtensions* wayland();
|
||||
|
||||
void setupWindow() override;
|
||||
QQuickWindow* disownWindow() override;
|
||||
|
||||
void setWidth(qint32 width) override;
|
||||
void setHeight(qint32 height) override;
|
||||
|
||||
void setAnchors(Anchors anchors) override;
|
||||
[[nodiscard]] Anchors anchors() const override;
|
||||
|
||||
void setExclusiveZone(qint32 zone) override;
|
||||
[[nodiscard]] qint32 exclusiveZone() const override;
|
||||
|
||||
void setExclusionMode(ExclusionMode::Enum exclusionMode) override;
|
||||
[[nodiscard]] ExclusionMode::Enum exclusionMode() const override;
|
||||
|
||||
void setMargins(Margins margins) override;
|
||||
[[nodiscard]] Margins margins() const override;
|
||||
|
||||
protected slots:
|
||||
void updateExclusionZone();
|
||||
|
||||
private:
|
||||
WaylandShellWindowExtensions* mWayland = nullptr;
|
||||
|
||||
LayerShellQt::Window* shellWindow = nullptr;
|
||||
Layer::Enum mLayer = Layer::Top;
|
||||
QString mScope;
|
||||
KeyboardFocus::Enum mKeyboardFocus = KeyboardFocus::None;
|
||||
ScreenConfiguration::Enum mScreenConfiguration = ScreenConfiguration::Window;
|
||||
|
||||
bool connected = false;
|
||||
|
||||
friend class WaylandShellWindowExtensions;
|
||||
};
|
||||
|
||||
class WaylandShellWindowExtensions: public QObject {
|
||||
Q_OBJECT;
|
||||
/// The shell layer the window sits in. Defaults to `Layer.Top`.
|
||||
Q_PROPERTY(Layer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged);
|
||||
Q_PROPERTY(QString scope READ scope WRITE setScope);
|
||||
/// 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);
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("WaylandShellWindowExtensions cannot be created");
|
||||
|
||||
public:
|
||||
explicit WaylandShellWindowExtensions(WaylandShellWindow* window):
|
||||
QObject(window), window(window) {}
|
||||
|
||||
void setLayer(Layer::Enum layer);
|
||||
[[nodiscard]] Layer::Enum layer() const;
|
||||
|
||||
void setScope(const QString& scope);
|
||||
[[nodiscard]] QString scope() const;
|
||||
|
||||
void setKeyboardFocus(KeyboardFocus::Enum focus);
|
||||
[[nodiscard]] KeyboardFocus::Enum keyboardFocus() const;
|
||||
|
||||
void setScreenConfiguration(ScreenConfiguration::Enum configuration);
|
||||
[[nodiscard]] ScreenConfiguration::Enum screenConfiguration() const;
|
||||
|
||||
signals:
|
||||
void layerChanged();
|
||||
void keyboardFocusChanged();
|
||||
|
||||
private:
|
||||
WaylandShellWindow* window;
|
||||
|
||||
friend class WaylandShellWindow;
|
||||
};
|
56
src/core/main.cpp
Normal file
56
src/core/main.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include <qcommandlineoption.h>
|
||||
#include <qcommandlineparser.h>
|
||||
#include <qdir.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qstandardpaths.h>
|
||||
#include <qstring.h>
|
||||
|
||||
#include "rootwrapper.hpp"
|
||||
|
||||
#ifdef CONF_LAYERSHELL
|
||||
#include <LayerShellQt/shell.h>
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const auto app = QGuiApplication(argc, argv);
|
||||
QGuiApplication::setApplicationName("quickshell");
|
||||
QGuiApplication::setApplicationVersion("0.0.1");
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
auto configOption = QCommandLineOption({"c", "config"}, "Path to configuration file.", "path");
|
||||
parser.addOption(configOption);
|
||||
parser.process(app);
|
||||
|
||||
QString configPath;
|
||||
if (parser.isSet(configOption)) {
|
||||
configPath = parser.value(configOption);
|
||||
} else {
|
||||
configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
configPath = QDir(QDir(configPath).filePath("quickshell")).filePath("shell.qml");
|
||||
}
|
||||
|
||||
qInfo() << "config file path:" << configPath;
|
||||
|
||||
if (!QFile(configPath).exists()) {
|
||||
qCritical() << "config file does not exist";
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if CONF_LAYERSHELL
|
||||
LayerShellQt::Shell::useLayerShell();
|
||||
#endif
|
||||
|
||||
// Base window transparency appears to be additive.
|
||||
// Use a fully transparent window with a colored rect.
|
||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||
|
||||
auto root = RootWrapper(configPath);
|
||||
|
||||
return QGuiApplication::exec();
|
||||
}
|
15
src/core/module.md
Normal file
15
src/core/module.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
name = "QuickShell"
|
||||
description = "Core QuickShell types"
|
||||
headers = [
|
||||
"qmlglobal.hpp",
|
||||
"qmlscreen.hpp",
|
||||
"reload.hpp",
|
||||
"shell.hpp",
|
||||
"variants.hpp",
|
||||
"proxywindow.hpp",
|
||||
"layershell.hpp",
|
||||
"region.hpp",
|
||||
"persistentprops.hpp",
|
||||
]
|
||||
-----
|
||||
The core types provided by QuickShell
|
24
src/core/persistentprops.cpp
Normal file
24
src/core/persistentprops.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include "persistentprops.hpp"
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
void PersistentProperties::onReload(QObject* oldInstance) {
|
||||
if (qobject_cast<PersistentProperties*>(oldInstance) == nullptr) {
|
||||
emit this->loaded();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* metaObject = this->metaObject();
|
||||
for (auto i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) {
|
||||
const auto prop = metaObject->property(i);
|
||||
auto oldProp = oldInstance->property(prop.name());
|
||||
|
||||
if (oldProp.isValid()) {
|
||||
this->setProperty(prop.name(), oldProp);
|
||||
}
|
||||
}
|
||||
|
||||
emit this->loaded();
|
||||
emit this->reloaded();
|
||||
}
|
57
src/core/persistentprops.hpp
Normal file
57
src/core/persistentprops.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include "reload.hpp"
|
||||
|
||||
///! Object that holds properties that can persist across a config reload.
|
||||
/// PersistentProperties holds properties declated in it across a reload, which is
|
||||
/// often useful for things like keeping expandable popups open and styling them.
|
||||
///
|
||||
/// Below is an example of using `PersistentProperties` to keep track of the state
|
||||
/// of an expandable panel. When the configuration is reloaded, the `expanderOpen` property
|
||||
/// will be saved and the expandable panel will stay in the open/closed state.
|
||||
///
|
||||
/// ```qml
|
||||
/// PersistentProperties {
|
||||
/// id: persist
|
||||
/// reloadableId: "persistedStates"
|
||||
///
|
||||
/// property bool expanderOpen: false
|
||||
/// }
|
||||
///
|
||||
/// Button {
|
||||
/// id: expanderButton
|
||||
/// anchors.centerIn: parent
|
||||
/// text: "toggle expander"
|
||||
/// onClicked: persist.expanderOpen = !persist.expanderOpen
|
||||
/// }
|
||||
///
|
||||
/// Rectangle {
|
||||
/// anchors.top: expanderButton.bottom
|
||||
/// anchors.left: expanderButton.left
|
||||
/// anchors.right: expanderButton.right
|
||||
/// height: 100
|
||||
///
|
||||
/// color: "lightblue"
|
||||
/// visible: persist.expanderOpen
|
||||
/// }
|
||||
/// ```
|
||||
class PersistentProperties: public Reloadable {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
PersistentProperties(QObject* parent = nullptr): Reloadable(parent) {}
|
||||
|
||||
void onReload(QObject* oldInstance) override;
|
||||
|
||||
signals:
|
||||
/// Called every time the reload stage completes.
|
||||
/// Will be called every time, including when nothing was loaded from an old instance.
|
||||
void loaded();
|
||||
/// Called every time the properties are reloaded.
|
||||
/// Will not be called if no old instance was loaded.
|
||||
void reloaded();
|
||||
};
|
216
src/core/proxywindow.cpp
Normal file
216
src/core/proxywindow.cpp
Normal file
|
@ -0,0 +1,216 @@
|
|||
#include "proxywindow.hpp"
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qregion.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "qmlscreen.hpp"
|
||||
#include "region.hpp"
|
||||
#include "reload.hpp"
|
||||
|
||||
ProxyWindowBase::ProxyWindowBase(QObject* parent): Reloadable(parent) {
|
||||
this->mContentItem = new QQuickItem(); // NOLINT
|
||||
this->mContentItem->setParent(this);
|
||||
|
||||
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged);
|
||||
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onHeightChanged);
|
||||
}
|
||||
|
||||
ProxyWindowBase::~ProxyWindowBase() {
|
||||
if (this->window != nullptr) {
|
||||
this->window->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyWindowBase::onReload(QObject* oldInstance) {
|
||||
auto* old = qobject_cast<ProxyWindowBase*>(oldInstance);
|
||||
|
||||
if (old == nullptr || old->window == nullptr) {
|
||||
this->window = new QQuickWindow();
|
||||
} else {
|
||||
this->window = old->disownWindow();
|
||||
}
|
||||
|
||||
this->setupWindow();
|
||||
|
||||
Reloadable::reloadRecursive(this->mContentItem, oldInstance);
|
||||
|
||||
this->mContentItem->setParentItem(this->window->contentItem());
|
||||
|
||||
this->mContentItem->setWidth(this->width());
|
||||
this->mContentItem->setHeight(this->height());
|
||||
|
||||
// without this the dangling screen pointer wont be updated to a real screen
|
||||
emit this->screenChanged();
|
||||
|
||||
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);
|
||||
QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged);
|
||||
QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged);
|
||||
QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged);
|
||||
|
||||
QObject::connect(this, &ProxyWindowBase::maskChanged, this, &ProxyWindowBase::onMaskChanged);
|
||||
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onMaskChanged);
|
||||
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onMaskChanged);
|
||||
// clang-format on
|
||||
|
||||
this->window->setScreen(this->mScreen);
|
||||
this->setWidth(this->mWidth);
|
||||
this->setHeight(this->mHeight);
|
||||
this->setColor(this->mColor);
|
||||
this->updateMask();
|
||||
}
|
||||
|
||||
QQuickWindow* ProxyWindowBase::disownWindow() {
|
||||
QObject::disconnect(this->window, nullptr, this, nullptr);
|
||||
|
||||
this->mContentItem->setParentItem(nullptr);
|
||||
|
||||
auto* window = this->window;
|
||||
this->window = nullptr;
|
||||
return window;
|
||||
}
|
||||
|
||||
QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; }
|
||||
QQuickItem* ProxyWindowBase::contentItem() const { return this->mContentItem; }
|
||||
|
||||
bool ProxyWindowBase::isVisible() const {
|
||||
if (this->window == nullptr) return this->mVisible;
|
||||
else return this->window->isVisible();
|
||||
}
|
||||
|
||||
void ProxyWindowBase::setVisible(bool visible) {
|
||||
if (this->window == nullptr) {
|
||||
this->mVisible = visible;
|
||||
emit this->visibleChanged();
|
||||
} else this->window->setVisible(visible);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void ProxyWindowBase::setScreen(QuickShellScreenInfo* 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, &ProxyWindowBase::onScreenDestroyed);
|
||||
}
|
||||
|
||||
if (this->window == nullptr) this->mScreen = qscreen;
|
||||
else this->window->setScreen(qscreen);
|
||||
}
|
||||
|
||||
void ProxyWindowBase::onScreenDestroyed() { this->mScreen = nullptr; }
|
||||
|
||||
QuickShellScreenInfo* ProxyWindowBase::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<ProxyWindowBase*>(this), // NOLINT
|
||||
qscreen
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
this->mMask->deleteLater();
|
||||
}
|
||||
|
||||
if (mask != nullptr) {
|
||||
mask->setParent(this);
|
||||
this->mMask = mask;
|
||||
QObject::connect(mask, &PendingRegion::changed, this, &ProxyWindowBase::maskChanged);
|
||||
emit this->maskChanged();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
if (this->mMask->mIntersection == Intersection::Combine) {
|
||||
mask = this->mMask->build();
|
||||
} else {
|
||||
auto windowRegion = QRegion(QRect(0, 0, this->width(), this->height()));
|
||||
mask = this->mMask->applyTo(windowRegion);
|
||||
}
|
||||
}
|
||||
|
||||
this->window->setMask(mask);
|
||||
}
|
||||
|
||||
QQmlListProperty<QObject> ProxyWindowBase::data() {
|
||||
return this->mContentItem->property("data").value<QQmlListProperty<QObject>>();
|
||||
}
|
||||
|
||||
void ProxyWindowBase::onWidthChanged() { this->mContentItem->setWidth(this->width()); }
|
||||
|
||||
void ProxyWindowBase::onHeightChanged() { this->mContentItem->setHeight(this->height()); }
|
||||
|
||||
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);
|
||||
}
|
190
src/core/proxywindow.hpp
Normal file
190
src/core/proxywindow.hpp
Normal file
|
@ -0,0 +1,190 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qevent.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "qmlscreen.hpp"
|
||||
#include "region.hpp"
|
||||
#include "reload.hpp"
|
||||
|
||||
// Proxy to an actual window exposing a limited property set with the ability to
|
||||
// transfer it to a new window.
|
||||
|
||||
///! Base class for reloadable windows
|
||||
/// Base class for reloadable windows. See [ShellWindow] and [FloatingWindow]
|
||||
///
|
||||
/// [ShellWindow]: ../shellwindow
|
||||
/// [FloatingWindow]: ../floatingwindow
|
||||
class ProxyWindowBase: public Reloadable {
|
||||
Q_OBJECT;
|
||||
/// The QtQuick window backing this window.
|
||||
///
|
||||
/// > [!WARNING] Do not expect values set via this property to work correctly.
|
||||
/// > Values set this way will almost certainly misbehave across a reload, possibly
|
||||
/// > even without one.
|
||||
/// >
|
||||
/// > Use **only** if you know what you are doing.
|
||||
Q_PROPERTY(QQuickWindow* _backingWindow READ backingWindow);
|
||||
Q_PROPERTY(QQuickItem* contentItem READ contentItem);
|
||||
/// If the window is shown or hidden. Defaults to true.
|
||||
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
|
||||
Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged);
|
||||
Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged);
|
||||
/// The screen that the window currently occupies.
|
||||
///
|
||||
/// > [!INFO] This cannot be changed while the window is visible.
|
||||
Q_PROPERTY(QuickShellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged);
|
||||
/// The background color of the window. Defaults to white.
|
||||
///
|
||||
/// > [!WARNING] This seems to behave weirdly when using transparent colors on some systems.
|
||||
/// > Using a colored content item over a transparent window is the recommended way to work around this:
|
||||
/// > ```qml
|
||||
/// > ProxyWindow {
|
||||
/// > Rectangle {
|
||||
/// > anchors.fill: parent
|
||||
/// > color: "#20ffffff"
|
||||
/// >
|
||||
/// > // your content here
|
||||
/// > }
|
||||
/// > }
|
||||
/// > ```
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged);
|
||||
/// The clickthrough mask. Defaults to null.
|
||||
///
|
||||
/// If non null then the clickable areas of the window will be determined by the provided region.
|
||||
///
|
||||
/// ```qml
|
||||
/// ShellWindow {
|
||||
/// // The mask region is set to `rect`, meaning only `rect` is clickable.
|
||||
/// // All other clicks pass through the window to ones behind it.
|
||||
/// mask: Region { item: rect }
|
||||
///
|
||||
/// Rectangle {
|
||||
/// id: rect
|
||||
///
|
||||
/// anchors.centerIn: parent
|
||||
/// width: 100
|
||||
/// height: 100
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If the provided region's intersection mode is `Combine` (the default),
|
||||
/// then the region will be used as is. Otherwise it will be applied on top of the window region.
|
||||
///
|
||||
/// For example, setting the intersection mode to `Xor` will invert the mask and make everything in
|
||||
/// the mask region not clickable and pass through clicks inside it through the window.
|
||||
///
|
||||
/// ```qml
|
||||
/// ShellWindow {
|
||||
/// // The mask region is set to `rect`, but the intersection mode is set to `Xor`.
|
||||
/// // This inverts the mask causing all clicks inside `rect` to be passed to the window
|
||||
/// // behind this one.
|
||||
/// mask: Region { item: rect; intersection: Intersection.Xor }
|
||||
///
|
||||
/// Rectangle {
|
||||
/// id: rect
|
||||
///
|
||||
/// anchors.centerIn: parent
|
||||
/// width: 100
|
||||
/// height: 100
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged);
|
||||
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
||||
Q_CLASSINFO("DefaultProperty", "data");
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("use ShellWindow or FloatingWindow");
|
||||
|
||||
public:
|
||||
explicit ProxyWindowBase(QObject* parent = nullptr);
|
||||
~ProxyWindowBase() override;
|
||||
|
||||
ProxyWindowBase(ProxyWindowBase&) = delete;
|
||||
ProxyWindowBase(ProxyWindowBase&&) = delete;
|
||||
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();
|
||||
|
||||
[[nodiscard]] QQuickWindow* backingWindow() const;
|
||||
[[nodiscard]] QQuickItem* contentItem() const;
|
||||
|
||||
[[nodiscard]] virtual bool isVisible() const;
|
||||
virtual void setVisible(bool visible);
|
||||
|
||||
[[nodiscard]] virtual qint32 width() const;
|
||||
virtual void setWidth(qint32 width);
|
||||
|
||||
[[nodiscard]] virtual qint32 height() const;
|
||||
virtual void setHeight(qint32 height);
|
||||
|
||||
virtual void setScreen(QuickShellScreenInfo* screen);
|
||||
[[nodiscard]] QuickShellScreenInfo* screen() const;
|
||||
|
||||
[[nodiscard]] QColor color() const;
|
||||
void setColor(QColor color);
|
||||
|
||||
[[nodiscard]] PendingRegion* mask() const;
|
||||
void setMask(PendingRegion* mask);
|
||||
|
||||
QQmlListProperty<QObject> data();
|
||||
|
||||
signals:
|
||||
void windowConnected();
|
||||
void visibleChanged();
|
||||
void widthChanged();
|
||||
void heightChanged();
|
||||
void screenChanged();
|
||||
void colorChanged();
|
||||
void maskChanged();
|
||||
|
||||
private slots:
|
||||
void onMaskChanged();
|
||||
void onWidthChanged();
|
||||
void onHeightChanged();
|
||||
void onScreenDestroyed();
|
||||
|
||||
protected:
|
||||
bool mVisible = true;
|
||||
qint32 mWidth = 100;
|
||||
qint32 mHeight = 100;
|
||||
QScreen* mScreen = nullptr;
|
||||
QColor mColor = Qt::white;
|
||||
PendingRegion* mMask = nullptr;
|
||||
QQuickWindow* window = nullptr;
|
||||
QQuickItem* mContentItem = nullptr;
|
||||
|
||||
private:
|
||||
void updateMask();
|
||||
};
|
||||
|
||||
// qt attempts to resize the window but fails because wayland
|
||||
// and only resizes the graphics context which looks terrible.
|
||||
|
||||
///! Standard floating window.
|
||||
class ProxyFloatingWindow: public ProxyWindowBase {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(FloatingWindow);
|
||||
|
||||
public:
|
||||
// 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;
|
||||
};
|
76
src/core/qmlglobal.cpp
Normal file
76
src/core/qmlglobal.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include "qmlglobal.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcontext.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "qmlscreen.hpp"
|
||||
#include "rootwrapper.hpp"
|
||||
|
||||
QuickShellGlobal::QuickShellGlobal(QObject* parent): QObject(parent) {
|
||||
auto* app = QCoreApplication::instance();
|
||||
auto* guiApp = qobject_cast<QGuiApplication*>(app);
|
||||
|
||||
if (guiApp != nullptr) {
|
||||
// clang-format off
|
||||
QObject::connect(guiApp, &QGuiApplication::primaryScreenChanged, this, &QuickShellGlobal::updateScreens);
|
||||
QObject::connect(guiApp, &QGuiApplication::screenAdded, this, &QuickShellGlobal::updateScreens);
|
||||
QObject::connect(guiApp, &QGuiApplication::screenRemoved, this, &QuickShellGlobal::updateScreens);
|
||||
// clang-format on
|
||||
|
||||
this->updateScreens();
|
||||
}
|
||||
}
|
||||
|
||||
qsizetype QuickShellGlobal::screensCount(QQmlListProperty<QuickShellScreenInfo>* prop) {
|
||||
return static_cast<QuickShellGlobal*>(prop->object)->mScreens.size(); // NOLINT
|
||||
}
|
||||
|
||||
QuickShellScreenInfo*
|
||||
QuickShellGlobal::screenAt(QQmlListProperty<QuickShellScreenInfo>* prop, qsizetype i) {
|
||||
return static_cast<QuickShellGlobal*>(prop->object)->mScreens.at(i); // NOLINT
|
||||
}
|
||||
|
||||
QQmlListProperty<QuickShellScreenInfo> QuickShellGlobal::screens() {
|
||||
return QQmlListProperty<QuickShellScreenInfo>(
|
||||
this,
|
||||
nullptr,
|
||||
&QuickShellGlobal::screensCount,
|
||||
&QuickShellGlobal::screenAt
|
||||
);
|
||||
}
|
||||
|
||||
void QuickShellGlobal::reload(bool hard) {
|
||||
auto* rootobj = QQmlEngine::contextForObject(this)->engine()->parent();
|
||||
auto* root = qobject_cast<RootWrapper*>(rootobj);
|
||||
|
||||
if (root == nullptr) {
|
||||
qWarning() << "cannot find RootWrapper for reload, ignoring request";
|
||||
return;
|
||||
}
|
||||
|
||||
root->reloadGraph(hard);
|
||||
}
|
||||
|
||||
void QuickShellGlobal::updateScreens() {
|
||||
auto screens = QGuiApplication::screens();
|
||||
this->mScreens.resize(screens.size());
|
||||
|
||||
for (auto i = 0; i < screens.size(); i++) {
|
||||
if (this->mScreens[i] != nullptr) {
|
||||
this->mScreens[i]->screen = nullptr;
|
||||
this->mScreens[i]->setParent(nullptr); // delete if not owned by the js engine
|
||||
}
|
||||
|
||||
this->mScreens[i] = new QuickShellScreenInfo(this, screens[i]);
|
||||
}
|
||||
|
||||
emit this->screensChanged();
|
||||
}
|
64
src/core/qmlglobal.hpp
Normal file
64
src/core/qmlglobal.hpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "qmlscreen.hpp"
|
||||
|
||||
class QuickShellGlobal: public QObject {
|
||||
Q_OBJECT;
|
||||
/// All currently connected screens.
|
||||
///
|
||||
/// This property updates as connected screens change.
|
||||
///
|
||||
/// #### Reusing a window on every screen
|
||||
/// ```qml
|
||||
/// ShellRoot {
|
||||
/// Variants {
|
||||
/// ShellWindow {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// // see Variants for details
|
||||
/// variants: QuickShell.screens.map(screen => ({ screen }))
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This creates an instance of your window once on every screen.
|
||||
/// As screens are added or removed your window will be created or destroyed on those screens.
|
||||
Q_PROPERTY(QQmlListProperty<QuickShellScreenInfo> screens READ screens NOTIFY screensChanged);
|
||||
QML_SINGLETON;
|
||||
QML_NAMED_ELEMENT(QuickShell);
|
||||
|
||||
public:
|
||||
QuickShellGlobal(QObject* parent = nullptr);
|
||||
|
||||
QQmlListProperty<QuickShellScreenInfo> screens();
|
||||
|
||||
/// Reload the shell from the [ShellRoot].
|
||||
///
|
||||
/// `hard` - perform a hard reload. If this is false, QuickShell will attempt to reuse windows
|
||||
/// that already exist. If true windows will be recreated.
|
||||
///
|
||||
/// See [Reloadable] for more information on what can be reloaded and how.
|
||||
///
|
||||
/// [Reloadable]: ../reloadable
|
||||
Q_INVOKABLE void reload(bool hard);
|
||||
|
||||
signals:
|
||||
void screensChanged();
|
||||
|
||||
public slots:
|
||||
void updateScreens();
|
||||
|
||||
private:
|
||||
static qsizetype screensCount(QQmlListProperty<QuickShellScreenInfo>* prop);
|
||||
static QuickShellScreenInfo* screenAt(QQmlListProperty<QuickShellScreenInfo>* prop, qsizetype i);
|
||||
|
||||
QVector<QuickShellScreenInfo*> mScreens;
|
||||
};
|
110
src/core/qmlscreen.cpp
Normal file
110
src/core/qmlscreen.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include "qmlscreen.hpp"
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qscreen.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
QuickShellScreenInfo::QuickShellScreenInfo(QObject* parent, QScreen* screen):
|
||||
QObject(parent), screen(screen) {
|
||||
|
||||
if (this->screen != nullptr) {
|
||||
// clang-format off
|
||||
QObject::connect(this->screen, &QScreen::geometryChanged, this, &QuickShellScreenInfo::geometryChanged);
|
||||
QObject::connect(this->screen, &QScreen::physicalDotsPerInchChanged, this, &QuickShellScreenInfo::physicalPixelDensityChanged);
|
||||
QObject::connect(this->screen, &QScreen::logicalDotsPerInchChanged, this, &QuickShellScreenInfo::logicalPixelDensityChanged);
|
||||
QObject::connect(this->screen, &QScreen::orientationChanged, this, &QuickShellScreenInfo::orientationChanged);
|
||||
QObject::connect(this->screen, &QScreen::primaryOrientationChanged, this, &QuickShellScreenInfo::primaryOrientationChanged);
|
||||
QObject::connect(this->screen, &QObject::destroyed, this, &QuickShellScreenInfo::screenDestroyed);
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
bool QuickShellScreenInfo::operator==(QuickShellScreenInfo& other) const {
|
||||
return this->screen == other.screen;
|
||||
}
|
||||
|
||||
void QuickShellScreenInfo::warnDangling() const {
|
||||
if (this->dangling) {
|
||||
qWarning() << "attempted to use dangling screen object";
|
||||
}
|
||||
}
|
||||
|
||||
QString QuickShellScreenInfo::name() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return "{ NULL SCREEN }";
|
||||
}
|
||||
|
||||
return this->screen->name();
|
||||
}
|
||||
|
||||
qint32 QuickShellScreenInfo::width() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this->screen->size().width();
|
||||
}
|
||||
|
||||
qint32 QuickShellScreenInfo::height() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this->screen->size().height();
|
||||
}
|
||||
|
||||
qreal QuickShellScreenInfo::physicalPixelDensity() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return this->screen->physicalDotsPerInch() / 25.4;
|
||||
}
|
||||
|
||||
qreal QuickShellScreenInfo::logicalPixelDensity() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return this->screen->logicalDotsPerInch() / 25.4;
|
||||
}
|
||||
|
||||
qreal QuickShellScreenInfo::devicePixelRatio() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return this->screen->devicePixelRatio();
|
||||
}
|
||||
|
||||
Qt::ScreenOrientation QuickShellScreenInfo::orientation() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return Qt::PrimaryOrientation;
|
||||
}
|
||||
|
||||
return this->screen->orientation();
|
||||
}
|
||||
|
||||
Qt::ScreenOrientation QuickShellScreenInfo::primaryOrientation() const {
|
||||
if (this->screen == nullptr) {
|
||||
this->warnDangling();
|
||||
return Qt::PrimaryOrientation;
|
||||
}
|
||||
|
||||
return this->screen->primaryOrientation();
|
||||
}
|
||||
|
||||
void QuickShellScreenInfo::screenDestroyed() {
|
||||
this->screen = nullptr;
|
||||
this->dangling = true;
|
||||
}
|
73
src/core/qmlscreen.hpp
Normal file
73
src/core/qmlscreen.hpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qscreen.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
// unfortunately QQuickScreenInfo is private.
|
||||
|
||||
/// Monitor object useful for setting the monitor for a [ShellWindow]
|
||||
/// or querying information about the monitor.
|
||||
///
|
||||
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
|
||||
/// > be marked as dangling and all properties will return default values.
|
||||
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
|
||||
///
|
||||
/// Due to some technical limitations, it was not possible to reuse the native qml [Screen] type.
|
||||
///
|
||||
/// [ShellWindow]: ../shellwindow
|
||||
/// [Screen]: https://doc.qt.io/qt-6/qml-qtquick-screen.html
|
||||
class QuickShellScreenInfo: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(ShellScreen);
|
||||
QML_UNCREATABLE("ShellScreen can only be obtained via QuickShell.screens");
|
||||
// clang-format off
|
||||
/// The name of the screen as seen by the operating system.
|
||||
///
|
||||
/// Usually something like `DP-1`, `HDMI-1`, `eDP-1`.
|
||||
Q_PROPERTY(QString name READ name CONSTANT);
|
||||
Q_PROPERTY(qint32 width READ width NOTIFY geometryChanged);
|
||||
Q_PROPERTY(qint32 height READ height NOTIFY geometryChanged);
|
||||
/// The number of physical pixels per millimeter.
|
||||
Q_PROPERTY(qreal physicalPixelDensity READ physicalPixelDensity NOTIFY physicalPixelDensityChanged);
|
||||
/// The number of device-independent (scaled) pixels per millimeter.
|
||||
Q_PROPERTY(qreal logicalPixelDensity READ logicalPixelDensity NOTIFY logicalPixelDensityChanged);
|
||||
/// The ratio between physical pixels and device-independent (scaled) pixels.
|
||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY physicalPixelDensityChanged);
|
||||
Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged);
|
||||
Q_PROPERTY(Qt::ScreenOrientation primatyOrientation READ primaryOrientation NOTIFY primaryOrientationChanged);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
QuickShellScreenInfo(QObject* parent, QScreen* screen); //: QObject(parent), screen(screen) {}
|
||||
|
||||
bool operator==(QuickShellScreenInfo& other) const;
|
||||
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] qint32 width() const;
|
||||
[[nodiscard]] qint32 height() const;
|
||||
[[nodiscard]] qreal physicalPixelDensity() const;
|
||||
[[nodiscard]] qreal logicalPixelDensity() const;
|
||||
[[nodiscard]] qreal devicePixelRatio() const;
|
||||
[[nodiscard]] Qt::ScreenOrientation orientation() const;
|
||||
[[nodiscard]] Qt::ScreenOrientation primaryOrientation() const;
|
||||
|
||||
QScreen* screen;
|
||||
|
||||
private:
|
||||
void warnDangling() const;
|
||||
bool dangling = false;
|
||||
|
||||
signals:
|
||||
void geometryChanged();
|
||||
void physicalPixelDensityChanged();
|
||||
void logicalPixelDensityChanged();
|
||||
void orientationChanged();
|
||||
void primaryOrientationChanged();
|
||||
|
||||
private slots:
|
||||
void screenDestroyed();
|
||||
};
|
107
src/core/region.cpp
Normal file
107
src/core/region.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
#include "region.hpp"
|
||||
#include <cmath>
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qpoint.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qregion.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
||||
QObject::connect(this, &PendingRegion::shapeChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::intersectionChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::itemChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::xChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
|
||||
}
|
||||
|
||||
void PendingRegion::setItem(QQuickItem* item) {
|
||||
if (this->mItem != nullptr) {
|
||||
QObject::disconnect(this->mItem, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mItem = item;
|
||||
|
||||
QObject::connect(this->mItem, &QQuickItem::xChanged, this, &PendingRegion::itemChanged);
|
||||
QObject::connect(this->mItem, &QQuickItem::yChanged, this, &PendingRegion::itemChanged);
|
||||
QObject::connect(this->mItem, &QQuickItem::widthChanged, this, &PendingRegion::itemChanged);
|
||||
QObject::connect(this->mItem, &QQuickItem::heightChanged, this, &PendingRegion::itemChanged);
|
||||
}
|
||||
|
||||
void PendingRegion::onItemDestroyed() { this->mItem = nullptr; }
|
||||
|
||||
QQmlListProperty<PendingRegion> PendingRegion::regions() {
|
||||
return QQmlListProperty<PendingRegion>(
|
||||
this,
|
||||
nullptr,
|
||||
PendingRegion::regionsAppend,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
bool PendingRegion::empty() const {
|
||||
return this->mItem == nullptr && this->mX == 0 && this->mY == 0 && this->mWidth == 0
|
||||
&& this->mHeight == 0;
|
||||
}
|
||||
|
||||
QRegion PendingRegion::build() const {
|
||||
auto type = QRegion::Rectangle;
|
||||
switch (this->mShape) {
|
||||
case RegionShape::Rect: type = QRegion::Rectangle; break;
|
||||
case RegionShape::Ellipse: type = QRegion::Ellipse; break;
|
||||
}
|
||||
|
||||
QRegion region;
|
||||
|
||||
if (this->empty()) {
|
||||
region = QRegion();
|
||||
} else if (this->mItem != nullptr) {
|
||||
auto origin = this->mItem->mapToScene(QPointF(0, 0));
|
||||
auto extent = this->mItem->mapToScene(QPointF(this->mItem->width(), this->mItem->height()));
|
||||
auto size = extent - origin;
|
||||
|
||||
region = QRegion(
|
||||
static_cast<int>(origin.x()),
|
||||
static_cast<int>(origin.y()),
|
||||
static_cast<int>(std::ceil(size.x())),
|
||||
static_cast<int>(std::ceil(size.y())),
|
||||
type
|
||||
);
|
||||
} else {
|
||||
region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type);
|
||||
}
|
||||
|
||||
for (const auto& childRegion: this->mRegions) {
|
||||
region = childRegion->applyTo(region);
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
QRegion PendingRegion::applyTo(QRegion& region) const {
|
||||
switch (this->mIntersection) {
|
||||
case Intersection::Combine: region = region.united(this->build()); break;
|
||||
case Intersection::Subtract: region = region.subtracted(this->build()); break;
|
||||
case Intersection::Intersect: region = region.intersected(this->build()); break;
|
||||
case Intersection::Xor: region = region.xored(this->build()); break;
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
void PendingRegion::regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region) {
|
||||
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||
region->setParent(self);
|
||||
self->mRegions.append(region);
|
||||
|
||||
QObject::connect(region, &PendingRegion::changed, self, &PendingRegion::childrenChanged);
|
||||
emit self->childrenChanged();
|
||||
}
|
125
src/core/region.hpp
Normal file
125
src/core/region.hpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qregion.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
/// Shape of a Region.
|
||||
namespace RegionShape { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
Rect = 0,
|
||||
Ellipse = 1,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace RegionShape
|
||||
|
||||
///! Intersection strategy for Regions.
|
||||
namespace Intersection { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
/// Combine this region, leaving a union of this and the other region. (opposite of `Subtract`)
|
||||
Combine = 0,
|
||||
/// Subtract this region, cutting this region out of the other. (opposite of `Combine`)
|
||||
Subtract = 1,
|
||||
/// Create an intersection of this region and the other, leaving only
|
||||
/// the area covered by both. (opposite of `Xor`)
|
||||
Intersect = 2,
|
||||
/// Create an intersection of this region and the other, leaving only
|
||||
/// the area not covered by both. (opposite of `Intersect`)
|
||||
Xor = 3,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace Intersection
|
||||
|
||||
///! A composable region used as a mask.
|
||||
class PendingRegion: public QObject {
|
||||
Q_OBJECT;
|
||||
/// Defaults to `Rect`.
|
||||
Q_PROPERTY(RegionShape::Enum shape MEMBER mShape NOTIFY shapeChanged);
|
||||
/// The way this region interacts with its parent region. Defaults to `Combine`.
|
||||
Q_PROPERTY(Intersection::Enum intersection MEMBER mIntersection NOTIFY intersectionChanged);
|
||||
|
||||
/// The item that determines the geometry of the region.
|
||||
/// `item` overrides `x`, `y`, `width` and `height`.
|
||||
Q_PROPERTY(QQuickItem* item MEMBER mItem WRITE setItem NOTIFY itemChanged);
|
||||
|
||||
/// Defaults to 0. Does nothing if `item` is set.
|
||||
Q_PROPERTY(qint32 x MEMBER mX NOTIFY xChanged);
|
||||
/// Defaults to 0. Does nothing if `item` is set.
|
||||
Q_PROPERTY(qint32 y MEMBER mY NOTIFY yChanged);
|
||||
/// Defaults to 0. Does nothing if `item` is set.
|
||||
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
|
||||
/// Defaults to 0. Does nothing if `item` is set.
|
||||
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
|
||||
|
||||
/// Regions to apply on top of this region.
|
||||
///
|
||||
/// Regions can be nested to create a more complex region.
|
||||
/// For example this will create a square region with a cutout in the middle.
|
||||
/// ```qml
|
||||
/// Region {
|
||||
/// width: 100; height: 100;
|
||||
///
|
||||
/// Region {
|
||||
/// x: 50; y: 50;
|
||||
/// width: 50; height: 50;
|
||||
/// intersection: Intersection.Subtract
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
Q_PROPERTY(QQmlListProperty<PendingRegion> regions READ regions);
|
||||
Q_CLASSINFO("DefaultProperty", "regions");
|
||||
QML_NAMED_ELEMENT(Region);
|
||||
|
||||
public:
|
||||
explicit PendingRegion(QObject* parent = nullptr);
|
||||
|
||||
void setItem(QQuickItem* item);
|
||||
|
||||
QQmlListProperty<PendingRegion> regions();
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] QRegion build() const;
|
||||
[[nodiscard]] QRegion applyTo(QRegion& region) const;
|
||||
|
||||
RegionShape::Enum mShape = RegionShape::Rect;
|
||||
Intersection::Enum mIntersection = Intersection::Combine;
|
||||
|
||||
signals:
|
||||
void shapeChanged();
|
||||
void intersectionChanged();
|
||||
void itemChanged();
|
||||
void xChanged();
|
||||
void yChanged();
|
||||
void widthChanged();
|
||||
void heightChanged();
|
||||
void childrenChanged();
|
||||
void changed();
|
||||
|
||||
private slots:
|
||||
void onItemDestroyed();
|
||||
|
||||
private:
|
||||
static void regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region);
|
||||
|
||||
QQuickItem* mItem = nullptr;
|
||||
|
||||
qint32 mX = 0;
|
||||
qint32 mY = 0;
|
||||
qint32 mWidth = 0;
|
||||
qint32 mHeight = 0;
|
||||
|
||||
QList<PendingRegion*> mRegions;
|
||||
};
|
77
src/core/reload.cpp
Normal file
77
src/core/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;
|
||||
}
|
111
src/core/reload.hpp
Normal file
111
src/core/reload.hpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#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 [ProxyWindowBase] and [PersistentProperties]
|
||||
///
|
||||
/// [ProxyWindowBase]: ../proxywindowbase
|
||||
/// [PersistentProperties]: ../persistentproperties
|
||||
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 {
|
||||
/// FloatingWindow {
|
||||
/// // this FloatingWindow 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.
|
||||
// If non null the old instance may or may not be of the same type, and should be checked
|
||||
// by `onReload`.
|
||||
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;
|
||||
};
|
81
src/core/rootwrapper.cpp
Normal file
81
src/core/rootwrapper.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "rootwrapper.hpp"
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include <qfileinfo.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qurl.h>
|
||||
|
||||
#include "shell.hpp"
|
||||
#include "watcher.hpp"
|
||||
|
||||
RootWrapper::RootWrapper(QString rootPath):
|
||||
QObject(nullptr), rootPath(std::move(rootPath)), engine(this) {
|
||||
this->reloadGraph(true);
|
||||
|
||||
if (this->root == nullptr) {
|
||||
qCritical() << "could not create scene graph, exiting";
|
||||
exit(-1); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
void RootWrapper::reloadGraph(bool hard) {
|
||||
if (this->root != nullptr) {
|
||||
this->engine.clearComponentCache();
|
||||
}
|
||||
|
||||
auto component = QQmlComponent(&this->engine, QUrl::fromLocalFile(this->rootPath));
|
||||
|
||||
auto* obj = component.beginCreate(this->engine.rootContext());
|
||||
|
||||
if (obj == nullptr) {
|
||||
qWarning() << component.errorString().toStdString().c_str();
|
||||
qWarning() << "failed to create root component";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* newRoot = qobject_cast<ShellRoot*>(obj);
|
||||
if (newRoot == nullptr) {
|
||||
qWarning() << "root component was not a QuickShell.ShellRoot";
|
||||
delete obj;
|
||||
return;
|
||||
}
|
||||
|
||||
component.completeCreate();
|
||||
|
||||
newRoot->onReload(hard ? nullptr : this->root);
|
||||
|
||||
if (this->root != nullptr) {
|
||||
this->root->deleteLater();
|
||||
this->root = nullptr;
|
||||
}
|
||||
|
||||
this->root = newRoot;
|
||||
this->onConfigChanged();
|
||||
}
|
||||
|
||||
void RootWrapper::onConfigChanged() {
|
||||
auto config = this->root->config();
|
||||
|
||||
if (config.mWatchFiles && this->configWatcher == nullptr) {
|
||||
this->configWatcher = new FiletreeWatcher();
|
||||
this->configWatcher->addPath(QFileInfo(this->rootPath).dir().path());
|
||||
|
||||
QObject::connect(this->root, &ShellRoot::configChanged, this, &RootWrapper::onConfigChanged);
|
||||
|
||||
QObject::connect(
|
||||
this->configWatcher,
|
||||
&FiletreeWatcher::fileChanged,
|
||||
this,
|
||||
&RootWrapper::onWatchedFilesChanged
|
||||
);
|
||||
} else if (!config.mWatchFiles && this->configWatcher != nullptr) {
|
||||
this->configWatcher->deleteLater();
|
||||
this->configWatcher = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); }
|
28
src/core/rootwrapper.hpp
Normal file
28
src/core/rootwrapper.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qurl.h>
|
||||
|
||||
#include "shell.hpp"
|
||||
#include "watcher.hpp"
|
||||
|
||||
class RootWrapper: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit RootWrapper(QString rootPath);
|
||||
|
||||
void reloadGraph(bool hard);
|
||||
|
||||
private slots:
|
||||
void onConfigChanged();
|
||||
void onWatchedFilesChanged();
|
||||
|
||||
private:
|
||||
QString rootPath;
|
||||
QQmlEngine engine;
|
||||
ShellRoot* root = nullptr;
|
||||
FiletreeWatcher* configWatcher = nullptr;
|
||||
};
|
11
src/core/shell.cpp
Normal file
11
src/core/shell.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "shell.hpp"
|
||||
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
void ShellRoot::setConfig(ShellConfig config) {
|
||||
this->mConfig = config;
|
||||
|
||||
emit this->configChanged();
|
||||
}
|
||||
|
||||
ShellConfig ShellRoot::config() const { return this->mConfig; }
|
37
src/core/shell.hpp
Normal file
37
src/core/shell.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlist.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "reload.hpp"
|
||||
|
||||
class ShellConfig {
|
||||
Q_GADGET;
|
||||
Q_PROPERTY(bool watchFiles MEMBER mWatchFiles);
|
||||
|
||||
public:
|
||||
bool mWatchFiles = true;
|
||||
};
|
||||
|
||||
///! Root config element
|
||||
class ShellRoot: public ReloadPropagator {
|
||||
Q_OBJECT;
|
||||
/// If `config.watchFiles` is true the configuration will be reloaded whenever it changes.
|
||||
/// Defaults to true.
|
||||
Q_PROPERTY(ShellConfig config READ config WRITE setConfig);
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit ShellRoot(QObject* parent = nullptr): ReloadPropagator(parent) {}
|
||||
|
||||
void setConfig(ShellConfig config);
|
||||
[[nodiscard]] ShellConfig config() const;
|
||||
|
||||
signals:
|
||||
void configChanged();
|
||||
|
||||
private:
|
||||
ShellConfig mConfig;
|
||||
};
|
1
src/core/shellwindow.cpp
Normal file
1
src/core/shellwindow.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "shellwindow.hpp" // NOLINT
|
134
src/core/shellwindow.hpp
Normal file
134
src/core/shellwindow.hpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qscreen.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "proxywindow.hpp"
|
||||
|
||||
class Anchors {
|
||||
Q_GADGET;
|
||||
Q_PROPERTY(bool left MEMBER mLeft);
|
||||
Q_PROPERTY(bool right MEMBER mRight);
|
||||
Q_PROPERTY(bool top MEMBER mTop);
|
||||
Q_PROPERTY(bool bottom MEMBER mBottom);
|
||||
|
||||
public:
|
||||
bool mLeft = false;
|
||||
bool mRight = false;
|
||||
bool mTop = false;
|
||||
bool mBottom = false;
|
||||
};
|
||||
|
||||
class Margins {
|
||||
Q_GADGET;
|
||||
Q_PROPERTY(qint32 left MEMBER mLeft);
|
||||
Q_PROPERTY(qint32 right MEMBER mRight);
|
||||
Q_PROPERTY(qint32 top MEMBER mTop);
|
||||
Q_PROPERTY(qint32 bottom MEMBER mBottom);
|
||||
|
||||
public:
|
||||
qint32 mLeft = 0;
|
||||
qint32 mRight = 0;
|
||||
qint32 mTop = 0;
|
||||
qint32 mBottom = 0;
|
||||
};
|
||||
|
||||
namespace ExclusionMode { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
/// Respect the exclusion zone of other shell layers and optionally set one
|
||||
Normal = 0,
|
||||
/// Ignore exclusion zones of other shell layers. You cannot set an exclusion zone in this mode.
|
||||
Ignore = 1,
|
||||
/// Decide the exclusion zone based on the window dimensions and anchors.
|
||||
///
|
||||
/// Will attempt to reseve exactly enough space for the window and its margins if
|
||||
/// exactly 3 anchors are connected.
|
||||
Auto = 2,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace ExclusionMode
|
||||
|
||||
///! Decorationless window attached to screen edges by anchors.
|
||||
/// Decorationless window attached to screen edges by anchors.
|
||||
///
|
||||
/// #### Example
|
||||
/// The following snippet creates a white bar attached to the bottom of the screen.
|
||||
///
|
||||
/// ```qml
|
||||
/// ShellWindow {
|
||||
/// anchors {
|
||||
/// left: true
|
||||
/// bottom: true
|
||||
/// right: true
|
||||
/// }
|
||||
///
|
||||
/// Text {
|
||||
/// anchors.horizontalCenter: parent.horizontalCenter
|
||||
/// anchors.verticalCenter: parent.verticalCenter
|
||||
/// text: "Hello!"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
class ProxyShellWindow: public ProxyWindowBase {
|
||||
// clang-format off
|
||||
Q_OBJECT;
|
||||
/// Anchors attach a shell window to the sides of the screen.
|
||||
/// By default all anchors are disabled to avoid blocking the entire screen due to a misconfiguration.
|
||||
///
|
||||
/// > [!INFO] When two opposite anchors are attached at the same time, the corrosponding dimension
|
||||
/// > (width or height) will be forced to equal the screen width/height.
|
||||
/// > Margins can be used to create anchored windows that are also disconnected from the monitor sides.
|
||||
Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged);
|
||||
/// The amount of space reserved for the shell layer relative to its anchors.
|
||||
///
|
||||
/// > [!INFO] Some systems will require exactly 3 anchors to be attached for the exclusion zone to take
|
||||
/// > effect.
|
||||
Q_PROPERTY(qint32 exclusionZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusionZoneChanged);
|
||||
/// Defaults to `ExclusionMode.Normal`.
|
||||
Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged);
|
||||
/// Offsets from the sides of the screen.
|
||||
///
|
||||
/// > [!INFO] Only applies to edges with anchors
|
||||
Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit ProxyShellWindow(QObject* parent = nullptr): ProxyWindowBase(parent) {}
|
||||
|
||||
QQmlListProperty<QObject> data();
|
||||
|
||||
virtual void setAnchors(Anchors anchors) = 0;
|
||||
[[nodiscard]] virtual Anchors anchors() const = 0;
|
||||
|
||||
virtual void setExclusiveZone(qint32 zone) = 0;
|
||||
[[nodiscard]] virtual qint32 exclusiveZone() const = 0;
|
||||
|
||||
virtual void setExclusionMode(ExclusionMode::Enum exclusionMode) = 0;
|
||||
[[nodiscard]] virtual ExclusionMode::Enum exclusionMode() const = 0;
|
||||
|
||||
virtual void setMargins(Margins margins) = 0;
|
||||
[[nodiscard]] virtual Margins margins() const = 0;
|
||||
|
||||
signals:
|
||||
void anchorsChanged();
|
||||
void marginsChanged();
|
||||
void exclusionZoneChanged();
|
||||
void exclusionModeChanged();
|
||||
|
||||
protected:
|
||||
ExclusionMode::Enum mExclusionMode = ExclusionMode::Normal;
|
||||
qint32 mExclusionZone = 0;
|
||||
Anchors mAnchors;
|
||||
Margins mMargins;
|
||||
};
|
149
src/core/variants.cpp
Normal file
149
src/core/variants.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
#include "variants.hpp"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlengine.h>
|
||||
|
||||
#include "reload.hpp"
|
||||
|
||||
void Variants::onReload(QObject* oldInstance) {
|
||||
auto* old = qobject_cast<Variants*>(oldInstance);
|
||||
|
||||
for (auto& [variant, instanceObj]: this->instances.values) {
|
||||
QObject* oldInstance = nullptr;
|
||||
if (old != nullptr) {
|
||||
auto& values = old->instances.values;
|
||||
|
||||
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 (matchcount > 0) {
|
||||
oldInstance = values.takeAt(matchi).second;
|
||||
}
|
||||
}
|
||||
|
||||
auto* instance = qobject_cast<Reloadable*>(instanceObj);
|
||||
|
||||
if (instance != nullptr) instance->onReload(oldInstance);
|
||||
else Reloadable::reloadChildrenRecursive(instanceObj, oldInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void Variants::setVariants(QVariantList variants) {
|
||||
this->mVariants = std::move(variants);
|
||||
this->updateVariants();
|
||||
}
|
||||
|
||||
void Variants::componentComplete() {
|
||||
this->Reloadable::componentComplete();
|
||||
this->updateVariants();
|
||||
}
|
||||
|
||||
void Variants::updateVariants() {
|
||||
if (this->mComponent == nullptr) {
|
||||
qWarning() << "Variants instance does not have a component specified";
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up removed entries
|
||||
for (auto iter = this->instances.values.begin(); iter < this->instances.values.end();) {
|
||||
if (this->mVariants.contains(iter->first)) {
|
||||
iter++;
|
||||
} else {
|
||||
iter->second->deleteLater();
|
||||
iter = this->instances.values.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter = this->mVariants.begin(); iter < this->mVariants.end(); iter++) {
|
||||
auto& variantObj = *iter;
|
||||
if (!variantObj.canConvert<QVariantMap>()) {
|
||||
qWarning() << "value passed to Variants is not an object and will be ignored:" << variantObj;
|
||||
} else {
|
||||
auto variant = variantObj.value<QVariantMap>();
|
||||
|
||||
for (auto iter2 = this->mVariants.begin(); iter2 < iter; iter2++) {
|
||||
if (*iter2 == variantObj) {
|
||||
qWarning() << "same value specified twice in Variants, duplicates will be ignored:"
|
||||
<< variantObj;
|
||||
goto outer;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->instances.contains(variant)) {
|
||||
continue; // we dont need to recreate this one
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
outer:;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool AwfulMap<K, V>::contains(const K& key) const {
|
||||
return std::ranges::any_of(this->values, [&](const QPair<K, V>& pair) {
|
||||
return pair.first == key;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
V* AwfulMap<K, V>::get(const K& key) {
|
||||
for (auto& [k, v]: this->values) {
|
||||
if (key == k) {
|
||||
return &v;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void AwfulMap<K, V>::insert(K key, V value) {
|
||||
this->values.push_back(QPair<K, V>(key, value));
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool AwfulMap<K, V>::remove(const K& key) {
|
||||
for (auto iter = this->values.begin(); iter < this->values.end(); iter++) {
|
||||
if (iter->first == key) {
|
||||
this->values.erase(iter);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
56
src/core/variants.hpp
Normal file
56
src/core/variants.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qmap.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "reload.hpp"
|
||||
|
||||
// extremely inefficient map
|
||||
template <typename K, typename V>
|
||||
class AwfulMap {
|
||||
public:
|
||||
[[nodiscard]] bool contains(const K& key) const;
|
||||
[[nodiscard]] V* get(const K& key);
|
||||
void insert(K key, V value); // assumes no duplicates
|
||||
bool remove(const K& key); // returns true if anything was removed
|
||||
QList<QPair<K, V>> values;
|
||||
};
|
||||
|
||||
///! Creates instances of a component based on a given set of variants.
|
||||
/// Creates and destroys instances of the given component when the given property changes.
|
||||
///
|
||||
/// See [QuickShell.screens] for an example of using `Variants` to create copies of a window per
|
||||
/// screen.
|
||||
///
|
||||
/// [QuickShell.screens]: ../quickshell#prop.screens
|
||||
class Variants: public Reloadable {
|
||||
Q_OBJECT;
|
||||
/// The component to create instances of
|
||||
Q_PROPERTY(QQmlComponent* component MEMBER mComponent);
|
||||
/// The list of sets of properties to create instances with.
|
||||
/// Each set creates an instance of the component, which are updated when the input sets update.
|
||||
Q_PROPERTY(QList<QVariant> variants MEMBER mVariants WRITE setVariants);
|
||||
Q_CLASSINFO("DefaultProperty", "component");
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit Variants(QObject* parent = nullptr): Reloadable(parent) {}
|
||||
|
||||
void onReload(QObject* oldInstance) override;
|
||||
|
||||
void componentComplete() override;
|
||||
|
||||
private:
|
||||
void setVariants(QVariantList variants);
|
||||
void updateVariants();
|
||||
|
||||
QQmlComponent* mComponent = nullptr;
|
||||
QVariantList mVariants;
|
||||
AwfulMap<QVariantMap, QObject*> instances;
|
||||
};
|
38
src/core/watcher.cpp
Normal file
38
src/core/watcher.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "watcher.hpp"
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qfilesystemwatcher.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
FiletreeWatcher::FiletreeWatcher(QObject* parent): QObject(parent) {
|
||||
QObject::connect(
|
||||
&this->watcher,
|
||||
&QFileSystemWatcher::fileChanged,
|
||||
this,
|
||||
&FiletreeWatcher::onFileChanged
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
&this->watcher,
|
||||
&QFileSystemWatcher::directoryChanged,
|
||||
this,
|
||||
&FiletreeWatcher::onDirectoryChanged
|
||||
);
|
||||
}
|
||||
void FiletreeWatcher::addPath(const QString& path) {
|
||||
this->watcher.addPath(path);
|
||||
|
||||
if (QFileInfo(path).isDir()) {
|
||||
auto dir = QDir(path);
|
||||
|
||||
for (auto& entry: dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot)) {
|
||||
this->addPath(dir.filePath(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FiletreeWatcher::onDirectoryChanged(const QString& path) { this->addPath(path); }
|
||||
|
||||
void FiletreeWatcher::onFileChanged(const QString& path) { emit this->fileChanged(path); }
|
24
src/core/watcher.hpp
Normal file
24
src/core/watcher.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qfilesystemwatcher.h>
|
||||
#include <qobject.h>
|
||||
|
||||
class FiletreeWatcher: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit FiletreeWatcher(QObject* parent = nullptr);
|
||||
|
||||
void addPath(const QString& path);
|
||||
|
||||
signals:
|
||||
void fileChanged(const QString& path);
|
||||
|
||||
private slots:
|
||||
void onDirectoryChanged(const QString& path);
|
||||
void onFileChanged(const QString& path);
|
||||
|
||||
private:
|
||||
QFileSystemWatcher watcher;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue