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 \nativetype 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 \readonly
1270
1271 This attached property is true if this delegate is the current item; otherwise false.
1272
1273 It is attached to each instance of the delegate.
1274
1275 \snippet qml/gridview/gridview.qml isCurrentItem
1276*/
1277
1278/*!
1279 \qmlattachedproperty GridView QtQuick::GridView::view
1280 \readonly
1281
1282 This attached property holds the view that manages this delegate instance.
1283
1284 It is attached to each instance of the delegate and also to the header, the footer
1285 and the highlight delegates.
1286*/
1287
1288/*!
1289 \qmlattachedproperty bool QtQuick::GridView::delayRemove
1290
1291 This attached property holds whether the delegate may be destroyed. It
1292 is attached to each instance of the delegate. The default value is false.
1293
1294 It is sometimes necessary to delay the destruction of an item
1295 until an animation completes. The example delegate below ensures that the
1296 animation completes before the item is removed from the list.
1297
1298 \snippet qml/gridview/gridview.qml delayRemove
1299
1300 If a \l remove transition has been specified, it will not be applied until
1301 delayRemove is returned to \c false.
1302*/
1303
1304/*!
1305 \qmlattachedsignal QtQuick::GridView::add()
1306 This attached signal is emitted immediately after an item is added to the view.
1307*/
1308
1309/*!
1310 \qmlattachedsignal QtQuick::GridView::remove()
1311 This attached signal is emitted immediately before an item is removed from the view.
1312
1313 If a \l remove transition has been specified, it is applied after
1314 this signal is handled, providing that \l delayRemove is false.
1315*/
1316
1317
1318/*!
1319 \qmlproperty model QtQuick::GridView::model
1320 This property holds the model providing data for the grid.
1321
1322 The model provides the set of data that is used to create the items
1323 in the view. Models can be created directly in QML using \l ListModel,
1324 \l DelegateModel, \l ObjectModel, or provided by C++ model classes.
1325 If a C++ model class is used, it must be a subclass of
1326 \l QAbstractItemModel or a simple list.
1327
1328 \sa {qml-data-models}{Data Models}
1329*/
1330
1331/*!
1332 \qmlproperty Component QtQuick::GridView::delegate
1333
1334 The delegate provides a template defining each item instantiated by the view.
1335 The index is exposed as an accessible \c index property. Properties of the
1336 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
1337
1338 The number of objects and bindings in the delegate has a direct effect on the
1339 flicking performance of the view. If at all possible, place functionality
1340 that is not needed for the normal display of the delegate in a \l Loader which
1341 can load additional components when needed.
1342
1343 The item size of the GridView is determined by cellHeight and cellWidth. It will not resize the items
1344 based on the size of the root item in the delegate.
1345
1346 The default \l {QQuickItem::z}{stacking order} of delegate instances is \c 1.
1347
1348 \note Delegates are instantiated as needed and may be destroyed at any time.
1349 State should \e never be stored in a delegate.
1350*/
1351
1352/*!
1353 \qmlproperty int QtQuick::GridView::currentIndex
1354 \qmlproperty Item QtQuick::GridView::currentItem
1355
1356 The \c currentIndex property holds the index of the current item, and
1357 \c currentItem holds the current item. Setting the currentIndex to -1
1358 will clear the highlight and set currentItem to null.
1359
1360 If highlightFollowsCurrentItem is \c true, setting either of these
1361 properties will smoothly scroll the GridView so that the current
1362 item becomes visible.
1363
1364 Note that the position of the current item
1365 may only be approximate until it becomes visible in the view.
1366*/
1367
1368
1369/*!
1370 \qmlproperty Item QtQuick::GridView::highlightItem
1371
1372 This holds the highlight item created from the \l highlight component.
1373
1374 The highlightItem is managed by the view unless
1375 \l highlightFollowsCurrentItem is set to false.
1376 The default \l {QQuickItem::z}{stacking order}
1377 of the highlight item is \c 0.
1378
1379 \sa highlight, highlightFollowsCurrentItem
1380*/
1381
1382
1383/*!
1384 \qmlproperty int QtQuick::GridView::count
1385 This property holds the number of items in the model.
1386*/
1387
1388/*!
1389 \qmlproperty bool QtQuick::GridView::reuseItems
1390
1391 This property enables you to reuse items that are instantiated
1392 from the \l delegate. If set to \c false, any currently
1393 pooled items are destroyed.
1394
1395 This property is \c false by default.
1396
1397 \since 5.15
1398
1399 \sa {Reusing items}, pooled(), reused()
1400*/
1401
1402/*!
1403 \qmlattachedsignal QtQuick::GridView::pooled()
1404
1405 This signal is emitted after an item has been added to the reuse
1406 pool. You can use it to pause ongoing timers or animations inside
1407 the item, or free up resources that cannot be reused.
1408
1409 This signal is emitted only if the \l reuseItems property is \c true.
1410
1411 \sa {Reusing items}, reuseItems, reused()
1412*/
1413
1414/*!
1415 \qmlattachedsignal QtQuick::GridView::reused()
1416
1417 This signal is emitted after an item has been reused. At this point, the
1418 item has been taken out of the pool and placed inside the content view,
1419 and the model properties such as \c index and \c row have been updated.
1420
1421 Other properties that are not provided by the model does not change when an
1422 item is reused. You should avoid storing any state inside a delegate, but if
1423 you do, manually reset that state on receiving this signal.
1424
1425 This signal is emitted when the item is reused, and not the first time the
1426 item is created.
1427
1428 This signal is emitted only if the \l reuseItems property is \c true.
1429
1430 \sa {Reusing items}, reuseItems, pooled()
1431*/
1432
1433/*!
1434 \qmlproperty Component QtQuick::GridView::highlight
1435 This property holds the component to use as the highlight.
1436
1437 An instance of the highlight component is created for each view.
1438 The geometry of the resulting component instance will be managed by the view
1439 so as to stay with the current item, unless the highlightFollowsCurrentItem property is false.
1440 The default \l {QQuickItem::z}{stacking order} of the highlight item is \c 0.
1441
1442 \sa highlightItem, highlightFollowsCurrentItem
1443*/
1444
1445/*!
1446 \qmlproperty bool QtQuick::GridView::highlightFollowsCurrentItem
1447 This property sets whether the highlight is managed by the view.
1448
1449 If this property is true (the default value), the highlight is moved smoothly
1450 to follow the current item. Otherwise, the
1451 highlight is not moved by the view, and any movement must be implemented
1452 by the highlight.
1453
1454 Here is a highlight with its motion defined by a \l {SpringAnimation} item:
1455
1456 \snippet qml/gridview/gridview.qml highlightFollowsCurrentItem
1457*/
1458
1459
1460/*!
1461 \qmlproperty int QtQuick::GridView::highlightMoveDuration
1462 This property holds the move animation duration of the highlight delegate.
1463
1464 highlightFollowsCurrentItem must be true for this property
1465 to have effect.
1466
1467 The default value for the duration is 150ms.
1468
1469 \sa highlightFollowsCurrentItem
1470*/
1471
1472/*!
1473 \qmlproperty real QtQuick::GridView::preferredHighlightBegin
1474 \qmlproperty real QtQuick::GridView::preferredHighlightEnd
1475 \qmlproperty enumeration QtQuick::GridView::highlightRangeMode
1476
1477 These properties define the preferred range of the highlight (for the current item)
1478 within the view. The \c preferredHighlightBegin value must be less than the
1479 \c preferredHighlightEnd value.
1480
1481 These properties affect the position of the current item when the view is scrolled.
1482 For example, if the currently selected item should stay in the middle of the
1483 view when it is scrolled, set the \c preferredHighlightBegin and
1484 \c preferredHighlightEnd values to the top and bottom coordinates of where the middle
1485 item would be. If the \c currentItem is changed programmatically, the view will
1486 automatically scroll so that the current item is in the middle of the view.
1487 Furthermore, the behavior of the current item index will occur whether or not a
1488 highlight exists.
1489
1490 Valid values for \c highlightRangeMode are:
1491
1492 \value GridView.ApplyRange the view attempts to maintain the highlight within the range.
1493 However, the highlight can move outside of the range at the ends of the view or due
1494 to mouse interaction.
1495 \value GridView.StrictlyEnforceRange the highlight never moves outside of the range.
1496 The current item changes if a keyboard or mouse action would cause the highlight to move
1497 outside of the range.
1498 \value GridView.NoHighlightRange the default value
1499*/
1500
1501
1502/*!
1503 \qmlproperty enumeration QtQuick::GridView::layoutDirection
1504 This property holds the layout direction of the grid.
1505
1506 Possible values:
1507
1508 \value Qt.LeftToRight (default) Items will be laid out starting in the top, left corner. The flow is
1509 dependent on the \l GridView::flow property.
1510 \value Qt.RightToLeft Items will be laid out starting in the top, right corner. The flow is dependent
1511 on the \l GridView::flow property.
1512
1513 \b Note: If GridView::flow is set to GridView.FlowLeftToRight, this is not to be confused if
1514 GridView::layoutDirection is set to Qt.RightToLeft. The GridView.FlowLeftToRight flow value simply
1515 indicates that the flow is horizontal.
1516
1517 \sa GridView::effectiveLayoutDirection, GridView::verticalLayoutDirection
1518*/
1519
1520
1521/*!
1522 \qmlproperty enumeration QtQuick::GridView::effectiveLayoutDirection
1523 This property holds the effective layout direction of the grid.
1524
1525 When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts,
1526 the visual layout direction of the grid will be mirrored. However, the
1527 property \l {GridView::layoutDirection}{layoutDirection} will remain unchanged.
1528
1529 \sa GridView::layoutDirection, {LayoutMirroring}{LayoutMirroring}
1530*/
1531
1532/*!
1533 \qmlproperty enumeration QtQuick::GridView::verticalLayoutDirection
1534 This property holds the vertical layout direction of the grid.
1535
1536 Possible values:
1537
1538 \value GridView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
1539 \value GridView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
1540
1541 \sa GridView::layoutDirection
1542*/
1543
1544/*!
1545 \qmlproperty bool QtQuick::GridView::keyNavigationWraps
1546 This property holds whether the grid wraps key navigation
1547
1548 If this is true, key navigation that would move the current item selection
1549 past one end of the view instead wraps around and moves the selection to
1550 the other end of the view.
1551
1552 By default, key navigation is not wrapped.
1553*/
1554
1555/*!
1556 \qmlproperty bool QtQuick::GridView::keyNavigationEnabled
1557 \since 5.7
1558
1559 This property holds whether the key navigation of the grid is enabled.
1560
1561 If this is \c true, the user can navigate the view with a keyboard.
1562 It is useful for applications that need to selectively enable or
1563 disable mouse and keyboard interaction.
1564
1565 By default, the value of this property is bound to
1566 \l {Flickable::}{interactive} to ensure behavior compatibility for
1567 existing applications. When explicitly set, it will cease to be bound to
1568 the interactive property.
1569
1570 \sa {Flickable::}{interactive}
1571*/
1572
1573/*!
1574 \qmlproperty int QtQuick::GridView::cacheBuffer
1575 This property determines whether delegates are retained outside the
1576 visible area of the view.
1577
1578 If this value is greater than zero, the view may keep as many delegates
1579 instantiated as will fit within the buffer specified. For example,
1580 if in a vertical view the delegate is 20 pixels high, there are 3
1581 columns and \c cacheBuffer is
1582 set to 40, then up to 6 delegates above and 6 delegates below the visible
1583 area may be created/retained. The buffered delegates are created asynchronously,
1584 allowing creation to occur across multiple frames and reducing the
1585 likelihood of skipping frames. In order to improve painting performance
1586 delegates outside the visible area are not painted.
1587
1588 The default value of this property is platform dependent, but will usually
1589 be a value greater than zero. Negative values are ignored.
1590
1591 Note that cacheBuffer is not a pixel buffer - it only maintains additional
1592 instantiated delegates.
1593
1594 \note Setting this property is not a replacement for creating efficient delegates.
1595 It can improve the smoothness of scrolling behavior at the expense of additional
1596 memory usage. The fewer objects and bindings in a delegate, the faster a
1597 view can be scrolled. It is important to realize that setting a cacheBuffer
1598 will only postpone issues caused by slow-loading delegates, it is not a
1599 solution for this scenario.
1600
1601 The cacheBuffer operates outside of any display margins specified by
1602 displayMarginBeginning or displayMarginEnd.
1603*/
1604
1605/*!
1606 \qmlproperty int QtQuick::GridView::displayMarginBeginning
1607 \qmlproperty int QtQuick::GridView::displayMarginEnd
1608 \since QtQuick 2.3
1609
1610 This property allows delegates to be displayed outside of the view geometry.
1611
1612 If this value is non-zero, the view will create extra delegates before the
1613 start of the view, or after the end. The view will create as many delegates
1614 as it can fit into the pixel size specified.
1615
1616 For example, if in a vertical view the delegate is 20 pixels high,
1617 there are 3 columns, and
1618 \c displayMarginBeginning and \c displayMarginEnd are both set to 40,
1619 then 6 delegates above and 6 delegates below will be created and shown.
1620
1621 The default value is 0.
1622
1623 This property is meant for allowing certain UI configurations,
1624 and not as a performance optimization. If you wish to create delegates
1625 outside of the view geometry for performance reasons, you probably
1626 want to use the cacheBuffer property instead.
1627*/
1628
1629void QQuickGridView::setHighlightMoveDuration(int duration)
1630{
1631 Q_D(QQuickGridView);
1632 if (d->highlightMoveDuration != duration) {
1633 if (d->highlightYAnimator) {
1634 d->highlightXAnimator->userDuration = duration;
1635 d->highlightYAnimator->userDuration = duration;
1636 }
1637 QQuickItemView::setHighlightMoveDuration(duration);
1638 }
1639}
1640
1641/*!
1642 \qmlproperty enumeration QtQuick::GridView::flow
1643 This property holds the flow of the grid.
1644
1645 Possible values:
1646
1647 \value GridView.FlowLeftToRight (default) Items are laid out from left to right, and the view scrolls vertically
1648 \value GridView.FlowTopToBottom Items are laid out from top to bottom, and the view scrolls horizontally
1649*/
1650QQuickGridView::Flow QQuickGridView::flow() const
1651{
1652 Q_D(const QQuickGridView);
1653 return d->flow;
1654}
1655
1656void QQuickGridView::setFlow(Flow flow)
1657{
1658 Q_D(QQuickGridView);
1659 if (d->flow != flow) {
1660 d->flow = flow;
1661 if (d->flow == FlowLeftToRight) {
1662 setContentWidth(-1);
1663 setFlickableDirection(VerticalFlick);
1664 } else {
1665 setContentHeight(-1);
1666 setFlickableDirection(HorizontalFlick);
1667 }
1668 setContentX(0);
1669 setContentY(0);
1670 d->regenerate(orientationChanged: true);
1671 emit flowChanged();
1672 }
1673}
1674
1675
1676/*!
1677 \qmlproperty real QtQuick::GridView::cellWidth
1678 \qmlproperty real QtQuick::GridView::cellHeight
1679
1680 These properties holds the width and height of each cell in the grid.
1681
1682 The default cell size is 100x100.
1683*/
1684qreal QQuickGridView::cellWidth() const
1685{
1686 Q_D(const QQuickGridView);
1687 return d->cellWidth;
1688}
1689
1690void QQuickGridView::setCellWidth(qreal cellWidth)
1691{
1692 Q_D(QQuickGridView);
1693 if (cellWidth != d->cellWidth && cellWidth > 0) {
1694 d->cellWidth = qMax(a: qreal(1), b: cellWidth);
1695 d->updateViewport();
1696 emit cellWidthChanged();
1697 d->forceLayoutPolish();
1698 QQuickFlickable::setContentX(d->contentXForPosition(pos: d->position()));
1699 }
1700}
1701
1702qreal QQuickGridView::cellHeight() const
1703{
1704 Q_D(const QQuickGridView);
1705 return d->cellHeight;
1706}
1707
1708void QQuickGridView::setCellHeight(qreal cellHeight)
1709{
1710 Q_D(QQuickGridView);
1711 if (cellHeight != d->cellHeight && cellHeight > 0) {
1712 d->cellHeight = qMax(a: qreal(1), b: cellHeight);
1713 d->updateViewport();
1714 emit cellHeightChanged();
1715 d->forceLayoutPolish();
1716 QQuickFlickable::setContentY(d->contentYForPosition(pos: d->position()));
1717 }
1718}
1719/*!
1720 \qmlproperty enumeration QtQuick::GridView::snapMode
1721
1722 This property determines how the view scrolling will settle following a drag or flick.
1723 The possible values are:
1724
1725 \value GridView.NoSnap (default) the view stops anywhere within the visible area.
1726 \value GridView.SnapToRow the view settles with a row (or column for \c GridView.FlowTopToBottom flow)
1727 aligned with the start of the view.
1728 \value GridView.SnapOneRow the view will settle no more than one row (or column for \c GridView.FlowTopToBottom flow)
1729 away from the first visible row at the time the mouse button is released.
1730 This mode is particularly useful for moving one page at a time.
1731*/
1732QQuickGridView::SnapMode QQuickGridView::snapMode() const
1733{
1734 Q_D(const QQuickGridView);
1735 return d->snapMode;
1736}
1737
1738void QQuickGridView::setSnapMode(SnapMode mode)
1739{
1740 Q_D(QQuickGridView);
1741 if (d->snapMode != mode) {
1742 d->snapMode = mode;
1743 emit snapModeChanged();
1744 }
1745}
1746
1747
1748/*!
1749 \qmlproperty Component QtQuick::GridView::footer
1750 This property holds the component to use as the footer.
1751
1752 An instance of the footer component is created for each view. The
1753 footer is positioned at the end of the view, after any items. The
1754 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
1755
1756 \sa header, footerItem
1757*/
1758/*!
1759 \qmlproperty Component QtQuick::GridView::header
1760 This property holds the component to use as the header.
1761
1762 An instance of the header component is created for each view. The
1763 header is positioned at the beginning of the view, before any items.
1764 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
1765
1766 \sa footer, headerItem
1767*/
1768
1769/*!
1770 \qmlproperty Item QtQuick::GridView::headerItem
1771 This holds the header item created from the \l header component.
1772
1773 An instance of the header component is created for each view. The
1774 header is positioned at the beginning of the view, before any items.
1775 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
1776
1777 \sa header, footerItem
1778*/
1779
1780/*!
1781 \qmlproperty Item QtQuick::GridView::footerItem
1782 This holds the footer item created from the \l footer component.
1783
1784 An instance of the footer component is created for each view. The
1785 footer is positioned at the end of the view, after any items. The
1786 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
1787
1788 \sa footer, headerItem
1789*/
1790
1791/*!
1792 \qmlproperty Transition QtQuick::GridView::populate
1793
1794 This property holds the transition to apply to the items that are initially created
1795 for a view.
1796
1797 It is applied to all items that are created when:
1798
1799 \list
1800 \li The view is first created
1801 \li The view's \l model changes in such a way that the visible delegates are completely replaced
1802 \li The view's \l model is \l {QAbstractItemModel::beginResetModel()}{reset},
1803 if the model is a QAbstractItemModel subclass
1804 \endlist
1805
1806 For example, here is a view that specifies such a transition:
1807
1808 \code
1809 GridView {
1810 ...
1811 populate: Transition {
1812 NumberAnimation { properties: "x,y"; duration: 1000 }
1813 }
1814 }
1815 \endcode
1816
1817 When the view is initialized, the view will create all the necessary items for the view,
1818 then animate them to their correct positions within the view over one second.
1819
1820 However when scrolling the view later, the populate transition does not
1821 run, even though delegates are being instantiated as they become visible.
1822 When the model changes in a way that new delegates become visible, the
1823 \l add transition is the one that runs. So you should not depend on the
1824 \c populate transition to initialize properties in the delegate, because it
1825 does not apply to every delegate. If your animation sets the \c to value of
1826 a property, the property should initially have the \c to value, and the
1827 animation should set the \c from value in case it is animated:
1828
1829 \code
1830 GridView {
1831 ...
1832 delegate: Rectangle {
1833 opacity: 1 // not necessary because it's the default; but don't set 0
1834 ...
1835 }
1836 populate: Transition {
1837 NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
1838 }
1839 }
1840 \endcode
1841
1842 For more details and examples on how to use view transitions, see the ViewTransition
1843 documentation.
1844
1845 \sa add, ViewTransition
1846*/
1847
1848/*!
1849 \qmlproperty Transition QtQuick::GridView::add
1850
1851 This property holds the transition to apply to items that are added to the view.
1852
1853 For example, here is a view that specifies such a transition:
1854
1855 \code
1856 GridView {
1857 ...
1858 add: Transition {
1859 NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
1860 }
1861 }
1862 \endcode
1863
1864 Whenever an item is added to the above view, the item will be animated from the position (100,100)
1865 to its final x,y position within the view, over one second. The transition only applies to
1866 the new items that are added to the view; it does not apply to the items below that are
1867 displaced by the addition of the new items. To animate the displaced items, set the \l displaced
1868 or \l addDisplaced properties.
1869
1870 For more details and examples on how to use view transitions, see the ViewTransition
1871 documentation.
1872
1873 \note This transition is not applied to the items that are created when the view is initially
1874 populated, or when the view's \l model changes. (In those cases, the \l populate transition is
1875 applied instead.) Additionally, this transition should \e not animate the height of the new item;
1876 doing so will cause any items beneath the new item to be laid out at the wrong position. Instead,
1877 the height can be animated within the \l {add}{onAdd} handler in the delegate.
1878
1879 \sa addDisplaced, populate, ViewTransition
1880*/
1881
1882/*!
1883 \qmlproperty Transition QtQuick::GridView::addDisplaced
1884
1885 This property holds the transition to apply to items within the view that are displaced by
1886 the addition of other items to the view.
1887
1888 For example, here is a view that specifies such a transition:
1889
1890 \code
1891 GridView {
1892 ...
1893 addDisplaced: Transition {
1894 NumberAnimation { properties: "x,y"; duration: 1000 }
1895 }
1896 }
1897 \endcode
1898
1899 Whenever an item is added to the above view, all items beneath the new item are displaced, causing
1900 them to move down (or sideways, if horizontally orientated) within the view. As this
1901 displacement occurs, the items' movement to their new x,y positions within the view will be
1902 animated by a NumberAnimation over one second, as specified. This transition is not applied to
1903 the new item that has been added to the view; to animate the added items, set the \l add
1904 property.
1905
1906 If an item is displaced by multiple types of operations at the same time, it is not defined as to
1907 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
1908 if it is not necessary to specify different transitions depending on whether an item is displaced
1909 by an add, move or remove operation, consider setting the \l displaced property instead.
1910
1911 For more details and examples on how to use view transitions, see the ViewTransition
1912 documentation.
1913
1914 \note This transition is not applied to the items that are created when the view is initially
1915 populated, or when the view's \l model changes. In those cases, the \l populate transition is
1916 applied instead.
1917
1918 \sa displaced, add, populate, ViewTransition
1919*/
1920/*!
1921 \qmlproperty Transition QtQuick::GridView::move
1922
1923 This property holds the transition to apply to items in the view that are being moved due
1924 to a move operation in the view's \l model.
1925
1926 For example, here is a view that specifies such a transition:
1927
1928 \code
1929 GridView {
1930 ...
1931 move: Transition {
1932 NumberAnimation { properties: "x,y"; duration: 1000 }
1933 }
1934 }
1935 \endcode
1936
1937 Whenever the \l model performs a move operation to move a particular set of indexes, the
1938 respective items in the view will be animated to their new positions in the view over one
1939 second. The transition only applies to the items that are the subject of the move operation
1940 in the model; it does not apply to items below them that are displaced by the move operation.
1941 To animate the displaced items, set the \l displaced or \l moveDisplaced properties.
1942
1943 For more details and examples on how to use view transitions, see the ViewTransition
1944 documentation.
1945
1946 \sa moveDisplaced, ViewTransition
1947*/
1948
1949/*!
1950 \qmlproperty Transition QtQuick::GridView::moveDisplaced
1951
1952 This property holds the transition to apply to items that are displaced by a move operation in
1953 the view's \l model.
1954
1955 For example, here is a view that specifies such a transition:
1956
1957 \code
1958 GridView {
1959 ...
1960 moveDisplaced: Transition {
1961 NumberAnimation { properties: "x,y"; duration: 1000 }
1962 }
1963 }
1964 \endcode
1965
1966 Whenever the \l model performs a move operation to move a particular set of indexes, the items
1967 between the source and destination indexes of the move operation are displaced, causing them
1968 to move upwards or downwards (or sideways, if horizontally orientated) within the view. As this
1969 displacement occurs, the items' movement to their new x,y positions within the view will be
1970 animated by a NumberAnimation over one second, as specified. This transition is not applied to
1971 the items that are the actual subjects of the move operation; to animate the moved items, set
1972 the \l move property.
1973
1974 If an item is displaced by multiple types of operations at the same time, it is not defined as to
1975 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
1976 if it is not necessary to specify different transitions depending on whether an item is displaced
1977 by an add, move or remove operation, consider setting the \l displaced property instead.
1978
1979 For more details and examples on how to use view transitions, see the ViewTransition
1980 documentation.
1981
1982 \sa displaced, move, ViewTransition
1983*/
1984
1985/*!
1986 \qmlproperty Transition QtQuick::GridView::remove
1987
1988 This property holds the transition to apply to items that are removed from the view.
1989
1990 For example, here is a view that specifies such a transition:
1991
1992 \code
1993 GridView {
1994 ...
1995 remove: Transition {
1996 ParallelAnimation {
1997 NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
1998 NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
1999 }
2000 }
2001 }
2002 \endcode
2003
2004 Whenever an item is removed from the above view, the item will be animated to the position (100,100)
2005 over one second, and in parallel will also change its opacity to 0. The transition
2006 only applies to the items that are removed from the view; it does not apply to the items below
2007 them that are displaced by the removal of the items. To animate the displaced items, set the
2008 \l displaced or \l removeDisplaced properties.
2009
2010 Note that by the time the transition is applied, the item has already been removed from the
2011 model; any references to the model data for the removed index will not be valid.
2012
2013 Additionally, if the \l delayRemove attached property has been set for a delegate item, the
2014 remove transition will not be applied until \l delayRemove becomes false again.
2015
2016 For more details and examples on how to use view transitions, see the ViewTransition
2017 documentation.
2018
2019 \sa removeDisplaced, ViewTransition
2020*/
2021
2022/*!
2023 \qmlproperty Transition QtQuick::GridView::removeDisplaced
2024
2025 This property holds the transition to apply to items in the view that are displaced by the
2026 removal of other items in the view.
2027
2028 For example, here is a view that specifies such a transition:
2029
2030 \code
2031 GridView {
2032 ...
2033 removeDisplaced: Transition {
2034 NumberAnimation { properties: "x,y"; duration: 1000 }
2035 }
2036 }
2037 \endcode
2038
2039 Whenever an item is removed from the above view, all items beneath it are displaced, causing
2040 them to move upwards (or sideways, if horizontally orientated) within the view. As this
2041 displacement occurs, the items' movement to their new x,y positions within the view will be
2042 animated by a NumberAnimation over one second, as specified. This transition is not applied to
2043 the item that has actually been removed from the view; to animate the removed items, set the
2044 \l remove property.
2045
2046 If an item is displaced by multiple types of operations at the same time, it is not defined as to
2047 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
2048 if it is not necessary to specify different transitions depending on whether an item is displaced
2049 by an add, move or remove operation, consider setting the \l displaced property instead.
2050
2051 For more details and examples on how to use view transitions, see the ViewTransition
2052 documentation.
2053
2054 \sa displaced, remove, ViewTransition
2055*/
2056
2057/*!
2058 \qmlproperty Transition QtQuick::GridView::displaced
2059 This property holds the generic transition to apply to items that have been displaced by
2060 any model operation that affects the view.
2061
2062 This is a convenience for specifying a generic transition for items that are displaced
2063 by add, move or remove operations, without having to specify the individual addDisplaced,
2064 moveDisplaced and removeDisplaced properties. For example, here is a view that specifies
2065 a displaced transition:
2066
2067 \code
2068 GridView {
2069 ...
2070 displaced: Transition {
2071 NumberAnimation { properties: "x,y"; duration: 1000 }
2072 }
2073 }
2074 \endcode
2075
2076 When any item is added, moved or removed within the above view, the items below it are
2077 displaced, causing them to move down (or sideways, if horizontally orientated) within the
2078 view. As this displacement occurs, the items' movement to their new x,y positions within
2079 the view will be animated by a NumberAnimation over one second, as specified.
2080
2081 If a view specifies this generic displaced transition as well as a specific addDisplaced,
2082 moveDisplaced or removeDisplaced transition, the more specific transition will be used
2083 instead of the generic displaced transition when the relevant operation occurs, providing that
2084 the more specific transition has not been disabled (by setting \l {Transition::enabled}{enabled}
2085 to false). If it has indeed been disabled, the generic displaced transition is applied instead.
2086
2087 For more details and examples on how to use view transitions, see the ViewTransition
2088 documentation.
2089
2090 \sa addDisplaced, moveDisplaced, removeDisplaced, ViewTransition
2091*/
2092
2093void QQuickGridView::viewportMoved(Qt::Orientations orient)
2094{
2095 Q_D(QQuickGridView);
2096 QQuickItemView::viewportMoved(orient);
2097 if (!d->itemCount)
2098 return;
2099 if (d->inViewportMoved)
2100 return;
2101 d->inViewportMoved = true;
2102
2103 if (yflick()) {
2104 if (d->isContentFlowReversed())
2105 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore;
2106 else
2107 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter;
2108 } else {
2109 if (d->isContentFlowReversed())
2110 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore;
2111 else
2112 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter;
2113 }
2114
2115 d->refillOrLayout();
2116
2117 // Set visibility of items to eliminate cost of items outside the visible area.
2118 qreal from = d->isContentFlowReversed() ? -d->position()-d->displayMarginBeginning-d->size() : d->position()-d->displayMarginBeginning;
2119 qreal to = d->isContentFlowReversed() ? -d->position()+d->displayMarginEnd : d->position()+d->size()+d->displayMarginEnd;
2120 for (FxViewItem *item : std::as_const(t&: d->visibleItems)) {
2121 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(item);
2122 QQuickItemPrivate::get(item: gridItem->item)->setCulled(gridItem->rowPos() + d->rowSize() < from || gridItem->rowPos() > to);
2123 }
2124 if (d->currentItem) {
2125 FxGridItemSG *item = static_cast<FxGridItemSG*>(d->currentItem);
2126 QQuickItemPrivate::get(item: item->item)->setCulled(item->rowPos() + d->rowSize() < from || item->rowPos() > to);
2127 }
2128
2129 if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving)
2130 d->moveReason = QQuickGridViewPrivate::Mouse;
2131 if (d->moveReason != QQuickGridViewPrivate::SetIndex) {
2132 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) {
2133 // reposition highlight
2134 qreal pos = d->highlight->position();
2135 qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position();
2136 if (pos > viewPos + d->highlightRangeEnd - d->highlight->size())
2137 pos = viewPos + d->highlightRangeEnd - d->highlight->size();
2138 if (pos < viewPos + d->highlightRangeStart)
2139 pos = viewPos + d->highlightRangeStart;
2140
2141 if (pos != d->highlight->position()) {
2142 d->highlightXAnimator->stop();
2143 d->highlightYAnimator->stop();
2144 FxGridItemSG *sgHighlight = static_cast<FxGridItemSG *>(d->highlight.get());
2145 sgHighlight->setPosition(col: sgHighlight->colPos(), row: pos);
2146 } else {
2147 d->updateHighlight();
2148 }
2149
2150 // update current index
2151 int idx = d->snapIndex();
2152 if (idx >= 0 && idx != d->currentIndex) {
2153 d->updateCurrent(modelIndex: idx);
2154 if (d->currentItem
2155 && static_cast<FxGridItemSG*>(d->currentItem)->colPos()
2156 != static_cast<FxGridItemSG*>(d->highlight.get())->colPos()
2157 && d->autoHighlight) {
2158 if (d->flow == FlowLeftToRight)
2159 d->highlightXAnimator->to = d->currentItem->itemX();
2160 else
2161 d->highlightYAnimator->to = d->currentItem->itemY();
2162 }
2163 }
2164 }
2165 }
2166
2167 d->inViewportMoved = false;
2168}
2169
2170void QQuickGridView::keyPressEvent(QKeyEvent *event)
2171{
2172 Q_D(QQuickGridView);
2173 if (d->model && d->model->count() && ((d->interactive && !d->explicitKeyNavigationEnabled)
2174 || (d->explicitKeyNavigationEnabled && d->keyNavigationEnabled))) {
2175 d->moveReason = QQuickGridViewPrivate::SetIndex;
2176 int oldCurrent = currentIndex();
2177 switch (event->key()) {
2178 case Qt::Key_Up:
2179 moveCurrentIndexUp();
2180 break;
2181 case Qt::Key_Down:
2182 moveCurrentIndexDown();
2183 break;
2184 case Qt::Key_Left:
2185 moveCurrentIndexLeft();
2186 break;
2187 case Qt::Key_Right:
2188 moveCurrentIndexRight();
2189 break;
2190 default:
2191 break;
2192 }
2193 if (oldCurrent != currentIndex() || d->wrap) {
2194 event->accept();
2195 return;
2196 }
2197 }
2198 event->ignore();
2199 QQuickItemView::keyPressEvent(event);
2200}
2201
2202void QQuickGridView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2203{
2204 Q_D(QQuickGridView);
2205 d->resetColumns();
2206
2207 if (newGeometry.width() != oldGeometry.width()
2208 && newGeometry.height() != oldGeometry.height()) {
2209 d->setPosition(d->position());
2210 } else if (newGeometry.width() != oldGeometry.width()) {
2211 QQuickFlickable::setContentX(d->contentXForPosition(pos: d->position()));
2212 } else if (newGeometry.height() != oldGeometry.height()) {
2213 QQuickFlickable::setContentY(d->contentYForPosition(pos: d->position()));
2214 }
2215
2216 QQuickItemView::geometryChange(newGeometry, oldGeometry);
2217}
2218
2219void QQuickGridView::initItem(int index, QObject *obj)
2220{
2221 QQuickItemView::initItem(index, item: obj);
2222
2223 // setting the view from the FxViewItem wrapper is too late if the delegate
2224 // needs access to the view in Component.onCompleted
2225 QQuickItem *item = qmlobject_cast<QQuickItem*>(object: obj);
2226 if (item) {
2227 QQuickGridViewAttached *attached = static_cast<QQuickGridViewAttached *>(
2228 qmlAttachedPropertiesObject<QQuickGridView>(obj: item));
2229 if (attached)
2230 attached->setView(this);
2231 }
2232}
2233
2234/*!
2235 \qmlmethod QtQuick::GridView::moveCurrentIndexUp()
2236
2237 Move the currentIndex up one item in the view.
2238 The current index will wrap if keyNavigationWraps is true and it
2239 is currently at the end. This method has no effect if the \l count is zero.
2240
2241 \b Note: methods should only be called after the Component has completed.
2242*/
2243
2244
2245void QQuickGridView::moveCurrentIndexUp()
2246{
2247 Q_D(QQuickGridView);
2248 const int count = d->model ? d->model->count() : 0;
2249 if (!count)
2250 return;
2251 if (d->verticalLayoutDirection == QQuickItemView::TopToBottom) {
2252 if (d->flow == QQuickGridView::FlowLeftToRight) {
2253 if (currentIndex() >= d->columns || d->wrap) {
2254 int index = currentIndex() - d->columns;
2255 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2256 }
2257 } else {
2258 if (currentIndex() > 0 || d->wrap) {
2259 int index = currentIndex() - 1;
2260 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2261 }
2262 }
2263 } else {
2264 if (d->flow == QQuickGridView::FlowLeftToRight) {
2265 if (currentIndex() < count - d->columns || d->wrap) {
2266 int index = currentIndex()+d->columns;
2267 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2268 }
2269 } else {
2270 if (currentIndex() < count - 1 || d->wrap) {
2271 int index = currentIndex() + 1;
2272 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2273 }
2274 }
2275 }
2276}
2277
2278/*!
2279 \qmlmethod QtQuick::GridView::moveCurrentIndexDown()
2280
2281 Move the currentIndex down one item in the view.
2282 The current index will wrap if keyNavigationWraps is true and it
2283 is currently at the end. This method has no effect if the \l count is zero.
2284
2285 \b Note: methods should only be called after the Component has completed.
2286*/
2287void QQuickGridView::moveCurrentIndexDown()
2288{
2289 Q_D(QQuickGridView);
2290 const int count = d->model ? d->model->count() : 0;
2291 if (!count)
2292 return;
2293
2294 if (d->verticalLayoutDirection == QQuickItemView::TopToBottom) {
2295 if (d->flow == QQuickGridView::FlowLeftToRight) {
2296 if (currentIndex() < count - d->columns || d->wrap) {
2297 int index = currentIndex()+d->columns;
2298 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2299 }
2300 } else {
2301 if (currentIndex() < count - 1 || d->wrap) {
2302 int index = currentIndex() + 1;
2303 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2304 }
2305 }
2306 } else {
2307 if (d->flow == QQuickGridView::FlowLeftToRight) {
2308 if (currentIndex() >= d->columns || d->wrap) {
2309 int index = currentIndex() - d->columns;
2310 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2311 }
2312 } else {
2313 if (currentIndex() > 0 || d->wrap) {
2314 int index = currentIndex() - 1;
2315 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2316 }
2317 }
2318 }
2319}
2320
2321/*!
2322 \qmlmethod QtQuick::GridView::moveCurrentIndexLeft()
2323
2324 Move the currentIndex left one item in the view.
2325 The current index will wrap if keyNavigationWraps is true and it
2326 is currently at the end. This method has no effect if the \l count is zero.
2327
2328 \b Note: methods should only be called after the Component has completed.
2329*/
2330void QQuickGridView::moveCurrentIndexLeft()
2331{
2332 Q_D(QQuickGridView);
2333 const int count = d->model ? d->model->count() : 0;
2334 if (!count)
2335 return;
2336 if (effectiveLayoutDirection() == Qt::LeftToRight) {
2337 if (d->flow == QQuickGridView::FlowLeftToRight) {
2338 if (currentIndex() > 0 || d->wrap) {
2339 int index = currentIndex() - 1;
2340 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2341 }
2342 } else {
2343 if (currentIndex() >= d->columns || d->wrap) {
2344 int index = currentIndex() - d->columns;
2345 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2346 }
2347 }
2348 } else {
2349 if (d->flow == QQuickGridView::FlowLeftToRight) {
2350 if (currentIndex() < count - 1 || d->wrap) {
2351 int index = currentIndex() + 1;
2352 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2353 }
2354 } else {
2355 if (currentIndex() < count - d->columns || d->wrap) {
2356 int index = currentIndex() + d->columns;
2357 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2358 }
2359 }
2360 }
2361}
2362
2363
2364/*!
2365 \qmlmethod QtQuick::GridView::moveCurrentIndexRight()
2366
2367 Move the currentIndex right one item in the view.
2368 The current index will wrap if keyNavigationWraps is true and it
2369 is currently at the end. This method has no effect if the \l count is zero.
2370
2371 \b Note: methods should only be called after the Component has completed.
2372*/
2373void QQuickGridView::moveCurrentIndexRight()
2374{
2375 Q_D(QQuickGridView);
2376 const int count = d->model ? d->model->count() : 0;
2377 if (!count)
2378 return;
2379 if (effectiveLayoutDirection() == Qt::LeftToRight) {
2380 if (d->flow == QQuickGridView::FlowLeftToRight) {
2381 if (currentIndex() < count - 1 || d->wrap) {
2382 int index = currentIndex() + 1;
2383 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2384 }
2385 } else {
2386 if (currentIndex() < count - d->columns || d->wrap) {
2387 int index = currentIndex()+d->columns;
2388 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2389 }
2390 }
2391 } else {
2392 if (d->flow == QQuickGridView::FlowLeftToRight) {
2393 if (currentIndex() > 0 || d->wrap) {
2394 int index = currentIndex() - 1;
2395 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2396 }
2397 } else {
2398 if (currentIndex() >= d->columns || d->wrap) {
2399 int index = currentIndex() - d->columns;
2400 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2401 }
2402 }
2403 }
2404}
2405
2406bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
2407{
2408 Q_Q(QQuickGridView);
2409
2410 if (q->size().isNull())
2411 return false;
2412
2413 int modelIndex = change.index;
2414 int count = change.count;
2415
2416 int index = visibleItems.size() ? mapFromModel(modelIndex) : 0;
2417
2418 if (index < 0) {
2419 int i = visibleItems.size() - 1;
2420 while (i > 0 && visibleItems.at(i)->index == -1)
2421 --i;
2422 if (visibleItems.at(i)->index + 1 == modelIndex) {
2423 // Special case of appending an item to the model.
2424 index = visibleItems.size();
2425 } else {
2426 if (modelIndex <= visibleIndex) {
2427 // Insert before visible items
2428 visibleIndex += count;
2429 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
2430 if (item->index != -1 && item->index >= modelIndex)
2431 item->index += count;
2432 }
2433 }
2434 return true;
2435 }
2436 }
2437
2438 qreal tempPos = isContentFlowReversed() ? -position()-size()+q->width()+1 : position();
2439 qreal colPos = 0;
2440 qreal rowPos = 0;
2441 int colNum = 0;
2442 if (visibleItems.size()) {
2443 if (index < visibleItems.size()) {
2444 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(i: index));
2445 colPos = gridItem->colPos();
2446 rowPos = gridItem->rowPos();
2447 colNum = qFloor(v: (colPos+colSize()/2) / colSize());
2448 } else {
2449 // appending items to visible list
2450 FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(i: index-1));
2451 rowPos = gridItem->rowPos();
2452 colNum = qFloor(v: (gridItem->colPos()+colSize()/2) / colSize());
2453 if (++colNum >= columns) {
2454 colNum = 0;
2455 rowPos += rowSize();
2456 }
2457 colPos = colNum * colSize();
2458 }
2459 }
2460
2461#if QT_CONFIG(quick_viewtransitions)
2462 // Update the indexes of the following visible items.
2463 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
2464 if (item->index != -1 && item->index >= modelIndex) {
2465 item->index += count;
2466 if (change.isMove())
2467 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: false);
2468 else
2469 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: false);
2470 }
2471 }
2472#endif
2473
2474 int prevVisibleCount = visibleItems.size();
2475 if (insertResult->visiblePos.isValid() && rowPos < insertResult->visiblePos) {
2476 // Insert items before the visible item.
2477 int insertionIdx = index;
2478 int i = count - 1;
2479 int from = tempPos - buffer - displayMarginBeginning;
2480
2481 if (rowPos > from && insertionIdx < visibleIndex) {
2482 // items won't be visible, just note the size for repositioning
2483 insertResult->countChangeBeforeVisible += count;
2484 insertResult->sizeChangesBeforeVisiblePos += ((count + columns - 1) / columns) * rowSize();
2485 } else {
2486 while (i >= 0) {
2487 // item is before first visible e.g. in cache buffer
2488 FxViewItem *item = nullptr;
2489 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: modelIndex + i))))
2490 item->index = modelIndex + i;
2491 if (!item)
2492 item = createItem(modelIndex: modelIndex + i, incubationMode: QQmlIncubator::Synchronous);
2493 if (!item)
2494 return false;
2495
2496 QQuickItemPrivate::get(item: item->item)->setCulled(false);
2497 visibleItems.insert(i: insertionIdx, t: item);
2498 if (insertionIdx == 0)
2499 insertResult->changedFirstItem = true;
2500 if (!change.isMove()) {
2501 addedItems->append(t: item);
2502#if QT_CONFIG(quick_viewtransitions)
2503 if (transitioner)
2504 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
2505 else
2506#endif
2507 item->moveTo(pos: QPointF(colPos, rowPos), immediate: true);
2508 }
2509 insertResult->sizeChangesBeforeVisiblePos += rowSize();
2510
2511 if (--colNum < 0 ) {
2512 colNum = columns - 1;
2513 rowPos -= rowSize();
2514 }
2515 colPos = colNum * colSize();
2516 index++;
2517 i--;
2518 }
2519 }
2520
2521 // There may be gaps in the index sequence of visibleItems because
2522 // of the index shift/update done before the insertion just above.
2523 // Find if there is any...
2524 int firstOkIdx = -1;
2525 for (int i = 0; i <= insertionIdx && i < visibleItems.size() - 1; i++) {
2526 if (visibleItems.at(i)->index + 1 != visibleItems.at(i: i + 1)->index) {
2527 firstOkIdx = i + 1;
2528 break;
2529 }
2530 }
2531 // ... and remove all the items before that one
2532 for (int i = 0; i < firstOkIdx; i++) {
2533 FxViewItem *nvItem = visibleItems.takeFirst();
2534 addedItems->removeOne(t: nvItem);
2535 removeItem(item: nvItem);
2536 }
2537
2538 } else {
2539 int i = 0;
2540 int to = buffer+displayMarginEnd+tempPos+size()-1;
2541 while (i < count && rowPos <= to + rowSize()*(columns - colNum)/qreal(columns+1)) {
2542 FxViewItem *item = nullptr;
2543 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: modelIndex + i))))
2544 item->index = modelIndex + i;
2545 bool newItem = !item;
2546 if (!item)
2547 item = createItem(modelIndex: modelIndex + i, incubationMode: QQmlIncubator::Synchronous);
2548 if (!item)
2549 return false;
2550
2551 QQuickItemPrivate::get(item: item->item)->setCulled(false);
2552 visibleItems.insert(i: index, t: item);
2553 if (index == 0)
2554 insertResult->changedFirstItem = true;
2555 if (change.isMove()) {
2556 // we know this is a move target, since move displaced items that are
2557 // shuffled into view due to a move would be added in refill()
2558 if (newItem
2559#if QT_CONFIG(quick_viewtransitions)
2560 && transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::MoveTransition, asTarget: true)
2561#endif
2562 )
2563 movingIntoView->append(t: MovedItem(item, change.moveKey(index: item->index)));
2564 } else {
2565 addedItems->append(t: item);
2566#if QT_CONFIG(quick_viewtransitions)
2567 if (transitioner)
2568 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
2569 else
2570#endif
2571 item->moveTo(pos: QPointF(colPos, rowPos), immediate: true);
2572 }
2573 insertResult->sizeChangesAfterVisiblePos += rowSize();
2574
2575 if (++colNum >= columns) {
2576 colNum = 0;
2577 rowPos += rowSize();
2578 }
2579 colPos = colNum * colSize();
2580 ++index;
2581 ++i;
2582 }
2583 }
2584
2585 updateVisibleIndex();
2586
2587 return visibleItems.size() > prevVisibleCount;
2588}
2589
2590#if QT_CONFIG(quick_viewtransitions)
2591void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
2592{
2593 if (!transitioner)
2594 return;
2595
2596 int markerItemIndex = -1;
2597 for (int i=0; i<visibleItems.size(); i++) {
2598 if (visibleItems.at(i)->index == afterModelIndex) {
2599 markerItemIndex = i;
2600 break;
2601 }
2602 }
2603 if (markerItemIndex < 0)
2604 return;
2605
2606 const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
2607 int countItemsRemoved = -(removalResult.sizeChangesAfterVisiblePos / rowSize());
2608
2609 // account for whether first item has changed if < 1 row was removed before visible
2610 int changeBeforeVisible = insertionResult.countChangeBeforeVisible - removalResult.countChangeBeforeVisible;
2611 if (changeBeforeVisible != 0)
2612 countItemsRemoved += (changeBeforeVisible % columns) - (columns - 1);
2613
2614 countItemsRemoved -= removalResult.countChangeAfterVisibleItems;
2615
2616 for (int i=markerItemIndex+1; i<visibleItems.size(); i++) {
2617 FxGridItemSG *gridItem = static_cast<FxGridItemSG *>(visibleItems.at(i));
2618 if (gridItem->position() >= viewEndPos)
2619 break;
2620 if (!gridItem->transitionScheduledOrRunning()) {
2621 qreal origRowPos = gridItem->colPos();
2622 qreal origColPos = gridItem->rowPos();
2623 int indexDiff = gridItem->index - countItemsRemoved;
2624 gridItem->setPosition(col: (indexDiff % columns) * colSize(), row: (indexDiff / columns) * rowSize());
2625 gridItem->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
2626 gridItem->setPosition(col: origRowPos, row: origColPos);
2627 }
2628 }
2629}
2630#endif
2631
2632bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const
2633{
2634 // If we add or remove items before visible items, a layout may be
2635 // required to ensure item 0 is in the first column.
2636 return modelIndex < visibleIndex;
2637}
2638
2639/*!
2640 \qmlmethod QtQuick::GridView::positionViewAtIndex(int index, PositionMode mode)
2641
2642 Positions the view such that the \a index is at the position specified by
2643 \a mode:
2644
2645 \value GridView.Beginning position item at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
2646 \value GridView.Center position item in the center of the view.
2647 \value GridView.End position item at bottom (or right for horizontal orientation) of the view.
2648 \value GridView.Visible if any part of the item is visible then take no action, otherwise
2649 bring the item into view.
2650 \value GridView.Contain ensure the entire item is visible. If the item is larger than the view, the item
2651 is positioned at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
2652 \value GridView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid if
2653 \l highlightRangeMode is \c StrictlyEnforceRange or snapping is enabled via \l snapMode.
2654
2655 If positioning the view at the index would cause empty space to be displayed at
2656 the beginning or end of the view, the view will be positioned at the boundary.
2657
2658 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
2659 at a particular index. This is unreliable since removing items from the start
2660 of the view does not cause all other items to be repositioned.
2661 The correct way to bring an item into view is with \c positionViewAtIndex.
2662
2663 \b Note: methods should only be called after the Component has completed. To position
2664 the view at startup, this method should be called by Component.onCompleted. For
2665 example, to position the view at the end:
2666
2667 \code
2668 Component.onCompleted: positionViewAtIndex(count - 1, GridView.Beginning)
2669 \endcode
2670*/
2671
2672/*!
2673 \qmlmethod QtQuick::GridView::positionViewAtBeginning()
2674 \qmlmethod QtQuick::GridView::positionViewAtEnd()
2675
2676 Positions the view at the beginning or end, taking into account any header or footer.
2677
2678 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
2679 at a particular index. This is unreliable since removing items from the start
2680 of the list does not cause all other items to be repositioned, and because
2681 the actual start of the view can vary based on the size of the delegates.
2682
2683 \b Note: methods should only be called after the Component has completed. To position
2684 the view at startup, this method should be called by Component.onCompleted. For
2685 example, to position the view at the end on startup:
2686
2687 \code
2688 Component.onCompleted: positionViewAtEnd()
2689 \endcode
2690*/
2691
2692/*!
2693 \qmlmethod int QtQuick::GridView::indexAt(real x, real y)
2694
2695 Returns the index of the visible item containing the point \a x, \a y in
2696 \l {QQuickFlickable::contentItem}{content item} coordinates. If there is
2697 no item at the point specified, or the item is not visible -1 is returned.
2698
2699 If the item is outside the visible area, -1 is returned, regardless of
2700 whether an item will exist at that point when scrolled into view.
2701
2702 \note if you add a MouseArea as a child of the GridView, it will return
2703 positions in GridView coordinates rather than content item coordinates.
2704 To use those positions in a call to this function, you need to map them
2705 first:
2706
2707 \code
2708 GridView {
2709 id: view
2710 MouseArea {
2711 anchors.fill: parent
2712 onClicked: (mouse) => {
2713 let posInGridView = Qt.point(mouse.x, mouse.y)
2714 let posInContentItem = mapToItem(view.contentItem, posInGridView)
2715 let index = view.indexAt(posInContentItem.x, posInContentItem.y)
2716 }
2717 }
2718 }
2719 \endcode
2720
2721 \b Note: methods should only be called after the Component has completed.
2722
2723 \sa itemAt
2724*/
2725
2726/*!
2727 \qmlmethod Item QtQuick::GridView::itemAt(real x, real y)
2728
2729 Returns the visible item containing the point \a x, \a y in
2730 \l {QQuickFlickable::contentItem}{content item} coordinates. If there
2731 is no item at the point specified, or the item is not visible null is returned.
2732
2733 If the item is outside the visible area, null is returned, regardless of
2734 whether an item will exist at that point when scrolled into view.
2735
2736 \b Note: methods should only be called after the Component has completed.
2737
2738 \sa indexAt
2739*/
2740
2741/*!
2742 \qmlmethod Item QtQuick::GridView::itemAtIndex(int index)
2743
2744 Returns the item for \a index. If there is no item for that index, for example
2745 because it has not been created yet, or because it has been panned out of
2746 the visible area and removed from the cache, null is returned.
2747
2748 \b Note: this method should only be called after the Component has completed.
2749 The returned value should also not be stored since it can turn to null
2750 as soon as control goes out of the calling scope, if the view releases that item.
2751
2752 \since 5.13
2753*/
2754
2755/*!
2756 \qmlmethod QtQuick::GridView::forceLayout()
2757
2758 Responding to changes in the model is usually batched to happen only once
2759 per frame. This means that inside script blocks it is possible for the
2760 underlying model to have changed, but the GridView has not caught up yet.
2761
2762 This method forces the GridView to immediately respond to any outstanding
2763 changes in the model.
2764
2765 \since 5.1
2766
2767 \b Note: methods should only be called after the Component has completed.
2768*/
2769
2770QQuickGridViewAttached *QQuickGridView::qmlAttachedProperties(QObject *obj)
2771{
2772 return new QQuickGridViewAttached(obj);
2773}
2774
2775QT_END_NAMESPACE
2776
2777#include "moc_qquickgridview_p.cpp"
2778

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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