C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 25s
Python / static (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Failing after 1m25s
Rust / fmt (pull_request) Successful in 39s
Rust / clippy (pull_request) Successful in 59s
512 lines
14 KiB
C++
512 lines
14 KiB
C++
#include "snaplayout.hpp"
|
|
|
|
#include <QLoggingCategory>
|
|
#include <QQuickWindow>
|
|
#include <QScopeGuard>
|
|
#include <QtGui/private/qfont_p.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
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)
|
|
: QQuickGridLayoutItem(item, row, column, rowSpan, columnSpan, alignment) {}
|
|
|
|
void SnapGridLayoutItem::setGeometry(const QRectF& rect) {
|
|
QQuickLayoutAttached* info = attachedLayoutObject(m_item, false);
|
|
engineRect = info ? rect.marginsRemoved(info->effectiveQMargins()) : rect;
|
|
placed = true;
|
|
}
|
|
|
|
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();
|
|
|
|
applySpacing();
|
|
invalidate();
|
|
ensureLayoutItemsUpdated(QQuickLayout::ApplySizeHints);
|
|
|
|
if (qobject_cast<QQuickLayout*>(parentItem())) return;
|
|
rearrange(QSizeF(width(), height()));
|
|
}
|
|
|
|
void SnapLayout::itemChange(ItemChange change, const ItemChangeData& value) {
|
|
QQuickLayout::itemChange(change, value);
|
|
if (change == ItemChildRemovedChange && value.item)
|
|
d_func()->childSizes.remove(value.item);
|
|
if (change == ItemDevicePixelRatioHasChanged || change == ItemSceneChange) {
|
|
applySpacing();
|
|
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);
|
|
if (!std::isnan(d->spacingRequest)) return d->spacingRequest;
|
|
return d->styleInfo->spacing(d->orientation);
|
|
}
|
|
|
|
void SnapLayout::applySpacing() {
|
|
Q_D(SnapLayout);
|
|
if (!isReady()) return;
|
|
|
|
const QQuickWindow* win = window();
|
|
const qreal dpr = win ? win->effectiveDevicePixelRatio() : 1.0;
|
|
|
|
qreal space = d->spacingRequest;
|
|
if (std::isnan(space)) space = d->styleInfo->spacing(d->orientation);
|
|
|
|
if (dpr > 0.0) space = qreal(std::llround(space * dpr)) / dpr;
|
|
|
|
d->engine.setSpacing(space, Qt::Horizontal | Qt::Vertical);
|
|
}
|
|
|
|
void SnapLayout::setSpacing(qreal space) {
|
|
Q_D(SnapLayout);
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
|
if (std::isnan(space) || d->spacingRequest == space) return;
|
|
#pragma GCC diagnostic pop
|
|
d->spacingRequest = space;
|
|
applySpacing();
|
|
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<QQuickLayout*>(parentItem()))
|
|
parentLayout->invalidate(this);
|
|
}
|
|
|
|
void SnapLayout::updateLayoutItems() {
|
|
Q_D(SnapLayout);
|
|
if (!isReady()) return;
|
|
|
|
const auto items = childItems();
|
|
for (auto entry = d->childSizes.begin(); entry != d->childSizes.end();) {
|
|
if (items.contains(entry.key()))
|
|
++entry;
|
|
else
|
|
entry = d->childSizes.erase(entry);
|
|
}
|
|
|
|
d->engine.deleteItems();
|
|
insertLayoutItems();
|
|
}
|
|
|
|
QQuickItem* SnapLayout::itemAt(int index) const {
|
|
Q_D(const SnapLayout);
|
|
return static_cast<QQuickGridLayoutItem*>(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);
|
|
|
|
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);
|
|
d->childSizes.remove(item);
|
|
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<QQuickItem*> current;
|
|
for (QQuickItem* it = parentItem(); it; it = it->parentItem()) {
|
|
if (qobject_cast<SnapLayout*>(it)) break;
|
|
current.append(it);
|
|
}
|
|
|
|
for (const QPointer<QQuickItem>& 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<QQuickItem>& ancestor :
|
|
std::as_const(d->ancestorListeners)) {
|
|
if (ancestor)
|
|
QQuickItemPrivate::get(ancestor.data())
|
|
->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
|
|
}
|
|
d->ancestorListeners.clear();
|
|
}
|
|
|
|
void SnapLayout::arrangeChildren() {
|
|
Q_D(SnapLayout);
|
|
const QQuickWindow* win = window();
|
|
const qreal dpr = win ? win->effectiveDevicePixelRatio() : 1.0;
|
|
if (dpr <= 0.0) return;
|
|
|
|
const bool horiz = d->orientation == Qt::Horizontal;
|
|
const QPointF origin = absoluteOrigin(this);
|
|
const qint64 originXDev = std::llround(origin.x() * dpr);
|
|
const qint64 originYDev = std::llround(origin.y() * dpr);
|
|
|
|
qreal prevEndExact = 0.0;
|
|
qint64 prevNearDev = 0;
|
|
qint64 prevSizeDev = 0;
|
|
bool started = false;
|
|
|
|
for (int i = 0; i < d->engine.itemCount(); ++i) {
|
|
SnapGridLayoutItem* gridItem =
|
|
static_cast<SnapGridLayoutItem*>(d->engine.itemAt(i));
|
|
if (!gridItem->placed) continue;
|
|
QQuickItem* child = gridItem->layoutItem();
|
|
const QRectF& cell = gridItem->engineRect;
|
|
|
|
const qreal mainExact = (horiz ? cell.x() : cell.y()) * dpr;
|
|
const qreal mainSizeExact =
|
|
(horiz ? cell.width() : cell.height()) * dpr;
|
|
const qreal crossExact = (horiz ? cell.y() : cell.x()) * dpr;
|
|
const qint64 mainSizeDev = std::llround(mainSizeExact);
|
|
const qint64 crossSizeDev =
|
|
std::llround((horiz ? cell.height() : cell.width()) * dpr);
|
|
const qint64 engineWidth = horiz ? mainSizeDev : crossSizeDev;
|
|
const qint64 engineHeight = horiz ? crossSizeDev : mainSizeDev;
|
|
|
|
qint64 mainNear = 0;
|
|
if (!started) {
|
|
mainNear = std::llround(mainExact);
|
|
started = true;
|
|
} else {
|
|
mainNear = prevNearDev + prevSizeDev +
|
|
std::llround(mainExact - prevEndExact);
|
|
}
|
|
|
|
const qint64 mainFarDev = std::llround(mainExact + mainSizeExact);
|
|
|
|
const auto stored = d->childSizes.constFind(child);
|
|
qint64 width = engineWidth;
|
|
qint64 height = engineHeight;
|
|
if (horiz)
|
|
width = std::max(mainFarDev - mainNear, qint64(0));
|
|
else
|
|
height = std::max(mainFarDev - mainNear, qint64(0));
|
|
|
|
if (stored != d->childSizes.constEnd() &&
|
|
stored.value().engineWidth == engineWidth &&
|
|
stored.value().engineHeight == engineHeight &&
|
|
std::llround(child->width() * dpr) == stored.value().width &&
|
|
std::llround(child->height() * dpr) == stored.value().height) {
|
|
width = stored.value().width;
|
|
height = stored.value().height;
|
|
} else {
|
|
d->childSizes[child] = SnapLayoutPrivate::ChildSize{
|
|
engineWidth, engineHeight, width, height};
|
|
}
|
|
|
|
prevEndExact = mainExact + mainSizeExact;
|
|
prevNearDev = mainNear;
|
|
prevSizeDev = horiz ? width : height;
|
|
|
|
const qint64 crossNear = std::llround(crossExact);
|
|
const qint64 nearX = originXDev + (horiz ? mainNear : crossNear);
|
|
const qint64 nearY = originYDev + (horiz ? crossNear : mainNear);
|
|
|
|
const QRectF snapped(
|
|
qreal(nearX) / dpr - origin.x(),
|
|
qreal(nearY) / dpr - origin.y(),
|
|
qreal(width) / dpr,
|
|
qreal(height) / dpr);
|
|
|
|
const QSizeF oldSize(child->width(), child->height());
|
|
const QSizeF newSize = snapped.size();
|
|
|
|
child->setPosition(snapped.topLeft());
|
|
if (newSize == oldSize) {
|
|
if (QQuickLayout* lay = qobject_cast<QQuickLayout*>(child)) {
|
|
if (lay->invalidatedArrangement()) lay->rearrange(newSize);
|
|
}
|
|
} else {
|
|
child->setSize(newSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
for (int i = 0; i < d->engine.itemCount(); ++i)
|
|
static_cast<SnapGridLayoutItem*>(d->engine.itemAt(i))->placed = false;
|
|
d->engine.setGeometries(QRectF(QPointF(0, 0), size), d->styleInfo);
|
|
arrangeChildren();
|
|
d->rearranging = false;
|
|
|
|
for (auto childItem : std::as_const(d->invalidateAfterRearrange))
|
|
invalidate(childItem);
|
|
d->invalidateAfterRearrange.clear();
|
|
}
|
|
|
|
} // namespace ZShell::components
|