1/*
2 * SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "overlayzstackingattached.h"
8
9#include "loggingcategory.h"
10
11#include <QQuickItem>
12
13OverlayZStackingAttached::OverlayZStackingAttached(QObject *parent)
14 : QObject(parent)
15 , m_layer(defaultLayerForPopupType(popup: parent))
16 , m_parentPopup(nullptr)
17 , m_pending(false)
18{
19 Q_ASSERT(parent);
20 if (!isPopup(object: parent)) {
21 qCWarning(KirigamiLog) << "OverlayZStacking must be attached to a Popup";
22 return;
23 }
24
25 connect(sender: parent, SIGNAL(parentChanged()), receiver: this, SLOT(updateParentPopup()));
26 connect(sender: parent, SIGNAL(closed()), receiver: this, SLOT(dispatchPendingSignal()));
27 // Note: aboutToShow is too late, as QQuickPopup has already created modal
28 // dimmer based off current z index.
29}
30
31OverlayZStackingAttached::~OverlayZStackingAttached()
32{
33}
34
35qreal OverlayZStackingAttached::z() const
36{
37 if (!m_parentPopup) {
38 const_cast<OverlayZStackingAttached *>(this)->updateParentPopupSilent();
39 }
40
41 qreal layerZ = defaultZForLayer(layer: m_layer);
42 qreal parentZ = parentPopupZ() + 1;
43
44 return std::max(a: layerZ, b: parentZ);
45}
46
47OverlayZStackingAttached::Layer OverlayZStackingAttached::layer() const
48{
49 return m_layer;
50}
51
52void OverlayZStackingAttached::setLayer(Layer layer)
53{
54 if (m_layer == layer) {
55 return;
56 }
57
58 m_layer = layer;
59 Q_EMIT layerChanged();
60 enqueueSignal();
61}
62
63OverlayZStackingAttached *OverlayZStackingAttached::qmlAttachedProperties(QObject *object)
64{
65 return new OverlayZStackingAttached(object);
66}
67
68void OverlayZStackingAttached::enqueueSignal()
69{
70 if (isVisible(popup: parent())) {
71 m_pending = true;
72 } else {
73 Q_EMIT zChanged();
74 }
75}
76
77void OverlayZStackingAttached::dispatchPendingSignal()
78{
79 if (m_pending) {
80 m_pending = false;
81 Q_EMIT zChanged();
82 }
83}
84
85void OverlayZStackingAttached::updateParentPopup()
86{
87 const qreal oldZ = parentPopupZ();
88
89 updateParentPopupSilent();
90
91 if (oldZ != parentPopupZ()) {
92 enqueueSignal();
93 }
94}
95
96void OverlayZStackingAttached::updateParentPopupSilent()
97{
98 auto popup = findParentPopup(popup: parent());
99 setParentPopup(popup);
100}
101
102void OverlayZStackingAttached::setParentPopup(QObject *parentPopup)
103{
104 if (m_parentPopup == parentPopup) {
105 return;
106 }
107
108 if (m_parentPopup) {
109 disconnect(sender: m_parentPopup.data(), SIGNAL(zChanged()), receiver: this, SLOT(enqueueSignal()));
110 }
111
112 // Ideally, we would also connect to all the parent items' parentChanged() on the way to parent popup.
113 m_parentPopup = parentPopup;
114
115 if (m_parentPopup) {
116 connect(sender: m_parentPopup.data(), SIGNAL(zChanged()), receiver: this, SLOT(enqueueSignal()));
117 }
118}
119
120qreal OverlayZStackingAttached::parentPopupZ() const
121{
122 if (m_parentPopup) {
123 return m_parentPopup->property(name: "z").toReal();
124 }
125 return -1;
126}
127
128bool OverlayZStackingAttached::isVisible(const QObject *popup)
129{
130 if (!isPopup(object: popup)) {
131 return false;
132 }
133
134 return popup->property(name: "visible").toBool();
135}
136
137bool OverlayZStackingAttached::isPopup(const QObject *object)
138{
139 return object && object->inherits(classname: "QQuickPopup");
140}
141
142QObject *OverlayZStackingAttached::findParentPopup(const QObject *popup)
143{
144 auto item = findParentPopupItem(popup);
145 if (!item) {
146 return nullptr;
147 }
148 auto parentPopup = item->parent();
149 if (!isPopup(object: popup)) {
150 return nullptr;
151 }
152 return parentPopup;
153}
154
155QQuickItem *OverlayZStackingAttached::findParentPopupItem(const QObject *popup)
156{
157 if (!isPopup(object: popup)) {
158 return nullptr;
159 }
160
161 QQuickItem *parentItem = popup->property(name: "parent").value<QQuickItem *>();
162 if (!parentItem) {
163 return nullptr;
164 }
165
166 QQuickItem *item = parentItem;
167 do {
168 if (item && item->inherits(classname: "QQuickPopupItem")) {
169 return item;
170 }
171 item = item->parentItem();
172 } while (item);
173
174 return nullptr;
175}
176
177OverlayZStackingAttached::Layer OverlayZStackingAttached::defaultLayerForPopupType(const QObject *popup)
178{
179 if (popup) {
180 if (popup->inherits(classname: "QQuickDialog")) {
181 return Layer::Dialog;
182 } else if (popup->inherits(classname: "QQuickDrawer")) {
183 return Layer::Drawer;
184 } else if (popup->inherits(classname: "QQuickMenu")) {
185 return Layer::Menu;
186 } else if (popup->inherits(classname: "QQuickToolTip")) {
187 return Layer::ToolTip;
188 }
189 }
190 return DefaultLowest;
191}
192
193qreal OverlayZStackingAttached::defaultZForLayer(Layer layer)
194{
195 switch (layer) {
196 case DefaultLowest:
197 return 0;
198 case Drawer:
199 return 100;
200 case FullScreen:
201 return 200;
202 case Dialog:
203 return 300;
204 case Menu:
205 return 400;
206 case Notification:
207 return 500;
208 case ToolTip:
209 return 600;
210 }
211 return 0;
212}
213
214#include "moc_overlayzstackingattached.cpp"
215

source code of kirigami/src/overlayzstackingattached.cpp