Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qqstylekitlayout_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ static qreal layoutWidth(QQStyleKitLayoutItem *li, qreal availableWidth = .0)
++ {
++ Q_ASSERT(li);
++ Q_ASSERT(li->item());
++ QQuickItem *item = li->item();
++ qreal w = item->implicitWidth();
++ if (li->fillWidth())
++ w = qMax(.0, availableWidth - li->margins().left() - li->margins().right());
++ return qMax(.0, w);
++ }
++
++ static qreal layoutHeight(QQStyleKitLayoutItem *li, qreal availableHeight = .0)
++ {
++ Q_ASSERT(li);
++ Q_ASSERT(li->item());
++ QQuickItem *item = li->item();
++ qreal h = item->implicitHeight();
++ if (li->fillHeight())
++ h = qMax(.0, availableHeight - li->margins().top() - li->margins().bottom());
++ return qMax(.0, h);
++ }
++
++ static qreal totalWidth(const QList<QQStyleKitLayoutItem *> &items, qreal spacing)
++ {
++ qreal total = .0;
++ for (QQStyleKitLayoutItem *li : items) {
++ if (li->item() && li->item()->isVisible())
++ total += layoutWidth(li) + li->margins().left() + li->margins().right() + spacing;
++ }
++ return total;
++ }
++
++ static qreal totalHeight(const QList<QQStyleKitLayoutItem *> &items)
++ {
++ qreal maxHeight = .0;
++ for (QQStyleKitLayoutItem *li : items) {
++ if (li->item() && li->item()->isVisible()) {
++ const qreal h = layoutHeight(li) + li->margins().top() + li->margins().bottom();
++ if (h > maxHeight)
++ maxHeight = h;
++ }
++ }
++ return maxHeight;
++ }
++
++ static qreal vAlignY(QQStyleKitLayoutItem *li, qreal containerY, qreal containerHeight)
++ {
++ Q_ASSERT(li);
++ Q_ASSERT(li->item());
++
++ const auto itemHeight = layoutHeight(li, containerHeight);
++ const auto vAlign = li->alignment() & Qt::AlignVertical_Mask;
++ const auto margins = li->margins();
++ if (vAlign & Qt::AlignTop)
++ return containerY + margins.top();
++ if (vAlign & Qt::AlignBottom)
++ return containerY + containerHeight - itemHeight - margins.bottom();
++ return containerY + margins.top()
++ + (containerHeight - margins.top() - margins.bottom() - itemHeight) / 2.0;
++ }
++
++ QQStyleKitLayoutItem::QQStyleKitLayoutItem(QObject *parent)
++ : QObject(parent)
++ {
++ }
++
++ QQuickItem *QQStyleKitLayoutItem::item() const
++ {
++ return m_item;
++ }
++
++ void QQStyleKitLayoutItem::setItem(QQuickItem *item)
++ {
++ if (m_item == item)
++ return;
++
++ if (m_item)
++ disconnect(m_item, nullptr, this, nullptr);
++
++ m_item = item;
++ if (m_item) {
++ connect(m_item, &QQuickItem::implicitWidthChanged, this, [this]() { emit itemChanged(); });
++ connect(m_item, &QQuickItem::implicitHeightChanged, this, [this]() { emit itemChanged(); });
++ connect(m_item, &QQuickItem::visibleChanged, this, [this]() { emit itemChanged(); });
++ }
++ // TODO: parentchanged
++ emit itemChanged();
++ }
++
++ qreal QQStyleKitLayoutItem::x() const
++ {
++ return m_x;
++ }
++
++ void QQStyleKitLayoutItem::setX(qreal x)
++ {
++ if (qFuzzyCompare(m_x, x))
++ return;
++
++ m_x = x;
++ emit xChanged();
++ }
++
++ qreal QQStyleKitLayoutItem::y() const
++ {
++ return m_y;
++ }
++
++ void QQStyleKitLayoutItem::setY(qreal y)
++ {
++ if (qFuzzyCompare(m_y, y))
++ return;
++
++ m_y = y;
++ emit yChanged();
++ }
++
++ qreal QQStyleKitLayoutItem::width() const
++ {
++ return m_width;
++ }
++
++ void QQStyleKitLayoutItem::setWidth(qreal width)
++ {
++ if (qFuzzyCompare(m_width, width))
++ return;
++
++ m_width = width;
++ emit widthChanged();
++ }
++
++ qreal QQStyleKitLayoutItem::height() const
++ {
++ return m_height;
++ }
++
++ void QQStyleKitLayoutItem::setHeight(qreal height)
++ {
++ if (qFuzzyCompare(m_height, height))
++ return;
++
++ m_height = height;
++ emit heightChanged();
++ }
++
++ Qt::Alignment QQStyleKitLayoutItem::alignment() const
++ {
++ return m_alignment;
++ }
++
++ void QQStyleKitLayoutItem::setAlignment(Qt::Alignment alignment)
++ {
++ if (m_alignment == alignment)
++ return;
++
++ m_alignment = alignment;
++ emit alignmentChanged();
++ }
++
++ QMarginsF QQStyleKitLayoutItem::margins() const
++ {
++ return m_margins;
++ }
++
++ void QQStyleKitLayoutItem::setMargins(const QMarginsF &margins)
++ {
++ if (m_margins == margins)
++ return;
++
++ m_margins = margins;
++ emit marginsChanged();
++ }
++
++ bool QQStyleKitLayoutItem::fillWidth() const
++ {
++ return m_fillWidth;
++ }
++
++ void QQStyleKitLayoutItem::setFillWidth(bool fill)
++ {
++ if (m_fillWidth == fill)
++ return;
++
++ m_fillWidth = fill;
++ emit fillWidthChanged();
++ }
++
++ bool QQStyleKitLayoutItem::fillHeight() const
++ {
++ return m_fillHeight;
++ }
++
++ void QQStyleKitLayoutItem::setFillHeight(bool fill)
++ {
++ if (m_fillHeight == fill)
++ return;
++
++ m_fillHeight = fill;
++ emit fillHeightChanged();
++ }
++
++ QQStyleKitLayout::QQStyleKitLayout(QQuickItem *parent)
++ : QQuickItem(parent)
++ , m_mirrored(false)
++ , m_enabled(true)
++ {
++ }
++
++ QQuickItem *QQStyleKitLayout::container() const
++ {
++ return m_container;
++ }
++
++ void QQStyleKitLayout::setContainer(QQuickItem *container)
++ {
++ if (m_container == container)
++ return;
++
++ m_container = container;
++ emit containerChanged();
++ connect(m_container, &QQuickItem::widthChanged, this, &QQStyleKitLayout::polish);
++ connect(m_container, &QQuickItem::heightChanged, this, &QQStyleKitLayout::polish);
++
++ polish();
++ }
++
++ QQmlListProperty<QQStyleKitLayoutItem> QQStyleKitLayout::layoutItems()
++ {
++ return QQmlListProperty<QQStyleKitLayoutItem>(const_cast<QQStyleKitLayout *>(this),
++ nullptr,
++ &QQStyleKitLayout::layoutItem_append,
++ &QQStyleKitLayout::layoutItem_count,
++ &QQStyleKitLayout::layoutItem_at,
++ &QQStyleKitLayout::layoutItem_clear);
++ }
++
++ void QQStyleKitLayout::layoutItem_append(QQmlListProperty<QQStyleKitLayoutItem> *list, QQStyleKitLayoutItem *item)
++ {
++ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
++ if (layout && item) {
++ layout->m_layoutItems.append(item);
++ connect(item, &QQStyleKitLayoutItem::itemChanged, layout, &QQStyleKitLayout::polish);
++ connect(item, &QQStyleKitLayoutItem::alignmentChanged, layout, &QQStyleKitLayout::polish);
++ connect(item, &QQStyleKitLayoutItem::marginsChanged, layout, &QQStyleKitLayout::polish);
++ connect(item, &QQStyleKitLayoutItem::fillWidthChanged, layout, &QQStyleKitLayout::polish);
++ connect(item, &QQStyleKitLayoutItem::fillHeightChanged, layout, &QQStyleKitLayout::polish);
++ emit layout->layoutItemsChanged();
++ layout->polish();
++ }
++ }
++
++ qsizetype QQStyleKitLayout::layoutItem_count(QQmlListProperty<QQStyleKitLayoutItem> *list)
++ {
++ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
++ if (layout)
++ return layout->m_layoutItems.size();
++ return 0;
++ }
++
++ QQStyleKitLayoutItem *QQStyleKitLayout::layoutItem_at(QQmlListProperty<QQStyleKitLayoutItem> *list, qsizetype index)
++ {
++ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
++ if (layout)
++ return layout->m_layoutItems.value(index);
++ return nullptr;
++ }
++
++ void QQStyleKitLayout::layoutItem_clear(QQmlListProperty<QQStyleKitLayoutItem> *list)
++ {
++ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
++ if (layout) {
++ layout->m_layoutItems.clear();
++ emit layout->layoutItemsChanged();
++ layout->polish();
++ }
++ }
++
++ QMarginsF QQStyleKitLayout::padding() const
++ {
++ return m_padding;
++ }
++
++ QMarginsF QQStyleKitLayout::contentMargins() const
++ {
++ return m_contentMargins;
++ }
++
++ void QQStyleKitLayout::setContentMargins(const QMarginsF &margins)
++ {
++ if (m_contentMargins == margins)
++ return;
++
++ m_contentMargins = margins;
++ emit contentMarginsChanged();
++ polish();
++ }
++
++ qreal QQStyleKitLayout::spacing() const
++ {
++ return m_spacing;
++ }
++
++ void QQStyleKitLayout::setSpacing(qreal spacing)
++ {
++ if (qFuzzyCompare(m_spacing, spacing))
++ return;
++
++ m_spacing = spacing;
++ emit spacingChanged();
++ polish();
++ }
++
++ bool QQStyleKitLayout::isMirrored() const
++ {
++ return m_mirrored;
++ }
++
++ void QQStyleKitLayout::setMirrored(bool mirrored)
++ {
++ if (m_mirrored == mirrored)
++ return;
++
++ m_mirrored = mirrored;
++ emit mirroredChanged();
++ polish();
++ }
++
++ qreal QQStyleKitLayout::implicitWidth() const
++ {
++ return m_implicitWidth;
++ }
++
++ qreal QQStyleKitLayout::implicitHeight() const
++ {
++ return m_implicitHeight;
++ }
++
++ void QQStyleKitLayout::setImplicitWidth(qreal width)
++ {
++ if (qFuzzyCompare(m_implicitWidth, width))
++ return;
++
++ m_implicitWidth = width;
++ emit implicitWidthChanged();
++ polish();
++ }
++
++ void QQStyleKitLayout::setImplicitHeight(qreal height)
++ {
++ if (qFuzzyCompare(m_implicitHeight, height))
++ return;
++
++ m_implicitHeight = height;
++ emit implicitHeightChanged();
++ polish();
++ }
++
++ bool QQStyleKitLayout::isEnabled() const
++ {
++ return m_enabled;
++ }
++
++ void QQStyleKitLayout::setEnabled(bool enabled)
++ {
++ if (m_enabled == enabled)
++ return;
++
++ m_enabled = enabled;
++ emit enabledChanged();
++
++ if (m_enabled)
++ polish();
++ }
++
++ void QQStyleKitLayout::updatePolish()
++ {
++ if (!m_enabled)
++ return;
++
++ if (!m_container || m_container->width() <= 0 || m_container->height() <= 0)
++ return;
++
++ QList<QQStyleKitLayoutItem *> left;
++ QList<QQStyleKitLayoutItem *> right;
++ QList<QQStyleKitLayoutItem *> center;
++
++ for (QQStyleKitLayoutItem *li : m_layoutItems) {
++ if (!li->item() || !li->item()->isVisible())
++ continue;
++ if (li->fillWidth()) {
++ // fillWidth items always expand to fill the available width,
++ // so horizontal alignment has no effect.
++ left.append(li);
++ continue;
++ }
++ const auto hAlign = li->alignment() & Qt::AlignHorizontal_Mask;
++ const bool isMirrored = m_mirrored && !(hAlign & Qt::AlignAbsolute);
++ switch (hAlign) {
++ case Qt::AlignLeft:
++ if (isMirrored)
++ right.append(li);
++ else
++ left.append(li);
++ break;
++ case Qt::AlignRight:
++ if (isMirrored)
++ left.append(li);
++ else
++ right.append(li);
++ break;
++ default:
++ center.append(li);
++ break;
++ }
++ }
++
++ const qreal containerWidth = m_container->width() ? m_container->width() : m_container->implicitWidth();
++ const qreal containerHeight = m_container->height() ? m_container->height() : m_container->implicitHeight();
++ const qreal paddedX = m_contentMargins.left();
++ const qreal paddedY = m_contentMargins.top();
++ const qreal paddedWidth = qMax(containerWidth - m_contentMargins.left() - m_contentMargins.right(), .0);
++ const qreal paddedHeight = qMax(containerHeight - m_contentMargins.top() - m_contentMargins.bottom(), .0);
++
++ qreal maxTopMargin = .0;
++ qreal maxBottomMargin = .0;
++
++ // Position left-aligned items
++ {
++ qreal x = paddedX;
++ for (QQStyleKitLayoutItem *li : left) {
++ QQuickItem *item = li->item();
++ if (!item || !item->isVisible())
++ continue;
++
++ const QMarginsF margins = li->margins();
++ const qreal itemWidth = layoutWidth(li, paddedWidth);
++ const qreal itemHeight = layoutHeight(li, paddedHeight);
++ auto y = vAlignY(li, paddedY, paddedHeight);
++ li->setX(x + margins.left());
++ li->setY(y);
++ li->setWidth(itemWidth);
++ li->setHeight(itemHeight);
++ x += itemWidth + margins.left() + margins.right() + m_spacing;
++ maxTopMargin = qMax(maxTopMargin, margins.top());
++ maxBottomMargin = qMax(maxBottomMargin, margins.bottom());
++ }
++ }
++
++ // Position right-aligned items
++ {
++ qreal x = paddedX + paddedWidth;
++ for (QQStyleKitLayoutItem *li : right) {
++ QQuickItem *item = li->item();
++ if (!item || !item->isVisible())
++ continue;
++
++ const QMarginsF margins = li->margins();
++ const qreal itemWidth = layoutWidth(li, paddedWidth);
++ const qreal itemHeight = layoutHeight(li, paddedHeight);
++ x -= itemWidth + margins.right() + margins.left();
++ auto y = vAlignY(li, paddedY, paddedHeight);
++ li->setX(x + margins.left());
++ li->setY(y);
++ li->setWidth(itemWidth);
++ li->setHeight(itemHeight);
++ x -= m_spacing;
++ maxTopMargin = qMax(maxTopMargin, margins.top());
++ maxBottomMargin = qMax(maxBottomMargin, margins.bottom());
++ }
++ }
++
++ // Position center-aligned items
++ {
++ qreal x = paddedX + (paddedWidth - totalWidth(center, m_spacing)) / 2;
++ for (QQStyleKitLayoutItem *li : center) {
++ QQuickItem *item = li->item();
++ if (!item || !item->isVisible())
++ continue;
++
++ const QMarginsF margins = li->margins();
++ const qreal itemWidth = layoutWidth(li, paddedWidth);
++ const qreal itemHeight = layoutHeight(li, paddedHeight);
++ auto y = vAlignY(li, paddedY, paddedHeight);
++ li->setX(x + margins.left());
++ li->setY(y);
++ li->setWidth(itemWidth);
++ li->setHeight(itemHeight);
++ x += itemWidth + margins.left() + margins.right() + m_spacing;
++ maxTopMargin = qMax(maxTopMargin, margins.top());
++ maxBottomMargin = qMax(maxBottomMargin, margins.bottom());
++ }
++ }
++
++ const auto leftWidth = totalWidth(left, m_spacing);
++ const auto leftHeight = totalHeight(left);
++ const auto rightWidth = totalWidth(right, m_spacing);
++ const auto rightHeight = totalHeight(right);
++ const auto centerWidth = totalWidth(center, m_spacing);
++ const auto centerHeight = totalHeight(center);
++
++ const auto implicitWidth = leftWidth + rightWidth + centerWidth
++ - m_spacing * (left.isEmpty() ? 0 : 1)
++ - m_spacing * (right.isEmpty() ? 0 : 1)
++ - m_spacing * (center.isEmpty() ? 0 : 1)
++ + m_contentMargins.left() + m_contentMargins.right();
++ setImplicitWidth(implicitWidth);
++ const auto implicitHeight = qMax(qMax(leftHeight, rightHeight), centerHeight)
++ + m_contentMargins.top() + m_contentMargins.bottom();
++ setImplicitHeight(implicitHeight);
++
++ // HACK for control's contentItem
++ // QQuickControl determines the contentItem geometry based on the control size and padding
++ // So we include the layout's left/right widths into the padding calculation
++ auto leftPadding = m_contentMargins.left() + leftWidth;
++ auto topPadding = m_contentMargins.top() + maxTopMargin; // TODO: support vertical layout items
++ auto rightPadding = m_contentMargins.right() + rightWidth;
++ auto bottomPadding = m_contentMargins.bottom() + maxBottomMargin; // TODO: support vertical layout items
++ if (isMirrored())
++ std::swap(leftPadding, rightPadding);
++ QMarginsF newPadding = QMarginsF(leftPadding, topPadding, rightPadding, bottomPadding);
++ if (m_padding != newPadding) {
++ m_padding = newPadding;
++ emit paddingChanged();
++ }
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqstylekitlayout_p.cpp"