File indexing completed on 2024-04-21 03:56:00

0001 /*
0002  *  SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "overlayzstackingattached.h"
0008 
0009 #include "loggingcategory.h"
0010 
0011 #include <QQuickItem>
0012 
0013 OverlayZStackingAttached::OverlayZStackingAttached(QObject *parent)
0014     : QObject(parent)
0015     , m_layer(defaultLayerForPopupType(parent))
0016     , m_parentPopup(nullptr)
0017     , m_pending(false)
0018 {
0019     Q_ASSERT(parent);
0020     if (!isPopup(parent)) {
0021         qCWarning(KirigamiLog) << "OverlayZStacking must be attached to a Popup";
0022         return;
0023     }
0024 
0025     connect(parent, SIGNAL(parentChanged()), this, SLOT(updateParentPopup()));
0026     connect(parent, SIGNAL(closed()), this, SLOT(dispatchPendingSignal()));
0027     // Note: aboutToShow is too late, as QQuickPopup has already created modal
0028     // dimmer based off current z index.
0029 }
0030 
0031 OverlayZStackingAttached::~OverlayZStackingAttached()
0032 {
0033 }
0034 
0035 qreal OverlayZStackingAttached::z() const
0036 {
0037     if (!m_parentPopup) {
0038         const_cast<OverlayZStackingAttached *>(this)->updateParentPopupSilent();
0039     }
0040 
0041     qreal layerZ = defaultZForLayer(m_layer);
0042     qreal parentZ = parentPopupZ() + 1;
0043 
0044     return std::max(layerZ, parentZ);
0045 }
0046 
0047 OverlayZStackingAttached::Layer OverlayZStackingAttached::layer() const
0048 {
0049     return m_layer;
0050 }
0051 
0052 void OverlayZStackingAttached::setLayer(Layer layer)
0053 {
0054     if (m_layer == layer) {
0055         return;
0056     }
0057 
0058     m_layer = layer;
0059     Q_EMIT layerChanged();
0060     enqueueSignal();
0061 }
0062 
0063 OverlayZStackingAttached *OverlayZStackingAttached::qmlAttachedProperties(QObject *object)
0064 {
0065     return new OverlayZStackingAttached(object);
0066 }
0067 
0068 void OverlayZStackingAttached::enqueueSignal()
0069 {
0070     if (isVisible(parent())) {
0071         m_pending = true;
0072     } else {
0073         Q_EMIT zChanged();
0074     }
0075 }
0076 
0077 void OverlayZStackingAttached::dispatchPendingSignal()
0078 {
0079     if (m_pending) {
0080         m_pending = false;
0081         Q_EMIT zChanged();
0082     }
0083 }
0084 
0085 void OverlayZStackingAttached::updateParentPopup()
0086 {
0087     const qreal oldZ = parentPopupZ();
0088 
0089     updateParentPopupSilent();
0090 
0091     if (oldZ != parentPopupZ()) {
0092         enqueueSignal();
0093     }
0094 }
0095 
0096 void OverlayZStackingAttached::updateParentPopupSilent()
0097 {
0098     auto popup = findParentPopup(parent());
0099     setParentPopup(popup);
0100 }
0101 
0102 void OverlayZStackingAttached::setParentPopup(QObject *parentPopup)
0103 {
0104     if (m_parentPopup == parentPopup) {
0105         return;
0106     }
0107 
0108     if (m_parentPopup) {
0109         disconnect(m_parentPopup.data(), SIGNAL(zChanged()), this, SLOT(enqueueSignal()));
0110     }
0111 
0112     // Ideally, we would also connect to all the parent items' parentChanged() on the way to parent popup.
0113     m_parentPopup = parentPopup;
0114 
0115     if (m_parentPopup) {
0116         connect(m_parentPopup.data(), SIGNAL(zChanged()), this, SLOT(enqueueSignal()));
0117     }
0118 }
0119 
0120 qreal OverlayZStackingAttached::parentPopupZ() const
0121 {
0122     if (m_parentPopup) {
0123         return m_parentPopup->property("z").toReal();
0124     }
0125     return -1;
0126 }
0127 
0128 bool OverlayZStackingAttached::isVisible(const QObject *popup)
0129 {
0130     if (!isPopup(popup)) {
0131         return false;
0132     }
0133 
0134     return popup->property("visible").toBool();
0135 }
0136 
0137 bool OverlayZStackingAttached::isPopup(const QObject *object)
0138 {
0139     return object && object->inherits("QQuickPopup");
0140 }
0141 
0142 QObject *OverlayZStackingAttached::findParentPopup(const QObject *popup)
0143 {
0144     auto item = findParentPopupItem(popup);
0145     if (!item) {
0146         return nullptr;
0147     }
0148     auto parentPopup = item->parent();
0149     if (!isPopup(popup)) {
0150         return nullptr;
0151     }
0152     return parentPopup;
0153 }
0154 
0155 QQuickItem *OverlayZStackingAttached::findParentPopupItem(const QObject *popup)
0156 {
0157     if (!isPopup(popup)) {
0158         return nullptr;
0159     }
0160 
0161     QQuickItem *parentItem = popup->property("parent").value<QQuickItem *>();
0162     if (!parentItem) {
0163         return nullptr;
0164     }
0165 
0166     QQuickItem *item = parentItem;
0167     do {
0168         if (item && item->inherits("QQuickPopupItem")) {
0169             return item;
0170         }
0171         item = item->parentItem();
0172     } while (item);
0173 
0174     return nullptr;
0175 }
0176 
0177 OverlayZStackingAttached::Layer OverlayZStackingAttached::defaultLayerForPopupType(const QObject *popup)
0178 {
0179     if (popup) {
0180         if (popup->inherits("QQuickDialog")) {
0181             return Layer::Dialog;
0182         } else if (popup->inherits("QQuickDrawer")) {
0183             return Layer::Drawer;
0184         } else if (popup->inherits("QQuickMenu")) {
0185             return Layer::Menu;
0186         } else if (popup->inherits("QQuickToolTip")) {
0187             return Layer::ToolTip;
0188         }
0189     }
0190     return DefaultLowest;
0191 }
0192 
0193 qreal OverlayZStackingAttached::defaultZForLayer(Layer layer)
0194 {
0195     switch (layer) {
0196     case DefaultLowest:
0197         return 0;
0198     case Drawer:
0199         return 100;
0200     case FullScreen:
0201         return 200;
0202     case Dialog:
0203         return 300;
0204     case Menu:
0205         return 400;
0206     case Notification:
0207         return 500;
0208     case ToolTip:
0209         return 600;
0210     }
0211     return 0;
0212 }
0213 
0214 #include "moc_overlayzstackingattached.cpp"