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::setSpacing(qreal spacing)
136{
137 if (spacing == m_spacing) {
138 return;
139 }
140
141 m_spacing = spacing;
142
143 markAsDirty();
144
145 Q_EMIT spacingChanged();
146}
147
148qreal HeaderFooterLayout::spacing() const
149{
150 return m_spacing;
151}
152
153void HeaderFooterLayout::forceLayout()
154{
155 updatePolish();
156}
157
158void HeaderFooterLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
159{
160 if (newGeometry != oldGeometry) {
161 markAsDirty();
162 }
163
164 QQuickItem::geometryChange(newGeometry, oldGeometry);
165}
166
167void HeaderFooterLayout::componentComplete()
168{
169 QQuickItem::componentComplete();
170 if (m_isDirty) {
171 performLayout();
172 }
173}
174
175void HeaderFooterLayout::updatePolish()
176{
177 if (m_isDirty) {
178 performLayout();
179 }
180}
181
182void HeaderFooterLayout::markAsDirty()
183{
184 if (!m_isDirty) {
185 m_isDirty = true;
186 polish();
187 }
188}
189
190void HeaderFooterLayout::performLayout()
191{
192 if (!isComponentComplete() || m_performingLayout) {
193 return;
194 }
195
196 m_isDirty = false;
197 m_performingLayout = true;
198
199 // Implicit size has to be updated first, as it may propagate to the
200 // actual size which will be used below during layouting.
201 updateImplicitSize();
202
203 const QSizeF newSize = size();
204 qreal headerHeight = 0;
205 qreal footerHeight = 0;
206
207 if (m_header) {
208 m_header->setWidth(newSize.width());
209 if (m_header->isVisible()) {
210 headerHeight = m_header->height() + m_spacing;
211 }
212 }
213 if (m_footer) {
214 m_footer->setY(newSize.height() - m_footer->height());
215 m_footer->setWidth(newSize.width());
216 if (m_footer->isVisible()) {
217 footerHeight = m_footer->height() + m_spacing;
218 }
219 }
220 if (m_contentItem) {
221 m_contentItem->setY(headerHeight);
222 m_contentItem->setWidth(newSize.width());
223 m_contentItem->setHeight(newSize.height() - headerHeight - footerHeight);
224 }
225
226 m_performingLayout = false;
227}
228
229void HeaderFooterLayout::updateImplicitSize()
230{
231 qreal impWidth = 0;
232 qreal impHeight = 0;
233
234 if (m_header && m_header->isVisible()) {
235 impWidth = std::max(a: impWidth, b: m_header->implicitWidth());
236 impHeight += m_header->implicitHeight() + m_spacing;
237 }
238 if (m_footer && m_footer->isVisible()) {
239 impWidth = std::max(a: impWidth, b: m_footer->implicitWidth());
240 impHeight += m_footer->implicitHeight() + m_spacing;
241 }
242 if (m_contentItem && m_contentItem->isVisible()) {
243 impWidth = std::max(a: impWidth, b: m_contentItem->implicitWidth());
244 impHeight += m_contentItem->implicitHeight();
245 }
246 setImplicitSize(impWidth, impHeight);
247}
248
249void HeaderFooterLayout::disconnectItem(QQuickItem *item)
250{
251 if (item) {
252 disconnect(sender: item, signal: &QQuickItem::implicitWidthChanged, receiver: this, slot: &HeaderFooterLayout::markAsDirty);
253 disconnect(sender: item, signal: &QQuickItem::implicitHeightChanged, receiver: this, slot: &HeaderFooterLayout::markAsDirty);
254 disconnect(sender: item, signal: &QQuickItem::visibleChanged, receiver: this, slot: &HeaderFooterLayout::markAsDirty);
255 }
256}
257
258#include "moc_headerfooterlayout.cpp"
259

source code of kirigami/src/layouts/headerfooterlayout.cpp