#include "snaplayout.hpp" #include #include #include #include #include Q_LOGGING_CATEGORY(lcQuickLayouts, "qt.quick.layouts") namespace ZShell::components { namespace { qint64 originResidual(qreal logical, qreal dpr) { const qreal device = logical * dpr; return std::llround((device - std::floor(device)) * 1024.0) % 1024; } QPointF absoluteOrigin(const QQuickItem* item) { qreal x = 0.0; qreal y = 0.0; for (const QQuickItem* it = item; it; it = it->parentItem()) { x += it->x(); y += it->y(); } return QPointF(x, y); } } // namespace qreal SnapLayoutStyleInfo::spacing(Qt::Orientation /*orientation*/) const { #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_QNX) qreal spacing = 8.0; #else qreal spacing = 5.0; #endif #if !defined(Q_OS_MACOS) && !defined(Q_OS_HARMONY) spacing = qRound(spacing * (qreal(qt_defaultDpiX()) / 96.0)); #endif return spacing; } qreal SnapLayoutStyleInfo::windowMargin(Qt::Orientation /*orientation*/) const { return 0; } bool SnapLayoutStyleInfo::hasChangedCore() const { return false; } SnapGridLayoutItem::SnapGridLayoutItem( QQuickItem* item, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment, SnapLayout* layout) : QQuickGridLayoutItem(item, row, column, rowSpan, columnSpan, alignment) , m_layout(layout) {} void SnapGridLayoutItem::setGeometry(const QRectF& rect) { QQuickLayoutAttached* info = attachedLayoutObject(m_item, false); QRectF placed = info ? rect.marginsRemoved(info->effectiveQMargins()) : rect; const QQuickWindow* win = m_item->window(); const qreal dpr = win ? win->effectiveDevicePixelRatio() : 1.0; if (dpr > 0.0) { const QPointF origin = absoluteOrigin(m_layout); const qreal left = (origin.x() + placed.x()) * dpr; const qreal top = (origin.y() + placed.y()) * dpr; const qreal right = (origin.x() + placed.right()) * dpr; const qreal bottom = (origin.y() + placed.bottom()) * dpr; const qint64 nearX = std::llround(left); const qint64 nearY = std::llround(top); const qint64 farX = std::llround(right); const qint64 farY = std::llround(bottom); placed = QRectF( qreal(nearX) / dpr - origin.x(), qreal(nearY) / dpr - origin.y(), qreal(farX - nearX) / dpr, qreal(farY - nearY) / dpr); } const QSizeF oldSize(m_item->width(), m_item->height()); const QSizeF newSize = placed.size(); m_item->setPosition(placed.topLeft()); if (newSize == oldSize) { if (QQuickLayout* lay = qobject_cast(m_item)) { if (lay->invalidatedArrangement()) lay->rearrange(newSize); } } else { m_item->setSize(newSize); } } void SnapLayoutPrivate::mirrorChange() { Q_Q(SnapLayout); q->invalidate(); } SnapLayout::SnapLayout(QQuickItem* parent) : QQuickLayout(*new SnapLayoutPrivate, parent) { d_func()->styleInfo = new SnapLayoutStyleInfo; } SnapLayout::~SnapLayout() { Q_D(SnapLayout); clearAncestorListeners(); deactivateRecur(); delete d->styleInfo; } void SnapLayout::componentComplete() { QQuickLayout::componentComplete(); invalidate(); ensureLayoutItemsUpdated(QQuickLayout::ApplySizeHints); if (qobject_cast(parentItem())) return; rearrange(QSizeF(width(), height())); } void SnapLayout::itemChange(ItemChange change, const ItemChangeData& value) { QQuickLayout::itemChange(change, value); if (change == ItemDevicePixelRatioHasChanged || change == ItemSceneChange) invalidate(); } QSizeF SnapLayout::sizeHint(Qt::SizeHint whichSizeHint) const { Q_D(const SnapLayout); return d->engine.sizeHint(whichSizeHint, QSizeF(), d->styleInfo); } Qt::Orientation SnapLayout::orientation() const { return d_func()->orientation; } void SnapLayout::setOrientation(Qt::Orientation orientation) { Q_D(SnapLayout); if (d->orientation == orientation) return; d->orientation = orientation; invalidate(); emit orientationChanged(); } Qt::LayoutDirection SnapLayout::layoutDirection() const { return d_func()->layoutDirection; } void SnapLayout::setLayoutDirection(Qt::LayoutDirection direction) { Q_D(SnapLayout); if (d->layoutDirection == direction) return; d->layoutDirection = direction; invalidate(); emit layoutDirectionChanged(); } Qt::LayoutDirection SnapLayout::effectiveLayoutDirection() const { return !isMirrored() == (layoutDirection() == Qt::LeftToRight) ? Qt::LeftToRight : Qt::RightToLeft; } qreal SnapLayout::spacing() const { Q_D(const SnapLayout); return d->engine.spacing(d->orientation, d->styleInfo); } void SnapLayout::setSpacing(qreal space) { Q_D(SnapLayout); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" if (std::isnan(space) || spacing() == space) return; #pragma GCC diagnostic pop d->engine.setSpacing(space, Qt::Horizontal | Qt::Vertical); invalidate(); emit spacingChanged(); } bool SnapLayout::uniformCellSizes() const { Q_D(const SnapLayout); return d->engine.uniformCellWidths(); } void SnapLayout::setUniformCellSizes(bool uniformCellSizes) { Q_D(SnapLayout); if (d->engine.uniformCellHeights() == uniformCellSizes) return; d->engine.setUniformCellWidths(uniformCellSizes); d->engine.setUniformCellHeights(uniformCellSizes); invalidate(); emit uniformCellSizesChanged(); } void SnapLayout::setAlignment(QQuickItem* item, Qt::Alignment align) { Q_D(SnapLayout); if (QQuickGridLayoutItem* layoutItem = d->engine.findLayoutItem(item)) { layoutItem->setAlignment(align); d->engine.invalidate(); } maybeSubscribeToBaseLineOffsetChanges(item); } void SnapLayout::setStretchFactor( QQuickItem* item, int stretchFactor, Qt::Orientation orient) { Q_D(SnapLayout); if (QQuickGridLayoutItem* layoutItem = d->engine.findLayoutItem(item)) { layoutItem->setStretchFactor(stretchFactor, orient); d->engine.invalidate(); } } void SnapLayout::invalidate(QQuickItem* childItem) { Q_D(SnapLayout); if (!isReady()) return; if (childItem) { if (d->rearranging) { if (!d->invalidateAfterRearrange.contains(childItem)) d->invalidateAfterRearrange << childItem; return; } if (QQuickGridLayoutItem* layoutItem = d->engine.findLayoutItem(childItem)) { layoutItem->invalidate(); } } d->engine.invalidate(); QQuickLayout::invalidate(); if (auto* parentLayout = qobject_cast(parentItem())) parentLayout->invalidate(this); } void SnapLayout::updateLayoutItems() { Q_D(SnapLayout); if (!isReady()) return; d->engine.deleteItems(); insertLayoutItems(); } QQuickItem* SnapLayout::itemAt(int index) const { Q_D(const SnapLayout); return static_cast(d->engine.itemAt(index)) ->layoutItem(); } int SnapLayout::itemCount() const { Q_D(const SnapLayout); return d->engine.itemCount(); } void SnapLayout::insertLayoutItems() { Q_D(SnapLayout); const auto items = childItems(); for (QQuickItem* child : items) { Q_ASSERT(child); checkAnchors(child); if (shouldIgnoreItem(child)) continue; QQuickLayoutAttached* info = attachedLayoutObject(child, false); Qt::Alignment alignment; int hStretch = -1; int vStretch = -1; bool fillWidth = false; bool fillHeight = false; if (info) { alignment = info->alignment(); hStretch = info->horizontalStretchFactor(); vStretch = info->verticalStretchFactor(); fillWidth = info->fillWidth(); fillHeight = info->fillHeight(); } const int index = d->engine.rowCount(d->orientation); d->engine.insertRow(index, d->orientation); int gridRow = 0; int gridColumn = index; if (d->orientation == Qt::Vertical) qSwap(gridRow, gridColumn); QGridLayoutItem* layoutItem = new SnapGridLayoutItem( child, gridRow, gridColumn, 1, 1, alignment, this); if (hStretch >= 0) { if (!fillWidth) qWarning() << "horizontalStretchFactor requires fillWidth to " "also be set to true"; layoutItem->setStretchFactor(hStretch, Qt::Horizontal); } if (vStretch >= 0) { if (!fillHeight) qWarning() << "verticalStretchFactor requires fillHeight to " "also be set to true"; layoutItem->setStretchFactor(vStretch, Qt::Vertical); } d->engine.insertItem(layoutItem, index); } } void SnapLayout::removeGridItem(QGridLayoutItem* gridItem) { Q_D(SnapLayout); const int index = gridItem->firstRow(d->orientation); d->engine.removeItem(gridItem); d->engine.removeRows(index, 1, d->orientation); } void SnapLayout::itemDestroyed(QQuickItem* item) { if (!isReady()) return; Q_D(SnapLayout); if (QQuickGridLayoutItem* gridItem = d->engine.findLayoutItem(item)) { removeGridItem(gridItem); delete gridItem; invalidate(); } } void SnapLayout::itemVisibilityChanged(QQuickItem* item) { if (!isReady()) return; invalidate(item); } void SnapLayout::itemGeometryChanged( QQuickItem* item, QQuickGeometryChange change, const QRectF& oldGeometry) { Q_UNUSED(item); Q_UNUSED(oldGeometry); if (!isReady() || !change.positionChange()) return; Q_D(SnapLayout); const QQuickWindow* win = window(); const qreal dpr = win ? win->effectiveDevicePixelRatio() : 0.0; if (dpr <= 0.0) return; const QPointF origin = absoluteOrigin(this); if (originResidual(origin.x(), dpr) == d->originResidualX && originResidual(origin.y(), dpr) == d->originResidualY) return; polish(); } void SnapLayout::updateAncestorListeners() { Q_D(SnapLayout); QVector current; for (QQuickItem* it = parentItem(); it; it = it->parentItem()) { if (qobject_cast(it)) break; current.append(it); } for (const QPointer& ancestor : std::as_const(d->ancestorListeners)) { if (ancestor && !current.contains(ancestor.data())) QQuickItemPrivate::get(ancestor.data()) ->removeItemChangeListener(this, QQuickItemPrivate::Geometry); } for (QQuickItem* ancestor : std::as_const(current)) { if (!d->ancestorListeners.contains(ancestor)) QQuickItemPrivate::get(ancestor)->addItemChangeListener( this, QQuickItemPrivate::Geometry); } d->ancestorListeners.clear(); d->ancestorListeners.reserve(current.size()); for (QQuickItem* ancestor : std::as_const(current)) d->ancestorListeners.append(ancestor); } void SnapLayout::clearAncestorListeners() { Q_D(SnapLayout); for (const QPointer& ancestor : std::as_const(d->ancestorListeners)) { if (ancestor) QQuickItemPrivate::get(ancestor.data()) ->removeItemChangeListener(this, QQuickItemPrivate::Geometry); } d->ancestorListeners.clear(); } void SnapLayout::rearrange(const QSizeF& size) { Q_D(SnapLayout); if (!isReady() || !size.isValid()) return; const auto refCounter = qScopeGuard([&d] { --(d->recurRearrangeCounter); }); if (d->recurRearrangeCounter++ == 2) { qWarning() << "Qt Quick Layouts: Detected recursive rearrange. " "Aborting after two iterations."; return; } ensureLayoutItemsUpdated( QQuickLayout::ApplySizeHints | QQuickLayout::Recursive); d->rearranging = true; d->engine.setVisualDirection(effectiveLayoutDirection()); updateAncestorListeners(); if (const QQuickWindow* win = window()) { if (const qreal dpr = win->effectiveDevicePixelRatio(); dpr > 0.0) { const QPointF origin = absoluteOrigin(this); d->originResidualX = originResidual(origin.x(), dpr); d->originResidualY = originResidual(origin.y(), dpr); } } QQuickLayout::rearrange(size); d->engine.setGeometries(QRectF(QPointF(0, 0), size), d->styleInfo); d->rearranging = false; for (auto childItem : std::as_const(d->invalidateAfterRearrange)) invalidate(childItem); d->invalidateAfterRearrange.clear(); } } // namespace ZShell::components