QQuickPathViewPrivate::QQuickPathViewPrivate()
: path(0), currentIndex(0), currentItemOffset(0.0), startPc(0)
- , offset(0.0), offsetAdj(0.0), mappedRange(1.0)
+ , offset(0.0), offsetAdj(0.0), mappedRange(1.0), mappedCache(0.0)
, stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
, autoHighlight(true), highlightUp(false), layoutScheduled(false)
- , moving(false), flicking(false), dragging(false), requestedOnPath(false), inRequest(false)
+ , moving(false), flicking(false), dragging(false), inRequest(false)
, dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY)
, moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
- , firstIndex(-1), pathItems(-1), requestedIndex(-1), requestedZ(0)
+ , firstIndex(-1), pathItems(-1), requestedIndex(-1), cacheSize(0), requestedZ(0)
, moveReason(Other), moveDirection(Shortest), attType(0), highlightComponent(0), highlightItem(0)
, moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
, highlightPosition(0)
q, QQuickPathView, SLOT(movementEnding()))
}
-QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
+QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool async)
{
Q_Q(QQuickPathView);
requestedIndex = modelIndex;
- requestedOnPath = onPath;
requestedZ = z;
inRequest = true;
- QQuickItem *item = model->item(modelIndex, false);
+ QQuickItem *item = model->item(modelIndex, async);
if (item) {
item->setParentItem(q);
requestedIndex = -1;
- qPathViewAttachedType = attType;
- QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
- qPathViewAttachedType = 0;
- if (att)
- att->setOnPath(onPath);
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
}
att->setOnPath(false);
}
item->setParentItem(this);
- d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
+ d->updateItem(item, 1.0);
} else {
d->requestedIndex = -1;
if (!d->inRequest)
{
Q_D(QQuickPathView);
if (d->requestedIndex == index) {
+ QQuickItemPrivate::get(item)->setCulled(true);
item->setParentItem(this);
qPathViewAttachedType = d->attachedType();
QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
if (att) {
att->m_view = this;
qreal percent = d->positionOfIndex(index);
- foreach (const QString &attr, d->path->attributes())
- att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
- item->setZ(d->requestedZ);
- if (att)
- att->setOnPath(d->requestedOnPath);
+ if (percent < 1.0) {
+ foreach (const QString &attr, d->path->attributes())
+ att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
+ item->setZ(d->requestedZ);
+ }
+ att->setOnPath(percent < 1.0);
}
}
}
QQuickItem *p = items[i];
releaseItem(p);
}
+ if (requestedIndex >= 0) {
+ if (model)
+ model->cancel(requestedIndex);
+ requestedIndex = -1;
+ }
+
items.clear();
tl.clear();
}
void QQuickPathViewPrivate::updateMappedRange()
{
- if (model && pathItems != -1 && pathItems < modelCount)
- mappedRange = qreal(pathItems)/modelCount;
- else
+ if (model && pathItems != -1 && pathItems < modelCount) {
+ mappedRange = qreal(modelCount)/pathItems;
+ mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
+ } else {
mappedRange = 1.0;
+ mappedCache = 0.0;
+ }
}
qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
qreal globalPos = index + offset;
globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
if (pathItems != -1 && pathItems < modelCount) {
- globalPos += start * mappedRange;
+ globalPos += start / mappedRange;
globalPos = qmlMod(globalPos, 1.0);
- if (globalPos < mappedRange)
- pos = globalPos / mappedRange;
+ pos = globalPos * mappedRange;
} else {
pos = qmlMod(globalPos + start, 1.0);
}
return pos;
}
+// returns true if position is between lower and upper, taking into
+// account the circular space.
+bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower, qreal upper) const
+{
+ if (lower > upper) {
+ if (position > upper && position > lower)
+ position -= mappedRange;
+ lower -= mappedRange;
+ }
+ return position >= lower && position < upper;
+}
+
void QQuickPathViewPrivate::createHighlight()
{
Q_Q(QQuickPathView);
// calc normalized position of highlight relative to offset
qreal relativeHighlight = qmlMod(pos + offset, range) / range;
- if (!highlightUp && relativeHighlight > end * mappedRange) {
+ if (!highlightUp && relativeHighlight > end / mappedRange) {
qreal diff = 1.0 - relativeHighlight;
setOffset(offset + diff * range);
- } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) {
- qreal diff = relativeHighlight - (end - start) * mappedRange;
+ } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) {
+ qreal diff = relativeHighlight - (end - start) / mappedRange;
setOffset(offset - diff * range - 0.00001);
}
qreal pathPos = positionOfIndex(pos);
updateItem(highlightItem, pathPos);
if (QQuickPathViewAttached *att = attached(highlightItem))
- att->setOnPath(pathPos != -1.0);
+ att->setOnPath(pathPos < 1.0);
}
}
att->m_percent = percent;
foreach (const QString &attr, path->attributes())
att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
+ att->setOnPath(percent < 1.0);
}
- QPointF pf = path->pointAt(percent);
+ QQuickItemPrivate::get(item)->setCulled(percent >= 1.0);
+ QPointF pf = path->pointAt(qMin(percent, qreal(1.0)));
item->setX(pf.x() - item->width()/2);
item->setY(pf.y() - item->height()/2);
}
}
/*!
+ \qmlproperty int QtQuick2::PathView::cacheItemCount
+ This property holds the maximum number of items to cache off the path.
+
+ For example, a PathView with a model containing 20 items, a pathItemCount
+ of 10, and an cacheItemCount of 4 will create up to 14 items, with 10 visible
+ on the path and 4 invisible cached items.
+
+ The cached delegates are created asynchronously,
+ allowing creation to occur across multiple frames and reducing the
+ likelihood of skipping frames.
+
+ Setting this value can improve the smoothness of scrolling behavior at the expense
+ of additional memory usage. It is not a substitute for creating efficient
+ delegates; the fewer objects and bindings in a delegate, the faster a view can be
+ moved.
+
+ \sa pathItemCount
+*/
+int QQuickPathView::cacheItemCount() const
+{
+ Q_D(const QQuickPathView);
+ return d->cacheSize;
+}
+
+void QQuickPathView::setCacheItemCount(int i)
+{
+ Q_D(QQuickPathView);
+ if (i == d->cacheSize || i < 0)
+ return;
+
+ d->cacheSize = i;
+ d->updateMappedRange();
+ refill();
+ emit cacheItemCountChanged();
+}
+
+/*!
\qmlproperty enumeration QtQuick2::PathView::snapMode
This property determines how the items will settle following a drag or flick.
}
} else {
moveReason = QQuickPathViewPrivate::Mouse;
- qreal diff = (newPc - startPc)*modelCount*mappedRange;
+ int count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
+ qreal diff = (newPc - startPc)*count;
if (diff) {
q->setOffset(offset + diff);
}
qreal velocity = calcVelocity();
- qreal count = modelCount*mappedRange;
+ qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
qreal pixelVelocity = (path->path().length()/count) * velocity;
if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
return;
bool currentVisible = false;
+ int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
// first move existing items and remove items off path
int idx = d->firstIndex;
while (it != d->items.end()) {
qreal pos = d->positionOfIndex(idx);
QQuickItem *item = *it;
- if (pos >= 0.0) {
+ if (pos < 1.0) {
d->updateItem(item, pos);
if (idx == d->currentIndex) {
currentVisible = true;
}
++it;
} else {
- // qDebug() << "release";
- d->updateItem(item, 1.0);
- d->releaseItem(item);
- if (it == d->items.begin()) {
- if (++d->firstIndex >= d->modelCount)
- d->firstIndex = 0;
+ d->updateItem(item, pos);
+ if (QQuickPathViewAttached *att = d->attached(item))
+ att->setOnPath(pos < 1.0);
+ if (!d->isInBound(pos, d->mappedRange - d->mappedCache, 1.0 + d->mappedCache)) {
+// qDebug() << "release";
+ d->releaseItem(item);
+ if (it == d->items.begin()) {
+ if (++d->firstIndex >= d->modelCount) {
+ d->firstIndex = 0;
+ }
+ }
+ it = d->items.erase(it);
+ } else {
+ ++it;
}
- it = d->items.erase(it);
}
++idx;
if (idx >= d->modelCount)
idx = 0;
}
+
if (!d->items.count())
d->firstIndex = -1;
bool waiting = false;
if (d->modelCount) {
// add items to beginning and end
- int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
- if (d->items.count() < count) {
+ if (d->items.count() < count+d->cacheSize) {
int idx = qRound(d->modelCount - d->offset) % d->modelCount;
qreal startPos = 0.0;
if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
idx = (d->firstIndex + d->items.count()) % d->modelCount;
}
qreal pos = d->positionOfIndex(idx);
- while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
+ while ((d->isInBound(pos, startPos, 1.0 + d->mappedCache) || !d->items.count()) && d->items.count() < count+d->cacheSize) {
// qDebug() << "append" << idx;
- QQuickItem *item = d->getItem(idx, idx+1);
+ QQuickItem *item = d->getItem(idx, idx+1, pos >= 1.0);
if (!item) {
waiting = true;
break;
if (idx < 0)
idx = d->modelCount - 1;
pos = d->positionOfIndex(idx);
- while (!waiting && (pos >= 0.0 && pos < startPos) && d->items.count() < count) {
+ while (!waiting && d->isInBound(pos, d->mappedRange - d->mappedCache, startPos) && d->items.count() < count+d->cacheSize) {
// qDebug() << "prepend" << idx;
- QQuickItem *item = d->getItem(idx, idx+1);
+ QQuickItem *item = d->getItem(idx, idx+1, pos >= 1.0);
if (!item) {
waiting = true;
break;
if (!currentVisible) {
d->currentItemOffset = 1.0;
if (d->currentItem) {
- if (QQuickPathViewAttached *att = d->attached(d->currentItem))
- att->setOnPath(false);
+ d->updateItem(d->currentItem, 1.0);
} else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
- if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
- d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
+ if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
+ d->updateItem(d->currentItem, 1.0);
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setIsCurrentItem(true);
}
}
} else if (!waiting && !d->currentItem) {
- if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
+ if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
d->currentItem->setFocus(true);
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setIsCurrentItem(true);
return;
int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
if (itemIndex < items.count()) {
- if ((currentItem = getItem(currentIndex, currentIndex, true))) {
+ if ((currentItem = getItem(currentIndex, currentIndex))) {
currentItem->setFocus(true);
if (QQuickPathViewAttached *att = attached(currentItem))
att->setIsCurrentItem(true);
}
} else if (currentIndex >= 0 && currentIndex < modelCount) {
- if ((currentItem = getItem(currentIndex, currentIndex, false))) {
- updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
+ if ((currentItem = getItem(currentIndex, currentIndex))) {
+ updateItem(currentItem, 1.0);
if (QQuickPathViewAttached *att = attached(currentItem))
att->setIsCurrentItem(true);
}
void positionViewAtIndex_data();
void indexAt_itemAt();
void indexAt_itemAt_data();
+ void cacheItemCount();
};
class TestObject : public QObject
QTest::newRow("Item 7 - 360, 200") << 360. << 200. << 7;
}
+void tst_QQuickPathView::cacheItemCount()
+{
+ QQuickView *window = createView();
+
+ window->setSource(testFileUrl("pathview3.qml"));
+ window->show();
+ qApp->processEvents();
+
+ QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject());
+ QVERIFY(pathview != 0);
+
+ QMetaObject::invokeMethod(pathview, "addColor", Q_ARG(QVariant, QString("orange")));
+ QMetaObject::invokeMethod(pathview, "addColor", Q_ARG(QVariant, QString("lightsteelblue")));
+ QMetaObject::invokeMethod(pathview, "addColor", Q_ARG(QVariant, QString("teal")));
+ QMetaObject::invokeMethod(pathview, "addColor", Q_ARG(QVariant, QString("aqua")));
+
+ pathview->setOffset(0);
+
+ pathview->setCacheItemCount(3);
+ QVERIFY(pathview->cacheItemCount() == 3);
+
+ QQmlIncubationController controller;
+ window->engine()->setIncubationController(&controller);
+
+ // Items on the path are created immediately
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 0));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 1));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 11));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 10));
+
+ const int cached[] = { 2, 3, 9, -1 }; // two appended, one prepended
+
+ int i = 0;
+ while (cached[i] >= 0) {
+ // items will be created one at a time
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", cached[i]) == 0);
+ QQuickItem *item = 0;
+ while (!item) {
+ bool b = false;
+ controller.incubateWhile(&b);
+ item = findItem<QQuickItem>(pathview, "wrapper", cached[i]);
+ }
+ ++i;
+ }
+
+ {
+ bool b = true;
+ controller.incubateWhile(&b);
+ }
+
+ // move view and confirm items in view are visible immediately and outside are created async
+ pathview->setOffset(4);
+
+ // Items on the path are created immediately
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 6));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 7));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 8));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 9));
+ // already created items within cache stay created
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 10));
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 11));
+
+ // one item prepended async.
+ QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 5) == 0);
+ QQuickItem *item = 0;
+ while (!item) {
+ bool b = false;
+ controller.incubateWhile(&b);
+ item = findItem<QQuickItem>(pathview, "wrapper", 5);
+ }
+
+ {
+ bool b = true;
+ controller.incubateWhile(&b);
+ }
+
+ delete window;
+}
+
QTEST_MAIN(tst_QQuickPathView)
#include "tst_qquickpathview.moc"