1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickgridview_p.h"
5#include "qquickflickable_p_p.h"
6#include "qquickitemview_p_p.h"
7
8#include <private/qqmlobjectmodel_p.h>
9#include <private/qquicksmoothedanimation_p_p.h>
10
11#include <QtGui/qevent.h>
12#include <QtCore/qmath.h>
13#include <QtCore/qcoreapplication.h>
14#include "qplatformdefs.h"
15
16#include <cmath>
17
18QT_BEGIN_NAMESPACE
19
20#ifndef QML_FLICK_SNAPONETHRESHOLD
21#define QML_FLICK_SNAPONETHRESHOLD 30
22#endif
23
24//----------------------------------------------------------------------------
25
26class FxGridItemSG : public FxViewItem
27{
28public:
29 FxGridItemSG(QQuickItem *i, QQuickGridView *v, bool own) : FxViewItem(i, v, own, static_cast<QQuickItemViewAttached*>(qmlAttachedPropertiesObject<QQuickGridView>(obj: i))), view(v)
30 {
31 }
32
33 qreal position() const override {
34 return rowPos();
35 }
36
37 qreal endPosition() const override {
38 return endRowPos();
39 }
40
41 qreal size() const override {
42 return view->flow() == QQuickGridView::FlowLeftToRight ? view->cellHeight() : view->cellWidth();
43 }
44
45 qreal sectionSize() const override {
46 return 0.0;
47 }
48
49 qreal rowPos() const {
50 if (view->flow() == QQuickGridView::FlowLeftToRight)
51 return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -view->cellHeight()-itemY() : itemY());
52 else
53 return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-itemX() : itemX());
54 }
55
56 qreal colPos() const {
57 if (view->flow() == QQuickGridView::FlowLeftToRight) {
58 if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
59 qreal colSize = view->cellWidth();
60 int columns = view->width()/colSize;
61 return colSize * (columns-1) - itemX();
62 } else {
63 return itemX();
64 }
65 } else {
66 if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) {
67 return -view->cellHeight() - itemY();
68 } else {
69 return itemY();
70 }
71 }
72 }
73 qreal endRowPos() const {
74 if (view->flow() == QQuickGridView::FlowLeftToRight) {
75 if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop)
76 return -itemY();
77 else
78 return itemY() + view->cellHeight();
79 } else {
80 if (view->effectiveLayoutDirection() == Qt::RightToLeft)
81 return -itemX();
82 else
83 return itemX() + view->cellWidth();
84 }
85 }
86 void setPosition(qreal col, qreal row, bool immediate = false) {
87 moveTo(pos: pointForPosition(col, row), immediate);
88 }
89 bool contains(qreal x, qreal y) const override {
90 return (x >= itemX() && x < itemX() + view->cellWidth() &&
91 y >= itemY() && y < itemY() + view->cellHeight());
92 }
93
94 QQuickGridView *view;
95
96private:
97 QPointF pointForPosition(qreal col, qreal row) const {
98 qreal x;
99 qreal y;
100 if (view->flow() == QQuickGridView::FlowLeftToRight) {
101 x = col;
102 y = row;
103 if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
104 int columns = view->width()/view->cellWidth();
105 x = view->cellWidth() * (columns-1) - col;
106 }
107 } else {
108 x = row;
109 y = col;
110 if (view->effectiveLayoutDirection() == Qt::RightToLeft)
111 x = -view->cellWidth() - row;
112 }
113 if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop)
114 y = -view->cellHeight() - y;
115 return QPointF(x, y);
116 }
117};
118
119//----------------------------------------------------------------------------
120
121class QQuickGridViewPrivate : public QQuickItemViewPrivate
122{
123 Q_DECLARE_PUBLIC(QQuickGridView)
124
125public:
126 Qt::Orientation layoutOrientation() const override;
127 bool isContentFlowReversed() const override;
128
129 qreal positionAt(int index) const override;
130 qreal endPositionAt(int index) const override;
131 qreal originPosition() const override;
132 qreal lastPosition() const override;
133
134 qreal rowSize() const;
135 qreal colSize() const;
136 qreal colPosAt(int modelIndex) const;
137 qreal rowPosAt(int modelIndex) const;
138 qreal snapPosAt(qreal pos) const;
139 FxViewItem *snapItemAt(qreal pos) const;
140 int snapIndex() const;
141 qreal contentXForPosition(qreal pos) const;
142 qreal contentYForPosition(qreal pos) const;
143
144 void resetColumns();
145
146 bool addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer) override;
147 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) override;
148
149 void removeItem(FxViewItem *item);
150
151 FxViewItem *newViewItem(int index, QQuickItem *item) override;
152 void initializeViewItem(FxViewItem *item) override;
153 void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) override;
154 void repositionPackageItemAt(QQuickItem *item, int index) override;
155 void resetFirstItemPosition(qreal pos = 0.0) override;
156 void adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible) override;
157
158 void createHighlight(bool onDestruction = false) override;
159 void updateHighlight() override;
160 void resetHighlightPosition() override;
161
162 void setPosition(qreal pos) override;
163 void layoutVisibleItems(int fromModelIndex = 0) override;
164 bool applyInsertionChange(const QQmlChangeSet::Change &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) override;
165#if QT_CONFIG(quick_viewtransitions)
166 void translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) override;
167#endif
168 bool needsRefillForAddedOrRemovedIndex(int index) const override;
169
170 qreal headerSize() const override;
171 qreal footerSize() const override;
172 bool showHeaderForIndex(int index) const override;
173 bool showFooterForIndex(int index) const override;
174 void updateHeader() override;
175 void updateFooter() override;
176
177 void initializeComponentItem(QQuickItem *item) const override;
178
179 void changedVisibleIndex(int newIndex) override;
180 void initializeCurrentItem() override;
181
182 void updateViewport() override;
183 void fixupPosition() override;
184 void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override;
185 bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
186 QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity) override;
187
188 QQuickItemViewAttached *getAttachedObject(const QObject *object) const override;
189
190 QQuickGridView::Flow flow;
191 qreal cellWidth;
192 qreal cellHeight;
193 int columns;
194 QQuickGridView::SnapMode snapMode;
195
196 QSmoothedAnimation *highlightXAnimator;
197 QSmoothedAnimation *highlightYAnimator;
198
199 QQuickGridViewPrivate()
200 : flow(QQuickGridView::FlowLeftToRight)
201 , cellWidth(100), cellHeight(100), columns(1)
202 , snapMode(QQuickGridView::NoSnap)
203 , highlightXAnimator(nullptr), highlightYAnimator(nullptr)
204 {}
205 ~QQuickGridViewPrivate()
206 {
207 delete highlightXAnimator;
208 delete highlightYAnimator;
209 }
210};
211
212Qt::Orientation QQuickGridViewPrivate::layoutOrientation() const
213{
214 return flow == QQuickGridView::FlowLeftToRight ? Qt::Vertical : Qt::Horizontal;
215}
216
217bool QQuickGridViewPrivate::isContentFlowReversed() const
218{
219 Q_Q(const QQuickGridView);
220
221 return (flow == QQuickGridView::FlowLeftToRight && verticalLayoutDirection == QQuickItemView::BottomToTop)
222 || (flow == QQuickGridView::FlowTopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft);
223}
224
225void QQuickGridViewPrivate::changedVisibleIndex(int newIndex)
226{
227 visibleIndex = newIndex / columns * columns;
228}
229
230void QQuickGridViewPrivate::setPosition(qreal pos)
231{
232 Q_Q(QQuickGridView);
233 q->QQuickFlickable::setContentX(contentXForPosition(pos));
234 q->QQuickFlickable::setContentY(contentYForPosition(pos));
235}
236
237qreal QQuickGridViewPrivate::originPosition() const
238{
239 qreal pos = 0;
240 if (!visibleItems.isEmpty())
241 pos = static_cast<FxGridItemSG*>(visibleItems.first())->rowPos() - visibleIndex / columns * rowSize();
242 return pos;
243}
244
245qreal QQuickGridViewPrivate::lastPosition() const
246{
247 qreal pos = 0;
248 if (model && (model->count() || !visibleItems.isEmpty())) {
249 qreal lastRowPos = model->count() ? rowPosAt(modelIndex: model->count() - 1) : 0;
250 if (!visibleItems.isEmpty()) {
251 // If there are items in delayRemove state, they may be after any items linked to the model
252 lastRowPos = qMax(a: lastRowPos, b: static_cast<FxGridItemSG*>(visibleItems.last())->rowPos());
253 }
254 pos = lastRowPos + rowSize();
255 }
256 return pos;
257}
258
259qreal QQuickGridViewPrivate::positionAt(int index) const
260{
261 return rowPosAt(modelIndex: index);
262}
263
264qreal QQuickGridViewPrivate::endPositionAt(int index) const
265{
266 return rowPosAt(modelIndex: index) + rowSize();
267}
268
269qreal QQuickGridViewPrivate::rowSize() const {
270 return flow == QQuickGridView::FlowLeftToRight ? cellHeight : cellWidth;
271}
272qreal QQuickGridViewPrivate::colSize() const {
273 return flow == QQuickGridView::FlowLeftToRight ? cellWidth : cellHeight;
274}
275
276qreal QQuickGridViewPrivate::colPosAt(int modelIndex) const
277{
278 if (FxViewItem *item = visibleItem(modelIndex))
279 return static_cast<FxGridItemSG*>(item)->colPos();
280 if (!visibleItems.isEmpty()) {
281 if (modelIndex == visibleIndex) {
282 FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first());
283 return firstItem->colPos();
284 } else if (modelIndex < visibleIndex) {
285 int count = (visibleIndex - modelIndex) % columns;
286 int col = static_cast<FxGridItemSG*>(visibleItems.first())->colPos() / colSize();
287 col = (columns - count + col) % columns;
288 return col * colSize();
289 } else {
290 FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last());
291 int count = modelIndex - lastItem->index;
292 int col = lastItem->colPos() / colSize();
293 col = (col + count) % columns;
294 return col * colSize();
295 }
296 }
297 return (modelIndex % columns) * colSize();
298}
299
300qreal QQuickGridViewPrivate::rowPosAt(int modelIndex) const
301{
302 if (FxViewItem *item = visibleItem(modelIndex))
303 return static_cast<FxGridItemSG*>(item)->rowPos();
304 if (!visibleItems.isEmpty()) {
305 if (modelIndex == visibleIndex) {
306 FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first());
307 return firstItem->rowPos();
308 } else if (modelIndex < visibleIndex) {
309 FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first());
310 int firstCol = firstItem->colPos() / colSize();
311 int col = visibleIndex - modelIndex + (columns - firstCol - 1);
312 int rows = col / columns;
313 return firstItem->rowPos() - rows * rowSize();
314 } else {
315 FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last());
316 int count = modelIndex - lastItem->index;
317 int col = lastItem->colPos() + count * colSize();
318 int rows = col / (columns * colSize());
319 return lastItem->rowPos() + rows * rowSize();
320 }
321 }
322
323 qreal rowPos = ((modelIndex / columns) * rowSize());
324
325 if (flow == QQuickGridView::FlowLeftToRight && verticalLayoutDirection == QQuickItemView::TopToBottom) {
326 // Add the effective startpos of row 0. Start by subtracting minExtent, which will contain the
327 // height of the rows outside the beginning of the content item. (Rows can end up outside if
328 // e.g flicking the viewport a long way down, changing cellSize, and then flick back).
329 // NOTE: It's not clearly understood why the flow == QQuickGridView::FlowLeftToRight guard is
330 // needed, since the flow shouldn't normally affect the y postition of an index. But without
331 // it, several auto tests start failing, so we keep it until this part is better understood.
332 rowPos -= minExtent;
333 // minExtent will also contain the size of the topMargin (vData.startMargin), the header, and
334 // the highlightRangeStart. Those should be added before the start of row 0. So we need to subtract
335 // them from the rowPos. But only the largest of topMargin and highlightRangeStart will need
336 // to be taken into account, since having a topMargin will also ensure that currentItem ends
337 // up within the requested highlight range when view is positioned at the beginning.
338 rowPos += qMax(a: vData.startMargin, b: highlightRangeStart) + headerSize();
339 }
340
341 return rowPos;
342}
343
344qreal QQuickGridViewPrivate::snapPosAt(qreal pos) const
345{
346 Q_Q(const QQuickGridView);
347 qreal snapPos = 0;
348 if (!visibleItems.isEmpty()) {
349 qreal highlightStart = highlightRangeStart;
350 pos += highlightStart;
351 pos += rowSize()/2;
352 snapPos = static_cast<FxGridItemSG*>(visibleItems.first())->rowPos() - visibleIndex / columns * rowSize();
353 snapPos = pos - std::fmod(x: pos - snapPos, y: qreal(rowSize()));
354 snapPos -= highlightStart;
355 qreal maxExtent;
356 qreal minExtent;
357 if (isContentFlowReversed()) {
358 maxExtent = q->minXExtent()-size();
359 minExtent = q->maxXExtent()-size();
360 } else {
361 maxExtent = flow == QQuickGridView::FlowLeftToRight ? -q->maxYExtent() : -q->maxXExtent();
362 minExtent = flow == QQuickGridView::FlowLeftToRight ? -q->minYExtent() : -q->minXExtent();
363 }
364 if (snapPos > maxExtent)
365 snapPos = maxExtent;
366 if (snapPos < minExtent)
367 snapPos = minExtent;
368 }
369 return snapPos;
370}
371
372FxViewItem *QQuickGridViewPrivate::snapItemAt(qreal pos) const
373{
374 for (FxViewItem *item : visibleItems) {
375 if (item->index == -1)
376 continue;
377 qreal itemTop = item->position();
378 if (itemTop+rowSize()/2 >= pos && itemTop - rowSize()/2 <= pos)
379 return item;
380 }
381 return nullptr;
382}
383
384int QQuickGridViewPrivate::snapIndex() const
385{
386 int index = currentIndex;
387 for (FxViewItem *item : visibleItems) {
388 if (item->index == -1)
389 continue;
390 qreal itemTop = item->position();
391 FxGridItemSG *hItem = static_cast<FxGridItemSG*>(highlight.get());
392 if (itemTop >= hItem->rowPos()-rowSize()/2 && itemTop < hItem->rowPos()+rowSize()/2) {
393 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(item);
394 index = gridItem->index;
395 if (gridItem->colPos() >= hItem->colPos()-colSize()/2 && gridItem->colPos() < hItem->colPos()+colSize()/2)
396 return gridItem->index;
397 }
398 }
399 return index;
400}
401
402qreal QQuickGridViewPrivate::contentXForPosition(qreal pos) const
403{
404 Q_Q(const QQuickGridView);
405 if (flow == QQuickGridView::FlowLeftToRight) {
406 // vertical scroll
407 if (q->effectiveLayoutDirection() == Qt::LeftToRight) {
408 return -q->leftMargin();
409 } else {
410 qreal colSize = cellWidth;
411 int columns = (q->width() - q->leftMargin() - q->rightMargin()) / colSize;
412 return -q->width() + q->rightMargin() + (cellWidth * columns);
413 }
414 } else {
415 // horizontal scroll
416 if (q->effectiveLayoutDirection() == Qt::LeftToRight)
417 return pos;
418 else
419 return -pos - q->width();
420 }
421}
422
423qreal QQuickGridViewPrivate::contentYForPosition(qreal pos) const
424{
425 Q_Q(const QQuickGridView);
426 if (flow == QQuickGridView::FlowLeftToRight) {
427 // vertical scroll
428 if (verticalLayoutDirection == QQuickItemView::TopToBottom)
429 return pos;
430 else
431 return -pos - q->height();
432 } else {
433 // horizontal scroll
434 if (verticalLayoutDirection == QQuickItemView::TopToBottom)
435 return -q->topMargin();
436 else
437 return -q->height() + q->bottomMargin();
438 }
439}
440
441void QQuickGridViewPrivate::resetColumns()
442{
443 Q_Q(QQuickGridView);
444 qreal length = flow == QQuickGridView::FlowLeftToRight
445 ? q->width() - q->leftMargin() - q->rightMargin()
446 : q->height() - q->topMargin() - q->bottomMargin();
447 columns = qMax(a: 1, b: qFloor(v: length / colSize()));
448}
449
450FxViewItem *QQuickGridViewPrivate::newViewItem(int modelIndex, QQuickItem *item)
451{
452 Q_Q(QQuickGridView);
453 Q_UNUSED(modelIndex);
454 return new FxGridItemSG(item, q, false);
455}
456
457void QQuickGridViewPrivate::initializeViewItem(FxViewItem *item)
458{
459 QQuickItemViewPrivate::initializeViewItem(item);
460
461 // need to track current items that are animating
462 item->trackGeometry(track: true);
463}
464
465bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer)
466{
467 qreal colPos = colPosAt(modelIndex: visibleIndex);
468 qreal rowPos = rowPosAt(modelIndex: visibleIndex);
469 if (visibleItems.size()) {
470 FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.constLast());
471 rowPos = lastItem->rowPos();
472 int colNum = qFloor(v: (lastItem->colPos()+colSize()/2) / colSize());
473 if (++colNum >= columns) {
474 colNum = 0;
475 rowPos += rowSize();
476 }
477 colPos = colNum * colSize();
478 }
479
480 int modelIndex = findLastVisibleIndex();
481 modelIndex = modelIndex < 0 ? visibleIndex : modelIndex + 1;
482
483 if (visibleItems.size() && (bufferFrom > rowPos + rowSize()*2
484 || bufferTo < rowPosAt(modelIndex: visibleIndex) - rowSize())) {
485 // We've jumped more than a page. Estimate which items are now
486 // visible and fill from there.
487 int count = (fillFrom - (rowPos + rowSize())) / (rowSize()) * columns;
488 releaseVisibleItems(reusableFlag);
489 modelIndex += count;
490 if (modelIndex >= model->count())
491 modelIndex = model->count() - 1;
492 else if (modelIndex < 0)
493 modelIndex = 0;
494 modelIndex = modelIndex / columns * columns;
495 visibleIndex = modelIndex;
496 colPos = colPosAt(modelIndex: visibleIndex);
497 rowPos = rowPosAt(modelIndex: visibleIndex);
498 }
499
500 int colNum = qFloor(v: (colPos+colSize()/2) / colSize());
501 FxGridItemSG *item = nullptr;
502 bool changed = false;
503
504 QQmlIncubator::IncubationMode incubationMode = doBuffer ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested;
505
506 while (modelIndex < model->count() && rowPos <= fillTo + rowSize()*(columns - colNum)/(columns+1)) {
507 qCDebug(lcItemViewDelegateLifecycle) << "refill: append item" << modelIndex << colPos << rowPos;
508 if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex, incubationMode))))
509 break;
510#if QT_CONFIG(quick_viewtransitions)
511 if (!transitioner || !transitioner->canTransition(type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true)) // pos will be set by layoutVisibleItems()
512 item->setPosition(col: colPos, row: rowPos, immediate: true);
513#endif
514 QQuickItemPrivate::get(item: item->item)->setCulled(doBuffer);
515 visibleItems.append(t: item);
516 if (++colNum >= columns) {
517 colNum = 0;
518 rowPos += rowSize();
519 }
520 colPos = colNum * colSize();
521 ++modelIndex;
522 changed = true;
523 }
524
525 if (doBuffer && requestedIndex != -1) // already waiting for an item
526 return changed;
527
528 // Find first column
529 if (visibleItems.size()) {
530 FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.constFirst());
531 rowPos = firstItem->rowPos();
532 colPos = firstItem->colPos();
533 }
534 colNum = qFloor(v: (colPos+colSize()/2) / colSize());
535 if (--colNum < 0) {
536 colNum = columns - 1;
537 rowPos -= rowSize();
538 }
539
540 // Prepend
541 colPos = colNum * colSize();
542 while (visibleIndex > 0 && rowPos + rowSize() - 1 >= fillFrom - rowSize()*(colNum+1)/(columns+1)){
543 qCDebug(lcItemViewDelegateLifecycle) << "refill: prepend item" << visibleIndex-1 << "top pos" << rowPos << colPos;
544 if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex: visibleIndex-1, incubationMode))))
545 break;
546 --visibleIndex;
547#if QT_CONFIG(quick_viewtransitions)
548 if (!transitioner || !transitioner->canTransition(type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true)) // pos will be set by layoutVisibleItems()
549 item->setPosition(col: colPos, row: rowPos, immediate: true);
550#endif
551 QQuickItemPrivate::get(item: item->item)->setCulled(doBuffer);
552 visibleItems.prepend(t: item);
553 if (--colNum < 0) {
554 colNum = columns-1;
555 rowPos -= rowSize();
556 }
557 colPos = colNum * colSize();
558 changed = true;
559 }
560
561 return changed;
562}
563
564void QQuickGridViewPrivate::removeItem(FxViewItem *item)
565{
566#if QT_CONFIG(quick_viewtransitions)
567 if (item->transitionScheduledOrRunning()) {
568 qCDebug(lcItemViewDelegateLifecycle) << "\tnot releasing animating item:" << item->index << item->item->objectName();
569 item->releaseAfterTransition = true;
570 releasePendingTransition.append(t: item);
571 } else
572#endif
573 {
574 releaseItem(item, reusableFlag);
575 }
576}
577
578bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
579{
580 FxGridItemSG *item = nullptr;
581 bool changed = false;
582
583 while (visibleItems.size() > 1
584 && (item = static_cast<FxGridItemSG*>(visibleItems.constFirst()))
585 && item->rowPos()+rowSize()-1 < bufferFrom - rowSize()*(item->colPos()/colSize()+1)/(columns+1)) {
586 if (item->attached->delayRemove())
587 break;
588 qCDebug(lcItemViewDelegateLifecycle) << "refill: remove first" << visibleIndex << "top end pos" << item->endRowPos();
589 if (item->index != -1)
590 visibleIndex++;
591 visibleItems.removeFirst();
592 removeItem(item);
593 changed = true;
594 }
595 while (visibleItems.size() > 1
596 && (item = static_cast<FxGridItemSG*>(visibleItems.constLast()))
597 && item->rowPos() > bufferTo + rowSize()*(columns - item->colPos()/colSize())/(columns+1)) {
598 if (item->attached->delayRemove())
599 break;
600 qCDebug(lcItemViewDelegateLifecycle) << "refill: remove last" << visibleIndex+visibleItems.size()-1;
601 visibleItems.removeLast();
602 removeItem(item);
603 changed = true;
604 }
605
606 return changed;
607}
608
609void QQuickGridViewPrivate::updateViewport()
610{
611 resetColumns();
612 QQuickItemViewPrivate::updateViewport();
613}
614
615void QQuickGridViewPrivate::layoutVisibleItems(int fromModelIndex)
616{
617 if (visibleItems.size()) {
618 const qreal from = isContentFlowReversed() ? -position()-displayMarginBeginning-size() : position()-displayMarginBeginning;
619 const qreal to = isContentFlowReversed() ? -position()+displayMarginEnd : position()+size()+displayMarginEnd;
620
621 FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.constFirst());
622 qreal rowPos = firstItem->rowPos();
623 qreal colPos = firstItem->colPos();
624 int col = visibleIndex % columns;
625 if (colPos != col * colSize()) {
626 colPos = col * colSize();
627 firstItem->setPosition(col: colPos, row: rowPos);
628 }
629 firstItem->setVisible(firstItem->rowPos() + rowSize() >= from && firstItem->rowPos() <= to);
630 for (int i = 1; i < visibleItems.size(); ++i) {
631 FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.at(i));
632 if (++col >= columns) {
633 col = 0;
634 rowPos += rowSize();
635 }
636 colPos = col * colSize();
637 if (item->index >= fromModelIndex) {
638 item->setPosition(col: colPos, row: rowPos);
639 item->setVisible(item->rowPos() + rowSize() >= from && item->rowPos() <= to);
640 }
641 }
642 }
643}
644
645void QQuickGridViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer)
646{
647 int count = sizeBuffer / rowSize();
648 static_cast<FxGridItemSG *>(item)->setPosition(col: colPosAt(modelIndex: index + count), row: rowPosAt(modelIndex: index + count));
649}
650
651void QQuickGridViewPrivate::repositionPackageItemAt(QQuickItem *item, int index)
652{
653 Q_Q(QQuickGridView);
654 qreal pos = position();
655 if (flow == QQuickGridView::FlowLeftToRight) {
656 if (item->y() + item->height() > pos && item->y() < pos + q->height()) {
657 qreal y = (verticalLayoutDirection == QQuickItemView::TopToBottom)
658 ? rowPosAt(modelIndex: index)
659 : -rowPosAt(modelIndex: index) - item->height();
660 item->setPosition(QPointF(colPosAt(modelIndex: index), y));
661 }
662 } else {
663 if (item->x() + item->width() > pos && item->x() < pos + q->width()) {
664 qreal y = (verticalLayoutDirection == QQuickItemView::TopToBottom)
665 ? colPosAt(modelIndex: index)
666 : -colPosAt(modelIndex: index) - item->height();
667 if (flow == QQuickGridView::FlowTopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft)
668 item->setPosition(QPointF(-rowPosAt(modelIndex: index)-item->width(), y));
669 else
670 item->setPosition(QPointF(rowPosAt(modelIndex: index), y));
671 }
672 }
673}
674
675void QQuickGridViewPrivate::resetFirstItemPosition(qreal pos)
676{
677 FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.constFirst());
678 item->setPosition(col: 0, row: pos);
679}
680
681void QQuickGridViewPrivate::adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible)
682{
683 if (!visibleItems.size())
684 return;
685
686 int moveCount = (forwards - backwards) / rowSize();
687 if (moveCount == 0 && changeBeforeVisible != 0)
688 moveCount += (changeBeforeVisible % columns) - (columns - 1);
689
690 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.constFirst());
691 gridItem->setPosition(col: gridItem->colPos(), row: gridItem->rowPos() + ((moveCount / columns) * rowSize()));
692}
693
694void QQuickGridViewPrivate::createHighlight(bool onDestruction)
695{
696 bool changed = false;
697 if (highlight) {
698 if (trackedItem == highlight.get())
699 trackedItem = nullptr;
700 highlight.reset();
701
702 delete highlightXAnimator;
703 delete highlightYAnimator;
704 highlightXAnimator = nullptr;
705 highlightYAnimator = nullptr;
706
707 changed = true;
708 }
709
710 if (onDestruction)
711 return;
712
713 Q_Q(QQuickGridView);
714 if (currentItem) {
715 QQuickItem *item = createHighlightItem();
716 if (item) {
717 std::unique_ptr<FxGridItemSG> newHighlight
718 = std::make_unique<FxGridItemSG>(args&: item, args: q, args: true);
719 newHighlight->trackGeometry(track: true);
720 if (autoHighlight)
721 resetHighlightPosition();
722 highlightXAnimator = new QSmoothedAnimation;
723 highlightXAnimator->target = QQmlProperty(item, QLatin1String("x"));
724 highlightXAnimator->userDuration = highlightMoveDuration;
725 highlightYAnimator = new QSmoothedAnimation;
726 highlightYAnimator->target = QQmlProperty(item, QLatin1String("y"));
727 highlightYAnimator->userDuration = highlightMoveDuration;
728
729 highlight = std::move(newHighlight);
730 changed = true;
731 }
732 }
733 if (changed)
734 emit q->highlightItemChanged();
735}
736
737void QQuickGridViewPrivate::updateHighlight()
738{
739 applyPendingChanges();
740
741 if ((!currentItem && highlight) || (currentItem && !highlight))
742 createHighlight();
743 bool strictHighlight = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange;
744 if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) {
745 // auto-update highlight
746 highlightXAnimator->to = currentItem->itemX();
747 highlightYAnimator->to = currentItem->itemY();
748 highlight->item->setSize(currentItem->item->size());
749
750 highlightXAnimator->restart();
751 highlightYAnimator->restart();
752 }
753 updateTrackedItem();
754}
755
756void QQuickGridViewPrivate::resetHighlightPosition()
757{
758 if (highlight && currentItem) {
759 FxGridItemSG *cItem = static_cast<FxGridItemSG*>(currentItem);
760 static_cast<FxGridItemSG *>(highlight.get())->setPosition(col: cItem->colPos(), row: cItem->rowPos());
761 }
762}
763
764qreal QQuickGridViewPrivate::headerSize() const
765{
766 if (!header)
767 return 0.0;
768 return flow == QQuickGridView::FlowLeftToRight ? header->item->height() : header->item->width();
769}
770
771qreal QQuickGridViewPrivate::footerSize() const
772{
773 if (!footer)
774 return 0.0;
775 return flow == QQuickGridView::FlowLeftToRight? footer->item->height() : footer->item->width();
776}
777
778bool QQuickGridViewPrivate::showHeaderForIndex(int index) const
779{
780 return index / columns == 0;
781}
782
783bool QQuickGridViewPrivate::showFooterForIndex(int index) const
784{
785 return index / columns == (model->count()-1) / columns;
786}
787
788void QQuickGridViewPrivate::updateFooter()
789{
790 Q_Q(QQuickGridView);
791 bool created = false;
792 if (!footer) {
793 QQuickItem *item = createComponentItem(component: footerComponent, zValue: 1.0);
794 if (!item)
795 return;
796 footer = new FxGridItemSG(item, q, true);
797 footer->trackGeometry(track: true);
798 created = true;
799 }
800
801 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(footer);
802 qreal colOffset = 0;
803 qreal rowOffset = 0;
804 if (q->effectiveLayoutDirection() == Qt::RightToLeft) {
805 if (flow == QQuickGridView::FlowTopToBottom)
806 rowOffset += gridItem->item->width() - cellWidth;
807 else
808 colOffset += gridItem->item->width() - cellWidth;
809 }
810 if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
811 if (flow == QQuickGridView::FlowTopToBottom)
812 colOffset += gridItem->item->height() - cellHeight;
813 else
814 rowOffset += gridItem->item->height() - cellHeight;
815 }
816 if (visibleItems.size()) {
817 qreal endPos = lastPosition();
818 if (findLastVisibleIndex() == model->count()-1) {
819 gridItem->setPosition(col: colOffset, row: endPos + rowOffset);
820 } else {
821 qreal visiblePos = isContentFlowReversed() ? -position() : position() + size();
822 if (endPos <= visiblePos || gridItem->endPosition() <= endPos + rowOffset)
823 gridItem->setPosition(col: colOffset, row: endPos + rowOffset);
824 }
825 } else {
826 gridItem->setPosition(col: colOffset, row: rowOffset);
827 }
828
829 if (created)
830 emit q->footerItemChanged();
831}
832
833void QQuickGridViewPrivate::initializeComponentItem(QQuickItem *item) const
834{
835 QQuickGridViewAttached *attached = static_cast<QQuickGridViewAttached *>(
836 qmlAttachedPropertiesObject<QQuickGridView>(obj: item));
837 if (attached)
838 attached->setView(const_cast<QQuickGridView*>(q_func()));
839}
840
841void QQuickGridViewPrivate::updateHeader()
842{
843 Q_Q(QQuickGridView);
844 bool created = false;
845 if (!header) {
846 QQuickItem *item = createComponentItem(component: headerComponent, zValue: 1.0);
847 if (!item)
848 return;
849 header = new FxGridItemSG(item, q, true);
850 header->trackGeometry(track: true);
851 created = true;
852 }
853
854 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(header);
855 qreal colOffset = 0;
856 qreal rowOffset = -headerSize();
857 if (q->effectiveLayoutDirection() == Qt::RightToLeft) {
858 if (flow == QQuickGridView::FlowTopToBottom)
859 rowOffset += gridItem->item->width() - cellWidth;
860 else
861 colOffset += gridItem->item->width() - cellWidth;
862 }
863 if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
864 if (flow == QQuickGridView::FlowTopToBottom)
865 colOffset += gridItem->item->height() - cellHeight;
866 else
867 rowOffset += gridItem->item->height() - cellHeight;
868 }
869 if (visibleItems.size()) {
870 qreal startPos = originPosition();
871 if (visibleIndex == 0) {
872 gridItem->setPosition(col: colOffset, row: startPos + rowOffset);
873 } else {
874 qreal tempPos = isContentFlowReversed() ? -position()-size() : position();
875 qreal headerPos = isContentFlowReversed() ? gridItem->rowPos() + cellWidth - headerSize() : gridItem->rowPos();
876 if (tempPos <= startPos || headerPos > startPos + rowOffset)
877 gridItem->setPosition(col: colOffset, row: startPos + rowOffset);
878 }
879 } else {
880 if (isContentFlowReversed())
881 gridItem->setPosition(col: colOffset, row: rowOffset);
882 else
883 gridItem->setPosition(col: colOffset, row: -headerSize());
884 }
885
886 if (created)
887 emit q->headerItemChanged();
888}
889
890void QQuickGridViewPrivate::initializeCurrentItem()
891{
892 if (currentItem && currentIndex >= 0) {
893 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(currentItem);
894 FxViewItem *actualItem = visibleItem(modelIndex: currentIndex);
895
896 // don't reposition the item if it's about to be transitioned to another position
897 if ((!actualItem
898#if QT_CONFIG(quick_viewtransitions)
899 || !actualItem->transitionScheduledOrRunning()
900#endif
901 ))
902 gridItem->setPosition(col: colPosAt(modelIndex: currentIndex), row: rowPosAt(modelIndex: currentIndex));
903 }
904}
905
906void QQuickGridViewPrivate::fixupPosition()
907{
908 if (flow == QQuickGridView::FlowLeftToRight)
909 fixupY();
910 else
911 fixupX();
912}
913
914void QQuickGridViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent)
915{
916 if ((flow == QQuickGridView::FlowTopToBottom && &data == &vData)
917 || (flow == QQuickGridView::FlowLeftToRight && &data == &hData))
918 return;
919
920 fixupMode = moveReason == Mouse ? fixupMode : Immediate;
921
922 qreal viewPos = isContentFlowReversed() ? -position()-size() : position();
923
924 bool strictHighlightRange = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange;
925 if (snapMode != QQuickGridView::NoSnap) {
926 qreal tempPosition = isContentFlowReversed() ? -position()-size() : position();
927 if (snapMode == QQuickGridView::SnapOneRow && moveReason == Mouse) {
928 // if we've been dragged < rowSize()/2 then bias towards the next row
929 qreal dist = data.move.value() - data.pressPos;
930 qreal bias = 0;
931 if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < rowSize()/2)
932 bias = rowSize()/2;
933 else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -rowSize()/2)
934 bias = -rowSize()/2;
935 if (isContentFlowReversed())
936 bias = -bias;
937 tempPosition -= bias;
938 }
939 FxViewItem *topItem = snapItemAt(pos: tempPosition+highlightRangeStart);
940 if (strictHighlightRange && currentItem && (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))) {
941 // StrictlyEnforceRange always keeps an item in range
942 updateHighlight();
943 topItem = currentItem;
944 }
945 FxViewItem *bottomItem = snapItemAt(pos: tempPosition+highlightRangeEnd);
946 if (strictHighlightRange && currentItem && (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))) {
947 // StrictlyEnforceRange always keeps an item in range
948 updateHighlight();
949 bottomItem = currentItem;
950 }
951 qreal pos;
952 bool isInBounds = -position() > maxExtent && -position() <= minExtent;
953 if (topItem && (isInBounds || strictHighlightRange)) {
954 qreal headerPos = header ? static_cast<FxGridItemSG*>(header)->rowPos() : 0;
955 if (topItem->index == 0 && header && tempPosition+highlightRangeStart < headerPos+headerSize()/2 && !strictHighlightRange) {
956 pos = isContentFlowReversed() ? - headerPos + highlightRangeStart - size() : headerPos - highlightRangeStart;
957 } else {
958 if (isContentFlowReversed())
959 pos = qMax(a: qMin(a: -topItem->position() + highlightRangeStart - size(), b: -maxExtent), b: -minExtent);
960 else
961 pos = qMax(a: qMin(a: topItem->position() - highlightRangeStart, b: -maxExtent), b: -minExtent);
962 }
963 } else if (bottomItem && isInBounds) {
964 if (isContentFlowReversed())
965 pos = qMax(a: qMin(a: -bottomItem->position() + highlightRangeEnd - size(), b: -maxExtent), b: -minExtent);
966 else
967 pos = qMax(a: qMin(a: bottomItem->position() - highlightRangeEnd, b: -maxExtent), b: -minExtent);
968 } else {
969 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
970 return;
971 }
972
973 qreal dist = qAbs(t: data.move + pos);
974 if (dist > 0) {
975 timeline.reset(data.move);
976 if (fixupMode != Immediate) {
977 timeline.move(data.move, destination: -pos, QEasingCurve(QEasingCurve::InOutQuad), time: fixupDuration/2);
978 data.fixingUp = true;
979 } else {
980 timeline.set(data.move, -pos);
981 }
982 vTime = timeline.time();
983 }
984 } else if (haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange) {
985 if (currentItem) {
986 updateHighlight();
987 qreal pos = static_cast<FxGridItemSG*>(currentItem)->rowPos();
988 if (viewPos < pos + rowSize() - highlightRangeEnd)
989 viewPos = pos + rowSize() - highlightRangeEnd;
990 if (viewPos > pos - highlightRangeStart)
991 viewPos = pos - highlightRangeStart;
992 if (isContentFlowReversed())
993 viewPos = -viewPos-size();
994 timeline.reset(data.move);
995 if (viewPos != position()) {
996 if (fixupMode != Immediate) {
997 timeline.move(data.move, destination: -viewPos, QEasingCurve(QEasingCurve::InOutQuad), time: fixupDuration/2);
998 data.fixingUp = true;
999 } else {
1000 timeline.set(data.move, -viewPos);
1001 }
1002 }
1003 vTime = timeline.time();
1004 }
1005 } else {
1006 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1007 }
1008 data.inOvershoot = false;
1009 fixupMode = Normal;
1010}
1011
1012bool QQuickGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
1013 QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity)
1014{
1015 data.fixingUp = false;
1016 moveReason = Mouse;
1017 if ((!haveHighlightRange || highlightRange != QQuickGridView::StrictlyEnforceRange)
1018 && snapMode == QQuickGridView::NoSnap) {
1019 return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, eventType, velocity);
1020 }
1021 qreal maxDistance = 0;
1022 qreal dataValue = isContentFlowReversed() ? -data.move.value()+size() : data.move.value();
1023 // -ve velocity means list is moving up/left
1024 if (velocity > 0) {
1025 if (data.move.value() < minExtent) {
1026 if (snapMode == QQuickGridView::SnapOneRow) {
1027 // if we've been dragged < averageSize/2 then bias towards the next item
1028 qreal dist = data.move.value() - data.pressPos;
1029 qreal bias = dist < rowSize()/2 ? rowSize()/2 : 0;
1030 if (isContentFlowReversed())
1031 bias = -bias;
1032 data.flickTarget = -snapPosAt(pos: -dataValue - bias);
1033 maxDistance = qAbs(t: data.flickTarget - data.move.value());
1034 velocity = maxVelocity;
1035 } else {
1036 maxDistance = qAbs(t: minExtent - data.move.value());
1037 }
1038 }
1039 if (snapMode == QQuickGridView::NoSnap && highlightRange != QQuickGridView::StrictlyEnforceRange)
1040 data.flickTarget = minExtent;
1041 } else {
1042 if (data.move.value() > maxExtent) {
1043 if (snapMode == QQuickGridView::SnapOneRow) {
1044 // if we've been dragged < averageSize/2 then bias towards the next item
1045 qreal dist = data.move.value() - data.pressPos;
1046 qreal bias = -dist < rowSize()/2 ? rowSize()/2 : 0;
1047 if (isContentFlowReversed())
1048 bias = -bias;
1049 data.flickTarget = -snapPosAt(pos: -dataValue + bias);
1050 maxDistance = qAbs(t: data.flickTarget - data.move.value());
1051 velocity = -maxVelocity;
1052 } else {
1053 maxDistance = qAbs(t: maxExtent - data.move.value());
1054 }
1055 }
1056 if (snapMode == QQuickGridView::NoSnap && highlightRange != QQuickGridView::StrictlyEnforceRange)
1057 data.flickTarget = maxExtent;
1058 }
1059 bool overShoot = boundsBehavior & QQuickFlickable::OvershootBounds;
1060 if (maxDistance > 0 || overShoot) {
1061 // This mode requires the grid to stop exactly on a row boundary.
1062 qreal v = velocity;
1063 if (maxVelocity != -1 && maxVelocity < qAbs(t: v)) {
1064 if (v < 0)
1065 v = -maxVelocity;
1066 else
1067 v = maxVelocity;
1068 }
1069 qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
1070 qreal v2 = v * v;
1071 qreal overshootDist = 0.0;
1072 if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickGridView::SnapOneRow) {
1073 // + rowSize()/4 to encourage moving at least one item in the flick direction
1074 qreal dist = v2 / (accel * 2.0) + rowSize()/4;
1075 dist = qMin(a: dist, b: maxDistance);
1076 if (v > 0)
1077 dist = -dist;
1078 if (snapMode != QQuickGridView::SnapOneRow) {
1079 qreal distTemp = isContentFlowReversed() ? -dist : dist;
1080 data.flickTarget = -snapPosAt(pos: -dataValue + distTemp);
1081 }
1082 data.flickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget;
1083 if (overShoot) {
1084 if (data.flickTarget >= minExtent) {
1085 overshootDist = overShootDistance(velocity: vSize);
1086 data.flickTarget += overshootDist;
1087 } else if (data.flickTarget <= maxExtent) {
1088 overshootDist = overShootDistance(velocity: vSize);
1089 data.flickTarget -= overshootDist;
1090 }
1091 }
1092 qreal adjDist = -data.flickTarget + data.move.value();
1093 if (qAbs(t: adjDist) > qAbs(t: dist)) {
1094 // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration
1095 qreal adjv2 = accel * 2.0f * qAbs(t: adjDist);
1096 if (adjv2 > v2) {
1097 v2 = adjv2;
1098 v = qSqrt(v: v2);
1099 if (dist > 0)
1100 v = -v;
1101 }
1102 }
1103 dist = adjDist;
1104 accel = v2 / (2.0f * qAbs(t: dist));
1105 } else {
1106 data.flickTarget = velocity > 0 ? minExtent : maxExtent;
1107 overshootDist = overShoot ? overShootDistance(velocity: vSize) : 0;
1108 }
1109 timeline.reset(data.move);
1110 timeline.accel(data.move, velocity: v, accel, maxDistance: maxDistance + overshootDist);
1111 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
1112 return true;
1113 } else {
1114 timeline.reset(data.move);
1115 fixup(data, minExtent, maxExtent);
1116 return false;
1117 }
1118}
1119
1120QQuickItemViewAttached *QQuickGridViewPrivate::getAttachedObject(const QObject *object) const
1121{
1122 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickGridView>(obj: object);
1123 return static_cast<QQuickItemViewAttached *>(attachedObject);
1124}
1125
1126
1127//----------------------------------------------------------------------------
1128/*!
1129 \qmltype GridView
1130 \nativetype QQuickGridView
1131 \inqmlmodule QtQuick
1132 \ingroup qtquick-views
1133
1134 \inherits Flickable
1135 \brief For specifying a grid view of items provided by a model.
1136
1137 A GridView displays data from models created from built-in QML types like ListModel
1138 and XmlListModel, or custom model classes defined in C++ that inherit from
1139 QAbstractListModel.
1140
1141 A GridView has a \l model, which defines the data to be displayed, and
1142 a \l delegate, which defines how the data should be displayed. Items in a
1143 GridView are laid out horizontally or vertically. Grid views are inherently flickable
1144 as GridView inherits from \l Flickable.
1145
1146 \section1 Example Usage
1147
1148 The following example shows the definition of a simple list model defined
1149 in a file called \c ContactModel.qml:
1150
1151 \snippet qml/gridview/ContactModel.qml 0
1152
1153 \div {class="float-right"}
1154 \inlineimage gridview-simple.png
1155 \enddiv
1156
1157 This model can be referenced as \c ContactModel in other QML files. See \l{QML Modules}
1158 for more information about creating reusable components like this.
1159
1160 Another component can display this model data in a GridView, as in the following
1161 example, which creates a \c ContactModel component for its model, and a \l Column
1162 (containing \l Image and \l Text items) for its delegate.
1163
1164 \clearfloat
1165 \snippet qml/gridview/gridview.qml import
1166 \codeline
1167 \snippet qml/gridview/gridview.qml classdocs simple
1168
1169 \div {class="float-right"}
1170 \inlineimage gridview-highlight.png
1171 \enddiv
1172
1173 The view will create a new delegate for each item in the model. Note that the delegate
1174 is able to access the model's \c name and \c portrait data directly.
1175
1176 An improved grid view is shown below. The delegate is visually improved and is moved
1177 into a separate \c contactDelegate component.
1178
1179 \clearfloat
1180 \snippet qml/gridview/gridview.qml classdocs advanced
1181
1182 The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property,
1183 and \c focus is set to \c true to enable keyboard navigation for the grid view.
1184 The grid view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
1185
1186 Delegates are instantiated as needed and may be destroyed at any time.
1187 State should \e never be stored in a delegate.
1188
1189 GridView attaches a number of properties to the root item of the delegate, for example
1190 \c {GridView.isCurrentItem}. In the following example, the root delegate item can access
1191 this attached property directly as \c GridView.isCurrentItem, while the child
1192 \c contactInfo object must refer to this property as \c wrapper.GridView.isCurrentItem.
1193
1194 \snippet qml/gridview/gridview.qml isCurrentItem
1195
1196 \note Views do not set the \l{Item::}{clip} property automatically.
1197 If the view is not clipped by another item or the screen, it will be necessary
1198 to set this property to true in order to clip the items that are partially or
1199 fully outside the view.
1200
1201
1202 \section1 GridView Layouts
1203
1204 The layout of the items in a GridView can be controlled by these properties:
1205
1206 \list
1207 \li \l flow - controls whether items flow from left to right (as a series of rows)
1208 or from top to bottom (as a series of columns). This value can be either
1209 GridView.FlowLeftToRight or GridView.FlowTopToBottom.
1210 \li \l layoutDirection - controls the horizontal layout direction: that is, whether items
1211 are laid out from the left side of the view to the right, or vice-versa. This value can
1212 be either Qt.LeftToRight or Qt.RightToLeft.
1213 \li \l verticalLayoutDirection - controls the vertical layout direction: that is, whether items
1214 are laid out from the top of the view down towards the bottom of the view, or vice-versa.
1215 This value can be either GridView.TopToBottom or GridView.BottomToTop.
1216 \endlist
1217
1218 By default, a GridView flows from left to right, and items are laid out from left to right
1219 horizontally, and from top to bottom vertically.
1220
1221 These properties can be combined to produce a variety of layouts, as shown in the table below.
1222 The GridViews in the first row all have a \l flow value of GridView.FlowLeftToRight, but use
1223 different combinations of horizontal and vertical layout directions (specified by \l layoutDirection
1224 and \l verticalLayoutDirection respectively). Similarly, the GridViews in the second row below
1225 all have a \l flow value of GridView.FlowTopToBottom, but use different combinations of horizontal and
1226 vertical layout directions to lay out their items in different ways.
1227
1228 \table
1229 \header
1230 \li {4, 1}
1231 \b GridViews with GridView.FlowLeftToRight flow
1232 \row
1233 \li \b (H) Left to right \b (V) Top to bottom
1234 \image gridview-layout-lefttoright-ltr-ttb.png
1235 \li \b (H) Right to left \b (V) Top to bottom
1236 \image gridview-layout-lefttoright-rtl-ttb.png
1237 \li \b (H) Left to right \b (V) Bottom to top
1238 \image gridview-layout-lefttoright-ltr-btt.png
1239 \li \b (H) Right to left \b (V) Bottom to top
1240 \image gridview-layout-lefttoright-rtl-btt.png
1241 \header
1242 \li {4, 1}
1243 \b GridViews with GridView.FlowTopToBottom flow
1244 \row
1245 \li \b (H) Left to right \b (V) Top to bottom
1246 \image gridview-layout-toptobottom-ltr-ttb.png
1247 \li \b (H) Right to left \b (V) Top to bottom
1248 \image gridview-layout-toptobottom-rtl-ttb.png
1249 \li \b (H) Left to right \b (V) Bottom to top
1250 \image gridview-layout-toptobottom-ltr-btt.png
1251 \li \b (H) Right to left \b (V) Bottom to top
1252 \image gridview-layout-toptobottom-rtl-btt.png
1253 \endtable
1254
1255 \sa {QML Data Models}, ListView, PathView, {Qt Quick Examples - Views}
1256*/
1257
1258QQuickGridView::QQuickGridView(QQuickItem *parent)
1259 : QQuickItemView(*(new QQuickGridViewPrivate), parent)
1260{
1261}
1262
1263void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
1264{
1265 Q_D(QQuickGridView);
1266 if (d->autoHighlight != autoHighlight) {
1267 if (!autoHighlight && d->highlightXAnimator) {
1268 d->highlightXAnimator->stop();
1269 d->highlightYAnimator->stop();
1270 }
1271 QQuickItemView::setHighlightFollowsCurrentItem(autoHighlight);
1272 }
1273}
1274
1275/*!
1276 \qmlattachedproperty bool QtQuick::GridView::isCurrentItem
1277 \readonly
1278
1279 This attached property is true if this delegate is the current item; otherwise false.
1280
1281 It is attached to each instance of the delegate.
1282
1283 \snippet qml/gridview/gridview.qml isCurrentItem
1284*/
1285
1286/*!
1287 \qmlattachedproperty GridView QtQuick::GridView::view
1288 \readonly
1289
1290 This attached property holds the view that manages this delegate instance.
1291
1292 It is attached to each instance of the delegate and also to the header, the footer
1293 and the highlight delegates.
1294*/
1295
1296/*!
1297 \qmlattachedproperty bool QtQuick::GridView::delayRemove
1298
1299 This attached property holds whether the delegate may be destroyed. It
1300 is attached to each instance of the delegate. The default value is false.
1301
1302 It is sometimes necessary to delay the destruction of an item
1303 until an animation completes. The example delegate below ensures that the
1304 animation completes before the item is removed from the list.
1305
1306 \snippet qml/gridview/gridview.qml delayRemove
1307
1308 If a \l remove transition has been specified, it will not be applied until
1309 delayRemove is returned to \c false.
1310*/
1311
1312/*!
1313 \qmlattachedsignal QtQuick::GridView::add()
1314 This attached signal is emitted immediately after an item is added to the view.
1315*/
1316
1317/*!
1318 \qmlattachedsignal QtQuick::GridView::remove()
1319 This attached signal is emitted immediately before an item is removed from the view.
1320
1321 If a \l remove transition has been specified, it is applied after
1322 this signal is handled, providing that \l delayRemove is false.
1323*/
1324
1325
1326/*!
1327 \qmlproperty model QtQuick::GridView::model
1328 This property holds the model providing data for the grid.
1329
1330 The model provides the set of data that is used to create the items
1331 in the view. Models can be created directly in QML using \l ListModel,
1332 \l DelegateModel, \l ObjectModel, or provided by C++ model classes.
1333 If a C++ model class is used, it must be a subclass of
1334 \l QAbstractItemModel or a simple list.
1335
1336 \sa {qml-data-models}{Data Models}
1337*/
1338
1339/*!
1340 \qmlproperty Component QtQuick::GridView::delegate
1341
1342 The delegate provides a template defining each item instantiated by the view.
1343 The index is exposed as an accessible \c index property. Properties of the
1344 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
1345
1346 The number of objects and bindings in the delegate has a direct effect on the
1347 flicking performance of the view. If at all possible, place functionality
1348 that is not needed for the normal display of the delegate in a \l Loader which
1349 can load additional components when needed.
1350
1351 The item size of the GridView is determined by cellHeight and cellWidth. It will not resize the items
1352 based on the size of the root item in the delegate.
1353
1354 The default \l {QQuickItem::z}{stacking order} of delegate instances is \c 1.
1355
1356 \note Delegates are instantiated as needed and may be destroyed at any time.
1357 State should \e never be stored in a delegate.
1358*/
1359
1360/*!
1361 \qmlproperty enumeration QtQuick::GridView::delegateModelAccess
1362
1363 \include delegatemodelaccess.qdocinc
1364*/
1365
1366/*!
1367 \qmlproperty int QtQuick::GridView::currentIndex
1368 \qmlproperty Item QtQuick::GridView::currentItem
1369
1370 The \c currentIndex property holds the index of the current item, and
1371 \c currentItem holds the current item. Setting the currentIndex to -1
1372 will clear the highlight and set currentItem to null.
1373
1374 If highlightFollowsCurrentItem is \c true, setting either of these
1375 properties will smoothly scroll the GridView so that the current
1376 item becomes visible.
1377
1378 Note that the position of the current item
1379 may only be approximate until it becomes visible in the view.
1380*/
1381
1382
1383/*!
1384 \qmlproperty Item QtQuick::GridView::highlightItem
1385
1386 This holds the highlight item created from the \l highlight component.
1387
1388 The highlightItem is managed by the view unless
1389 \l highlightFollowsCurrentItem is set to false.
1390 The default \l {QQuickItem::z}{stacking order}
1391 of the highlight item is \c 0.
1392
1393 \sa highlight, highlightFollowsCurrentItem
1394*/
1395
1396
1397/*!
1398 \qmlproperty int QtQuick::GridView::count
1399 This property holds the number of items in the model.
1400*/
1401
1402/*!
1403 \qmlproperty bool QtQuick::GridView::reuseItems
1404
1405 This property enables you to reuse items that are instantiated
1406 from the \l delegate. If set to \c false, any currently
1407 pooled items are destroyed.
1408
1409 This property is \c false by default.
1410
1411 \since 5.15
1412
1413 \sa {Reusing items}, pooled(), reused()
1414*/
1415
1416/*!
1417 \qmlattachedsignal QtQuick::GridView::pooled()
1418
1419 This signal is emitted after an item has been added to the reuse
1420 pool. You can use it to pause ongoing timers or animations inside
1421 the item, or free up resources that cannot be reused.
1422
1423 This signal is emitted only if the \l reuseItems property is \c true.
1424
1425 \sa {Reusing items}, reuseItems, reused()
1426*/
1427
1428/*!
1429 \qmlattachedsignal QtQuick::GridView::reused()
1430
1431 This signal is emitted after an item has been reused. At this point, the
1432 item has been taken out of the pool and placed inside the content view,
1433 and the model properties such as \c index and \c row have been updated.
1434
1435 Other properties that are not provided by the model does not change when an
1436 item is reused. You should avoid storing any state inside a delegate, but if
1437 you do, manually reset that state on receiving this signal.
1438
1439 This signal is emitted when the item is reused, and not the first time the
1440 item is created.
1441
1442 This signal is emitted only if the \l reuseItems property is \c true.
1443
1444 \sa {Reusing items}, reuseItems, pooled()
1445*/
1446
1447/*!
1448 \qmlproperty Component QtQuick::GridView::highlight
1449 This property holds the component to use as the highlight.
1450
1451 An instance of the highlight component is created for each view.
1452 The geometry of the resulting component instance will be managed by the view
1453 so as to stay with the current item, unless the highlightFollowsCurrentItem property is false.
1454 The default \l {QQuickItem::z}{stacking order} of the highlight item is \c 0.
1455
1456 \sa highlightItem, highlightFollowsCurrentItem
1457*/
1458
1459/*!
1460 \qmlproperty bool QtQuick::GridView::highlightFollowsCurrentItem
1461 This property sets whether the highlight is managed by the view.
1462
1463 If this property is true (the default value), the highlight is moved smoothly
1464 to follow the current item. Otherwise, the
1465 highlight is not moved by the view, and any movement must be implemented
1466 by the highlight.
1467
1468 Here is a highlight with its motion defined by a \l {SpringAnimation} item:
1469
1470 \snippet qml/gridview/gridview.qml highlightFollowsCurrentItem
1471*/
1472
1473
1474/*!
1475 \qmlproperty int QtQuick::GridView::highlightMoveDuration
1476 This property holds the move animation duration of the highlight delegate.
1477
1478 highlightFollowsCurrentItem must be true for this property
1479 to have effect.
1480
1481 The default value for the duration is 150ms.
1482
1483 \sa highlightFollowsCurrentItem
1484*/
1485
1486/*!
1487 \qmlproperty real QtQuick::GridView::preferredHighlightBegin
1488 \qmlproperty real QtQuick::GridView::preferredHighlightEnd
1489 \qmlproperty enumeration QtQuick::GridView::highlightRangeMode
1490
1491 These properties define the preferred range of the highlight (for the current item)
1492 within the view. The \c preferredHighlightBegin value must be less than the
1493 \c preferredHighlightEnd value.
1494
1495 These properties affect the position of the current item when the view is scrolled.
1496 For example, if the currently selected item should stay in the middle of the
1497 view when it is scrolled, set the \c preferredHighlightBegin and
1498 \c preferredHighlightEnd values to the top and bottom coordinates of where the middle
1499 item would be. If the \c currentItem is changed programmatically, the view will
1500 automatically scroll so that the current item is in the middle of the view.
1501 Furthermore, the behavior of the current item index will occur whether or not a
1502 highlight exists.
1503
1504 Valid values for \c highlightRangeMode are:
1505
1506 \value GridView.ApplyRange the view attempts to maintain the highlight within the range.
1507 However, the highlight can move outside of the range at the ends of the view or due
1508 to mouse interaction.
1509 \value GridView.StrictlyEnforceRange the highlight never moves outside of the range.
1510 The current item changes if a keyboard or mouse action would cause the highlight to move
1511 outside of the range.
1512 \value GridView.NoHighlightRange the default value
1513*/
1514
1515
1516/*!
1517 \qmlproperty enumeration QtQuick::GridView::layoutDirection
1518 This property holds the layout direction of the grid.
1519
1520 Possible values:
1521
1522 \value Qt.LeftToRight (default) Items will be laid out starting in the top, left corner. The flow is
1523 dependent on the \l GridView::flow property.
1524 \value Qt.RightToLeft Items will be laid out starting in the top, right corner. The flow is dependent
1525 on the \l GridView::flow property.
1526
1527 \b Note: If GridView::flow is set to GridView.FlowLeftToRight, this is not to be confused if
1528 GridView::layoutDirection is set to Qt.RightToLeft. The GridView.FlowLeftToRight flow value simply
1529 indicates that the flow is horizontal.
1530
1531 \sa GridView::effectiveLayoutDirection, GridView::verticalLayoutDirection
1532*/
1533
1534
1535/*!
1536 \qmlproperty enumeration QtQuick::GridView::effectiveLayoutDirection
1537 This property holds the effective layout direction of the grid.
1538
1539 When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts,
1540 the visual layout direction of the grid will be mirrored. However, the
1541 property \l {GridView::layoutDirection}{layoutDirection} will remain unchanged.
1542
1543 \sa GridView::layoutDirection, {LayoutMirroring}{LayoutMirroring}
1544*/
1545
1546/*!
1547 \qmlproperty enumeration QtQuick::GridView::verticalLayoutDirection
1548 This property holds the vertical layout direction of the grid.
1549
1550 Possible values:
1551
1552 \value GridView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
1553 \value GridView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
1554
1555 \sa GridView::layoutDirection
1556*/
1557
1558/*!
1559 \qmlproperty bool QtQuick::GridView::keyNavigationWraps
1560 This property holds whether the grid wraps key navigation
1561
1562 If this is true, key navigation that would move the current item selection
1563 past one end of the view instead wraps around and moves the selection to
1564 the other end of the view.
1565
1566 By default, key navigation is not wrapped.
1567*/
1568
1569/*!
1570 \qmlproperty bool QtQuick::GridView::keyNavigationEnabled
1571 \since 5.7
1572
1573 This property holds whether the key navigation of the grid is enabled.
1574
1575 If this is \c true, the user can navigate the view with a keyboard.
1576 It is useful for applications that need to selectively enable or
1577 disable mouse and keyboard interaction.
1578
1579 By default, the value of this property is bound to
1580 \l {Flickable::}{interactive} to ensure behavior compatibility for
1581 existing applications. When explicitly set, it will cease to be bound to
1582 the interactive property.
1583
1584 \sa {Flickable::}{interactive}
1585*/
1586
1587/*!
1588 \qmlproperty int QtQuick::GridView::cacheBuffer
1589 This property determines whether delegates are retained outside the
1590 visible area of the view.
1591
1592 If this value is greater than zero, the view may keep as many delegates
1593 instantiated as will fit within the buffer specified. For example,
1594 if in a vertical view the delegate is 20 pixels high, there are 3
1595 columns and \c cacheBuffer is
1596 set to 40, then up to 6 delegates above and 6 delegates below the visible
1597 area may be created/retained. The buffered delegates are created asynchronously,
1598 allowing creation to occur across multiple frames and reducing the
1599 likelihood of skipping frames. In order to improve painting performance
1600 delegates outside the visible area are not painted.
1601
1602 The default value of this property is platform dependent, but will usually
1603 be a value greater than zero. Negative values are ignored.
1604
1605 Note that cacheBuffer is not a pixel buffer - it only maintains additional
1606 instantiated delegates.
1607
1608 \note Setting this property is not a replacement for creating efficient delegates.
1609 It can improve the smoothness of scrolling behavior at the expense of additional
1610 memory usage. The fewer objects and bindings in a delegate, the faster a
1611 view can be scrolled. It is important to realize that setting a cacheBuffer
1612 will only postpone issues caused by slow-loading delegates, it is not a
1613 solution for this scenario.
1614
1615 The cacheBuffer operates outside of any display margins specified by
1616 displayMarginBeginning or displayMarginEnd.
1617*/
1618
1619/*!
1620 \qmlproperty int QtQuick::GridView::displayMarginBeginning
1621 \qmlproperty int QtQuick::GridView::displayMarginEnd
1622 \since QtQuick 2.3
1623
1624 This property allows delegates to be displayed outside of the view geometry.
1625
1626 If this value is non-zero, the view will create extra delegates before the
1627 start of the view, or after the end. The view will create as many delegates
1628 as it can fit into the pixel size specified.
1629
1630 For example, if in a vertical view the delegate is 20 pixels high,
1631 there are 3 columns, and
1632 \c displayMarginBeginning and \c displayMarginEnd are both set to 40,
1633 then 6 delegates above and 6 delegates below will be created and shown.
1634
1635 The default value is 0.
1636
1637 This property is meant for allowing certain UI configurations,
1638 and not as a performance optimization. If you wish to create delegates
1639 outside of the view geometry for performance reasons, you probably
1640 want to use the cacheBuffer property instead.
1641*/
1642
1643void QQuickGridView::setHighlightMoveDuration(int duration)
1644{
1645 Q_D(QQuickGridView);
1646 if (d->highlightMoveDuration != duration) {
1647 if (d->highlightYAnimator) {
1648 d->highlightXAnimator->userDuration = duration;
1649 d->highlightYAnimator->userDuration = duration;
1650 }
1651 QQuickItemView::setHighlightMoveDuration(duration);
1652 }
1653}
1654
1655/*!
1656 \qmlproperty enumeration QtQuick::GridView::flow
1657 This property holds the flow of the grid.
1658
1659 Possible values:
1660
1661 \value GridView.FlowLeftToRight (default) Items are laid out from left to right, and the view scrolls vertically
1662 \value GridView.FlowTopToBottom Items are laid out from top to bottom, and the view scrolls horizontally
1663*/
1664QQuickGridView::Flow QQuickGridView::flow() const
1665{
1666 Q_D(const QQuickGridView);
1667 return d->flow;
1668}
1669
1670void QQuickGridView::setFlow(Flow flow)
1671{
1672 Q_D(QQuickGridView);
1673 if (d->flow != flow) {
1674 d->flow = flow;
1675 if (d->flow == FlowLeftToRight) {
1676 setContentWidth(-1);
1677 setFlickableDirection(VerticalFlick);
1678 } else {
1679 setContentHeight(-1);
1680 setFlickableDirection(HorizontalFlick);
1681 }
1682 setContentX(0);
1683 setContentY(0);
1684 d->regenerate(orientationChanged: true);
1685 emit flowChanged();
1686 }
1687}
1688
1689
1690/*!
1691 \qmlproperty real QtQuick::GridView::cellWidth
1692 \qmlproperty real QtQuick::GridView::cellHeight
1693
1694 These properties holds the width and height of each cell in the grid.
1695
1696 The default cell size is 100x100.
1697*/
1698qreal QQuickGridView::cellWidth() const
1699{
1700 Q_D(const QQuickGridView);
1701 return d->cellWidth;
1702}
1703
1704void QQuickGridView::setCellWidth(qreal cellWidth)
1705{
1706 Q_D(QQuickGridView);
1707 if (cellWidth != d->cellWidth && cellWidth > 0) {
1708 d->cellWidth = qMax(a: qreal(1), b: cellWidth);
1709 d->updateViewport();
1710 emit cellWidthChanged();
1711 d->forceLayoutPolish();
1712 QQuickFlickable::setContentX(d->contentXForPosition(pos: d->position()));
1713 }
1714}
1715
1716qreal QQuickGridView::cellHeight() const
1717{
1718 Q_D(const QQuickGridView);
1719 return d->cellHeight;
1720}
1721
1722void QQuickGridView::setCellHeight(qreal cellHeight)
1723{
1724 Q_D(QQuickGridView);
1725 if (cellHeight != d->cellHeight && cellHeight > 0) {
1726 d->cellHeight = qMax(a: qreal(1), b: cellHeight);
1727 d->updateViewport();
1728 emit cellHeightChanged();
1729 d->forceLayoutPolish();
1730 QQuickFlickable::setContentY(d->contentYForPosition(pos: d->position()));
1731 }
1732}
1733/*!
1734 \qmlproperty enumeration QtQuick::GridView::snapMode
1735
1736 This property determines how the view scrolling will settle following a drag or flick.
1737 The possible values are:
1738
1739 \value GridView.NoSnap (default) the view stops anywhere within the visible area.
1740 \value GridView.SnapToRow the view settles with a row (or column for \c GridView.FlowTopToBottom flow)
1741 aligned with the start of the view.
1742 \value GridView.SnapOneRow the view will settle no more than one row (or column for \c GridView.FlowTopToBottom flow)
1743 away from the first visible row at the time the mouse button is released.
1744 This mode is particularly useful for moving one page at a time.
1745*/
1746QQuickGridView::SnapMode QQuickGridView::snapMode() const
1747{
1748 Q_D(const QQuickGridView);
1749 return d->snapMode;
1750}
1751
1752void QQuickGridView::setSnapMode(SnapMode mode)
1753{
1754 Q_D(QQuickGridView);
1755 if (d->snapMode != mode) {
1756 d->snapMode = mode;
1757 emit snapModeChanged();
1758 }
1759}
1760
1761
1762/*!
1763 \qmlproperty Component QtQuick::GridView::footer
1764 This property holds the component to use as the footer.
1765
1766 An instance of the footer component is created for each view. The
1767 footer is positioned at the end of the view, after any items. The
1768 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
1769
1770 \sa header, footerItem
1771*/
1772/*!
1773 \qmlproperty Component QtQuick::GridView::header
1774 This property holds the component to use as the header.
1775
1776 An instance of the header component is created for each view. The
1777 header is positioned at the beginning of the view, before any items.
1778 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
1779
1780 \sa footer, headerItem
1781*/
1782
1783/*!
1784 \qmlproperty Item QtQuick::GridView::headerItem
1785 This holds the header item created from the \l header component.
1786
1787 An instance of the header component is created for each view. The
1788 header is positioned at the beginning of the view, before any items.
1789 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
1790
1791 \sa header, footerItem
1792*/
1793
1794/*!
1795 \qmlproperty Item QtQuick::GridView::footerItem
1796 This holds the footer item created from the \l footer component.
1797
1798 An instance of the footer component is created for each view. The
1799 footer is positioned at the end of the view, after any items. The
1800 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
1801
1802 \sa footer, headerItem
1803*/
1804
1805/*!
1806 \qmlproperty Transition QtQuick::GridView::populate
1807
1808 This property holds the transition to apply to the items that are initially created
1809 for a view.
1810
1811 It is applied to all items that are created when:
1812
1813 \list
1814 \li The view is first created
1815 \li The view's \l model changes in such a way that the visible delegates are completely replaced
1816 \li The view's \l model is \l {QAbstractItemModel::beginResetModel()}{reset},
1817 if the model is a QAbstractItemModel subclass
1818 \endlist
1819
1820 For example, here is a view that specifies such a transition:
1821
1822 \code
1823 GridView {
1824 ...
1825 populate: Transition {
1826 NumberAnimation { properties: "x,y"; duration: 1000 }
1827 }
1828 }
1829 \endcode
1830
1831 When the view is initialized, the view will create all the necessary items for the view,
1832 then animate them to their correct positions within the view over one second.
1833
1834 However when scrolling the view later, the populate transition does not
1835 run, even though delegates are being instantiated as they become visible.
1836 When the model changes in a way that new delegates become visible, the
1837 \l add transition is the one that runs. So you should not depend on the
1838 \c populate transition to initialize properties in the delegate, because it
1839 does not apply to every delegate. If your animation sets the \c to value of
1840 a property, the property should initially have the \c to value, and the
1841 animation should set the \c from value in case it is animated:
1842
1843 \code
1844 GridView {
1845 ...
1846 delegate: Rectangle {
1847 opacity: 1 // not necessary because it's the default; but don't set 0
1848 ...
1849 }
1850 populate: Transition {
1851 NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
1852 }
1853 }
1854 \endcode
1855
1856 For more details and examples on how to use view transitions, see the ViewTransition
1857 documentation.
1858
1859 \sa add, ViewTransition
1860*/
1861
1862/*!
1863 \qmlproperty Transition QtQuick::GridView::add
1864
1865 This property holds the transition to apply to items that are added to the view.
1866
1867 For example, here is a view that specifies such a transition:
1868
1869 \code
1870 GridView {
1871 ...
1872 add: Transition {
1873 NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
1874 }
1875 }
1876 \endcode
1877
1878 Whenever an item is added to the above view, the item will be animated from the position (100,100)
1879 to its final x,y position within the view, over one second. The transition only applies to
1880 the new items that are added to the view; it does not apply to the items below that are
1881 displaced by the addition of the new items. To animate the displaced items, set the \l displaced
1882 or \l addDisplaced properties.
1883
1884 For more details and examples on how to use view transitions, see the ViewTransition
1885 documentation.
1886
1887 \note This transition is not applied to the items that are created when the view is initially
1888 populated, or when the view's \l model changes. (In those cases, the \l populate transition is
1889 applied instead.) Additionally, this transition should \e not animate the height of the new item;
1890 doing so will cause any items beneath the new item to be laid out at the wrong position. Instead,
1891 the height can be animated within the \l {add}{onAdd} handler in the delegate.
1892
1893 \sa addDisplaced, populate, ViewTransition
1894*/
1895
1896/*!
1897 \qmlproperty Transition QtQuick::GridView::addDisplaced
1898
1899 This property holds the transition to apply to items within the view that are displaced by
1900 the addition of other items to the view.
1901
1902 For example, here is a view that specifies such a transition:
1903
1904 \code
1905 GridView {
1906 ...
1907 addDisplaced: Transition {
1908 NumberAnimation { properties: "x,y"; duration: 1000 }
1909 }
1910 }
1911 \endcode
1912
1913 Whenever an item is added to the above view, all items beneath the new item are displaced, causing
1914 them to move down (or sideways, if horizontally orientated) within the view. As this
1915 displacement occurs, the items' movement to their new x,y positions within the view will be
1916 animated by a NumberAnimation over one second, as specified. This transition is not applied to
1917 the new item that has been added to the view; to animate the added items, set the \l add
1918 property.
1919
1920 If an item is displaced by multiple types of operations at the same time, it is not defined as to
1921 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
1922 if it is not necessary to specify different transitions depending on whether an item is displaced
1923 by an add, move or remove operation, consider setting the \l displaced property instead.
1924
1925 For more details and examples on how to use view transitions, see the ViewTransition
1926 documentation.
1927
1928 \note This transition is not applied to the items that are created when the view is initially
1929 populated, or when the view's \l model changes. In those cases, the \l populate transition is
1930 applied instead.
1931
1932 \sa displaced, add, populate, ViewTransition
1933*/
1934/*!
1935 \qmlproperty Transition QtQuick::GridView::move
1936
1937 This property holds the transition to apply to items in the view that are being moved due
1938 to a move operation in the view's \l model.
1939
1940 For example, here is a view that specifies such a transition:
1941
1942 \code
1943 GridView {
1944 ...
1945 move: Transition {
1946 NumberAnimation { properties: "x,y"; duration: 1000 }
1947 }
1948 }
1949 \endcode
1950
1951 Whenever the \l model performs a move operation to move a particular set of indexes, the
1952 respective items in the view will be animated to their new positions in the view over one
1953 second. The transition only applies to the items that are the subject of the move operation
1954 in the model; it does not apply to items below them that are displaced by the move operation.
1955 To animate the displaced items, set the \l displaced or \l moveDisplaced properties.
1956
1957 For more details and examples on how to use view transitions, see the ViewTransition
1958 documentation.
1959
1960 \sa moveDisplaced, ViewTransition
1961*/
1962
1963/*!
1964 \qmlproperty Transition QtQuick::GridView::moveDisplaced
1965
1966 This property holds the transition to apply to items that are displaced by a move operation in
1967 the view's \l model.
1968
1969 For example, here is a view that specifies such a transition:
1970
1971 \code
1972 GridView {
1973 ...
1974 moveDisplaced: Transition {
1975 NumberAnimation { properties: "x,y"; duration: 1000 }
1976 }
1977 }
1978 \endcode
1979
1980 Whenever the \l model performs a move operation to move a particular set of indexes, the items
1981 between the source and destination indexes of the move operation are displaced, causing them
1982 to move upwards or downwards (or sideways, if horizontally orientated) within the view. As this
1983 displacement occurs, the items' movement to their new x,y positions within the view will be
1984 animated by a NumberAnimation over one second, as specified. This transition is not applied to
1985 the items that are the actual subjects of the move operation; to animate the moved items, set
1986 the \l move property.
1987
1988 If an item is displaced by multiple types of operations at the same time, it is not defined as to
1989 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
1990 if it is not necessary to specify different transitions depending on whether an item is displaced
1991 by an add, move or remove operation, consider setting the \l displaced property instead.
1992
1993 For more details and examples on how to use view transitions, see the ViewTransition
1994 documentation.
1995
1996 \sa displaced, move, ViewTransition
1997*/
1998
1999/*!
2000 \qmlproperty Transition QtQuick::GridView::remove
2001
2002 This property holds the transition to apply to items that are removed from the view.
2003
2004 For example, here is a view that specifies such a transition:
2005
2006 \code
2007 GridView {
2008 ...
2009 remove: Transition {
2010 ParallelAnimation {
2011 NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
2012 NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
2013 }
2014 }
2015 }
2016 \endcode
2017
2018 Whenever an item is removed from the above view, the item will be animated to the position (100,100)
2019 over one second, and in parallel will also change its opacity to 0. The transition
2020 only applies to the items that are removed from the view; it does not apply to the items below
2021 them that are displaced by the removal of the items. To animate the displaced items, set the
2022 \l displaced or \l removeDisplaced properties.
2023
2024 Note that by the time the transition is applied, the item has already been removed from the
2025 model; any references to the model data for the removed index will not be valid.
2026
2027 Additionally, if the \l delayRemove attached property has been set for a delegate item, the
2028 remove transition will not be applied until \l delayRemove becomes false again.
2029
2030 For more details and examples on how to use view transitions, see the ViewTransition
2031 documentation.
2032
2033 \sa removeDisplaced, ViewTransition
2034*/
2035
2036/*!
2037 \qmlproperty Transition QtQuick::GridView::removeDisplaced
2038
2039 This property holds the transition to apply to items in the view that are displaced by the
2040 removal of other items in the view.
2041
2042 For example, here is a view that specifies such a transition:
2043
2044 \code
2045 GridView {
2046 ...
2047 removeDisplaced: Transition {
2048 NumberAnimation { properties: "x,y"; duration: 1000 }
2049 }
2050 }
2051 \endcode
2052
2053 Whenever an item is removed from the above view, all items beneath it are displaced, causing
2054 them to move upwards (or sideways, if horizontally orientated) within the view. As this
2055 displacement occurs, the items' movement to their new x,y positions within the view will be
2056 animated by a NumberAnimation over one second, as specified. This transition is not applied to
2057 the item that has actually been removed from the view; to animate the removed items, set the
2058 \l remove property.
2059
2060 If an item is displaced by multiple types of operations at the same time, it is not defined as to
2061 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
2062 if it is not necessary to specify different transitions depending on whether an item is displaced
2063 by an add, move or remove operation, consider setting the \l displaced property instead.
2064
2065 For more details and examples on how to use view transitions, see the ViewTransition
2066 documentation.
2067
2068 \sa displaced, remove, ViewTransition
2069*/
2070
2071/*!
2072 \qmlproperty Transition QtQuick::GridView::displaced
2073 This property holds the generic transition to apply to items that have been displaced by
2074 any model operation that affects the view.
2075
2076 This is a convenience for specifying a generic transition for items that are displaced
2077 by add, move or remove operations, without having to specify the individual addDisplaced,
2078 moveDisplaced and removeDisplaced properties. For example, here is a view that specifies
2079 a displaced transition:
2080
2081 \code
2082 GridView {
2083 ...
2084 displaced: Transition {
2085 NumberAnimation { properties: "x,y"; duration: 1000 }
2086 }
2087 }
2088 \endcode
2089
2090 When any item is added, moved or removed within the above view, the items below it are
2091 displaced, causing them to move down (or sideways, if horizontally orientated) within the
2092 view. As this displacement occurs, the items' movement to their new x,y positions within
2093 the view will be animated by a NumberAnimation over one second, as specified.
2094
2095 If a view specifies this generic displaced transition as well as a specific addDisplaced,
2096 moveDisplaced or removeDisplaced transition, the more specific transition will be used
2097 instead of the generic displaced transition when the relevant operation occurs, providing that
2098 the more specific transition has not been disabled (by setting \l {Transition::enabled}{enabled}
2099 to false). If it has indeed been disabled, the generic displaced transition is applied instead.
2100
2101 For more details and examples on how to use view transitions, see the ViewTransition
2102 documentation.
2103
2104 \sa addDisplaced, moveDisplaced, removeDisplaced, ViewTransition
2105*/
2106
2107void QQuickGridView::viewportMoved(Qt::Orientations orient)
2108{
2109 Q_D(QQuickGridView);
2110 QQuickItemView::viewportMoved(orient);
2111 if (!d->itemCount)
2112 return;
2113 if (d->inViewportMoved)
2114 return;
2115 d->inViewportMoved = true;
2116
2117 if (yflick()) {
2118 if (d->isContentFlowReversed())
2119 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore;
2120 else
2121 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter;
2122 } else {
2123 if (d->isContentFlowReversed())
2124 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore;
2125 else
2126 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter;
2127 }
2128
2129 d->refillOrLayout();
2130
2131 // Set visibility of items to eliminate cost of items outside the visible area.
2132 qreal from = d->isContentFlowReversed() ? -d->position()-d->displayMarginBeginning-d->size() : d->position()-d->displayMarginBeginning;
2133 qreal to = d->isContentFlowReversed() ? -d->position()+d->displayMarginEnd : d->position()+d->size()+d->displayMarginEnd;
2134 for (FxViewItem *item : std::as_const(t&: d->visibleItems)) {
2135 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(item);
2136 QQuickItemPrivate::get(item: gridItem->item)->setCulled(gridItem->rowPos() + d->rowSize() < from || gridItem->rowPos() > to);
2137 }
2138 if (d->currentItem) {
2139 FxGridItemSG *item = static_cast<FxGridItemSG*>(d->currentItem);
2140 QQuickItemPrivate::get(item: item->item)->setCulled(item->rowPos() + d->rowSize() < from || item->rowPos() > to);
2141 }
2142
2143 if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving)
2144 d->moveReason = QQuickGridViewPrivate::Mouse;
2145 if (d->moveReason != QQuickGridViewPrivate::SetIndex) {
2146 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) {
2147 // reposition highlight
2148 qreal pos = d->highlight->position();
2149 qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position();
2150 if (pos > viewPos + d->highlightRangeEnd - d->highlight->size())
2151 pos = viewPos + d->highlightRangeEnd - d->highlight->size();
2152 if (pos < viewPos + d->highlightRangeStart)
2153 pos = viewPos + d->highlightRangeStart;
2154
2155 if (pos != d->highlight->position()) {
2156 d->highlightXAnimator->stop();
2157 d->highlightYAnimator->stop();
2158 FxGridItemSG *sgHighlight = static_cast<FxGridItemSG *>(d->highlight.get());
2159 sgHighlight->setPosition(col: sgHighlight->colPos(), row: pos);
2160 } else {
2161 d->updateHighlight();
2162 }
2163
2164 // update current index
2165 int idx = d->snapIndex();
2166 if (idx >= 0 && idx != d->currentIndex) {
2167 d->updateCurrent(modelIndex: idx);
2168 if (d->currentItem
2169 && static_cast<FxGridItemSG*>(d->currentItem)->colPos()
2170 != static_cast<FxGridItemSG*>(d->highlight.get())->colPos()
2171 && d->autoHighlight) {
2172 if (d->flow == FlowLeftToRight)
2173 d->highlightXAnimator->to = d->currentItem->itemX();
2174 else
2175 d->highlightYAnimator->to = d->currentItem->itemY();
2176 }
2177 }
2178 }
2179 }
2180
2181 d->inViewportMoved = false;
2182}
2183
2184void QQuickGridView::keyPressEvent(QKeyEvent *event)
2185{
2186 Q_D(QQuickGridView);
2187 if (d->model && d->model->count() && ((d->interactive && !d->explicitKeyNavigationEnabled)
2188 || (d->explicitKeyNavigationEnabled && d->keyNavigationEnabled))) {
2189 d->moveReason = QQuickGridViewPrivate::SetIndex;
2190 int oldCurrent = currentIndex();
2191 switch (event->key()) {
2192 case Qt::Key_Up:
2193 moveCurrentIndexUp();
2194 break;
2195 case Qt::Key_Down:
2196 moveCurrentIndexDown();
2197 break;
2198 case Qt::Key_Left:
2199 moveCurrentIndexLeft();
2200 break;
2201 case Qt::Key_Right:
2202 moveCurrentIndexRight();
2203 break;
2204 default:
2205 break;
2206 }
2207 if (oldCurrent != currentIndex() || d->wrap) {
2208 event->accept();
2209 return;
2210 }
2211 }
2212 event->ignore();
2213 QQuickItemView::keyPressEvent(event);
2214}
2215
2216void QQuickGridView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2217{
2218 Q_D(QQuickGridView);
2219 d->resetColumns();
2220
2221 if (newGeometry.width() != oldGeometry.width()
2222 && newGeometry.height() != oldGeometry.height()) {
2223 d->setPosition(d->position());
2224 } else if (newGeometry.width() != oldGeometry.width()) {
2225 QQuickFlickable::setContentX(d->contentXForPosition(pos: d->position()));
2226 } else if (newGeometry.height() != oldGeometry.height()) {
2227 QQuickFlickable::setContentY(d->contentYForPosition(pos: d->position()));
2228 }
2229
2230 QQuickItemView::geometryChange(newGeometry, oldGeometry);
2231}
2232
2233void QQuickGridView::initItem(int index, QObject *obj)
2234{
2235 QQuickItemView::initItem(index, item: obj);
2236
2237 // setting the view from the FxViewItem wrapper is too late if the delegate
2238 // needs access to the view in Component.onCompleted
2239 QQuickItem *item = qmlobject_cast<QQuickItem*>(object: obj);
2240 if (item) {
2241 QQuickGridViewAttached *attached = static_cast<QQuickGridViewAttached *>(
2242 qmlAttachedPropertiesObject<QQuickGridView>(obj: item));
2243 if (attached)
2244 attached->setView(this);
2245 }
2246}
2247
2248/*!
2249 \qmlmethod QtQuick::GridView::moveCurrentIndexUp()
2250
2251 Move the currentIndex up one item in the view.
2252 The current index will wrap if keyNavigationWraps is true and it
2253 is currently at the end. This method has no effect if the \l count is zero.
2254
2255 \b Note: methods should only be called after the Component has completed.
2256*/
2257
2258
2259void QQuickGridView::moveCurrentIndexUp()
2260{
2261 Q_D(QQuickGridView);
2262 const int count = d->model ? d->model->count() : 0;
2263 if (!count)
2264 return;
2265 if (d->verticalLayoutDirection == QQuickItemView::TopToBottom) {
2266 if (d->flow == QQuickGridView::FlowLeftToRight) {
2267 if (currentIndex() >= d->columns || d->wrap) {
2268 int index = currentIndex() - d->columns;
2269 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2270 }
2271 } else {
2272 if (currentIndex() > 0 || d->wrap) {
2273 int index = currentIndex() - 1;
2274 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2275 }
2276 }
2277 } else {
2278 if (d->flow == QQuickGridView::FlowLeftToRight) {
2279 if (currentIndex() < count - d->columns || d->wrap) {
2280 int index = currentIndex()+d->columns;
2281 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2282 }
2283 } else {
2284 if (currentIndex() < count - 1 || d->wrap) {
2285 int index = currentIndex() + 1;
2286 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2287 }
2288 }
2289 }
2290}
2291
2292/*!
2293 \qmlmethod QtQuick::GridView::moveCurrentIndexDown()
2294
2295 Move the currentIndex down one item in the view.
2296 The current index will wrap if keyNavigationWraps is true and it
2297 is currently at the end. This method has no effect if the \l count is zero.
2298
2299 \b Note: methods should only be called after the Component has completed.
2300*/
2301void QQuickGridView::moveCurrentIndexDown()
2302{
2303 Q_D(QQuickGridView);
2304 const int count = d->model ? d->model->count() : 0;
2305 if (!count)
2306 return;
2307
2308 if (d->verticalLayoutDirection == QQuickItemView::TopToBottom) {
2309 if (d->flow == QQuickGridView::FlowLeftToRight) {
2310 if (currentIndex() < count - d->columns || d->wrap) {
2311 int index = currentIndex()+d->columns;
2312 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2313 }
2314 } else {
2315 if (currentIndex() < count - 1 || d->wrap) {
2316 int index = currentIndex() + 1;
2317 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2318 }
2319 }
2320 } else {
2321 if (d->flow == QQuickGridView::FlowLeftToRight) {
2322 if (currentIndex() >= d->columns || d->wrap) {
2323 int index = currentIndex() - d->columns;
2324 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2325 }
2326 } else {
2327 if (currentIndex() > 0 || d->wrap) {
2328 int index = currentIndex() - 1;
2329 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2330 }
2331 }
2332 }
2333}
2334
2335/*!
2336 \qmlmethod QtQuick::GridView::moveCurrentIndexLeft()
2337
2338 Move the currentIndex left one item in the view.
2339 The current index will wrap if keyNavigationWraps is true and it
2340 is currently at the end. This method has no effect if the \l count is zero.
2341
2342 \b Note: methods should only be called after the Component has completed.
2343*/
2344void QQuickGridView::moveCurrentIndexLeft()
2345{
2346 Q_D(QQuickGridView);
2347 const int count = d->model ? d->model->count() : 0;
2348 if (!count)
2349 return;
2350 if (effectiveLayoutDirection() == Qt::LeftToRight) {
2351 if (d->flow == QQuickGridView::FlowLeftToRight) {
2352 if (currentIndex() > 0 || d->wrap) {
2353 int index = currentIndex() - 1;
2354 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2355 }
2356 } else {
2357 if (currentIndex() >= d->columns || d->wrap) {
2358 int index = currentIndex() - d->columns;
2359 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2360 }
2361 }
2362 } else {
2363 if (d->flow == QQuickGridView::FlowLeftToRight) {
2364 if (currentIndex() < count - 1 || d->wrap) {
2365 int index = currentIndex() + 1;
2366 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2367 }
2368 } else {
2369 if (currentIndex() < count - d->columns || d->wrap) {
2370 int index = currentIndex() + d->columns;
2371 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2372 }
2373 }
2374 }
2375}
2376
2377
2378/*!
2379 \qmlmethod QtQuick::GridView::moveCurrentIndexRight()
2380
2381 Move the currentIndex right one item in the view.
2382 The current index will wrap if keyNavigationWraps is true and it
2383 is currently at the end. This method has no effect if the \l count is zero.
2384
2385 \b Note: methods should only be called after the Component has completed.
2386*/
2387void QQuickGridView::moveCurrentIndexRight()
2388{
2389 Q_D(QQuickGridView);
2390 const int count = d->model ? d->model->count() : 0;
2391 if (!count)
2392 return;
2393 if (effectiveLayoutDirection() == Qt::LeftToRight) {
2394 if (d->flow == QQuickGridView::FlowLeftToRight) {
2395 if (currentIndex() < count - 1 || d->wrap) {
2396 int index = currentIndex() + 1;
2397 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2398 }
2399 } else {
2400 if (currentIndex() < count - d->columns || d->wrap) {
2401 int index = currentIndex()+d->columns;
2402 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2403 }
2404 }
2405 } else {
2406 if (d->flow == QQuickGridView::FlowLeftToRight) {
2407 if (currentIndex() > 0 || d->wrap) {
2408 int index = currentIndex() - 1;
2409 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2410 }
2411 } else {
2412 if (currentIndex() >= d->columns || d->wrap) {
2413 int index = currentIndex() - d->columns;
2414 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2415 }
2416 }
2417 }
2418}
2419
2420bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
2421{
2422 Q_Q(QQuickGridView);
2423
2424 if (q->size().isNull())
2425 return false;
2426
2427 int modelIndex = change.index;
2428 int count = change.count;
2429
2430 int index = visibleItems.size() ? mapFromModel(modelIndex) : 0;
2431
2432 if (index < 0) {
2433 int i = visibleItems.size() - 1;
2434 while (i > 0 && visibleItems.at(i)->index == -1)
2435 --i;
2436 if (visibleItems.at(i)->index + 1 == modelIndex) {
2437 // Special case of appending an item to the model.
2438 index = visibleItems.size();
2439 } else {
2440 if (modelIndex <= visibleIndex) {
2441 // Insert before visible items
2442 visibleIndex += count;
2443 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
2444 if (item->index != -1 && item->index >= modelIndex)
2445 item->index += count;
2446 }
2447 }
2448 return true;
2449 }
2450 }
2451
2452 qreal tempPos = isContentFlowReversed() ? -position()-size()+q->width()+1 : position();
2453 qreal colPos = 0;
2454 qreal rowPos = 0;
2455 int colNum = 0;
2456 if (visibleItems.size()) {
2457 if (index < visibleItems.size()) {
2458 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(i: index));
2459 colPos = gridItem->colPos();
2460 rowPos = gridItem->rowPos();
2461 colNum = qFloor(v: (colPos+colSize()/2) / colSize());
2462 } else {
2463 // appending items to visible list
2464 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(i: index-1));
2465 rowPos = gridItem->rowPos();
2466 colNum = qFloor(v: (gridItem->colPos()+colSize()/2) / colSize());
2467 if (++colNum >= columns) {
2468 colNum = 0;
2469 rowPos += rowSize();
2470 }
2471 colPos = colNum * colSize();
2472 }
2473 }
2474
2475#if QT_CONFIG(quick_viewtransitions)
2476 // Update the indexes of the following visible items.
2477 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
2478 if (item->index != -1 && item->index >= modelIndex) {
2479 item->index += count;
2480 if (change.isMove())
2481 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: false);
2482 else
2483 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: false);
2484 }
2485 }
2486#endif
2487
2488 int prevVisibleCount = visibleItems.size();
2489 if (insertResult->visiblePos.isValid() && rowPos < insertResult->visiblePos) {
2490 // Insert items before the visible item.
2491 int insertionIdx = index;
2492 int i = count - 1;
2493 int from = tempPos - buffer - displayMarginBeginning;
2494
2495 if (rowPos > from && insertionIdx < visibleIndex) {
2496 // items won't be visible, just note the size for repositioning
2497 insertResult->countChangeBeforeVisible += count;
2498 insertResult->sizeChangesBeforeVisiblePos += ((count + columns - 1) / columns) * rowSize();
2499 } else {
2500 while (i >= 0) {
2501 // item is before first visible e.g. in cache buffer
2502 FxViewItem *item = nullptr;
2503 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: modelIndex + i))))
2504 item->index = modelIndex + i;
2505 if (!item)
2506 item = createItem(modelIndex: modelIndex + i, incubationMode: QQmlIncubator::Synchronous);
2507 if (!item)
2508 return false;
2509
2510 QQuickItemPrivate::get(item: item->item)->setCulled(false);
2511 visibleItems.insert(i: insertionIdx, t: item);
2512 if (insertionIdx == 0)
2513 insertResult->changedFirstItem = true;
2514 if (!change.isMove()) {
2515 addedItems->append(t: item);
2516#if QT_CONFIG(quick_viewtransitions)
2517 if (transitioner)
2518 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
2519 else
2520#endif
2521 item->moveTo(pos: QPointF(colPos, rowPos), immediate: true);
2522 }
2523 insertResult->sizeChangesBeforeVisiblePos += rowSize();
2524
2525 if (--colNum < 0 ) {
2526 colNum = columns - 1;
2527 rowPos -= rowSize();
2528 }
2529 colPos = colNum * colSize();
2530 index++;
2531 i--;
2532 }
2533 }
2534
2535 // There may be gaps in the index sequence of visibleItems because
2536 // of the index shift/update done before the insertion just above.
2537 // Find if there is any...
2538 int firstOkIdx = -1;
2539 for (int i = 0; i <= insertionIdx && i < visibleItems.size() - 1; i++) {
2540 if (visibleItems.at(i)->index + 1 != visibleItems.at(i: i + 1)->index) {
2541 firstOkIdx = i + 1;
2542 break;
2543 }
2544 }
2545 // ... and remove all the items before that one
2546 for (int i = 0; i < firstOkIdx; i++) {
2547 FxViewItem *nvItem = visibleItems.takeFirst();
2548 addedItems->removeOne(t: nvItem);
2549 removeItem(item: nvItem);
2550 }
2551
2552 } else {
2553 int i = 0;
2554 int to = buffer+displayMarginEnd+tempPos+size()-1;
2555 while (i < count && rowPos <= to + rowSize()*(columns - colNum)/qreal(columns+1)) {
2556 FxViewItem *item = nullptr;
2557 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: modelIndex + i))))
2558 item->index = modelIndex + i;
2559 bool newItem = !item;
2560 if (!item)
2561 item = createItem(modelIndex: modelIndex + i, incubationMode: QQmlIncubator::Synchronous);
2562 if (!item)
2563 return false;
2564
2565 QQuickItemPrivate::get(item: item->item)->setCulled(false);
2566 visibleItems.insert(i: index, t: item);
2567 if (index == 0)
2568 insertResult->changedFirstItem = true;
2569 if (change.isMove()) {
2570 // we know this is a move target, since move displaced items that are
2571 // shuffled into view due to a move would be added in refill()
2572 if (newItem
2573#if QT_CONFIG(quick_viewtransitions)
2574 && transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::MoveTransition, asTarget: true)
2575#endif
2576 )
2577 movingIntoView->append(t: MovedItem(item, change.moveKey(index: item->index)));
2578 } else {
2579 addedItems->append(t: item);
2580#if QT_CONFIG(quick_viewtransitions)
2581 if (transitioner)
2582 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
2583 else
2584#endif
2585 item->moveTo(pos: QPointF(colPos, rowPos), immediate: true);
2586 }
2587 insertResult->sizeChangesAfterVisiblePos += rowSize();
2588
2589 if (++colNum >= columns) {
2590 colNum = 0;
2591 rowPos += rowSize();
2592 }
2593 colPos = colNum * colSize();
2594 ++index;
2595 ++i;
2596 }
2597 }
2598
2599 updateVisibleIndex();
2600
2601 return visibleItems.size() > prevVisibleCount;
2602}
2603
2604#if QT_CONFIG(quick_viewtransitions)
2605void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
2606{
2607 if (!transitioner)
2608 return;
2609
2610 int markerItemIndex = -1;
2611 for (int i=0; i<visibleItems.size(); i++) {
2612 if (visibleItems.at(i)->index == afterModelIndex) {
2613 markerItemIndex = i;
2614 break;
2615 }
2616 }
2617 if (markerItemIndex < 0)
2618 return;
2619
2620 const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
2621 int countItemsRemoved = -(removalResult.sizeChangesAfterVisiblePos / rowSize());
2622
2623 // account for whether first item has changed if < 1 row was removed before visible
2624 int changeBeforeVisible = insertionResult.countChangeBeforeVisible - removalResult.countChangeBeforeVisible;
2625 if (changeBeforeVisible != 0)
2626 countItemsRemoved += (changeBeforeVisible % columns) - (columns - 1);
2627
2628 countItemsRemoved -= removalResult.countChangeAfterVisibleItems;
2629
2630 for (int i=markerItemIndex+1; i<visibleItems.size(); i++) {
2631 FxGridItemSG *gridItem = static_cast<FxGridItemSG *>(visibleItems.at(i));
2632 if (gridItem->position() >= viewEndPos)
2633 break;
2634 if (!gridItem->transitionScheduledOrRunning()) {
2635 qreal origRowPos = gridItem->colPos();
2636 qreal origColPos = gridItem->rowPos();
2637 int indexDiff = gridItem->index - countItemsRemoved;
2638 gridItem->setPosition(col: (indexDiff % columns) * colSize(), row: (indexDiff / columns) * rowSize());
2639 gridItem->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
2640 gridItem->setPosition(col: origRowPos, row: origColPos);
2641 }
2642 }
2643}
2644#endif
2645
2646bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const
2647{
2648 // If we add or remove items before visible items, a layout may be
2649 // required to ensure item 0 is in the first column.
2650 return modelIndex < visibleIndex;
2651}
2652
2653/*!
2654 \qmlmethod QtQuick::GridView::positionViewAtIndex(int index, PositionMode mode)
2655
2656 Positions the view such that the \a index is at the position specified by
2657 \a mode:
2658
2659 \value GridView.Beginning position item at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
2660 \value GridView.Center position item in the center of the view.
2661 \value GridView.End position item at bottom (or right for horizontal orientation) of the view.
2662 \value GridView.Visible if any part of the item is visible then take no action, otherwise
2663 bring the item into view.
2664 \value GridView.Contain ensure the entire item is visible. If the item is larger than the view, the item
2665 is positioned at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
2666 \value GridView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid if
2667 \l highlightRangeMode is \c StrictlyEnforceRange or snapping is enabled via \l snapMode.
2668
2669 If positioning the view at the index would cause empty space to be displayed at
2670 the beginning or end of the view, the view will be positioned at the boundary.
2671
2672 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
2673 at a particular index. This is unreliable since removing items from the start
2674 of the view does not cause all other items to be repositioned.
2675 The correct way to bring an item into view is with \c positionViewAtIndex.
2676
2677 \b Note: methods should only be called after the Component has completed. To position
2678 the view at startup, this method should be called by Component.onCompleted. For
2679 example, to position the view at the end:
2680
2681 \code
2682 Component.onCompleted: positionViewAtIndex(count - 1, GridView.Beginning)
2683 \endcode
2684*/
2685
2686/*!
2687 \qmlmethod QtQuick::GridView::positionViewAtBeginning()
2688 \qmlmethod QtQuick::GridView::positionViewAtEnd()
2689
2690 Positions the view at the beginning or end, taking into account any header or footer.
2691
2692 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
2693 at a particular index. This is unreliable since removing items from the start
2694 of the list does not cause all other items to be repositioned, and because
2695 the actual start of the view can vary based on the size of the delegates.
2696
2697 \b Note: methods should only be called after the Component has completed. To position
2698 the view at startup, this method should be called by Component.onCompleted. For
2699 example, to position the view at the end on startup:
2700
2701 \code
2702 Component.onCompleted: positionViewAtEnd()
2703 \endcode
2704*/
2705
2706/*!
2707 \qmlmethod int QtQuick::GridView::indexAt(real x, real y)
2708
2709 Returns the index of the visible item containing the point \a x, \a y in
2710 \l {QQuickFlickable::contentItem}{content item} coordinates. If there is
2711 no item at the point specified, or the item is not visible -1 is returned.
2712
2713 If the item is outside the visible area, -1 is returned, regardless of
2714 whether an item will exist at that point when scrolled into view.
2715
2716 \note if you add a MouseArea as a child of the GridView, it will return
2717 positions in GridView coordinates rather than content item coordinates.
2718 To use those positions in a call to this function, you need to map them
2719 first:
2720
2721 \code
2722 GridView {
2723 id: view
2724 MouseArea {
2725 anchors.fill: parent
2726 onClicked: (mouse) => {
2727 let posInGridView = Qt.point(mouse.x, mouse.y)
2728 let posInContentItem = mapToItem(view.contentItem, posInGridView)
2729 let index = view.indexAt(posInContentItem.x, posInContentItem.y)
2730 }
2731 }
2732 }
2733 \endcode
2734
2735 \b Note: methods should only be called after the Component has completed.
2736
2737 \sa itemAt
2738*/
2739
2740/*!
2741 \qmlmethod Item QtQuick::GridView::itemAt(real x, real y)
2742
2743 Returns the visible item containing the point \a x, \a y in
2744 \l {QQuickFlickable::contentItem}{content item} coordinates. If there
2745 is no item at the point specified, or the item is not visible null is returned.
2746
2747 If the item is outside the visible area, null is returned, regardless of
2748 whether an item will exist at that point when scrolled into view.
2749
2750 \b Note: methods should only be called after the Component has completed.
2751
2752 \sa indexAt
2753*/
2754
2755/*!
2756 \qmlmethod Item QtQuick::GridView::itemAtIndex(int index)
2757
2758 Returns the item for \a index. If there is no item for that index, for example
2759 because it has not been created yet, or because it has been panned out of
2760 the visible area and removed from the cache, null is returned.
2761
2762 \b Note: this method should only be called after the Component has completed.
2763 The returned value should also not be stored since it can turn to null
2764 as soon as control goes out of the calling scope, if the view releases that item.
2765
2766 \since 5.13
2767*/
2768
2769/*!
2770 \qmlmethod QtQuick::GridView::forceLayout()
2771
2772 Responding to changes in the model is usually batched to happen only once
2773 per frame. This means that inside script blocks it is possible for the
2774 underlying model to have changed, but the GridView has not caught up yet.
2775
2776 This method forces the GridView to immediately respond to any outstanding
2777 changes in the model.
2778
2779 \since 5.1
2780
2781 \b Note: methods should only be called after the Component has completed.
2782*/
2783
2784QQuickGridViewAttached *QQuickGridView::qmlAttachedProperties(QObject *obj)
2785{
2786 return new QQuickGridViewAttached(obj);
2787}
2788
2789QT_END_NAMESPACE
2790
2791#include "moc_qquickgridview_p.cpp"
2792

source code of qtdeclarative/src/quick/items/qquickgridview.cpp