1/*
2 * SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "padding.h"
9
10#include <QMarginsF>
11#include <qnumeric.h>
12#include <qtypes.h>
13
14class PaddingPrivate
15{
16 Padding *const q;
17
18public:
19 enum Paddings {
20 Left = 1 << 0,
21 Top = 1 << 1,
22 Right = 1 << 2,
23 Bottom = 1 << 3,
24 Horizontal = Left | Right,
25 Vertical = Top | Bottom,
26 All = Horizontal | Vertical
27 };
28
29 PaddingPrivate(Padding *qq)
30 : q(qq)
31 {
32 }
33
34 void calculateImplicitSize();
35 void signalPaddings(const QMarginsF &oldPaddings, Paddings paddings);
36 QMarginsF paddings() const;
37 void disconnect();
38
39 QPointer<QQuickItem> m_contentItem;
40
41 qreal m_padding = 0;
42
43 std::optional<qreal> m_horizontalPadding;
44 std::optional<qreal> m_verticalPadding;
45
46 std::optional<qreal> m_leftPadding;
47 std::optional<qreal> m_topPadding;
48 std::optional<qreal> m_rightPadding;
49 std::optional<qreal> m_bottomPadding;
50};
51
52void PaddingPrivate::calculateImplicitSize()
53{
54 qreal impWidth = 0;
55 qreal impHeight = 0;
56
57 if (m_contentItem) {
58 impWidth += m_contentItem->implicitWidth();
59 impHeight += m_contentItem->implicitHeight();
60 }
61
62 impWidth += q->leftPadding() + q->rightPadding();
63 impHeight += q->topPadding() + q->bottomPadding();
64
65 q->setImplicitSize(impWidth, impHeight);
66}
67
68QMarginsF PaddingPrivate::paddings() const
69{
70 return {q->leftPadding(), q->topPadding(), q->rightPadding(), q->bottomPadding()};
71}
72
73void PaddingPrivate::signalPaddings(const QMarginsF &oldPaddings, Paddings which)
74{
75 if ((which & Left) && !qFuzzyCompare(p1: q->leftPadding(), p2: oldPaddings.left())) {
76 Q_EMIT q->leftPaddingChanged();
77 }
78 if ((which & Top) && !qFuzzyCompare(p1: q->topPadding(), p2: oldPaddings.top())) {
79 Q_EMIT q->topPaddingChanged();
80 }
81 if ((which & Right) && !qFuzzyCompare(p1: q->rightPadding(), p2: oldPaddings.right())) {
82 Q_EMIT q->rightPaddingChanged();
83 }
84 if ((which & Bottom) && !qFuzzyCompare(p1: q->bottomPadding(), p2: oldPaddings.bottom())) {
85 Q_EMIT q->bottomPaddingChanged();
86 }
87 if ((which == Horizontal || which == All)
88 && (!qFuzzyCompare(p1: q->leftPadding(), p2: oldPaddings.left()) || !qFuzzyCompare(p1: q->rightPadding(), p2: oldPaddings.right()))) {
89 Q_EMIT q->horizontalPaddingChanged();
90 }
91 if ((which == Vertical || which == All)
92 && (!qFuzzyCompare(p1: q->topPadding(), p2: oldPaddings.top()) || !qFuzzyCompare(p1: q->bottomPadding(), p2: oldPaddings.bottom()))) {
93 Q_EMIT q->verticalPaddingChanged();
94 }
95 if (!qFuzzyCompare(p1: q->leftPadding() + q->rightPadding(), p2: oldPaddings.left() + oldPaddings.right())) {
96 Q_EMIT q->availableWidthChanged();
97 }
98 if (!qFuzzyCompare(p1: q->topPadding() + q->bottomPadding(), p2: oldPaddings.top() + oldPaddings.bottom())) {
99 Q_EMIT q->availableHeightChanged();
100 }
101}
102
103void PaddingPrivate::disconnect()
104{
105 if (m_contentItem) {
106 QObject::disconnect(sender: m_contentItem, signal: &QQuickItem::implicitWidthChanged, receiver: q, slot: &Padding::polish);
107 QObject::disconnect(sender: m_contentItem, signal: &QQuickItem::implicitHeightChanged, receiver: q, slot: &Padding::polish);
108 QObject::disconnect(sender: m_contentItem, signal: &QQuickItem::visibleChanged, receiver: q, slot: &Padding::polish);
109 QObject::disconnect(sender: m_contentItem, signal: &QQuickItem::implicitWidthChanged, receiver: q, slot: &Padding::implicitContentWidthChanged);
110 QObject::disconnect(sender: m_contentItem, signal: &QQuickItem::implicitHeightChanged, receiver: q, slot: &Padding::implicitContentHeightChanged);
111 }
112}
113
114Padding::Padding(QQuickItem *parent)
115 : QQuickItem(parent)
116 , d(std::make_unique<PaddingPrivate>(args: this))
117{
118}
119
120Padding::~Padding()
121{
122 d->disconnect();
123}
124
125void Padding::setContentItem(QQuickItem *item)
126{
127 if (d->m_contentItem == item) {
128 return;
129 }
130
131 // Not hiding old contentItem unlike Control, because we can't reliably
132 // restore it or force `visibile:` binding re-evaluation.
133 if (d->m_contentItem) {
134 d->disconnect();
135 // Ideally, it should only unset the parent iff old item's parent is
136 // `this`. But QtQuick.Controls/Control doesn't do that, and we don't
137 // wanna even more inconsistencies with upstream.
138 d->m_contentItem->setParentItem(nullptr);
139 }
140
141 d->m_contentItem = item;
142
143 if (d->m_contentItem) {
144 d->m_contentItem->setParentItem(this);
145 connect(sender: d->m_contentItem, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &Padding::polish);
146 connect(sender: d->m_contentItem, signal: &QQuickItem::implicitHeightChanged, context: this, slot: &Padding::polish);
147 connect(sender: d->m_contentItem, signal: &QQuickItem::visibleChanged, context: this, slot: &Padding::polish);
148 connect(sender: d->m_contentItem, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &Padding::implicitContentWidthChanged);
149 connect(sender: d->m_contentItem, signal: &QQuickItem::implicitHeightChanged, context: this, slot: &Padding::implicitContentHeightChanged);
150 }
151
152 polish();
153
154 Q_EMIT contentItemChanged();
155 Q_EMIT implicitContentWidthChanged();
156 Q_EMIT implicitContentWidthChanged();
157}
158
159QQuickItem *Padding::contentItem()
160{
161 return d->m_contentItem;
162}
163
164void Padding::setPadding(qreal padding)
165{
166 if (qFuzzyCompare(p1: padding, p2: d->m_padding)) {
167 return;
168 }
169
170 const QMarginsF oldPadding = d->paddings();
171 d->m_padding = padding;
172
173 Q_EMIT paddingChanged();
174
175 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::All);
176
177 polish();
178}
179
180void Padding::resetPadding()
181{
182 if (qFuzzyCompare(p1: d->m_padding, p2: 0)) {
183 return;
184 }
185
186 const QMarginsF oldPadding = d->paddings();
187 d->m_padding = 0;
188
189 Q_EMIT paddingChanged();
190
191 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::All);
192
193 polish();
194}
195
196qreal Padding::padding() const
197{
198 return d->m_padding;
199}
200
201void Padding::setHorizontalPadding(qreal padding)
202{
203 if (qFuzzyCompare(p1: padding, p2: horizontalPadding()) && d->m_horizontalPadding.has_value()) {
204 return;
205 }
206
207 const QMarginsF oldPadding = d->paddings();
208 d->m_horizontalPadding = padding;
209
210 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Horizontal);
211
212 polish();
213}
214
215void Padding::resetHorizontalPadding()
216{
217 if (!d->m_horizontalPadding.has_value()) {
218 return;
219 }
220
221 const QMarginsF oldPadding = d->paddings();
222 d->m_horizontalPadding.reset();
223
224 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Horizontal);
225
226 polish();
227}
228
229qreal Padding::horizontalPadding() const
230{
231 return d->m_horizontalPadding.value_or(u&: d->m_padding);
232}
233
234void Padding::setVerticalPadding(qreal padding)
235{
236 if (qFuzzyCompare(p1: padding, p2: verticalPadding()) && d->m_verticalPadding.has_value()) {
237 return;
238 }
239
240 const QMarginsF oldPadding = d->paddings();
241 d->m_verticalPadding = padding;
242
243 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Vertical);
244
245 polish();
246}
247
248void Padding::resetVerticalPadding()
249{
250 if (!d->m_verticalPadding.has_value()) {
251 return;
252 }
253
254 const QMarginsF oldPadding = d->paddings();
255 d->m_verticalPadding.reset();
256
257 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Vertical);
258
259 polish();
260}
261
262qreal Padding::verticalPadding() const
263{
264 return d->m_verticalPadding.value_or(u&: d->m_padding);
265}
266
267void Padding::setLeftPadding(qreal padding)
268{
269 const QMarginsF oldPadding = d->paddings();
270 if (qFuzzyCompare(p1: padding, p2: oldPadding.left()) && d->m_leftPadding.has_value()) {
271 return;
272 }
273
274 d->m_leftPadding = padding;
275
276 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Left);
277
278 polish();
279}
280
281void Padding::resetLeftPadding()
282{
283 if (!d->m_leftPadding.has_value()) {
284 return;
285 }
286
287 const QMarginsF oldPadding = d->paddings();
288 d->m_leftPadding.reset();
289
290 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Left);
291
292 polish();
293}
294
295qreal Padding::leftPadding() const
296{
297 if (d->m_leftPadding.has_value()) {
298 return d->m_leftPadding.value();
299 } else {
300 return horizontalPadding();
301 }
302}
303
304void Padding::setTopPadding(qreal padding)
305{
306 const QMarginsF oldPadding = d->paddings();
307 if (qFuzzyCompare(p1: padding, p2: oldPadding.top()) && d->m_topPadding.has_value()) {
308 return;
309 }
310
311 d->m_topPadding = padding;
312
313 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Top);
314
315 polish();
316}
317
318void Padding::resetTopPadding()
319{
320 if (!d->m_topPadding.has_value()) {
321 return;
322 }
323
324 const QMarginsF oldPadding = d->paddings();
325 d->m_topPadding.reset();
326
327 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Top);
328
329 polish();
330}
331
332qreal Padding::topPadding() const
333{
334 if (d->m_topPadding.has_value()) {
335 return d->m_topPadding.value();
336 } else {
337 return verticalPadding();
338 }
339}
340
341void Padding::setRightPadding(qreal padding)
342{
343 const QMarginsF oldPadding = d->paddings();
344 if (qFuzzyCompare(p1: padding, p2: oldPadding.right()) && d->m_rightPadding.has_value()) {
345 return;
346 }
347
348 d->m_rightPadding = padding;
349
350 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Right);
351
352 polish();
353}
354
355void Padding::resetRightPadding()
356{
357 if (!d->m_rightPadding.has_value()) {
358 return;
359 }
360
361 const QMarginsF oldPadding = d->paddings();
362 d->m_rightPadding.reset();
363
364 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Right);
365
366 polish();
367}
368
369qreal Padding::rightPadding() const
370{
371 if (d->m_rightPadding.has_value()) {
372 return d->m_rightPadding.value();
373 } else {
374 return horizontalPadding();
375 }
376}
377
378void Padding::setBottomPadding(qreal padding)
379{
380 const QMarginsF oldPadding = d->paddings();
381 if (qFuzzyCompare(p1: padding, p2: oldPadding.bottom()) && d->m_bottomPadding.has_value()) {
382 return;
383 }
384
385 d->m_bottomPadding = padding;
386
387 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Bottom);
388
389 polish();
390}
391
392void Padding::resetBottomPadding()
393{
394 if (!d->m_bottomPadding.has_value()) {
395 return;
396 }
397
398 const QMarginsF oldPadding = d->paddings();
399 d->m_bottomPadding.reset();
400
401 d->signalPaddings(oldPaddings: oldPadding, which: PaddingPrivate::Bottom);
402
403 polish();
404}
405
406qreal Padding::bottomPadding() const
407{
408 if (d->m_bottomPadding.has_value()) {
409 return d->m_bottomPadding.value();
410 } else {
411 return verticalPadding();
412 }
413}
414
415qreal Padding::availableWidth() const
416{
417 return width() - leftPadding() - rightPadding();
418}
419
420qreal Padding::availableHeight() const
421{
422 return height() - topPadding() - bottomPadding();
423}
424
425qreal Padding::implicitContentWidth() const
426{
427 if (d->m_contentItem) {
428 return d->m_contentItem->implicitWidth();
429 } else {
430 return 0.0;
431 }
432}
433
434qreal Padding::implicitContentHeight() const
435{
436 if (d->m_contentItem) {
437 return d->m_contentItem->implicitHeight();
438 } else {
439 return 0.0;
440 }
441}
442
443void Padding::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
444{
445 if (newGeometry != oldGeometry) {
446 Q_EMIT availableWidthChanged();
447 Q_EMIT availableHeightChanged();
448 polish();
449 }
450
451 QQuickItem::geometryChange(newGeometry, oldGeometry);
452}
453
454void Padding::updatePolish()
455{
456 d->calculateImplicitSize();
457 if (!d->m_contentItem) {
458 return;
459 }
460
461 d->m_contentItem->setPosition(QPointF(leftPadding(), topPadding()));
462 d->m_contentItem->setSize(QSizeF(availableWidth(), availableHeight()));
463}
464
465void Padding::componentComplete()
466{
467 QQuickItem::componentComplete();
468 // This is important! We must have a geometry so our parents can lay out.
469 updatePolish();
470}
471
472#include "moc_padding.cpp"
473

source code of kirigami/src/padding.cpp