Revision control

Copy as Markdown

// 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"