From b4e71cb2c0afe640e62ceb7b7fb44c5fdb3546cf Mon Sep 17 00:00:00 2001 From: bbedward Date: Wed, 26 Nov 2025 10:29:51 -0500 Subject: [PATCH] core/window: add parentWindow property to FloatingWindow --- CMakeLists.txt | 1 + changelog/next.md | 1 + src/window/floatingwindow.cpp | 85 ++++++++++++++++++++++++- src/window/floatingwindow.hpp | 34 +++++++++- src/window/test/manual/parentwindow.qml | 42 ++++++++++++ 5 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 src/window/test/manual/parentwindow.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 966e8c3..8293f23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(quickshell VERSION "0.2.1" LANGUAGES CXX C) set(UNRELEASED_FEATURES "network.2" "colorquant-imagerect" + "window-parent" ) set(QT_MIN_VERSION "6.6.0") diff --git a/changelog/next.md b/changelog/next.md index 88c7484..1137e9a 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -30,6 +30,7 @@ set shell id. - Added ext-background-effect window blur support. - Added per-corner radius support to Region. - Added ColorQuantizer region selection. +- Added dialog window support to FloatingWindow. ## Other Changes diff --git a/src/window/floatingwindow.cpp b/src/window/floatingwindow.cpp index a0c9fdd..7a46bbf 100644 --- a/src/window/floatingwindow.cpp +++ b/src/window/floatingwindow.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -11,6 +11,27 @@ #include "proxywindow.hpp" #include "windowinterface.hpp" +ProxyFloatingWindow::ProxyFloatingWindow(QObject* parent): ProxyWindowBase(parent) { + this->bTargetVisible.setBinding([this] { + if (!this->bWantsVisible) return false; + auto* parent = this->bParentProxyWindow.value(); + if (!parent) return true; + return parent->bindableBackerVisibility().value(); + }); +} + +void ProxyFloatingWindow::targetVisibleChanged() { + if (this->window && this->bParentProxyWindow) { + auto* bw = this->bParentProxyWindow.value()->backingWindow(); + + if (bw != this->window->transientParent()) { + this->window->setTransientParent(bw); + } + } + + this->ProxyWindowBase::setVisible(this->bTargetVisible); +} + void ProxyFloatingWindow::connectWindow() { this->ProxyWindowBase::connectWindow(); @@ -19,6 +40,25 @@ void ProxyFloatingWindow::connectWindow() { this->window->setMaximumSize(this->bMaximumSize); } +void ProxyFloatingWindow::completeWindow() { + this->ProxyWindowBase::completeWindow(); + + auto* parent = this->bParentProxyWindow.value(); + this->window->setTransientParent(parent ? parent->backingWindow() : nullptr); +} + +void ProxyFloatingWindow::postCompleteWindow() { + this->ProxyWindowBase::setVisible(this->bTargetVisible); +} + +void ProxyFloatingWindow::onParentDestroyed() { + this->mParentWindow = nullptr; + this->bParentProxyWindow = nullptr; + emit this->parentWindowChanged(); +} + +void ProxyFloatingWindow::setVisible(bool visible) { this->bWantsVisible = visible; } + void ProxyFloatingWindow::trySetWidth(qint32 implicitWidth) { if (!this->window->isVisible()) { this->ProxyWindowBase::trySetWidth(implicitWidth); @@ -46,6 +86,42 @@ void ProxyFloatingWindow::onMaximumSizeChanged() { emit this->maximumSizeChanged(); } +QObject* ProxyFloatingWindow::parentWindow() const { return this->mParentWindow; } + +void ProxyFloatingWindow::setParentWindow(QObject* window) { + if (window == this->mParentWindow) return; + + if (this->window && this->window->isVisible()) { + qmlWarning(this) << "parentWindow cannot be changed after the window is visible."; + return; + } + + if (this->bParentProxyWindow) { + QObject::disconnect(this->bParentProxyWindow, nullptr, this, nullptr); + } + + if (this->mParentWindow) { + QObject::disconnect(this->mParentWindow, nullptr, this, nullptr); + } + + this->mParentWindow = nullptr; + this->bParentProxyWindow = nullptr; + + if (auto* proxy = ProxyWindowBase::forObject(window)) { + this->mParentWindow = window; + this->bParentProxyWindow = proxy; + + QObject::connect( + this->mParentWindow, + &QObject::destroyed, + this, + &ProxyFloatingWindow::onParentDestroyed + ); + } + + emit this->parentWindowChanged(); +} + // FloatingWindowInterface FloatingWindowInterface::FloatingWindowInterface(QObject* parent) @@ -57,6 +133,7 @@ FloatingWindowInterface::FloatingWindowInterface(QObject* parent) QObject::connect(this->window, &ProxyFloatingWindow::titleChanged, this, &FloatingWindowInterface::titleChanged); QObject::connect(this->window, &ProxyFloatingWindow::minimumSizeChanged, this, &FloatingWindowInterface::minimumSizeChanged); QObject::connect(this->window, &ProxyFloatingWindow::maximumSizeChanged, this, &FloatingWindowInterface::maximumSizeChanged); + QObject::connect(this->window, &ProxyFloatingWindow::parentWindowChanged, this, &FloatingWindowInterface::parentWindowChanged); QObject::connect(this->window, &ProxyWindowBase::windowConnected, this, &FloatingWindowInterface::onWindowConnected); // clang-format on } @@ -169,3 +246,9 @@ bool FloatingWindowInterface::startSystemResize(Qt::Edges edges) const { if (!qw) return false; return qw->startSystemResize(edges); } + +QObject* FloatingWindowInterface::parentWindow() const { return this->window->parentWindow(); } + +void FloatingWindowInterface::setParentWindow(QObject* window) { + this->window->setParentWindow(window); +} diff --git a/src/window/floatingwindow.hpp b/src/window/floatingwindow.hpp index 06b5b9e..e9e536a 100644 --- a/src/window/floatingwindow.hpp +++ b/src/window/floatingwindow.hpp @@ -16,9 +16,15 @@ class ProxyFloatingWindow: public ProxyWindowBase { Q_OBJECT; public: - explicit ProxyFloatingWindow(QObject* parent = nullptr): ProxyWindowBase(parent) {} + explicit ProxyFloatingWindow(QObject* parent = nullptr); void connectWindow() override; + void completeWindow() override; + void postCompleteWindow() override; + void setVisible(bool visible) override; + + [[nodiscard]] QObject* parentWindow() const; + void setParentWindow(QObject* window); // 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. @@ -29,11 +35,28 @@ signals: void minimumSizeChanged(); void maximumSizeChanged(); void titleChanged(); + void parentWindowChanged(); + +private slots: + void onParentDestroyed(); private: void onMinimumSizeChanged(); void onMaximumSizeChanged(); void onTitleChanged(); + void targetVisibleChanged(); + + QObject* mParentWindow = nullptr; + + Q_OBJECT_BINDABLE_PROPERTY(ProxyFloatingWindow, ProxyWindowBase*, bParentProxyWindow); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(ProxyFloatingWindow, bool, bWantsVisible, true); + + Q_OBJECT_BINDABLE_PROPERTY( + ProxyFloatingWindow, + bool, + bTargetVisible, + &ProxyFloatingWindow::targetVisibleChanged + ); public: Q_OBJECT_BINDABLE_PROPERTY( @@ -75,6 +98,11 @@ class FloatingWindowInterface: public WindowInterface { Q_PROPERTY(bool maximized READ isMaximized WRITE setMaximized NOTIFY maximizedChanged); /// Whether the window is currently fullscreen. Q_PROPERTY(bool fullscreen READ isFullscreen WRITE setFullscreen NOTIFY fullscreenChanged); + /// The parent window of this window. Setting this makes the window a child of the parent, + /// which affects window stacking behavior. + /// + /// > [!NOTE] This property cannot be changed after the window is visible. + Q_PROPERTY(QObject* parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged); // clang-format on QML_NAMED_ELEMENT(FloatingWindow); @@ -101,6 +129,9 @@ public: /// Start a system resize operation. Must be called during a pointer press/drag. Q_INVOKABLE [[nodiscard]] bool startSystemResize(Qt::Edges edges) const; + [[nodiscard]] QObject* parentWindow() const; + void setParentWindow(QObject* window); + signals: void minimumSizeChanged(); void maximumSizeChanged(); @@ -108,6 +139,7 @@ signals: void minimizedChanged(); void maximizedChanged(); void fullscreenChanged(); + void parentWindowChanged(); private slots: void onWindowConnected(); diff --git a/src/window/test/manual/parentwindow.qml b/src/window/test/manual/parentwindow.qml new file mode 100644 index 0000000..214ee25 --- /dev/null +++ b/src/window/test/manual/parentwindow.qml @@ -0,0 +1,42 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Fusion +import Quickshell + +Scope { + FloatingWindow { + id: control + color: contentItem.palette.window + ColumnLayout { + CheckBox { + id: parentCb + text: "Show parent" + } + + CheckBox { + id: dialogCb + text: "Show dialog" + } + } + } + + FloatingWindow { + id: parentw + Text { + text: "parent" + } + visible: parentCb.checked + color: contentItem.palette.window + + FloatingWindow { + id: dialog + parentWindow: parentw + visible: dialogCb.checked + color: contentItem.palette.window + + Text { + text: "dialog" + } + } + } +}