2758 lines
106 KiB
C++
2758 lines
106 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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 initializeComponentItem(QQuickItem *item) const 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();
|
|
}
|
|
}
|
|
|
|
qreal rowPos = ((modelIndex / columns) * rowSize());
|
|
|
|
if (flow == QQuickGridView::FlowLeftToRight && verticalLayoutDirection == QQuickItemView::TopToBottom) {
|
|
// Add the effective startpos of row 0. Start by subtracting minExtent, which will contain the
|
|
// height of the rows outside the beginning of the content item. (Rows can end up outside if
|
|
// e.g flicking the viewport a long way down, changing cellSize, and then flick back).
|
|
// NOTE: It's not clearly understood why the flow == QQuickGridView::FlowLeftToRight guard is
|
|
// needed, since the flow shouldn't normally affect the y postition of an index. But without
|
|
// it, several auto tests start failing, so we keep it until this part is better understood.
|
|
rowPos -= minExtent;
|
|
// minExtent will also contain the size of the topMargin (vData.startMargin), the header, and
|
|
// the highlightRangeStart. Those should be added before the start of row 0. So we need to subtract
|
|
// them from the rowPos. But only the largest of topMargin and highlightRangeStart will need
|
|
// to be taken into account, since having a topMargin will also ensure that currentItem ends
|
|
// up within the requested highlight range when view is positioned at the beginning.
|
|
rowPos += qMax(vData.startMargin, highlightRangeStart) + headerSize();
|
|
}
|
|
|
|
return rowPos;
|
|
}
|
|
|
|
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::initializeComponentItem(QQuickItem *item) const
|
|
{
|
|
QQuickGridViewAttached *attached = static_cast<QQuickGridViewAttached *>(
|
|
qmlAttachedPropertiesObject<QQuickGridView>(item));
|
|
if (attached)
|
|
attached->setView(const_cast<QQuickGridView*>(q_func()));
|
|
}
|
|
|
|
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 DelegateModel, \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 model.
|
|
*/
|
|
|
|
/*!
|
|
\qmlproperty bool QtQuick::GridView::reuseItems
|
|
|
|
This property enables you to reuse items that are instantiated
|
|
from the \l delegate. If set to \c false, any currently
|
|
pooled items are destroyed.
|
|
|
|
This property is \c false by default.
|
|
|
|
\since 5.15
|
|
|
|
\sa {Reusing items}, pooled(), reused()
|
|
*/
|
|
|
|
/*!
|
|
\qmlattachedsignal QtQuick::GridView::pooled()
|
|
|
|
This signal is emitted after an item has been added to the reuse
|
|
pool. You can use it to pause ongoing timers or animations inside
|
|
the item, or free up resources that cannot be reused.
|
|
|
|
This signal is emitted only if the \l reuseItems property is \c true.
|
|
|
|
\sa {Reusing items}, reuseItems, reused()
|
|
*/
|
|
|
|
/*!
|
|
\qmlattachedsignal QtQuick::GridView::reused()
|
|
|
|
This signal is emitted after an item has been reused. At this point, the
|
|
item has been taken out of the pool and placed inside the content view,
|
|
and the model properties such as \c index and \c row have been updated.
|
|
|
|
Other properties that are not provided by the model does not change when an
|
|
item is reused. You should avoid storing any state inside a delegate, but if
|
|
you do, manually reset that state on receiving this signal.
|
|
|
|
This signal is emitted when the item is reused, and not the first time the
|
|
item is created.
|
|
|
|
This signal is emitted only if the \l reuseItems property is \c true.
|
|
|
|
\sa {Reusing items}, reuseItems, pooled()
|
|
*/
|
|
|
|
/*!
|
|
\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:
|
|
|
|
\value 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.
|
|
\value 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.
|
|
\value GridView.NoHighlightRange the default value
|
|
*/
|
|
|
|
|
|
/*!
|
|
\qmlproperty enumeration QtQuick::GridView::layoutDirection
|
|
This property holds the layout direction of the grid.
|
|
|
|
Possible values:
|
|
|
|
\value Qt.LeftToRight (default) Items will be laid out starting in the top, left corner. The flow is
|
|
dependent on the \l GridView::flow property.
|
|
\value Qt.RightToLeft Items will be laid out starting in the top, right corner. The flow is dependent
|
|
on the \l GridView::flow property.
|
|
|
|
\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:
|
|
|
|
\value GridView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
|
|
\value GridView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
|
|
|
|
\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:
|
|
|
|
\value GridView.FlowLeftToRight (default) Items are laid out from left to right, and the view scrolls vertically
|
|
\value GridView.FlowTopToBottom Items are laid out from top to bottom, and the view scrolls horizontally
|
|
*/
|
|
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();
|
|
QQuickFlickable::setContentX(d->contentXForPosition(d->position()));
|
|
}
|
|
}
|
|
|
|
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();
|
|
QQuickFlickable::setContentY(d->contentYForPosition(d->position()));
|
|
}
|
|
}
|
|
/*!
|
|
\qmlproperty enumeration QtQuick::GridView::snapMode
|
|
|
|
This property determines how the view scrolling will settle following a drag or flick.
|
|
The possible values are:
|
|
|
|
\value GridView.NoSnap (default) the view stops anywhere within the visible area.
|
|
\value GridView.SnapToRow the view settles with a row (or column for \c GridView.FlowTopToBottom flow)
|
|
aligned with the start of the view.
|
|
\value 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.
|
|
*/
|
|
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::beginResetModel()}{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::geometryChange(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::geometryChange(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:
|
|
|
|
\value GridView.Beginning position item at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
|
|
\value GridView.Center position item in the center of the view.
|
|
\value GridView.End position item at bottom (or right for horizontal orientation) of the view.
|
|
\value GridView.Visible if any part of the item is visible then take no action, otherwise
|
|
bring the item into view.
|
|
\value 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.
|
|
\value GridView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid if
|
|
\l highlightRangeMode is \c StrictlyEnforceRange or snapping is enabled via \l snapMode.
|
|
|
|
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"
|