#pragma once #include #include #include #include #include #include #include #include #include #include "region.hpp" #include "scavenge.hpp" // Proxy to an actual window exposing a limited property set with the ability to // transfer it to a new window. // Detaching a window and touching any property is a use after free. // // NOTE: setting an `id` in qml will point to the proxy window and not the real window so things // like anchors must use `item`. class ProxyWindowBase: public Scavenger { 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); /// The content item of the window. Q_PROPERTY(QQuickItem* item READ item CONSTANT); /// The visibility of the window. /// /// > [!INFO] Windows are not visible by default so you will need to set this to make the window /// > appear. 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 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 /// ProxyShellWindow { /// // 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 /// ProxyShellWindow { /// // 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 data READ data); Q_CLASSINFO("DefaultProperty", "data"); protected: void earlyInit(QObject* old) override; QQuickWindow* window = nullptr; public: explicit ProxyWindowBase(QObject* parent = nullptr): Scavenger(parent) {} ~ProxyWindowBase() override; ProxyWindowBase(ProxyWindowBase&) = delete; ProxyWindowBase(ProxyWindowBase&&) = delete; void operator=(ProxyWindowBase&) = delete; void operator=(ProxyWindowBase&&) = delete; // Disown the backing window and delete all its children. virtual QQuickWindow* disownWindow(); QQuickWindow* backingWindow(); QQuickItem* item(); virtual bool isVisible(); virtual void setVisible(bool value); virtual qint32 width(); virtual void setWidth(qint32 value); virtual qint32 height(); virtual void setHeight(qint32 value); QColor color(); void setColor(QColor value); PendingRegion* mask(); void setMask(PendingRegion* mask); QQmlListProperty data(); signals: void visibleChanged(bool visible); void widthChanged(qint32 width); void heightChanged(qint32 width); void colorChanged(QColor color); void maskChanged(); private slots: void onMaskChanged(); private: static QQmlListProperty dataBacker(QQmlListProperty* prop); static void dataAppend(QQmlListProperty* prop, QObject* obj); static qsizetype dataCount(QQmlListProperty* prop); static QObject* dataAt(QQmlListProperty* prop, qsizetype i); static void dataClear(QQmlListProperty* prop); static void dataReplace(QQmlListProperty* prop, qsizetype i, QObject* obj); static void dataRemoveLast(QQmlListProperty* prop); PendingRegion* mMask = nullptr; }; // qt attempts to resize the window but fails because wayland // and only resizes the graphics context which looks terrible. class ProxyFloatingWindow: public ProxyWindowBase { Q_OBJECT; QML_ELEMENT; public: void earlyInit(QObject* old) override; void componentComplete() override; void setWidth(qint32 value) override; void setHeight(qint32 value) override; private: bool geometryLocked = false; };