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

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