1/*
2 * SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "headerfooterlayout.h"
9
10#include <QDebug>
11#include <QTimer>
12
13HeaderFooterLayout::HeaderFooterLayout(QQuickItem *parent)
14 : QQuickItem(parent)
15 , m_isDirty(false)
16 , m_performingLayout(false)
17{
18}
19
20HeaderFooterLayout::~HeaderFooterLayout()
21{
22 disconnectItem(item: m_header);
23 disconnectItem(item: m_contentItem);
24 disconnectItem(item: m_footer);
25};
26
27void HeaderFooterLayout::setHeader(QQuickItem *item)
28{
29 if (m_header == item) {
30 return;
31 }
32
33 if (m_header) {
34 disconnectItem(item: m_header);
35 m_header->setParentItem(nullptr);
36 }
37
38 m_header = item;
39
40 if (m_header) {
41 m_header->setParentItem(this);
42 if (m_header->z() == 0) {
43 m_header->setZ(1);
44 }
45
46 connect(sender: m_header, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
47 connect(sender: m_header, signal: &QQuickItem::implicitHeightChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
48 connect(sender: m_header, signal: &QQuickItem::visibleChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
49
50 if (m_header->inherits(classname: "QQuickTabBar") || m_header->inherits(classname: "QQuickToolBar") || m_header->inherits(classname: "QQuickDialogButtonBox")) {
51 // Assume 0 is Header for all 3 types
52 m_header->setProperty(name: "position", value: 0);
53 }
54 }
55
56 markAsDirty();
57
58 Q_EMIT headerChanged();
59}
60
61QQuickItem *HeaderFooterLayout::header()
62{
63 return m_header;
64}
65
66void HeaderFooterLayout::setContentItem(QQuickItem *item)
67{
68 if (m_contentItem == item) {
69 return;
70 }
71
72 if (m_contentItem) {
73 disconnectItem(item: m_contentItem);
74 m_contentItem->setParentItem(nullptr);
75 }
76
77 m_contentItem = item;
78
79 if (m_contentItem) {
80 m_contentItem->setParentItem(this);
81 connect(sender: m_contentItem, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
82 connect(sender: m_contentItem, signal: &QQuickItem::implicitHeightChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
83 connect(sender: m_contentItem, signal: &QQuickItem::visibleChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
84 }
85
86 markAsDirty();
87
88 Q_EMIT contentItemChanged();
89}
90
91QQuickItem *HeaderFooterLayout::contentItem()
92{
93 return m_contentItem;
94}
95
96void HeaderFooterLayout::setFooter(QQuickItem *item)
97{
98 if (m_footer == item) {
99 return;
100 }
101
102 if (m_footer) {
103 disconnectItem(item: m_footer);
104 m_footer->setParentItem(nullptr);
105 }
106
107 m_footer = item;
108
109 if (m_footer) {
110 m_footer->setParentItem(this);
111 if (m_footer->z() == 0) {
112 m_footer->setZ(1);
113 }
114
115 connect(sender: m_footer, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
116 connect(sender: m_footer, signal: &QQuickItem::implicitHeightChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
117 connect(sender: m_footer, signal: &QQuickItem::visibleChanged, context: this, slot: &HeaderFooterLayout::markAsDirty);
118
119 if (m_footer->inherits(classname: "QQuickTabBar") || m_footer->inherits(classname: "QQuickToolBar") || m_footer->inherits(classname: "QQuickDialogButtonBox")) {
120 // Assume 1 is Footer for all 3 types
121 m_footer->setProperty(name: "position", value: 1);
122 }
123 }
124
125 markAsDirty();
126
127 Q_EMIT footerChanged();
128}
129
130QQuickItem *HeaderFooterLayout::footer()
131{
132 return m_footer;
133}
134
135void HeaderFooterLayout::forceLayout()
136{
137 updatePolish();
138}
139
140void HeaderFooterLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
141{
142 if (newGeometry != oldGeometry) {
143 markAsDirty();
144 }
145
146 QQuickItem::geometryChange(newGeometry, oldGeometry);
147}
148
149void HeaderFooterLayout::componentComplete()
150{
151 QQuickItem::componentComplete();
152 if (m_isDirty) {
153 performLayout();
154 }
155}
156
157void HeaderFooterLayout::updatePolish()
158{
159 if (m_isDirty) {
160 performLayout();
161 }
162}
163
164void HeaderFooterLayout::markAsDirty()
165{
166 if (!m_isDirty) {
167 m_isDirty = true;
168 polish();
169 }
170}
171
172void HeaderFooterLayout::performLayout()
173{
174 if (!isComponentComplete() || m_performingLayout) {
175 return;
176 }
177
178 m_isDirty = false;
179 m_performingLayout = true;
180
181 // Implicit size has to be updated first, as it may propagate to the
182 // actual size which will be used below during layouting.
183 updateImplicitSize();
184
185 const QSizeF newSize = size();
186 qreal headerHeight = 0;
187 qreal footerHeight = 0;
188
189 if (m_header) {
190 m_header->setWidth(newSize.width());
191 if (m_header->isVisible()) {
192 headerHeight = m_header->height();
193 }
194 }
195 if (m_footer) {
196 m_footer->setY(newSize.height() - m_footer->height());
197 m_footer->setWidth(newSize.width());
198 if (m_footer->isVisible()) {
199 footerHeight = m_footer->height();
200 }
201 }
202 if (m_contentItem) {
203 m_contentItem->setY(headerHeight);
204 m_contentItem->setWidth(newSize.width());
205 m_contentItem->setHeight(newSize.height() - headerHeight - footerHeight);
206 }
207
208 m_performingLayout = false;
209}
210
211void HeaderFooterLayout::updateImplicitSize()
212{
213 qreal impWidth = 0;
214 qreal impHeight = 0;
215
216 if (m_header && m_header->isVisible()) {
217 impWidth = std::max(a: impWidth, b: m_header->implicitWidth());
218 impHeight += m_header->implicitHeight();
219 }
220 if (m_footer && m_footer->isVisible()) {
221 impWidth = std::max(a: impWidth, b: m_footer->implicitWidth());
222 impHeight += m_footer->implicitHeight();
223 }
224 if (m_contentItem && m_contentItem->isVisible()) {
225 impWidth = std::max(a: impWidth, b: m_contentItem->implicitWidth());
226 impHeight += m_contentItem->implicitHeight();
227 }
228 setImplicitSize(impWidth, impHeight);
229}
230
231void HeaderFooterLayout::disconnectItem(QQuickItem *item)
232{
233 if (item) {
234 disconnect(sender: item, signal: &QQuickItem::implicitWidthChanged, receiver: this, slot: &HeaderFooterLayout::markAsDirty);
235 disconnect(sender: item, signal: &QQuickItem::implicitHeightChanged, receiver: this, slot: &HeaderFooterLayout::markAsDirty);
236 disconnect(sender: item, signal: &QQuickItem::visibleChanged, receiver: this, slot: &HeaderFooterLayout::markAsDirty);
237 }
238}
239
240#include "moc_headerfooterlayout.cpp"
241

source code of kirigami/src/headerfooterlayout.cpp