| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickgridview_p.h" |
| #include "qquickflickable_p_p.h" |
| #include "qquickitemview_p_p.h" |
| |
| #include <private/qqmlobjectmodel_p.h> |
| #include <private/qquicksmoothedanimation_p_p.h> |
| |
| #include <QtGui/qevent.h> |
| #include <QtCore/qmath.h> |
| #include <QtCore/qcoreapplication.h> |
| #include "qplatformdefs.h" |
| |
| #include <cmath> |
| |
| QT_BEGIN_NAMESPACE |
| |
| #ifndef QML_FLICK_SNAPONETHRESHOLD |
| #define QML_FLICK_SNAPONETHRESHOLD 30 |
| #endif |
| |
| //---------------------------------------------------------------------------- |
| |
| class FxGridItemSG : public FxViewItem |
| { |
| public: |
| FxGridItemSG(QQuickItem *i, QQuickGridView *v, bool own) : FxViewItem(i, v, own, static_cast<QQuickItemViewAttached*>(qmlAttachedPropertiesObject<QQuickGridView>(i))), view(v) |
| { |
| } |
| |
| qreal position() const override { |
| return rowPos(); |
| } |
| |
| qreal endPosition() const override { |
| return endRowPos(); |
| } |
| |
| qreal size() const override { |
| return view->flow() == QQuickGridView::FlowLeftToRight ? view->cellHeight() : view->cellWidth(); |
| } |
| |
| qreal sectionSize() const override { |
| return 0.0; |
| } |
| |
| qreal rowPos() const { |
| if (view->flow() == QQuickGridView::FlowLeftToRight) |
| return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -view->cellHeight()-itemY() : itemY()); |
| else |
| return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-itemX() : itemX()); |
| } |
| |
| qreal colPos() const { |
| if (view->flow() == QQuickGridView::FlowLeftToRight) { |
| if (view->effectiveLayoutDirection() == Qt::RightToLeft) { |
| qreal colSize = view->cellWidth(); |
| int columns = view->width()/colSize; |
| return colSize * (columns-1) - itemX(); |
| } else { |
| return itemX(); |
| } |
| } else { |
| if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) { |
| return -view->cellHeight() - itemY(); |
| } else { |
| return itemY(); |
| } |
| } |
| } |
| qreal endRowPos() const { |
| if (view->flow() == QQuickGridView::FlowLeftToRight) { |
| if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) |
| return -itemY(); |
| else |
| return itemY() + view->cellHeight(); |
| } else { |
| if (view->effectiveLayoutDirection() == Qt::RightToLeft) |
| return -itemX(); |
| else |
| return itemX() + view->cellWidth(); |
| } |
| } |
| void setPosition(qreal col, qreal row, bool immediate = false) { |
| moveTo(pointForPosition(col, row), immediate); |
| } |
| bool contains(qreal x, qreal y) const override { |
| return (x >= itemX() && x < itemX() + view->cellWidth() && |
| y >= itemY() && y < itemY() + view->cellHeight()); |
| } |
| |
| QQuickGridView *view; |
| |
| private: |
| QPointF pointForPosition(qreal col, qreal row) const { |
| qreal x; |
| qreal y; |
| if (view->flow() == QQuickGridView::FlowLeftToRight) { |
| x = col; |
| y = row; |
| if (view->effectiveLayoutDirection() == Qt::RightToLeft) { |
| int columns = view->width()/view->cellWidth(); |
| x = view->cellWidth() * (columns-1) - col; |
| } |
| } else { |
| x = row; |
| y = col; |
| if (view->effectiveLayoutDirection() == Qt::RightToLeft) |
| x = -view->cellWidth() - row; |
| } |
| if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) |
| y = -view->cellHeight() - y; |
| return QPointF(x, y); |
| } |
| }; |
| |
| //---------------------------------------------------------------------------- |
| |
| class QQuickGridViewPrivate : public QQuickItemViewPrivate |
| { |
| Q_DECLARE_PUBLIC(QQuickGridView) |
| |
| public: |
| Qt::Orientation layoutOrientation() const override; |
| bool isContentFlowReversed() const override; |
| |
| qreal positionAt(int index) const override; |
| qreal endPositionAt(int index) const override; |
| qreal originPosition() const override; |
| qreal lastPosition() const override; |
| |
| qreal rowSize() const; |
| qreal colSize() const; |
| qreal colPosAt(int modelIndex) const; |
| qreal rowPosAt(int modelIndex) const; |
| qreal snapPosAt(qreal pos) const; |
| FxViewItem *snapItemAt(qreal pos) const; |
| int snapIndex() const; |
| qreal contentXForPosition(qreal pos) const; |
| qreal contentYForPosition(qreal pos) const; |
| |
| void resetColumns(); |
| |
| bool addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer) override; |
| bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) override; |
| |
| void removeItem(FxViewItem *item); |
| |
| FxViewItem *newViewItem(int index, QQuickItem *item) override; |
| void initializeViewItem(FxViewItem *item) override; |
| void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) override; |
| void repositionPackageItemAt(QQuickItem *item, int index) override; |
| void resetFirstItemPosition(qreal pos = 0.0) override; |
| void adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible) override; |
| |
| void createHighlight(bool onDestruction = false) override; |
| void updateHighlight() override; |
| void resetHighlightPosition() override; |
| |
| void setPosition(qreal pos) override; |
| void layoutVisibleItems(int fromModelIndex = 0) override; |
| bool applyInsertionChange(const QQmlChangeSet::Change &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) override; |
| void translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) override; |
| bool needsRefillForAddedOrRemovedIndex(int index) const override; |
| |
| qreal headerSize() const override; |
| qreal footerSize() const override; |
| bool showHeaderForIndex(int index) const override; |
| bool showFooterForIndex(int index) const override; |
| void updateHeader() override; |
| void updateFooter() override; |
| |
| void changedVisibleIndex(int newIndex) override; |
| void initializeCurrentItem() override; |
| |
| void updateViewport() override; |
| void fixupPosition() override; |
| void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override; |
| bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, |
| QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) override; |
| |
| QQuickGridView::Flow flow; |
| qreal cellWidth; |
| qreal cellHeight; |
| int columns; |
| QQuickGridView::SnapMode snapMode; |
| |
| QSmoothedAnimation *highlightXAnimator; |
| QSmoothedAnimation *highlightYAnimator; |
| |
| QQuickGridViewPrivate() |
| : flow(QQuickGridView::FlowLeftToRight) |
| , cellWidth(100), cellHeight(100), columns(1) |
| , snapMode(QQuickGridView::NoSnap) |
| , highlightXAnimator(nullptr), highlightYAnimator(nullptr) |
| {} |
| ~QQuickGridViewPrivate() |
| { |
| delete highlightXAnimator; |
| delete highlightYAnimator; |
| } |
| }; |
| |
| Qt::Orientation QQuickGridViewPrivate::layoutOrientation() const |
| { |
| return flow == QQuickGridView::FlowLeftToRight ? Qt::Vertical : Qt::Horizontal; |
| } |
| |
| bool QQuickGridViewPrivate::isContentFlowReversed() const |
| { |
| Q_Q(const QQuickGridView); |
| |
| return (flow == QQuickGridView::FlowLeftToRight && verticalLayoutDirection == QQuickItemView::BottomToTop) |
| || (flow == QQuickGridView::FlowTopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft); |
| } |
| |
| void QQuickGridViewPrivate::changedVisibleIndex(int newIndex) |
| { |
| visibleIndex = newIndex / columns * columns; |
| } |
| |
| void QQuickGridViewPrivate::setPosition(qreal pos) |
| { |
| Q_Q(QQuickGridView); |
| q->QQuickFlickable::setContentX(contentXForPosition(pos)); |
| q->QQuickFlickable::setContentY(contentYForPosition(pos)); |
| } |
| |
| qreal QQuickGridViewPrivate::originPosition() const |
| { |
| qreal pos = 0; |
| if (!visibleItems.isEmpty()) |
| pos = static_cast<FxGridItemSG*>(visibleItems.first())->rowPos() - visibleIndex / columns * rowSize(); |
| return pos; |
| } |
| |
| qreal QQuickGridViewPrivate::lastPosition() const |
| { |
| qreal pos = 0; |
| if (model && (model->count() || !visibleItems.isEmpty())) { |
| qreal lastRowPos = model->count() ? rowPosAt(model->count() - 1) : 0; |
| if (!visibleItems.isEmpty()) { |
| // If there are items in delayRemove state, they may be after any items linked to the model |
| lastRowPos = qMax(lastRowPos, static_cast<FxGridItemSG*>(visibleItems.last())->rowPos()); |
| } |
| pos = lastRowPos + rowSize(); |
| } |
| return pos; |
| } |
| |
| qreal QQuickGridViewPrivate::positionAt(int index) const |
| { |
| return rowPosAt(index); |
| } |
| |
| qreal QQuickGridViewPrivate::endPositionAt(int index) const |
| { |
| return rowPosAt(index) + rowSize(); |
| } |
| |
| qreal QQuickGridViewPrivate::rowSize() const { |
| return flow == QQuickGridView::FlowLeftToRight ? cellHeight : cellWidth; |
| } |
| qreal QQuickGridViewPrivate::colSize() const { |
| return flow == QQuickGridView::FlowLeftToRight ? cellWidth : cellHeight; |
| } |
| |
| qreal QQuickGridViewPrivate::colPosAt(int modelIndex) const |
| { |
| if (FxViewItem *item = visibleItem(modelIndex)) |
| return static_cast<FxGridItemSG*>(item)->colPos(); |
| if (!visibleItems.isEmpty()) { |
| if (modelIndex == visibleIndex) { |
| FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); |
| return firstItem->colPos(); |
| } else if (modelIndex < visibleIndex) { |
| int count = (visibleIndex - modelIndex) % columns; |
| int col = static_cast<FxGridItemSG*>(visibleItems.first())->colPos() / colSize(); |
| col = (columns - count + col) % columns; |
| return col * colSize(); |
| } else { |
| FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last()); |
| int count = modelIndex - lastItem->index; |
| int col = lastItem->colPos() / colSize(); |
| col = (col + count) % columns; |
| return col * colSize(); |
| } |
| } |
| return (modelIndex % columns) * colSize(); |
| } |
| |
| qreal QQuickGridViewPrivate::rowPosAt(int modelIndex) const |
| { |
| if (FxViewItem *item = visibleItem(modelIndex)) |
| return static_cast<FxGridItemSG*>(item)->rowPos(); |
| if (!visibleItems.isEmpty()) { |
| if (modelIndex == visibleIndex) { |
| FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); |
| return firstItem->rowPos(); |
| } else if (modelIndex < visibleIndex) { |
| FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); |
| int firstCol = firstItem->colPos() / colSize(); |
| int col = visibleIndex - modelIndex + (columns - firstCol - 1); |
| int rows = col / columns; |
| return firstItem->rowPos() - rows * rowSize(); |
| } else { |
| FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last()); |
| int count = modelIndex - lastItem->index; |
| int col = lastItem->colPos() + count * colSize(); |
| int rows = col / (columns * colSize()); |
| return lastItem->rowPos() + rows * rowSize(); |
| } |
| } |
| return (modelIndex / columns) * rowSize(); |
| } |
| |
| |
| qreal QQuickGridViewPrivate::snapPosAt(qreal pos) const |
| { |
| Q_Q(const QQuickGridView); |
| qreal snapPos = 0; |
| if (!visibleItems.isEmpty()) { |
| qreal highlightStart = highlightRangeStart; |
| pos += highlightStart; |
| pos += rowSize()/2; |
| snapPos = static_cast<FxGridItemSG*>(visibleItems.first())->rowPos() - visibleIndex / columns * rowSize(); |
| snapPos = pos - std::fmod(pos - snapPos, qreal(rowSize())); |
| snapPos -= highlightStart; |
| qreal maxExtent; |
| qreal minExtent; |
| if (isContentFlowReversed()) { |
| maxExtent = q->minXExtent()-size(); |
| minExtent = q->maxXExtent()-size(); |
| } else { |
| maxExtent = flow == QQuickGridView::FlowLeftToRight ? -q->maxYExtent() : -q->maxXExtent(); |
| minExtent = flow == QQuickGridView::FlowLeftToRight ? -q->minYExtent() : -q->minXExtent(); |
| } |
| if (snapPos > maxExtent) |
| snapPos = maxExtent; |
| if (snapPos < minExtent) |
| snapPos = minExtent; |
| } |
| return snapPos; |
| } |
| |
| FxViewItem *QQuickGridViewPrivate::snapItemAt(qreal pos) const |
| { |
| for (FxViewItem *item : visibleItems) { |
| if (item->index == -1) |
| continue; |
| qreal itemTop = item->position(); |
| if (itemTop+rowSize()/2 >= pos && itemTop - rowSize()/2 <= pos) |
| return item; |
| } |
| return nullptr; |
| } |
| |
| int QQuickGridViewPrivate::snapIndex() const |
| { |
| int index = currentIndex; |
| for (FxViewItem *item : visibleItems) { |
| if (item->index == -1) |
| continue; |
| qreal itemTop = item->position(); |
| FxGridItemSG *hItem = static_cast<FxGridItemSG*>(highlight); |
| if (itemTop >= hItem->rowPos()-rowSize()/2 && itemTop < hItem->rowPos()+rowSize()/2) { |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(item); |
| index = gridItem->index; |
| if (gridItem->colPos() >= hItem->colPos()-colSize()/2 && gridItem->colPos() < hItem->colPos()+colSize()/2) |
| return gridItem->index; |
| } |
| } |
| return index; |
| } |
| |
| qreal QQuickGridViewPrivate::contentXForPosition(qreal pos) const |
| { |
| Q_Q(const QQuickGridView); |
| if (flow == QQuickGridView::FlowLeftToRight) { |
| // vertical scroll |
| if (q->effectiveLayoutDirection() == Qt::LeftToRight) { |
| return -q->leftMargin(); |
| } else { |
| qreal colSize = cellWidth; |
| int columns = (q->width() - q->leftMargin() - q->rightMargin()) / colSize; |
| return -q->width() + q->rightMargin() + (cellWidth * columns); |
| } |
| } else { |
| // horizontal scroll |
| if (q->effectiveLayoutDirection() == Qt::LeftToRight) |
| return pos; |
| else |
| return -pos - q->width(); |
| } |
| } |
| |
| qreal QQuickGridViewPrivate::contentYForPosition(qreal pos) const |
| { |
| Q_Q(const QQuickGridView); |
| if (flow == QQuickGridView::FlowLeftToRight) { |
| // vertical scroll |
| if (verticalLayoutDirection == QQuickItemView::TopToBottom) |
| return pos; |
| else |
| return -pos - q->height(); |
| } else { |
| // horizontal scroll |
| if (verticalLayoutDirection == QQuickItemView::TopToBottom) |
| return -q->topMargin(); |
| else |
| return -q->height() + q->bottomMargin(); |
| } |
| } |
| |
| void QQuickGridViewPrivate::resetColumns() |
| { |
| Q_Q(QQuickGridView); |
| qreal length = flow == QQuickGridView::FlowLeftToRight |
| ? q->width() - q->leftMargin() - q->rightMargin() |
| : q->height() - q->topMargin() - q->bottomMargin(); |
| columns = qMax(1, qFloor(length / colSize())); |
| } |
| |
| FxViewItem *QQuickGridViewPrivate::newViewItem(int modelIndex, QQuickItem *item) |
| { |
| Q_Q(QQuickGridView); |
| Q_UNUSED(modelIndex); |
| return new FxGridItemSG(item, q, false); |
| } |
| |
| void QQuickGridViewPrivate::initializeViewItem(FxViewItem *item) |
| { |
| QQuickItemViewPrivate::initializeViewItem(item); |
| |
| // need to track current items that are animating |
| item->trackGeometry(true); |
| } |
| |
| bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer) |
| { |
| qreal colPos = colPosAt(visibleIndex); |
| qreal rowPos = rowPosAt(visibleIndex); |
| if (visibleItems.count()) { |
| FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.constLast()); |
| rowPos = lastItem->rowPos(); |
| int colNum = qFloor((lastItem->colPos()+colSize()/2) / colSize()); |
| if (++colNum >= columns) { |
| colNum = 0; |
| rowPos += rowSize(); |
| } |
| colPos = colNum * colSize(); |
| } |
| |
| int modelIndex = findLastVisibleIndex(); |
| modelIndex = modelIndex < 0 ? visibleIndex : modelIndex + 1; |
| |
| if (visibleItems.count() && (bufferFrom > rowPos + rowSize()*2 |
| || bufferTo < rowPosAt(visibleIndex) - rowSize())) { |
| // We've jumped more than a page. Estimate which items are now |
| // visible and fill from there. |
| int count = (fillFrom - (rowPos + rowSize())) / (rowSize()) * columns; |
| releaseVisibleItems(reusableFlag); |
| modelIndex += count; |
| if (modelIndex >= model->count()) |
| modelIndex = model->count() - 1; |
| else if (modelIndex < 0) |
| modelIndex = 0; |
| modelIndex = modelIndex / columns * columns; |
| visibleIndex = modelIndex; |
| colPos = colPosAt(visibleIndex); |
| rowPos = rowPosAt(visibleIndex); |
| } |
| |
| int colNum = qFloor((colPos+colSize()/2) / colSize()); |
| FxGridItemSG *item = nullptr; |
| bool changed = false; |
| |
| QQmlIncubator::IncubationMode incubationMode = doBuffer ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested; |
| |
| while (modelIndex < model->count() && rowPos <= fillTo + rowSize()*(columns - colNum)/(columns+1)) { |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: append item" << modelIndex << colPos << rowPos; |
| if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex, incubationMode)))) |
| break; |
| if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems() |
| item->setPosition(colPos, rowPos, true); |
| QQuickItemPrivate::get(item->item)->setCulled(doBuffer); |
| visibleItems.append(item); |
| if (++colNum >= columns) { |
| colNum = 0; |
| rowPos += rowSize(); |
| } |
| colPos = colNum * colSize(); |
| ++modelIndex; |
| changed = true; |
| } |
| |
| if (doBuffer && requestedIndex != -1) // already waiting for an item |
| return changed; |
| |
| // Find first column |
| if (visibleItems.count()) { |
| FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.constFirst()); |
| rowPos = firstItem->rowPos(); |
| colPos = firstItem->colPos(); |
| } |
| colNum = qFloor((colPos+colSize()/2) / colSize()); |
| if (--colNum < 0) { |
| colNum = columns - 1; |
| rowPos -= rowSize(); |
| } |
| |
| // Prepend |
| colPos = colNum * colSize(); |
| while (visibleIndex > 0 && rowPos + rowSize() - 1 >= fillFrom - rowSize()*(colNum+1)/(columns+1)){ |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: prepend item" << visibleIndex-1 << "top pos" << rowPos << colPos; |
| if (!(item = static_cast<FxGridItemSG*>(createItem(visibleIndex-1, incubationMode)))) |
| break; |
| --visibleIndex; |
| if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems() |
| item->setPosition(colPos, rowPos, true); |
| QQuickItemPrivate::get(item->item)->setCulled(doBuffer); |
| visibleItems.prepend(item); |
| if (--colNum < 0) { |
| colNum = columns-1; |
| rowPos -= rowSize(); |
| } |
| colPos = colNum * colSize(); |
| changed = true; |
| } |
| |
| return changed; |
| } |
| |
| void QQuickGridViewPrivate::removeItem(FxViewItem *item) |
| { |
| if (item->transitionScheduledOrRunning()) { |
| qCDebug(lcItemViewDelegateLifecycle) << "\tnot releasing animating item:" << item->index << item->item->objectName(); |
| item->releaseAfterTransition = true; |
| releasePendingTransition.append(item); |
| } else { |
| releaseItem(item, QQmlDelegateModel::NotReusable); |
| } |
| } |
| |
| bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) |
| { |
| FxGridItemSG *item = nullptr; |
| bool changed = false; |
| |
| while (visibleItems.count() > 1 |
| && (item = static_cast<FxGridItemSG*>(visibleItems.constFirst())) |
| && item->rowPos()+rowSize()-1 < bufferFrom - rowSize()*(item->colPos()/colSize()+1)/(columns+1)) { |
| if (item->attached->delayRemove()) |
| break; |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: remove first" << visibleIndex << "top end pos" << item->endRowPos(); |
| if (item->index != -1) |
| visibleIndex++; |
| visibleItems.removeFirst(); |
| removeItem(item); |
| changed = true; |
| } |
| while (visibleItems.count() > 1 |
| && (item = static_cast<FxGridItemSG*>(visibleItems.constLast())) |
| && item->rowPos() > bufferTo + rowSize()*(columns - item->colPos()/colSize())/(columns+1)) { |
| if (item->attached->delayRemove()) |
| break; |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: remove last" << visibleIndex+visibleItems.count()-1; |
| visibleItems.removeLast(); |
| removeItem(item); |
| changed = true; |
| } |
| |
| return changed; |
| } |
| |
| void QQuickGridViewPrivate::updateViewport() |
| { |
| resetColumns(); |
| QQuickItemViewPrivate::updateViewport(); |
| } |
| |
| void QQuickGridViewPrivate::layoutVisibleItems(int fromModelIndex) |
| { |
| if (visibleItems.count()) { |
| const qreal from = isContentFlowReversed() ? -position()-displayMarginBeginning-size() : position()-displayMarginBeginning; |
| const qreal to = isContentFlowReversed() ? -position()+displayMarginEnd : position()+size()+displayMarginEnd; |
| |
| FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.constFirst()); |
| qreal rowPos = firstItem->rowPos(); |
| qreal colPos = firstItem->colPos(); |
| int col = visibleIndex % columns; |
| if (colPos != col * colSize()) { |
| colPos = col * colSize(); |
| firstItem->setPosition(colPos, rowPos); |
| } |
| firstItem->setVisible(firstItem->rowPos() + rowSize() >= from && firstItem->rowPos() <= to); |
| for (int i = 1; i < visibleItems.count(); ++i) { |
| FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.at(i)); |
| if (++col >= columns) { |
| col = 0; |
| rowPos += rowSize(); |
| } |
| colPos = col * colSize(); |
| if (item->index >= fromModelIndex) { |
| item->setPosition(colPos, rowPos); |
| item->setVisible(item->rowPos() + rowSize() >= from && item->rowPos() <= to); |
| } |
| } |
| } |
| } |
| |
| void QQuickGridViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) |
| { |
| int count = sizeBuffer / rowSize(); |
| static_cast<FxGridItemSG *>(item)->setPosition(colPosAt(index + count), rowPosAt(index + count)); |
| } |
| |
| void QQuickGridViewPrivate::repositionPackageItemAt(QQuickItem *item, int index) |
| { |
| Q_Q(QQuickGridView); |
| qreal pos = position(); |
| if (flow == QQuickGridView::FlowLeftToRight) { |
| if (item->y() + item->height() > pos && item->y() < pos + q->height()) { |
| qreal y = (verticalLayoutDirection == QQuickItemView::TopToBottom) |
| ? rowPosAt(index) |
| : -rowPosAt(index) - item->height(); |
| item->setPosition(QPointF(colPosAt(index), y)); |
| } |
| } else { |
| if (item->x() + item->width() > pos && item->x() < pos + q->width()) { |
| qreal y = (verticalLayoutDirection == QQuickItemView::TopToBottom) |
| ? colPosAt(index) |
| : -colPosAt(index) - item->height(); |
| if (flow == QQuickGridView::FlowTopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft) |
| item->setPosition(QPointF(-rowPosAt(index)-item->width(), y)); |
| else |
| item->setPosition(QPointF(rowPosAt(index), y)); |
| } |
| } |
| } |
| |
| void QQuickGridViewPrivate::resetFirstItemPosition(qreal pos) |
| { |
| FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.constFirst()); |
| item->setPosition(0, pos); |
| } |
| |
| void QQuickGridViewPrivate::adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible) |
| { |
| if (!visibleItems.count()) |
| return; |
| |
| int moveCount = (forwards - backwards) / rowSize(); |
| if (moveCount == 0 && changeBeforeVisible != 0) |
| moveCount += (changeBeforeVisible % columns) - (columns - 1); |
| |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.constFirst()); |
| gridItem->setPosition(gridItem->colPos(), gridItem->rowPos() + ((moveCount / columns) * rowSize())); |
| } |
| |
| void QQuickGridViewPrivate::createHighlight(bool onDestruction) |
| { |
| bool changed = false; |
| if (highlight) { |
| if (trackedItem == highlight) |
| trackedItem = nullptr; |
| delete highlight; |
| highlight = nullptr; |
| |
| delete highlightXAnimator; |
| delete highlightYAnimator; |
| highlightXAnimator = nullptr; |
| highlightYAnimator = nullptr; |
| |
| changed = true; |
| } |
| |
| if (onDestruction) |
| return; |
| |
| Q_Q(QQuickGridView); |
| if (currentItem) { |
| QQuickItem *item = createHighlightItem(); |
| if (item) { |
| FxGridItemSG *newHighlight = new FxGridItemSG(item, q, true); |
| newHighlight->trackGeometry(true); |
| if (autoHighlight) |
| resetHighlightPosition(); |
| highlightXAnimator = new QSmoothedAnimation; |
| highlightXAnimator->target = QQmlProperty(item, QLatin1String("x")); |
| highlightXAnimator->userDuration = highlightMoveDuration; |
| highlightYAnimator = new QSmoothedAnimation; |
| highlightYAnimator->target = QQmlProperty(item, QLatin1String("y")); |
| highlightYAnimator->userDuration = highlightMoveDuration; |
| |
| highlight = newHighlight; |
| changed = true; |
| } |
| } |
| if (changed) |
| emit q->highlightItemChanged(); |
| } |
| |
| void QQuickGridViewPrivate::updateHighlight() |
| { |
| applyPendingChanges(); |
| |
| if ((!currentItem && highlight) || (currentItem && !highlight)) |
| createHighlight(); |
| bool strictHighlight = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange; |
| if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) { |
| // auto-update highlight |
| highlightXAnimator->to = currentItem->itemX(); |
| highlightYAnimator->to = currentItem->itemY(); |
| highlight->item->setSize(currentItem->item->size()); |
| |
| highlightXAnimator->restart(); |
| highlightYAnimator->restart(); |
| } |
| updateTrackedItem(); |
| } |
| |
| void QQuickGridViewPrivate::resetHighlightPosition() |
| { |
| if (highlight && currentItem) { |
| FxGridItemSG *cItem = static_cast<FxGridItemSG*>(currentItem); |
| static_cast<FxGridItemSG*>(highlight)->setPosition(cItem->colPos(), cItem->rowPos()); |
| } |
| } |
| |
| qreal QQuickGridViewPrivate::headerSize() const |
| { |
| if (!header) |
| return 0.0; |
| return flow == QQuickGridView::FlowLeftToRight ? header->item->height() : header->item->width(); |
| } |
| |
| qreal QQuickGridViewPrivate::footerSize() const |
| { |
| if (!footer) |
| return 0.0; |
| return flow == QQuickGridView::FlowLeftToRight? footer->item->height() : footer->item->width(); |
| } |
| |
| bool QQuickGridViewPrivate::showHeaderForIndex(int index) const |
| { |
| return index / columns == 0; |
| } |
| |
| bool QQuickGridViewPrivate::showFooterForIndex(int index) const |
| { |
| return index / columns == (model->count()-1) / columns; |
| } |
| |
| void QQuickGridViewPrivate::updateFooter() |
| { |
| Q_Q(QQuickGridView); |
| bool created = false; |
| if (!footer) { |
| QQuickItem *item = createComponentItem(footerComponent, 1.0); |
| if (!item) |
| return; |
| footer = new FxGridItemSG(item, q, true); |
| footer->trackGeometry(true); |
| created = true; |
| } |
| |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(footer); |
| qreal colOffset = 0; |
| qreal rowOffset = 0; |
| if (q->effectiveLayoutDirection() == Qt::RightToLeft) { |
| if (flow == QQuickGridView::FlowTopToBottom) |
| rowOffset += gridItem->item->width() - cellWidth; |
| else |
| colOffset += gridItem->item->width() - cellWidth; |
| } |
| if (verticalLayoutDirection == QQuickItemView::BottomToTop) { |
| if (flow == QQuickGridView::FlowTopToBottom) |
| colOffset += gridItem->item->height() - cellHeight; |
| else |
| rowOffset += gridItem->item->height() - cellHeight; |
| } |
| if (visibleItems.count()) { |
| qreal endPos = lastPosition(); |
| if (findLastVisibleIndex() == model->count()-1) { |
| gridItem->setPosition(colOffset, endPos + rowOffset); |
| } else { |
| qreal visiblePos = isContentFlowReversed() ? -position() : position() + size(); |
| if (endPos <= visiblePos || gridItem->endPosition() <= endPos + rowOffset) |
| gridItem->setPosition(colOffset, endPos + rowOffset); |
| } |
| } else { |
| gridItem->setPosition(colOffset, rowOffset); |
| } |
| |
| if (created) |
| emit q->footerItemChanged(); |
| } |
| |
| void QQuickGridViewPrivate::updateHeader() |
| { |
| Q_Q(QQuickGridView); |
| bool created = false; |
| if (!header) { |
| QQuickItem *item = createComponentItem(headerComponent, 1.0); |
| if (!item) |
| return; |
| header = new FxGridItemSG(item, q, true); |
| header->trackGeometry(true); |
| created = true; |
| } |
| |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(header); |
| qreal colOffset = 0; |
| qreal rowOffset = -headerSize(); |
| if (q->effectiveLayoutDirection() == Qt::RightToLeft) { |
| if (flow == QQuickGridView::FlowTopToBottom) |
| rowOffset += gridItem->item->width() - cellWidth; |
| else |
| colOffset += gridItem->item->width() - cellWidth; |
| } |
| if (verticalLayoutDirection == QQuickItemView::BottomToTop) { |
| if (flow == QQuickGridView::FlowTopToBottom) |
| colOffset += gridItem->item->height() - cellHeight; |
| else |
| rowOffset += gridItem->item->height() - cellHeight; |
| } |
| if (visibleItems.count()) { |
| qreal startPos = originPosition(); |
| if (visibleIndex == 0) { |
| gridItem->setPosition(colOffset, startPos + rowOffset); |
| } else { |
| qreal tempPos = isContentFlowReversed() ? -position()-size() : position(); |
| qreal headerPos = isContentFlowReversed() ? gridItem->rowPos() + cellWidth - headerSize() : gridItem->rowPos(); |
| if (tempPos <= startPos || headerPos > startPos + rowOffset) |
| gridItem->setPosition(colOffset, startPos + rowOffset); |
| } |
| } else { |
| if (isContentFlowReversed()) |
| gridItem->setPosition(colOffset, rowOffset); |
| else |
| gridItem->setPosition(colOffset, -headerSize()); |
| } |
| |
| if (created) |
| emit q->headerItemChanged(); |
| } |
| |
| void QQuickGridViewPrivate::initializeCurrentItem() |
| { |
| if (currentItem && currentIndex >= 0) { |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(currentItem); |
| FxViewItem *actualItem = visibleItem(currentIndex); |
| |
| // don't reposition the item if it's about to be transitioned to another position |
| if ((!actualItem || !actualItem->transitionScheduledOrRunning())) |
| gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex)); |
| } |
| } |
| |
| void QQuickGridViewPrivate::fixupPosition() |
| { |
| if (flow == QQuickGridView::FlowLeftToRight) |
| fixupY(); |
| else |
| fixupX(); |
| } |
| |
| void QQuickGridViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) |
| { |
| if ((flow == QQuickGridView::FlowTopToBottom && &data == &vData) |
| || (flow == QQuickGridView::FlowLeftToRight && &data == &hData)) |
| return; |
| |
| fixupMode = moveReason == Mouse ? fixupMode : Immediate; |
| |
| qreal viewPos = isContentFlowReversed() ? -position()-size() : position(); |
| |
| bool strictHighlightRange = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange; |
| if (snapMode != QQuickGridView::NoSnap) { |
| qreal tempPosition = isContentFlowReversed() ? -position()-size() : position(); |
| if (snapMode == QQuickGridView::SnapOneRow && moveReason == Mouse) { |
| // if we've been dragged < rowSize()/2 then bias towards the next row |
| qreal dist = data.move.value() - data.pressPos; |
| qreal bias = 0; |
| if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < rowSize()/2) |
| bias = rowSize()/2; |
| else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -rowSize()/2) |
| bias = -rowSize()/2; |
| if (isContentFlowReversed()) |
| bias = -bias; |
| tempPosition -= bias; |
| } |
| FxViewItem *topItem = snapItemAt(tempPosition+highlightRangeStart); |
| if (strictHighlightRange && currentItem && (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))) { |
| // StrictlyEnforceRange always keeps an item in range |
| updateHighlight(); |
| topItem = currentItem; |
| } |
| FxViewItem *bottomItem = snapItemAt(tempPosition+highlightRangeEnd); |
| if (strictHighlightRange && currentItem && (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))) { |
| // StrictlyEnforceRange always keeps an item in range |
| updateHighlight(); |
| bottomItem = currentItem; |
| } |
| qreal pos; |
| bool isInBounds = -position() > maxExtent && -position() <= minExtent; |
| if (topItem && (isInBounds || strictHighlightRange)) { |
| qreal headerPos = header ? static_cast<FxGridItemSG*>(header)->rowPos() : 0; |
| if (topItem->index == 0 && header && tempPosition+highlightRangeStart < headerPos+headerSize()/2 && !strictHighlightRange) { |
| pos = isContentFlowReversed() ? - headerPos + highlightRangeStart - size() : headerPos - highlightRangeStart; |
| } else { |
| if (isContentFlowReversed()) |
| pos = qMax(qMin(-topItem->position() + highlightRangeStart - size(), -maxExtent), -minExtent); |
| else |
| pos = qMax(qMin(topItem->position() - highlightRangeStart, -maxExtent), -minExtent); |
| } |
| } else if (bottomItem && isInBounds) { |
| if (isContentFlowReversed()) |
| pos = qMax(qMin(-bottomItem->position() + highlightRangeEnd - size(), -maxExtent), -minExtent); |
| else |
| pos = qMax(qMin(bottomItem->position() - highlightRangeEnd, -maxExtent), -minExtent); |
| } else { |
| QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); |
| return; |
| } |
| |
| qreal dist = qAbs(data.move + pos); |
| if (dist > 0) { |
| timeline.reset(data.move); |
| if (fixupMode != Immediate) { |
| timeline.move(data.move, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); |
| data.fixingUp = true; |
| } else { |
| timeline.set(data.move, -pos); |
| } |
| vTime = timeline.time(); |
| } |
| } else if (haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange) { |
| if (currentItem) { |
| updateHighlight(); |
| qreal pos = static_cast<FxGridItemSG*>(currentItem)->rowPos(); |
| if (viewPos < pos + rowSize() - highlightRangeEnd) |
| viewPos = pos + rowSize() - highlightRangeEnd; |
| if (viewPos > pos - highlightRangeStart) |
| viewPos = pos - highlightRangeStart; |
| if (isContentFlowReversed()) |
| viewPos = -viewPos-size(); |
| timeline.reset(data.move); |
| if (viewPos != position()) { |
| if (fixupMode != Immediate) { |
| timeline.move(data.move, -viewPos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); |
| data.fixingUp = true; |
| } else { |
| timeline.set(data.move, -viewPos); |
| } |
| } |
| vTime = timeline.time(); |
| } |
| } else { |
| QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); |
| } |
| data.inOvershoot = false; |
| fixupMode = Normal; |
| } |
| |
| bool QQuickGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, |
| QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) |
| { |
| data.fixingUp = false; |
| moveReason = Mouse; |
| if ((!haveHighlightRange || highlightRange != QQuickGridView::StrictlyEnforceRange) |
| && snapMode == QQuickGridView::NoSnap) { |
| return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity); |
| } |
| qreal maxDistance = 0; |
| qreal dataValue = isContentFlowReversed() ? -data.move.value()+size() : data.move.value(); |
| // -ve velocity means list is moving up/left |
| if (velocity > 0) { |
| if (data.move.value() < minExtent) { |
| if (snapMode == QQuickGridView::SnapOneRow) { |
| // if we've been dragged < averageSize/2 then bias towards the next item |
| qreal dist = data.move.value() - data.pressPos; |
| qreal bias = dist < rowSize()/2 ? rowSize()/2 : 0; |
| if (isContentFlowReversed()) |
| bias = -bias; |
| data.flickTarget = -snapPosAt(-dataValue - bias); |
| maxDistance = qAbs(data.flickTarget - data.move.value()); |
| velocity = maxVelocity; |
| } else { |
| maxDistance = qAbs(minExtent - data.move.value()); |
| } |
| } |
| if (snapMode == QQuickGridView::NoSnap && highlightRange != QQuickGridView::StrictlyEnforceRange) |
| data.flickTarget = minExtent; |
| } else { |
| if (data.move.value() > maxExtent) { |
| if (snapMode == QQuickGridView::SnapOneRow) { |
| // if we've been dragged < averageSize/2 then bias towards the next item |
| qreal dist = data.move.value() - data.pressPos; |
| qreal bias = -dist < rowSize()/2 ? rowSize()/2 : 0; |
| if (isContentFlowReversed()) |
| bias = -bias; |
| data.flickTarget = -snapPosAt(-dataValue + bias); |
| maxDistance = qAbs(data.flickTarget - data.move.value()); |
| velocity = -maxVelocity; |
| } else { |
| maxDistance = qAbs(maxExtent - data.move.value()); |
| } |
| } |
| if (snapMode == QQuickGridView::NoSnap && highlightRange != QQuickGridView::StrictlyEnforceRange) |
| data.flickTarget = maxExtent; |
| } |
| bool overShoot = boundsBehavior & QQuickFlickable::OvershootBounds; |
| if (maxDistance > 0 || overShoot) { |
| // This mode requires the grid to stop exactly on a row boundary. |
| qreal v = velocity; |
| if (maxVelocity != -1 && maxVelocity < qAbs(v)) { |
| if (v < 0) |
| v = -maxVelocity; |
| else |
| v = maxVelocity; |
| } |
| qreal accel = deceleration; |
| qreal v2 = v * v; |
| qreal overshootDist = 0.0; |
| if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickGridView::SnapOneRow) { |
| // + rowSize()/4 to encourage moving at least one item in the flick direction |
| qreal dist = v2 / (accel * 2.0) + rowSize()/4; |
| dist = qMin(dist, maxDistance); |
| if (v > 0) |
| dist = -dist; |
| if (snapMode != QQuickGridView::SnapOneRow) { |
| qreal distTemp = isContentFlowReversed() ? -dist : dist; |
| data.flickTarget = -snapPosAt(-dataValue + distTemp); |
| } |
| data.flickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget; |
| if (overShoot) { |
| if (data.flickTarget >= minExtent) { |
| overshootDist = overShootDistance(vSize); |
| data.flickTarget += overshootDist; |
| } else if (data.flickTarget <= maxExtent) { |
| overshootDist = overShootDistance(vSize); |
| data.flickTarget -= overshootDist; |
| } |
| } |
| qreal adjDist = -data.flickTarget + data.move.value(); |
| if (qAbs(adjDist) > qAbs(dist)) { |
| // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration |
| qreal adjv2 = accel * 2.0f * qAbs(adjDist); |
| if (adjv2 > v2) { |
| v2 = adjv2; |
| v = qSqrt(v2); |
| if (dist > 0) |
| v = -v; |
| } |
| } |
| dist = adjDist; |
| accel = v2 / (2.0f * qAbs(dist)); |
| } else { |
| data.flickTarget = velocity > 0 ? minExtent : maxExtent; |
| overshootDist = overShoot ? overShootDistance(vSize) : 0; |
| } |
| timeline.reset(data.move); |
| timeline.accel(data.move, v, accel, maxDistance + overshootDist); |
| timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| return true; |
| } else { |
| timeline.reset(data.move); |
| fixup(data, minExtent, maxExtent); |
| return false; |
| } |
| } |
| |
| |
| //---------------------------------------------------------------------------- |
| /*! |
| \qmltype GridView |
| \instantiates QQuickGridView |
| \inqmlmodule QtQuick |
| \ingroup qtquick-views |
| |
| \inherits Flickable |
| \brief For specifying a grid view of items provided by a model. |
| |
| A GridView displays data from models created from built-in QML types like ListModel |
| and XmlListModel, or custom model classes defined in C++ that inherit from |
| QAbstractListModel. |
| |
| A GridView has a \l model, which defines the data to be displayed, and |
| a \l delegate, which defines how the data should be displayed. Items in a |
| GridView are laid out horizontally or vertically. Grid views are inherently flickable |
| as GridView inherits from \l Flickable. |
| |
| \section1 Example Usage |
| |
| The following example shows the definition of a simple list model defined |
| in a file called \c ContactModel.qml: |
| |
| \snippet qml/gridview/ContactModel.qml 0 |
| |
| \div {class="float-right"} |
| \inlineimage gridview-simple.png |
| \enddiv |
| |
| This model can be referenced as \c ContactModel in other QML files. See \l{QML Modules} |
| for more information about creating reusable components like this. |
| |
| Another component can display this model data in a GridView, as in the following |
| example, which creates a \c ContactModel component for its model, and a \l Column |
| (containing \l Image and \l Text items) for its delegate. |
| |
| \clearfloat |
| \snippet qml/gridview/gridview.qml import |
| \codeline |
| \snippet qml/gridview/gridview.qml classdocs simple |
| |
| \div {class="float-right"} |
| \inlineimage gridview-highlight.png |
| \enddiv |
| |
| The view will create a new delegate for each item in the model. Note that the delegate |
| is able to access the model's \c name and \c portrait data directly. |
| |
| An improved grid view is shown below. The delegate is visually improved and is moved |
| into a separate \c contactDelegate component. |
| |
| \clearfloat |
| \snippet qml/gridview/gridview.qml classdocs advanced |
| |
| The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property, |
| and \c focus is set to \c true to enable keyboard navigation for the grid view. |
| The grid view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details). |
| |
| Delegates are instantiated as needed and may be destroyed at any time. |
| State should \e never be stored in a delegate. |
| |
| GridView attaches a number of properties to the root item of the delegate, for example |
| \c {GridView.isCurrentItem}. In the following example, the root delegate item can access |
| this attached property directly as \c GridView.isCurrentItem, while the child |
| \c contactInfo object must refer to this property as \c wrapper.GridView.isCurrentItem. |
| |
| \snippet qml/gridview/gridview.qml isCurrentItem |
| |
| \note Views do not set the \l{Item::}{clip} property automatically. |
| If the view is not clipped by another item or the screen, it will be necessary |
| to set this property to true in order to clip the items that are partially or |
| fully outside the view. |
| |
| |
| \section1 GridView Layouts |
| |
| The layout of the items in a GridView can be controlled by these properties: |
| |
| \list |
| \li \l flow - controls whether items flow from left to right (as a series of rows) |
| or from top to bottom (as a series of columns). This value can be either |
| GridView.FlowLeftToRight or GridView.FlowTopToBottom. |
| \li \l layoutDirection - controls the horizontal layout direction: that is, whether items |
| are laid out from the left side of the view to the right, or vice-versa. This value can |
| be either Qt.LeftToRight or Qt.RightToLeft. |
| \li \l verticalLayoutDirection - controls the vertical layout direction: that is, whether items |
| are laid out from the top of the view down towards the bottom of the view, or vice-versa. |
| This value can be either GridView.TopToBottom or GridView.BottomToTop. |
| \endlist |
| |
| By default, a GridView flows from left to right, and items are laid out from left to right |
| horizontally, and from top to bottom vertically. |
| |
| These properties can be combined to produce a variety of layouts, as shown in the table below. |
| The GridViews in the first row all have a \l flow value of GridView.FlowLeftToRight, but use |
| different combinations of horizontal and vertical layout directions (specified by \l layoutDirection |
| and \l verticalLayoutDirection respectively). Similarly, the GridViews in the second row below |
| all have a \l flow value of GridView.FlowTopToBottom, but use different combinations of horizontal and |
| vertical layout directions to lay out their items in different ways. |
| |
| \table |
| \header |
| \li {4, 1} |
| \b GridViews with GridView.FlowLeftToRight flow |
| \row |
| \li \b (H) Left to right \b (V) Top to bottom |
| \image gridview-layout-lefttoright-ltr-ttb.png |
| \li \b (H) Right to left \b (V) Top to bottom |
| \image gridview-layout-lefttoright-rtl-ttb.png |
| \li \b (H) Left to right \b (V) Bottom to top |
| \image gridview-layout-lefttoright-ltr-btt.png |
| \li \b (H) Right to left \b (V) Bottom to top |
| \image gridview-layout-lefttoright-rtl-btt.png |
| \header |
| \li {4, 1} |
| \b GridViews with GridView.FlowTopToBottom flow |
| \row |
| \li \b (H) Left to right \b (V) Top to bottom |
| \image gridview-layout-toptobottom-ltr-ttb.png |
| \li \b (H) Right to left \b (V) Top to bottom |
| \image gridview-layout-toptobottom-rtl-ttb.png |
| \li \b (H) Left to right \b (V) Bottom to top |
| \image gridview-layout-toptobottom-ltr-btt.png |
| \li \b (H) Right to left \b (V) Bottom to top |
| \image gridview-layout-toptobottom-rtl-btt.png |
| \endtable |
| |
| \sa {QML Data Models}, ListView, PathView, {Qt Quick Examples - Views} |
| */ |
| |
| QQuickGridView::QQuickGridView(QQuickItem *parent) |
| : QQuickItemView(*(new QQuickGridViewPrivate), parent) |
| { |
| } |
| |
| QQuickGridView::~QQuickGridView() |
| { |
| } |
| |
| void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight) |
| { |
| Q_D(QQuickGridView); |
| if (d->autoHighlight != autoHighlight) { |
| if (!autoHighlight && d->highlightXAnimator) { |
| d->highlightXAnimator->stop(); |
| d->highlightYAnimator->stop(); |
| } |
| QQuickItemView::setHighlightFollowsCurrentItem(autoHighlight); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQuick::GridView::isCurrentItem |
| This attached property is true if this delegate is the current item; otherwise false. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| /*! |
| \qmlattachedproperty GridView QtQuick::GridView::view |
| This attached property holds the view that manages this delegate instance. |
| |
| It is attached to each instance of the delegate and also to the header, the footer |
| and the highlight delegates. |
| |
| \snippet qml/gridview/gridview.qml isCurrentItem |
| */ |
| |
| /*! |
| \qmlattachedproperty bool QtQuick::GridView::delayRemove |
| This attached property holds whether the delegate may be destroyed. It |
| is attached to each instance of the delegate. The default value is false. |
| |
| It is sometimes necessary to delay the destruction of an item |
| until an animation completes. The example delegate below ensures that the |
| animation completes before the item is removed from the list. |
| |
| \snippet qml/gridview/gridview.qml delayRemove |
| |
| If a \l remove transition has been specified, it will not be applied until |
| delayRemove is returned to \c false. |
| */ |
| |
| /*! |
| \qmlattachedsignal QtQuick::GridView::add() |
| This attached signal is emitted immediately after an item is added to the view. |
| */ |
| |
| /*! |
| \qmlattachedsignal QtQuick::GridView::remove() |
| This attached signal is emitted immediately before an item is removed from the view. |
| |
| If a \l remove transition has been specified, it is applied after |
| this signal is handled, providing that \l delayRemove is false. |
| */ |
| |
| |
| /*! |
| \qmlproperty model QtQuick::GridView::model |
| This property holds the model providing data for the grid. |
| |
| The model provides the set of data that is used to create the items |
| in the view. Models can be created directly in QML using \l ListModel, |
| \l XmlListModel, \l DelegateModel, or \l ObjectModel, or provided by C++ |
| model classes. If a C++ model class is used, it must be a subclass of |
| \l QAbstractItemModel or a simple list. |
| |
| \sa {qml-data-models}{Data Models} |
| */ |
| |
| /*! |
| \qmlproperty Component QtQuick::GridView::delegate |
| |
| The delegate provides a template defining each item instantiated by the view. |
| The index is exposed as an accessible \c index property. Properties of the |
| model are also available depending upon the type of \l {qml-data-models}{Data Model}. |
| |
| The number of objects and bindings in the delegate has a direct effect on the |
| flicking performance of the view. If at all possible, place functionality |
| that is not needed for the normal display of the delegate in a \l Loader which |
| can load additional components when needed. |
| |
| The item size of the GridView is determined by cellHeight and cellWidth. It will not resize the items |
| based on the size of the root item in the delegate. |
| |
| The default \l {QQuickItem::z}{stacking order} of delegate instances is \c 1. |
| |
| \note Delegates are instantiated as needed and may be destroyed at any time. |
| State should \e never be stored in a delegate. |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::GridView::currentIndex |
| \qmlproperty Item QtQuick::GridView::currentItem |
| |
| The \c currentIndex property holds the index of the current item, and |
| \c currentItem holds the current item. Setting the currentIndex to -1 |
| will clear the highlight and set currentItem to null. |
| |
| If highlightFollowsCurrentItem is \c true, setting either of these |
| properties will smoothly scroll the GridView so that the current |
| item becomes visible. |
| |
| Note that the position of the current item |
| may only be approximate until it becomes visible in the view. |
| */ |
| |
| |
| /*! |
| \qmlproperty Item QtQuick::GridView::highlightItem |
| |
| This holds the highlight item created from the \l highlight component. |
| |
| The highlightItem is managed by the view unless |
| \l highlightFollowsCurrentItem is set to false. |
| The default \l {QQuickItem::z}{stacking order} |
| of the highlight item is \c 0. |
| |
| \sa highlight, highlightFollowsCurrentItem |
| */ |
| |
| |
| /*! |
| \qmlproperty int QtQuick::GridView::count |
| This property holds the number of items in the view. |
| */ |
| |
| |
| /*! |
| \qmlproperty Component QtQuick::GridView::highlight |
| This property holds the component to use as the highlight. |
| |
| An instance of the highlight component is created for each view. |
| The geometry of the resulting component instance will be managed by the view |
| so as to stay with the current item, unless the highlightFollowsCurrentItem property is false. |
| The default \l {QQuickItem::z}{stacking order} of the highlight item is \c 0. |
| |
| \sa highlightItem, highlightFollowsCurrentItem |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::GridView::highlightFollowsCurrentItem |
| This property sets whether the highlight is managed by the view. |
| |
| If this property is true (the default value), the highlight is moved smoothly |
| to follow the current item. Otherwise, the |
| highlight is not moved by the view, and any movement must be implemented |
| by the highlight. |
| |
| Here is a highlight with its motion defined by a \l {SpringAnimation} item: |
| |
| \snippet qml/gridview/gridview.qml highlightFollowsCurrentItem |
| */ |
| |
| |
| /*! |
| \qmlproperty int QtQuick::GridView::highlightMoveDuration |
| This property holds the move animation duration of the highlight delegate. |
| |
| highlightFollowsCurrentItem must be true for this property |
| to have effect. |
| |
| The default value for the duration is 150ms. |
| |
| \sa highlightFollowsCurrentItem |
| */ |
| |
| /*! |
| \qmlproperty real QtQuick::GridView::preferredHighlightBegin |
| \qmlproperty real QtQuick::GridView::preferredHighlightEnd |
| \qmlproperty enumeration QtQuick::GridView::highlightRangeMode |
| |
| These properties define the preferred range of the highlight (for the current item) |
| within the view. The \c preferredHighlightBegin value must be less than the |
| \c preferredHighlightEnd value. |
| |
| These properties affect the position of the current item when the view is scrolled. |
| For example, if the currently selected item should stay in the middle of the |
| view when it is scrolled, set the \c preferredHighlightBegin and |
| \c preferredHighlightEnd values to the top and bottom coordinates of where the middle |
| item would be. If the \c currentItem is changed programmatically, the view will |
| automatically scroll so that the current item is in the middle of the view. |
| Furthermore, the behavior of the current item index will occur whether or not a |
| highlight exists. |
| |
| Valid values for \c highlightRangeMode are: |
| |
| \list |
| \li GridView.ApplyRange - the view attempts to maintain the highlight within the range. |
| However, the highlight can move outside of the range at the ends of the view or due |
| to mouse interaction. |
| \li GridView.StrictlyEnforceRange - the highlight never moves outside of the range. |
| The current item changes if a keyboard or mouse action would cause the highlight to move |
| outside of the range. |
| \li GridView.NoHighlightRange - this is the default value. |
| \endlist |
| */ |
| |
| |
| /*! |
| \qmlproperty enumeration QtQuick::GridView::layoutDirection |
| This property holds the layout direction of the grid. |
| |
| Possible values: |
| |
| \list |
| \li Qt.LeftToRight (default) - Items will be laid out starting in the top, left corner. The flow is |
| dependent on the \l GridView::flow property. |
| \li Qt.RightToLeft - Items will be laid out starting in the top, right corner. The flow is dependent |
| on the \l GridView::flow property. |
| \endlist |
| |
| \b Note: If GridView::flow is set to GridView.FlowLeftToRight, this is not to be confused if |
| GridView::layoutDirection is set to Qt.RightToLeft. The GridView.FlowLeftToRight flow value simply |
| indicates that the flow is horizontal. |
| |
| \sa GridView::effectiveLayoutDirection, GridView::verticalLayoutDirection |
| */ |
| |
| |
| /*! |
| \qmlproperty enumeration QtQuick::GridView::effectiveLayoutDirection |
| This property holds the effective layout direction of the grid. |
| |
| When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, |
| the visual layout direction of the grid will be mirrored. However, the |
| property \l {GridView::layoutDirection}{layoutDirection} will remain unchanged. |
| |
| \sa GridView::layoutDirection, {LayoutMirroring}{LayoutMirroring} |
| */ |
| |
| /*! |
| \qmlproperty enumeration QtQuick::GridView::verticalLayoutDirection |
| This property holds the vertical layout direction of the grid. |
| |
| Possible values: |
| |
| \list |
| \li GridView.TopToBottom (default) - Items are laid out from the top of the view down to the bottom of the view. |
| \li GridView.BottomToTop - Items are laid out from the bottom of the view up to the top of the view. |
| \endlist |
| |
| \sa GridView::layoutDirection |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::GridView::keyNavigationWraps |
| This property holds whether the grid wraps key navigation |
| |
| If this is true, key navigation that would move the current item selection |
| past one end of the view instead wraps around and moves the selection to |
| the other end of the view. |
| |
| By default, key navigation is not wrapped. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::GridView::keyNavigationEnabled |
| \since 5.7 |
| |
| This property holds whether the key navigation of the grid is enabled. |
| |
| If this is \c true, the user can navigate the view with a keyboard. |
| It is useful for applications that need to selectively enable or |
| disable mouse and keyboard interaction. |
| |
| By default, the value of this property is bound to |
| \l {Flickable::}{interactive} to ensure behavior compatibility for |
| existing applications. When explicitly set, it will cease to be bound to |
| the interactive property. |
| |
| \sa {Flickable::}{interactive} |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::GridView::cacheBuffer |
| This property determines whether delegates are retained outside the |
| visible area of the view. |
| |
| If this value is greater than zero, the view may keep as many delegates |
| instantiated as will fit within the buffer specified. For example, |
| if in a vertical view the delegate is 20 pixels high, there are 3 |
| columns and \c cacheBuffer is |
| set to 40, then up to 6 delegates above and 6 delegates below the visible |
| area may be created/retained. The buffered delegates are created asynchronously, |
| allowing creation to occur across multiple frames and reducing the |
| likelihood of skipping frames. In order to improve painting performance |
| delegates outside the visible area are not painted. |
| |
| The default value of this property is platform dependent, but will usually |
| be a value greater than zero. Negative values are ignored. |
| |
| Note that cacheBuffer is not a pixel buffer - it only maintains additional |
| instantiated delegates. |
| |
| \note Setting this property is not a replacement for creating efficient delegates. |
| It can improve the smoothness of scrolling behavior at the expense of additional |
| memory usage. The fewer objects and bindings in a delegate, the faster a |
| view can be scrolled. It is important to realize that setting a cacheBuffer |
| will only postpone issues caused by slow-loading delegates, it is not a |
| solution for this scenario. |
| |
| The cacheBuffer operates outside of any display margins specified by |
| displayMarginBeginning or displayMarginEnd. |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::GridView::displayMarginBeginning |
| \qmlproperty int QtQuick::GridView::displayMarginEnd |
| \since QtQuick 2.3 |
| |
| This property allows delegates to be displayed outside of the view geometry. |
| |
| If this value is non-zero, the view will create extra delegates before the |
| start of the view, or after the end. The view will create as many delegates |
| as it can fit into the pixel size specified. |
| |
| For example, if in a vertical view the delegate is 20 pixels high, |
| there are 3 columns, and |
| \c displayMarginBeginning and \c displayMarginEnd are both set to 40, |
| then 6 delegates above and 6 delegates below will be created and shown. |
| |
| The default value is 0. |
| |
| This property is meant for allowing certain UI configurations, |
| and not as a performance optimization. If you wish to create delegates |
| outside of the view geometry for performance reasons, you probably |
| want to use the cacheBuffer property instead. |
| */ |
| |
| void QQuickGridView::setHighlightMoveDuration(int duration) |
| { |
| Q_D(QQuickGridView); |
| if (d->highlightMoveDuration != duration) { |
| if (d->highlightYAnimator) { |
| d->highlightXAnimator->userDuration = duration; |
| d->highlightYAnimator->userDuration = duration; |
| } |
| QQuickItemView::setHighlightMoveDuration(duration); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::GridView::flow |
| This property holds the flow of the grid. |
| |
| Possible values: |
| |
| \list |
| \li GridView.FlowLeftToRight (default) - Items are laid out from left to right, and the view scrolls vertically |
| \li GridView.FlowTopToBottom - Items are laid out from top to bottom, and the view scrolls horizontally |
| \endlist |
| */ |
| QQuickGridView::Flow QQuickGridView::flow() const |
| { |
| Q_D(const QQuickGridView); |
| return d->flow; |
| } |
| |
| void QQuickGridView::setFlow(Flow flow) |
| { |
| Q_D(QQuickGridView); |
| if (d->flow != flow) { |
| d->flow = flow; |
| if (d->flow == FlowLeftToRight) { |
| setContentWidth(-1); |
| setFlickableDirection(VerticalFlick); |
| } else { |
| setContentHeight(-1); |
| setFlickableDirection(HorizontalFlick); |
| } |
| setContentX(0); |
| setContentY(0); |
| d->regenerate(true); |
| emit flowChanged(); |
| } |
| } |
| |
| |
| /*! |
| \qmlproperty real QtQuick::GridView::cellWidth |
| \qmlproperty real QtQuick::GridView::cellHeight |
| |
| These properties holds the width and height of each cell in the grid. |
| |
| The default cell size is 100x100. |
| */ |
| qreal QQuickGridView::cellWidth() const |
| { |
| Q_D(const QQuickGridView); |
| return d->cellWidth; |
| } |
| |
| void QQuickGridView::setCellWidth(qreal cellWidth) |
| { |
| Q_D(QQuickGridView); |
| if (cellWidth != d->cellWidth && cellWidth > 0) { |
| d->cellWidth = qMax(qreal(1), cellWidth); |
| d->updateViewport(); |
| emit cellWidthChanged(); |
| d->forceLayoutPolish(); |
| } |
| } |
| |
| qreal QQuickGridView::cellHeight() const |
| { |
| Q_D(const QQuickGridView); |
| return d->cellHeight; |
| } |
| |
| void QQuickGridView::setCellHeight(qreal cellHeight) |
| { |
| Q_D(QQuickGridView); |
| if (cellHeight != d->cellHeight && cellHeight > 0) { |
| d->cellHeight = qMax(qreal(1), cellHeight); |
| d->updateViewport(); |
| emit cellHeightChanged(); |
| d->forceLayoutPolish(); |
| } |
| } |
| /*! |
| \qmlproperty enumeration QtQuick::GridView::snapMode |
| |
| This property determines how the view scrolling will settle following a drag or flick. |
| The possible values are: |
| |
| \list |
| \li GridView.NoSnap (default) - the view stops anywhere within the visible area. |
| \li GridView.SnapToRow - the view settles with a row (or column for \c GridView.FlowTopToBottom flow) |
| aligned with the start of the view. |
| \li GridView.SnapOneRow - the view will settle no more than one row (or column for \c GridView.FlowTopToBottom flow) |
| away from the first visible row at the time the mouse button is released. |
| This mode is particularly useful for moving one page at a time. |
| \endlist |
| |
| */ |
| QQuickGridView::SnapMode QQuickGridView::snapMode() const |
| { |
| Q_D(const QQuickGridView); |
| return d->snapMode; |
| } |
| |
| void QQuickGridView::setSnapMode(SnapMode mode) |
| { |
| Q_D(QQuickGridView); |
| if (d->snapMode != mode) { |
| d->snapMode = mode; |
| emit snapModeChanged(); |
| } |
| } |
| |
| |
| /*! |
| \qmlproperty Component QtQuick::GridView::footer |
| This property holds the component to use as the footer. |
| |
| An instance of the footer component is created for each view. The |
| footer is positioned at the end of the view, after any items. The |
| default \l {QQuickItem::z}{stacking order} of the footer is \c 1. |
| |
| \sa header, footerItem |
| */ |
| /*! |
| \qmlproperty Component QtQuick::GridView::header |
| This property holds the component to use as the header. |
| |
| An instance of the header component is created for each view. The |
| header is positioned at the beginning of the view, before any items. |
| The default \l {QQuickItem::z}{stacking order} of the header is \c 1. |
| |
| \sa footer, headerItem |
| */ |
| |
| /*! |
| \qmlproperty Item QtQuick::GridView::headerItem |
| This holds the header item created from the \l header component. |
| |
| An instance of the header component is created for each view. The |
| header is positioned at the beginning of the view, before any items. |
| The default \l {QQuickItem::z}{stacking order} of the header is \c 1. |
| |
| \sa header, footerItem |
| */ |
| |
| /*! |
| \qmlproperty Item QtQuick::GridView::footerItem |
| This holds the footer item created from the \l footer component. |
| |
| An instance of the footer component is created for each view. The |
| footer is positioned at the end of the view, after any items. The |
| default \l {QQuickItem::z}{stacking order} of the footer is \c 1. |
| |
| \sa footer, headerItem |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::populate |
| |
| This property holds the transition to apply to the items that are initially created |
| for a view. |
| |
| It is applied to all items that are created when: |
| |
| \list |
| \li The view is first created |
| \li The view's \l model changes in such a way that the visible delegates are completely replaced |
| \li The view's \l model is \l {QAbstractItemModel::reset()}{reset}, if the model is a QAbstractItemModel subclass |
| \endlist |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| populate: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| When the view is initialized, the view will create all the necessary items for the view, |
| then animate them to their correct positions within the view over one second. |
| |
| However when scrolling the view later, the populate transition does not |
| run, even though delegates are being instantiated as they become visible. |
| When the model changes in a way that new delegates become visible, the |
| \l add transition is the one that runs. So you should not depend on the |
| \c populate transition to initialize properties in the delegate, because it |
| does not apply to every delegate. If your animation sets the \c to value of |
| a property, the property should initially have the \c to value, and the |
| animation should set the \c from value in case it is animated: |
| |
| \code |
| GridView { |
| ... |
| delegate: Rectangle { |
| opacity: 1 // not necessary because it's the default; but don't set 0 |
| ... |
| } |
| populate: Transition { |
| NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa add, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::add |
| |
| This property holds the transition to apply to items that are added to the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| add: Transition { |
| NumberAnimation { properties: "x,y"; from: 100; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever an item is added to the above view, the item will be animated from the position (100,100) |
| to its final x,y position within the view, over one second. The transition only applies to |
| the new items that are added to the view; it does not apply to the items below that are |
| displaced by the addition of the new items. To animate the displaced items, set the \l displaced |
| or \l addDisplaced properties. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \note This transition is not applied to the items that are created when the view is initially |
| populated, or when the view's \l model changes. (In those cases, the \l populate transition is |
| applied instead.) Additionally, this transition should \e not animate the height of the new item; |
| doing so will cause any items beneath the new item to be laid out at the wrong position. Instead, |
| the height can be animated within the \l {add}{onAdd} handler in the delegate. |
| |
| \sa addDisplaced, populate, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::addDisplaced |
| |
| This property holds the transition to apply to items within the view that are displaced by |
| the addition of other items to the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| addDisplaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever an item is added to the above view, all items beneath the new item are displaced, causing |
| them to move down (or sideways, if horizontally orientated) within the view. As this |
| displacement occurs, the items' movement to their new x,y positions within the view will be |
| animated by a NumberAnimation over one second, as specified. This transition is not applied to |
| the new item that has been added to the view; to animate the added items, set the \l add |
| property. |
| |
| If an item is displaced by multiple types of operations at the same time, it is not defined as to |
| whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally, |
| if it is not necessary to specify different transitions depending on whether an item is displaced |
| by an add, move or remove operation, consider setting the \l displaced property instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \note This transition is not applied to the items that are created when the view is initially |
| populated, or when the view's \l model changes. In those cases, the \l populate transition is |
| applied instead. |
| |
| \sa displaced, add, populate, ViewTransition |
| */ |
| /*! |
| \qmlproperty Transition QtQuick::GridView::move |
| |
| This property holds the transition to apply to items in the view that are being moved due |
| to a move operation in the view's \l model. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| move: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever the \l model performs a move operation to move a particular set of indexes, the |
| respective items in the view will be animated to their new positions in the view over one |
| second. The transition only applies to the items that are the subject of the move operation |
| in the model; it does not apply to items below them that are displaced by the move operation. |
| To animate the displaced items, set the \l displaced or \l moveDisplaced properties. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa moveDisplaced, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::moveDisplaced |
| |
| This property holds the transition to apply to items that are displaced by a move operation in |
| the view's \l model. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| moveDisplaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever the \l model performs a move operation to move a particular set of indexes, the items |
| between the source and destination indexes of the move operation are displaced, causing them |
| to move upwards or downwards (or sideways, if horizontally orientated) within the view. As this |
| displacement occurs, the items' movement to their new x,y positions within the view will be |
| animated by a NumberAnimation over one second, as specified. This transition is not applied to |
| the items that are the actual subjects of the move operation; to animate the moved items, set |
| the \l move property. |
| |
| If an item is displaced by multiple types of operations at the same time, it is not defined as to |
| whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally, |
| if it is not necessary to specify different transitions depending on whether an item is displaced |
| by an add, move or remove operation, consider setting the \l displaced property instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa displaced, move, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::remove |
| |
| This property holds the transition to apply to items that are removed from the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| remove: Transition { |
| ParallelAnimation { |
| NumberAnimation { property: "opacity"; to: 0; duration: 1000 } |
| NumberAnimation { properties: "x,y"; to: 100; duration: 1000 } |
| } |
| } |
| } |
| \endcode |
| |
| Whenever an item is removed from the above view, the item will be animated to the position (100,100) |
| over one second, and in parallel will also change its opacity to 0. The transition |
| only applies to the items that are removed from the view; it does not apply to the items below |
| them that are displaced by the removal of the items. To animate the displaced items, set the |
| \l displaced or \l removeDisplaced properties. |
| |
| Note that by the time the transition is applied, the item has already been removed from the |
| model; any references to the model data for the removed index will not be valid. |
| |
| Additionally, if the \l delayRemove attached property has been set for a delegate item, the |
| remove transition will not be applied until \l delayRemove becomes false again. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa removeDisplaced, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::removeDisplaced |
| |
| This property holds the transition to apply to items in the view that are displaced by the |
| removal of other items in the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| GridView { |
| ... |
| removeDisplaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever an item is removed from the above view, all items beneath it are displaced, causing |
| them to move upwards (or sideways, if horizontally orientated) within the view. As this |
| displacement occurs, the items' movement to their new x,y positions within the view will be |
| animated by a NumberAnimation over one second, as specified. This transition is not applied to |
| the item that has actually been removed from the view; to animate the removed items, set the |
| \l remove property. |
| |
| If an item is displaced by multiple types of operations at the same time, it is not defined as to |
| whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally, |
| if it is not necessary to specify different transitions depending on whether an item is displaced |
| by an add, move or remove operation, consider setting the \l displaced property instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa displaced, remove, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::GridView::displaced |
| This property holds the generic transition to apply to items that have been displaced by |
| any model operation that affects the view. |
| |
| This is a convenience for specifying a generic transition for items that are displaced |
| by add, move or remove operations, without having to specify the individual addDisplaced, |
| moveDisplaced and removeDisplaced properties. For example, here is a view that specifies |
| a displaced transition: |
| |
| \code |
| GridView { |
| ... |
| displaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| When any item is added, moved or removed within the above view, the items below it are |
| displaced, causing them to move down (or sideways, if horizontally orientated) within the |
| view. As this displacement occurs, the items' movement to their new x,y positions within |
| the view will be animated by a NumberAnimation over one second, as specified. |
| |
| If a view specifies this generic displaced transition as well as a specific addDisplaced, |
| moveDisplaced or removeDisplaced transition, the more specific transition will be used |
| instead of the generic displaced transition when the relevant operation occurs, providing that |
| the more specific transition has not been disabled (by setting \l {Transition::enabled}{enabled} |
| to false). If it has indeed been disabled, the generic displaced transition is applied instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa addDisplaced, moveDisplaced, removeDisplaced, ViewTransition |
| */ |
| |
| void QQuickGridView::viewportMoved(Qt::Orientations orient) |
| { |
| Q_D(QQuickGridView); |
| QQuickItemView::viewportMoved(orient); |
| if (!d->itemCount) |
| return; |
| if (d->inViewportMoved) |
| return; |
| d->inViewportMoved = true; |
| |
| if (yflick()) { |
| if (d->isContentFlowReversed()) |
| d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore; |
| else |
| d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter; |
| } else { |
| if (d->isContentFlowReversed()) |
| d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore; |
| else |
| d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter; |
| } |
| |
| d->refillOrLayout(); |
| |
| // Set visibility of items to eliminate cost of items outside the visible area. |
| qreal from = d->isContentFlowReversed() ? -d->position()-d->displayMarginBeginning-d->size() : d->position()-d->displayMarginBeginning; |
| qreal to = d->isContentFlowReversed() ? -d->position()+d->displayMarginEnd : d->position()+d->size()+d->displayMarginEnd; |
| for (FxViewItem *item : qAsConst(d->visibleItems)) { |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(item); |
| QQuickItemPrivate::get(gridItem->item)->setCulled(gridItem->rowPos() + d->rowSize() < from || gridItem->rowPos() > to); |
| } |
| if (d->currentItem) { |
| FxGridItemSG *item = static_cast<FxGridItemSG*>(d->currentItem); |
| QQuickItemPrivate::get(item->item)->setCulled(item->rowPos() + d->rowSize() < from || item->rowPos() > to); |
| } |
| |
| if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving) |
| d->moveReason = QQuickGridViewPrivate::Mouse; |
| if (d->moveReason != QQuickGridViewPrivate::SetIndex) { |
| if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { |
| // reposition highlight |
| qreal pos = d->highlight->position(); |
| qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position(); |
| if (pos > viewPos + d->highlightRangeEnd - d->highlight->size()) |
| pos = viewPos + d->highlightRangeEnd - d->highlight->size(); |
| if (pos < viewPos + d->highlightRangeStart) |
| pos = viewPos + d->highlightRangeStart; |
| |
| if (pos != d->highlight->position()) { |
| d->highlightXAnimator->stop(); |
| d->highlightYAnimator->stop(); |
| static_cast<FxGridItemSG*>(d->highlight)->setPosition(static_cast<FxGridItemSG*>(d->highlight)->colPos(), pos); |
| } else { |
| d->updateHighlight(); |
| } |
| |
| // update current index |
| int idx = d->snapIndex(); |
| if (idx >= 0 && idx != d->currentIndex) { |
| d->updateCurrent(idx); |
| if (d->currentItem && static_cast<FxGridItemSG*>(d->currentItem)->colPos() != static_cast<FxGridItemSG*>(d->highlight)->colPos() && d->autoHighlight) { |
| if (d->flow == FlowLeftToRight) |
| d->highlightXAnimator->to = d->currentItem->itemX(); |
| else |
| d->highlightYAnimator->to = d->currentItem->itemY(); |
| } |
| } |
| } |
| } |
| |
| d->inViewportMoved = false; |
| } |
| |
| void QQuickGridView::keyPressEvent(QKeyEvent *event) |
| { |
| Q_D(QQuickGridView); |
| if (d->model && d->model->count() && ((d->interactive && !d->explicitKeyNavigationEnabled) |
| || (d->explicitKeyNavigationEnabled && d->keyNavigationEnabled))) { |
| d->moveReason = QQuickGridViewPrivate::SetIndex; |
| int oldCurrent = currentIndex(); |
| switch (event->key()) { |
| case Qt::Key_Up: |
| moveCurrentIndexUp(); |
| break; |
| case Qt::Key_Down: |
| moveCurrentIndexDown(); |
| break; |
| case Qt::Key_Left: |
| moveCurrentIndexLeft(); |
| break; |
| case Qt::Key_Right: |
| moveCurrentIndexRight(); |
| break; |
| default: |
| break; |
| } |
| if (oldCurrent != currentIndex() || d->wrap) { |
| event->accept(); |
| return; |
| } |
| } |
| event->ignore(); |
| QQuickItemView::keyPressEvent(event); |
| } |
| |
| void QQuickGridView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| { |
| Q_D(QQuickGridView); |
| d->resetColumns(); |
| |
| if (newGeometry.width() != oldGeometry.width() |
| && newGeometry.height() != oldGeometry.height()) { |
| d->setPosition(d->position()); |
| } else if (newGeometry.width() != oldGeometry.width()) { |
| QQuickFlickable::setContentX(d->contentXForPosition(d->position())); |
| } else if (newGeometry.height() != oldGeometry.height()) { |
| QQuickFlickable::setContentY(d->contentYForPosition(d->position())); |
| } |
| |
| QQuickItemView::geometryChanged(newGeometry, oldGeometry); |
| } |
| |
| void QQuickGridView::initItem(int index, QObject *obj) |
| { |
| QQuickItemView::initItem(index, obj); |
| |
| // setting the view from the FxViewItem wrapper is too late if the delegate |
| // needs access to the view in Component.onCompleted |
| QQuickItem *item = qmlobject_cast<QQuickItem*>(obj); |
| if (item) { |
| QQuickGridViewAttached *attached = static_cast<QQuickGridViewAttached *>( |
| qmlAttachedPropertiesObject<QQuickGridView>(item)); |
| if (attached) |
| attached->setView(this); |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQuick::GridView::moveCurrentIndexUp() |
| |
| Move the currentIndex up one item in the view. |
| The current index will wrap if keyNavigationWraps is true and it |
| is currently at the end. This method has no effect if the \l count is zero. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| |
| void QQuickGridView::moveCurrentIndexUp() |
| { |
| Q_D(QQuickGridView); |
| const int count = d->model ? d->model->count() : 0; |
| if (!count) |
| return; |
| if (d->verticalLayoutDirection == QQuickItemView::TopToBottom) { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() >= d->columns || d->wrap) { |
| int index = currentIndex() - d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } else { |
| if (currentIndex() > 0 || d->wrap) { |
| int index = currentIndex() - 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } |
| } else { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() < count - d->columns || d->wrap) { |
| int index = currentIndex()+d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } else { |
| if (currentIndex() < count - 1 || d->wrap) { |
| int index = currentIndex() + 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQuick::GridView::moveCurrentIndexDown() |
| |
| Move the currentIndex down one item in the view. |
| The current index will wrap if keyNavigationWraps is true and it |
| is currently at the end. This method has no effect if the \l count is zero. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickGridView::moveCurrentIndexDown() |
| { |
| Q_D(QQuickGridView); |
| const int count = d->model ? d->model->count() : 0; |
| if (!count) |
| return; |
| |
| if (d->verticalLayoutDirection == QQuickItemView::TopToBottom) { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() < count - d->columns || d->wrap) { |
| int index = currentIndex()+d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } else { |
| if (currentIndex() < count - 1 || d->wrap) { |
| int index = currentIndex() + 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } |
| } else { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() >= d->columns || d->wrap) { |
| int index = currentIndex() - d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } else { |
| if (currentIndex() > 0 || d->wrap) { |
| int index = currentIndex() - 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQuick::GridView::moveCurrentIndexLeft() |
| |
| Move the currentIndex left one item in the view. |
| The current index will wrap if keyNavigationWraps is true and it |
| is currently at the end. This method has no effect if the \l count is zero. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickGridView::moveCurrentIndexLeft() |
| { |
| Q_D(QQuickGridView); |
| const int count = d->model ? d->model->count() : 0; |
| if (!count) |
| return; |
| if (effectiveLayoutDirection() == Qt::LeftToRight) { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() > 0 || d->wrap) { |
| int index = currentIndex() - 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } else { |
| if (currentIndex() >= d->columns || d->wrap) { |
| int index = currentIndex() - d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } |
| } else { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() < count - 1 || d->wrap) { |
| int index = currentIndex() + 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } else { |
| if (currentIndex() < count - d->columns || d->wrap) { |
| int index = currentIndex() + d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } |
| } |
| } |
| |
| |
| /*! |
| \qmlmethod QtQuick::GridView::moveCurrentIndexRight() |
| |
| Move the currentIndex right one item in the view. |
| The current index will wrap if keyNavigationWraps is true and it |
| is currently at the end. This method has no effect if the \l count is zero. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickGridView::moveCurrentIndexRight() |
| { |
| Q_D(QQuickGridView); |
| const int count = d->model ? d->model->count() : 0; |
| if (!count) |
| return; |
| if (effectiveLayoutDirection() == Qt::LeftToRight) { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() < count - 1 || d->wrap) { |
| int index = currentIndex() + 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } else { |
| if (currentIndex() < count - d->columns || d->wrap) { |
| int index = currentIndex()+d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } |
| } else { |
| if (d->flow == QQuickGridView::FlowLeftToRight) { |
| if (currentIndex() > 0 || d->wrap) { |
| int index = currentIndex() - 1; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } else { |
| if (currentIndex() >= d->columns || d->wrap) { |
| int index = currentIndex() - d->columns; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } |
| } |
| } |
| |
| bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) |
| { |
| Q_Q(QQuickGridView); |
| |
| int modelIndex = change.index; |
| int count = change.count; |
| |
| int index = visibleItems.count() ? mapFromModel(modelIndex) : 0; |
| |
| if (index < 0) { |
| int i = visibleItems.count() - 1; |
| while (i > 0 && visibleItems.at(i)->index == -1) |
| --i; |
| if (visibleItems.at(i)->index + 1 == modelIndex) { |
| // Special case of appending an item to the model. |
| index = visibleItems.count(); |
| } else { |
| if (modelIndex <= visibleIndex) { |
| // Insert before visible items |
| visibleIndex += count; |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| if (item->index != -1 && item->index >= modelIndex) |
| item->index += count; |
| } |
| } |
| return true; |
| } |
| } |
| |
| qreal tempPos = isContentFlowReversed() ? -position()-size()+q->width()+1 : position(); |
| qreal colPos = 0; |
| qreal rowPos = 0; |
| int colNum = 0; |
| if (visibleItems.count()) { |
| if (index < visibleItems.count()) { |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(index)); |
| colPos = gridItem->colPos(); |
| rowPos = gridItem->rowPos(); |
| colNum = qFloor((colPos+colSize()/2) / colSize()); |
| } else { |
| // appending items to visible list |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(index-1)); |
| rowPos = gridItem->rowPos(); |
| colNum = qFloor((gridItem->colPos()+colSize()/2) / colSize()); |
| if (++colNum >= columns) { |
| colNum = 0; |
| rowPos += rowSize(); |
| } |
| colPos = colNum * colSize(); |
| } |
| } |
| |
| // Update the indexes of the following visible items. |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| if (item->index != -1 && item->index >= modelIndex) { |
| item->index += count; |
| if (change.isMove()) |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, false); |
| else |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, false); |
| } |
| } |
| |
| int prevVisibleCount = visibleItems.count(); |
| if (insertResult->visiblePos.isValid() && rowPos < insertResult->visiblePos) { |
| // Insert items before the visible item. |
| int insertionIdx = index; |
| int i = count - 1; |
| int from = tempPos - buffer - displayMarginBeginning; |
| |
| if (rowPos > from && insertionIdx < visibleIndex) { |
| // items won't be visible, just note the size for repositioning |
| insertResult->countChangeBeforeVisible += count; |
| insertResult->sizeChangesBeforeVisiblePos += ((count + columns - 1) / columns) * rowSize(); |
| } else { |
| while (i >= 0) { |
| // item is before first visible e.g. in cache buffer |
| FxViewItem *item = nullptr; |
| if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) |
| item->index = modelIndex + i; |
| if (!item) |
| item = createItem(modelIndex + i, QQmlIncubator::Synchronous); |
| if (!item) |
| return false; |
| |
| QQuickItemPrivate::get(item->item)->setCulled(false); |
| visibleItems.insert(insertionIdx, item); |
| if (insertionIdx == 0) |
| insertResult->changedFirstItem = true; |
| if (!change.isMove()) { |
| addedItems->append(item); |
| if (transitioner) |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true); |
| else |
| item->moveTo(QPointF(colPos, rowPos), true); |
| } |
| insertResult->sizeChangesBeforeVisiblePos += rowSize(); |
| |
| if (--colNum < 0 ) { |
| colNum = columns - 1; |
| rowPos -= rowSize(); |
| } |
| colPos = colNum * colSize(); |
| index++; |
| i--; |
| } |
| } |
| |
| // There may be gaps in the index sequence of visibleItems because |
| // of the index shift/update done before the insertion just above. |
| // Find if there is any... |
| int firstOkIdx = -1; |
| for (int i = 0; i <= insertionIdx && i < visibleItems.count() - 1; i++) { |
| if (visibleItems.at(i)->index + 1 != visibleItems.at(i + 1)->index) { |
| firstOkIdx = i + 1; |
| break; |
| } |
| } |
| // ... and remove all the items before that one |
| for (int i = 0; i < firstOkIdx; i++) { |
| FxViewItem *nvItem = visibleItems.takeFirst(); |
| addedItems->removeOne(nvItem); |
| removeItem(nvItem); |
| } |
| |
| } else { |
| int i = 0; |
| int to = buffer+displayMarginEnd+tempPos+size()-1; |
| while (i < count && rowPos <= to + rowSize()*(columns - colNum)/qreal(columns+1)) { |
| FxViewItem *item = nullptr; |
| if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) |
| item->index = modelIndex + i; |
| bool newItem = !item; |
| if (!item) |
| item = createItem(modelIndex + i, QQmlIncubator::Synchronous); |
| if (!item) |
| return false; |
| |
| QQuickItemPrivate::get(item->item)->setCulled(false); |
| visibleItems.insert(index, item); |
| if (index == 0) |
| insertResult->changedFirstItem = true; |
| if (change.isMove()) { |
| // we know this is a move target, since move displaced items that are |
| // shuffled into view due to a move would be added in refill() |
| if (newItem && transitioner && transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, true)) |
| movingIntoView->append(MovedItem(item, change.moveKey(item->index))); |
| } else { |
| addedItems->append(item); |
| if (transitioner) |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true); |
| else |
| item->moveTo(QPointF(colPos, rowPos), true); |
| } |
| insertResult->sizeChangesAfterVisiblePos += rowSize(); |
| |
| if (++colNum >= columns) { |
| colNum = 0; |
| rowPos += rowSize(); |
| } |
| colPos = colNum * colSize(); |
| ++index; |
| ++i; |
| } |
| } |
| |
| updateVisibleIndex(); |
| |
| return visibleItems.count() > prevVisibleCount; |
| } |
| |
| void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) |
| { |
| if (!transitioner) |
| return; |
| |
| int markerItemIndex = -1; |
| for (int i=0; i<visibleItems.count(); i++) { |
| if (visibleItems.at(i)->index == afterModelIndex) { |
| markerItemIndex = i; |
| break; |
| } |
| } |
| if (markerItemIndex < 0) |
| return; |
| |
| const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size(); |
| int countItemsRemoved = -(removalResult.sizeChangesAfterVisiblePos / rowSize()); |
| |
| // account for whether first item has changed if < 1 row was removed before visible |
| int changeBeforeVisible = insertionResult.countChangeBeforeVisible - removalResult.countChangeBeforeVisible; |
| if (changeBeforeVisible != 0) |
| countItemsRemoved += (changeBeforeVisible % columns) - (columns - 1); |
| |
| countItemsRemoved -= removalResult.countChangeAfterVisibleItems; |
| |
| for (int i=markerItemIndex+1; i<visibleItems.count(); i++) { |
| FxGridItemSG *gridItem = static_cast<FxGridItemSG *>(visibleItems.at(i)); |
| if (gridItem->position() >= viewEndPos) |
| break; |
| if (!gridItem->transitionScheduledOrRunning()) { |
| qreal origRowPos = gridItem->colPos(); |
| qreal origColPos = gridItem->rowPos(); |
| int indexDiff = gridItem->index - countItemsRemoved; |
| gridItem->setPosition((indexDiff % columns) * colSize(), (indexDiff / columns) * rowSize()); |
| gridItem->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false); |
| gridItem->setPosition(origRowPos, origColPos); |
| } |
| } |
| } |
| |
| bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const |
| { |
| // If we add or remove items before visible items, a layout may be |
| // required to ensure item 0 is in the first column. |
| return modelIndex < visibleIndex; |
| } |
| |
| /*! |
| \qmlmethod QtQuick::GridView::positionViewAtIndex(int index, PositionMode mode) |
| |
| Positions the view such that the \a index is at the position specified by |
| \a mode: |
| |
| \list |
| \li GridView.Beginning - position item at the top (or left for \c GridView.FlowTopToBottom flow) of the view. |
| \li GridView.Center - position item in the center of the view. |
| \li GridView.End - position item at bottom (or right for horizontal orientation) of the view. |
| \li GridView.Visible - if any part of the item is visible then take no action, otherwise |
| bring the item into view. |
| \li GridView.Contain - ensure the entire item is visible. If the item is larger than |
| the view the item is positioned at the top (or left for \c GridView.FlowTopToBottom flow) of the view. |
| \li GridView.SnapPosition - position the item at \l preferredHighlightBegin. This mode |
| is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled |
| via \l snapMode. |
| \endlist |
| |
| If positioning the view at the index would cause empty space to be displayed at |
| the beginning or end of the view, the view will be positioned at the boundary. |
| |
| It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view |
| at a particular index. This is unreliable since removing items from the start |
| of the view does not cause all other items to be repositioned. |
| The correct way to bring an item into view is with \c positionViewAtIndex. |
| |
| \b Note: methods should only be called after the Component has completed. To position |
| the view at startup, this method should be called by Component.onCompleted. For |
| example, to position the view at the end: |
| |
| \code |
| Component.onCompleted: positionViewAtIndex(count - 1, GridView.Beginning) |
| \endcode |
| */ |
| |
| /*! |
| \qmlmethod QtQuick::GridView::positionViewAtBeginning() |
| \qmlmethod QtQuick::GridView::positionViewAtEnd() |
| |
| Positions the view at the beginning or end, taking into account any header or footer. |
| |
| It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view |
| at a particular index. This is unreliable since removing items from the start |
| of the list does not cause all other items to be repositioned, and because |
| the actual start of the view can vary based on the size of the delegates. |
| |
| \b Note: methods should only be called after the Component has completed. To position |
| the view at startup, this method should be called by Component.onCompleted. For |
| example, to position the view at the end on startup: |
| |
| \code |
| Component.onCompleted: positionViewAtEnd() |
| \endcode |
| */ |
| |
| /*! |
| \qmlmethod int QtQuick::GridView::indexAt(real x, real y) |
| |
| Returns the index of the visible item containing the point \a x, \a y in content |
| coordinates. If there is no item at the point specified, or the item is |
| not visible -1 is returned. |
| |
| If the item is outside the visible area, -1 is returned, regardless of |
| whether an item will exist at that point when scrolled into view. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| /*! |
| \qmlmethod Item QtQuick::GridView::itemAt(real x, real y) |
| |
| Returns the visible item containing the point \a x, \a y in content |
| coordinates. If there is no item at the point specified, or the item is |
| not visible null is returned. |
| |
| If the item is outside the visible area, null is returned, regardless of |
| whether an item will exist at that point when scrolled into view. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| /*! |
| \qmlmethod Item QtQuick::GridView::itemAtIndex(int index) |
| |
| Returns the item for \a index. If there is no item for that index, for example |
| because it has not been created yet, or because it has been panned out of |
| the visible area and removed from the cache, null is returned. |
| |
| \b Note: this method should only be called after the Component has completed. |
| The returned value should also not be stored since it can turn to null |
| as soon as control goes out of the calling scope, if the view releases that item. |
| |
| \since 5.13 |
| */ |
| |
| /*! |
| \qmlmethod QtQuick::GridView::forceLayout() |
| |
| Responding to changes in the model is usually batched to happen only once |
| per frame. This means that inside script blocks it is possible for the |
| underlying model to have changed, but the GridView has not caught up yet. |
| |
| This method forces the GridView to immediately respond to any outstanding |
| changes in the model. |
| |
| \since 5.1 |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| QQuickGridViewAttached *QQuickGridView::qmlAttachedProperties(QObject *obj) |
| { |
| return new QQuickGridViewAttached(obj); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickgridview_p.cpp" |