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 | |
14 | class PaddingPrivate |
15 | { |
16 | Padding *const q; |
17 | |
18 | public: |
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 | |
52 | void 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 | |
68 | QMarginsF PaddingPrivate::paddings() const |
69 | { |
70 | return {q->leftPadding(), q->topPadding(), q->rightPadding(), q->bottomPadding()}; |
71 | } |
72 | |
73 | void 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 | |
103 | void 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 | |
114 | Padding::Padding(QQuickItem *parent) |
115 | : QQuickItem(parent) |
116 | , d(std::make_unique<PaddingPrivate>(args: this)) |
117 | { |
118 | } |
119 | |
120 | Padding::~Padding() |
121 | { |
122 | d->disconnect(); |
123 | } |
124 | |
125 | void 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 | |
159 | QQuickItem *Padding::contentItem() |
160 | { |
161 | return d->m_contentItem; |
162 | } |
163 | |
164 | void 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 | |
180 | void 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 | |
196 | qreal Padding::padding() const |
197 | { |
198 | return d->m_padding; |
199 | } |
200 | |
201 | void 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 | |
215 | void 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 | |
229 | qreal Padding::horizontalPadding() const |
230 | { |
231 | return d->m_horizontalPadding.value_or(u&: d->m_padding); |
232 | } |
233 | |
234 | void 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 | |
248 | void 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 | |
262 | qreal Padding::verticalPadding() const |
263 | { |
264 | return d->m_verticalPadding.value_or(u&: d->m_padding); |
265 | } |
266 | |
267 | void 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 | |
281 | void 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 | |
295 | qreal 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 | |
304 | void 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 | |
318 | void 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 | |
332 | qreal 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 | |
341 | void 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 | |
355 | void 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 | |
369 | qreal 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 | |
378 | void 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 | |
392 | void 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 | |
406 | qreal 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 | |
415 | qreal Padding::availableWidth() const |
416 | { |
417 | return width() - leftPadding() - rightPadding(); |
418 | } |
419 | |
420 | qreal Padding::availableHeight() const |
421 | { |
422 | return height() - topPadding() - bottomPadding(); |
423 | } |
424 | |
425 | qreal Padding::implicitContentWidth() const |
426 | { |
427 | if (d->m_contentItem) { |
428 | return d->m_contentItem->implicitWidth(); |
429 | } else { |
430 | return 0.0; |
431 | } |
432 | } |
433 | |
434 | qreal Padding::implicitContentHeight() const |
435 | { |
436 | if (d->m_contentItem) { |
437 | return d->m_contentItem->implicitHeight(); |
438 | } else { |
439 | return 0.0; |
440 | } |
441 | } |
442 | |
443 | void 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 | |
454 | void 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 | |
465 | void 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 | |