diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/quick/items/qquicktableview.cpp | 233 | ||||
-rw-r--r-- | src/quick/items/qquicktableview_p.h | 5 | ||||
-rw-r--r-- | src/quick/items/qquicktableview_p_p.h | 5 |
3 files changed, 204 insertions, 39 deletions
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index af4373317b..2b49200eaa 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -665,50 +665,179 @@ void QQuickTableViewPrivate::updateContentHeight() q->QQuickFlickable::setContentHeight(estimatedHeight); } -void QQuickTableViewPrivate::enforceTableAtOrigin() -{ - // Gaps before the first row/column can happen if rows/columns - // changes size while flicking e.g because of spacing changes or - // changes to a column maxWidth/row maxHeight. Check for this, and - // move the whole table rect accordingly. - bool layoutNeeded = false; - const qreal flickMargin = 50; - - const bool noMoreColumns = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge) == kEdgeIndexAtEnd; - const bool noMoreRows = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge) == kEdgeIndexAtEnd; - - if (noMoreColumns) { - if (!qFuzzyIsNull(loadedTableOuterRect.left())) { - // There are no more columns, but the table rect - // is not at origin. So we move it there. - loadedTableOuterRect.moveLeft(0); - layoutNeeded = true; +void QQuickTableViewPrivate::updateExtents() +{ + // When rows or columns outside the viewport are removed or added, or a rebuild + // forces us to guesstimate a new top-left, the edges of the table might end up + // out of sync with the edges of the content view. We detect this situation here, and + // move the origin to ensure that there will never be gaps at the end of the table. + // Normally we detect that the size of the whole table is not going to be equal to the + // size of the content view already when we load the last row/column, and especially + // before it's flicked completely inside the viewport. For those cases we simply adjust + // the origin/endExtent, to give a smooth flicking experience. + // But if flicking fast (e.g with a scrollbar), it can happen that the viewport ends up + // outside the end of the table in just one viewport update. To avoid a "blink" in the + // viewport when that happens, we "move" the loaded table into the viewport to cover it. + Q_Q(QQuickTableView); + + bool tableMovedHorizontally = false; + bool tableMovedVertically = false; + + const int nextLeftColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge); + const int nextRightColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge); + const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge); + const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge); + + if (syncHorizontally) { + const auto syncView_d = syncView->d_func(); + origin.rx() = syncView_d->origin.x(); + endExtent.rwidth() = syncView_d->endExtent.width(); + hData.markExtentsDirty(); + } else if (nextLeftColumn == kEdgeIndexAtEnd) { + // There are no more columns to load on the left side of the table. + // In that case, we ensure that the origin match the beginning of the table. + if (loadedTableOuterRect.left() > viewportRect.left()) { + // We have a blank area at the left end of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing origin), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + if (loadedTableOuterRect.left() > origin.x()) { + const qreal diff = loadedTableOuterRect.left() - origin.x(); + loadedTableOuterRect.moveLeft(loadedTableOuterRect.left() - diff); + loadedTableInnerRect.moveLeft(loadedTableInnerRect.left() - diff); + tableMovedHorizontally = true; + } } - } else { - if (loadedTableOuterRect.left() <= 0) { - // The table rect is at origin, or outside. But we still have - // more visible columns to the left. So we need to make some - // space so that they can be flicked in. - loadedTableOuterRect.moveLeft(flickMargin); - layoutNeeded = true; + origin.rx() = loadedTableOuterRect.left(); + hData.markExtentsDirty(); + } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) { + // The table rect is at the origin, or outside, but we still have more + // visible columns to the left. So we try to guesstimate how much space + // the rest of the columns will occupy, and move the origin accordingly. + const int columnsRemaining = nextLeftColumn + 1; + const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width(); + const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); + const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; + origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth; + hData.markExtentsDirty(); + } else if (nextRightColumn == kEdgeIndexAtEnd) { + // There are no more columns to load on the right side of the table. + // In that case, we ensure that the end of the content view match the end of the table. + if (loadedTableOuterRect.right() < viewportRect.right()) { + // We have a blank area at the right end of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing endExtent), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + const qreal w = qMin(viewportRect.right(), q->contentWidth() + endExtent.width()); + if (loadedTableOuterRect.right() < w) { + const qreal diff = loadedTableOuterRect.right() - w; + loadedTableOuterRect.moveRight(loadedTableOuterRect.right() - diff); + loadedTableInnerRect.moveRight(loadedTableInnerRect.right() - diff); + tableMovedHorizontally = true; + } } + endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth(); + hData.markExtentsDirty(); + } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) { + // The right-most column is outside the end of the content view, and we + // still have more visible columns in the model. This can happen if the application + // has set a fixed content width. + const int columnsRemaining = tableSize.width() - nextRightColumn; + const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width(); + const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); + const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; + const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth(); + endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth; + hData.markExtentsDirty(); } - if (noMoreRows) { - if (!qFuzzyIsNull(loadedTableOuterRect.top())) { - loadedTableOuterRect.moveTop(0); - layoutNeeded = true; + if (syncVertically) { + const auto syncView_d = syncView->d_func(); + origin.ry() = syncView_d->origin.y(); + endExtent.rheight() = syncView_d->endExtent.height(); + vData.markExtentsDirty(); + } else if (nextTopRow == kEdgeIndexAtEnd) { + // There are no more rows to load on the top side of the table. + // In that case, we ensure that the origin match the beginning of the table. + if (loadedTableOuterRect.top() > viewportRect.top()) { + // We have a blank area at the top of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing origin), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + if (loadedTableOuterRect.top() > origin.y()) { + const qreal diff = loadedTableOuterRect.top() - origin.y(); + loadedTableOuterRect.moveTop(loadedTableOuterRect.top() - diff); + loadedTableInnerRect.moveTop(loadedTableInnerRect.top() - diff); + tableMovedVertically = true; + } } - } else { - if (loadedTableOuterRect.top() <= 0) { - loadedTableOuterRect.moveTop(flickMargin); - layoutNeeded = true; + origin.ry() = loadedTableOuterRect.top(); + vData.markExtentsDirty(); + } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) { + // The table rect is at the origin, or outside, but we still have more + // visible rows at the top. So we try to guesstimate how much space + // the rest of the rows will occupy, and move the origin accordingly. + const int rowsRemaining = nextTopRow + 1; + const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height(); + const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); + const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing; + origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight; + vData.markExtentsDirty(); + } else if (nextBottomRow == kEdgeIndexAtEnd) { + // There are no more rows to load on the bottom side of the table. + // In that case, we ensure that the end of the content view match the end of the table. + if (loadedTableOuterRect.bottom() < viewportRect.bottom()) { + // We have a blank area at the bottom of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing endExtent), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + const qreal h = qMin(viewportRect.bottom(), q->contentHeight() + endExtent.height()); + if (loadedTableOuterRect.bottom() < h) { + const qreal diff = loadedTableOuterRect.bottom() - h; + loadedTableOuterRect.moveBottom(loadedTableOuterRect.bottom() - diff); + loadedTableInnerRect.moveBottom(loadedTableInnerRect.bottom() - diff); + tableMovedVertically = true; + } + } + endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight(); + vData.markExtentsDirty(); + } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) { + // The bottom-most row is outside the end of the content view, and we + // still have more visible rows in the model. This can happen if the application + // has set a fixed content height. + const int rowsRemaining = tableSize.height() - nextBottomRow; + const qreal remainingRowHeigts = rowsRemaining * averageEdgeSize.height(); + const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); + const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing; + const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight(); + endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight; + vData.markExtentsDirty(); + } + + if (tableMovedHorizontally || tableMovedVertically) { + qCDebug(lcTableViewDelegateLifecycle) << "move table to" << loadedTableOuterRect; + + // relayoutTableItems() will take care of moving the existing + // delegate items into the new loadedTableOuterRect. + relayoutTableItems(); + + // Inform the sync children that they need to rebuild to stay in sync + for (auto syncChild : qAsConst(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + syncChild_d->scheduledRebuildOptions |= RebuildOption::ViewportOnly; + if (tableMovedHorizontally) + syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn; + if (tableMovedVertically) + syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow; } } - if (layoutNeeded) { - qCDebug(lcTableViewDelegateLifecycle); - relayoutTableItems(); + if (hData.minExtentDirty || vData.minExtentDirty) { + qCDebug(lcTableViewDelegateLifecycle) << "move origin and endExtent to:" << origin << endExtent; + // updateBeginningEnd() will let the new extents take effect. This will also change the + // visualArea of the flickable, which again will cause any attached scrollbars to adjust + // the position of the handle. Note the latter will cause the viewport to move once more. + updateBeginningEnd(); } } @@ -1413,7 +1542,6 @@ void QQuickTableViewPrivate::processLoadRequest() switch (loadRequest.edge()) { case Qt::LeftEdge: case Qt::TopEdge: - enforceTableAtOrigin(); break; case Qt::RightEdge: updateAverageEdgeSize(); @@ -1424,6 +1552,7 @@ void QQuickTableViewPrivate::processLoadRequest() updateContentHeight(); break; } + updateExtents(); drainReusePoolAfterLoadRequest(); } @@ -1638,6 +1767,14 @@ void QQuickTableViewPrivate::beginRebuildTable() else if (rebuildOptions & RebuildOption::ViewportOnly) releaseLoadedItems(reusableFlag); + if (rebuildOptions & RebuildOption::All) { + origin = QPointF(0, 0); + endExtent = QSizeF(0, 0); + hData.markExtentsDirty(); + vData.markExtentsDirty(); + updateBeginningEnd(); + } + loadedColumns.clear(); loadedRows.clear(); loadedTableOuterRect = QRect(); @@ -1704,6 +1841,7 @@ void QQuickTableViewPrivate::layoutAfterLoadingInitialTable() updateAverageEdgeSize(); updateContentWidth(); updateContentHeight(); + updateExtents(); } void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge) @@ -2283,7 +2421,6 @@ void QQuickTableViewPrivate::modelResetCallback() void QQuickTableViewPrivate::scheduleRebuildIfFastFlick() { Q_Q(QQuickTableView); - // If the viewport has moved more than one page vertically or horizontally, we switch // strategy from refilling edges around the current table to instead rebuild the table // from scratch inside the new viewport. This will greatly improve performance when flicking @@ -2374,6 +2511,26 @@ QQuickTableView::QQuickTableView(QQuickTableViewPrivate &dd, QQuickItem *parent) setFlag(QQuickItem::ItemIsFocusScope); } +qreal QQuickTableView::minXExtent() const +{ + return QQuickFlickable::minXExtent() - d_func()->origin.x(); +} + +qreal QQuickTableView::maxXExtent() const +{ + return QQuickFlickable::maxXExtent() - d_func()->endExtent.width(); +} + +qreal QQuickTableView::minYExtent() const +{ + return QQuickFlickable::minYExtent() - d_func()->origin.y(); +} + +qreal QQuickTableView::maxYExtent() const +{ + return QQuickFlickable::maxYExtent() - d_func()->endExtent.height(); +} + int QQuickTableView::rows() const { return d_func()->tableSize.height(); diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h index 3d46221574..3b113efa4f 100644 --- a/src/quick/items/qquicktableview_p.h +++ b/src/quick/items/qquicktableview_p.h @@ -147,6 +147,11 @@ private: Q_DISABLE_COPY(QQuickTableView) Q_DECLARE_PRIVATE(QQuickTableView) + qreal minXExtent() const override; + qreal maxXExtent() const override; + qreal minYExtent() const override; + qreal maxYExtent() const override; + Q_PRIVATE_SLOT(d_func(), void _q_componentFinalized()) }; diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 748a1478ec..b66ac66dec 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -251,6 +251,9 @@ public: QRectF loadedTableOuterRect; QRectF loadedTableInnerRect; + QPointF origin = QPointF(0, 0); + QSizeF endExtent = QSizeF(0, 0); + QRectF viewportRect = QRectF(0, 0, -1, -1); QSize tableSize; @@ -350,7 +353,7 @@ public: void updateAverageEdgeSize(); void forceLayout(); - void enforceTableAtOrigin(); + void updateExtents(); void syncLoadedTableRectFromLoadedTable(); void syncLoadedTableFromLoadRequest(); |