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