aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick
diff options
context:
space:
mode:
authorBea Lam <bea.lam@nokia.com>2012-02-09 17:59:44 +1000
committerQt by Nokia <qt-info@nokia.com>2012-02-16 08:30:27 +0100
commit2df9abf7047afbe20b19d156ac37b58d9b047575 (patch)
tree0d4e13362d12f978d8d0c7871e161fd2f0fddeaa /src/quick
parent7127120b68ec08296b6e2980d1c9ae1a34e5f28d (diff)
Built-in transition support for ListView & GridView
ListView and GridView can now be assigned transitions to be run when: - Populating the view (when initially setting the model / resetting) - Adding items - Removing items - Moving items The ViewTransition attached object can be used from within a transition declaration to access various information about the items that are being transitioned. Task-number: QTBUG-21504 Change-Id: Ie5c75ea511c8b15acc3f06fccf19abe34d3677f9 Reviewed-by: Martin Jones <martin.jones@nokia.com>
Diffstat (limited to 'src/quick')
-rw-r--r--src/quick/items/qquickgridview.cpp416
-rw-r--r--src/quick/items/qquickitemsmodule.cpp1
-rw-r--r--src/quick/items/qquickitemview.cpp1126
-rw-r--r--src/quick/items/qquickitemview_p.h86
-rw-r--r--src/quick/items/qquickitemview_p_p.h139
-rw-r--r--src/quick/items/qquicklistview.cpp417
6 files changed, 2046 insertions, 139 deletions
diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp
index 522c09ae54..39c7eab518 100644
--- a/src/quick/items/qquickgridview.cpp
+++ b/src/quick/items/qquickgridview.cpp
@@ -92,9 +92,9 @@ public:
qreal rowPos() const {
if (view->flow() == QQuickGridView::LeftToRight)
- return item->y();
+ return itemY();
else
- return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-item->x() : item->x());
+ return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-itemX() : itemX());
}
qreal colPos() const {
@@ -102,45 +102,53 @@ public:
if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
qreal colSize = view->cellWidth();
int columns = view->width()/colSize;
- return colSize * (columns-1) - item->x();
+ return colSize * (columns-1) - itemX();
} else {
- return item->x();
+ return itemX();
}
} else {
- return item->y();
+ return itemY();
}
}
qreal endRowPos() const {
if (view->flow() == QQuickGridView::LeftToRight) {
- return item->y() + view->cellHeight();
+ return itemY() + view->cellHeight();
} else {
if (view->effectiveLayoutDirection() == Qt::RightToLeft)
- return -item->x();
+ return -itemX();
else
- return item->x() + view->cellWidth();
+ return itemX() + view->cellWidth();
}
}
void setPosition(qreal col, qreal row) {
+ moveTo(pointForPosition(col, row));
+ }
+ bool contains(qreal x, qreal y) const {
+ return (x >= itemX() && x < itemX() + view->cellWidth() &&
+ y >= itemY() && y < itemY() + view->cellHeight());
+ }
+ QQuickItemView *itemView() const {
+ return view;
+ }
+
+ QQuickGridView *view;
+
+private:
+ QPointF pointForPosition(qreal col, qreal row) const {
if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
if (view->flow() == QQuickGridView::LeftToRight) {
int columns = view->width()/view->cellWidth();
- item->setPos(QPointF((view->cellWidth() * (columns-1) - col), row));
+ return QPointF(view->cellWidth() * (columns-1) - col, row);
} else {
- item->setPos(QPointF(-view->cellWidth()-row, col));
+ return QPointF(-view->cellWidth() - row, col);
}
} else {
if (view->flow() == QQuickGridView::LeftToRight)
- item->setPos(QPointF(col, row));
+ return QPointF(col, row);
else
- item->setPos(QPointF(row, col));
+ return QPointF(row, col);
}
}
- bool contains(qreal x, qreal y) const {
- return (x >= item->x() && x < item->x() + view->cellWidth() &&
- y >= item->y() && y < item->y() + view->cellHeight());
- }
-
- QQuickGridView *view;
};
//----------------------------------------------------------------------------
@@ -173,6 +181,8 @@ public:
virtual bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
virtual FxViewItem *newViewItem(int index, QQuickItem *item);
+ virtual void initializeViewItem(FxViewItem *item);
+ virtual void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer);
virtual void repositionPackageItemAt(QQuickItem *item, int index);
virtual void resetFirstItemPosition(qreal pos = 0.0);
virtual void adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible);
@@ -183,7 +193,8 @@ public:
virtual void setPosition(qreal pos);
virtual void layoutVisibleItems(int fromModelIndex = 0);
- virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems);
+ virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView);
+ virtual void translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult);
virtual bool needsRefillForAddedOrRemovedIndex(int index) const;
virtual qreal headerSize() const;
@@ -309,8 +320,11 @@ qreal QQuickGridViewPrivate::colPosAt(int modelIndex) const
col = (columns - count + col) % columns;
return col * colSize();
} else {
- int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns;
- return static_cast<FxGridItemSG*>(visibleItems.last())->colPos() - count * colSize();
+ FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last());
+ int count = modelIndex - lastItem->index;
+ int col = lastItem->colPos() / colSize();
+ col = (col + count) % columns;
+ return col * colSize();
}
}
return (modelIndex % columns) * colSize();
@@ -415,6 +429,15 @@ FxViewItem *QQuickGridViewPrivate::newViewItem(int modelIndex, QQuickItem *item)
return new FxGridItemSG(item, q, false);
}
+void QQuickGridViewPrivate::initializeViewItem(FxViewItem *item)
+{
+ QQuickItemViewPrivate::initializeViewItem(item);
+
+ // need to track current items that are animating
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item->item);
+ itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
+}
+
bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool doBuffer)
{
qreal colPos = colPosAt(visibleIndex);
@@ -462,7 +485,8 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
#endif
if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex, doBuffer))))
break;
- item->setPosition(colPos, rowPos);
+ if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+ item->setPosition(colPos, rowPos);
item->item->setVisible(!doBuffer);
visibleItems.append(item);
if (++colNum >= columns) {
@@ -499,7 +523,8 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
if (!(item = static_cast<FxGridItemSG*>(createItem(visibleIndex-1, doBuffer))))
break;
--visibleIndex;
- item->setPosition(colPos, rowPos);
+ if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+ item->setPosition(colPos, rowPos);
item->item->setVisible(!doBuffer);
visibleItems.prepend(item);
if (--colNum < 0) {
@@ -529,7 +554,15 @@ bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
if (item->index != -1)
visibleIndex++;
visibleItems.removeFirst();
- releaseItem(item);
+ if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+ qDebug() << "\tnot releasing animating item:" << item->index << item->item->objectName();
+#endif
+ item->releaseAfterTransition = true;
+ releasePendingTransition.append(item);
+ } else {
+ releaseItem(item);
+ }
changed = true;
}
while (visibleItems.count() > 1
@@ -541,7 +574,15 @@ bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1;
#endif
visibleItems.removeLast();
- releaseItem(item);
+ if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+ qDebug() << "\tnot releasing animating item:" << item->index << item->item->objectName();
+#endif
+ item->releaseAfterTransition = true;
+ releasePendingTransition.append(item);
+ } else {
+ releaseItem(item);
+ }
changed = true;
}
@@ -567,7 +608,7 @@ void QQuickGridViewPrivate::layoutVisibleItems(int fromModelIndex)
if (colPos != col * colSize()) {
colPos = col * colSize();
firstItem->setPosition(colPos, rowPos);
- firstItem->item->setVisible(rowPos + rowSize() >= from && rowPos <= to);
+ firstItem->setVisible(firstItem->rowPos() + rowSize() >= from && firstItem->rowPos() <= to);
}
for (int i = 1; i < visibleItems.count(); ++i) {
FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.at(i));
@@ -578,12 +619,18 @@ void QQuickGridViewPrivate::layoutVisibleItems(int fromModelIndex)
colPos = col * colSize();
if (item->index >= fromModelIndex) {
item->setPosition(colPos, rowPos);
- item->item->setVisible(rowPos + rowSize() >= from && rowPos <= to);
+ item->setVisible(item->rowPos() + rowSize() >= from && item->rowPos() <= to);
}
}
}
}
+void QQuickGridViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer)
+{
+ int count = sizeBuffer / rowSize();
+ static_cast<FxGridItemSG *>(item)->setPosition(colPosAt(index + count), rowPosAt(index + count));
+}
+
void QQuickGridViewPrivate::repositionPackageItemAt(QQuickItem *item, int index)
{
Q_Q(QQuickGridView);
@@ -668,8 +715,8 @@ void QQuickGridViewPrivate::updateHighlight()
bool strictHighlight = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange;
if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) {
// auto-update highlight
- highlightXAnimator->to = currentItem->item->x();
- highlightYAnimator->to = currentItem->item->y();
+ highlightXAnimator->to = currentItem->itemX();
+ highlightYAnimator->to = currentItem->itemY();
highlight->item->setWidth(currentItem->item->width());
highlight->item->setHeight(currentItem->item->height());
@@ -797,7 +844,11 @@ void QQuickGridViewPrivate::initializeCurrentItem()
{
if (currentItem && currentIndex >= 0) {
FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(currentItem);
- gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex));
+ FxViewItem *actualItem = visibleItem(currentIndex);
+
+ // don't reposition the item if it's about to be transitioned to another position
+ if ((!actualItem || !actualItem->transitionScheduledOrRunning()))
+ gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex));
}
}
@@ -1144,17 +1195,17 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
/*!
\qmlattachedproperty bool QtQuick2::GridView::delayRemove
- This attached property holds whether the delegate may be destroyed.
-
- It is attached to each instance of the delegate.
+ This attached property holds whether the delegate may be destroyed. It
+ is attached to each instance of the delegate. The default value is false.
It is sometimes necessary to delay the destruction of an item
- until an animation completes.
-
- The example below ensures that the animation completes before
- the item is removed from the grid.
+ until an animation completes. The example delegate below ensures that the
+ animation completes before the item is removed from the list.
\snippet doc/src/snippets/declarative/gridview/gridview.qml delayRemove
+
+ If a \l remove transition has been specified, it will not be applied until
+ delayRemove is returned to \c false.
*/
/*!
@@ -1165,6 +1216,9 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
/*!
\qmlattachedsignal QtQuick2::GridView::onRemove()
This attached handler is called immediately before an item is removed from the view.
+
+ If a \l remove transition has been specified, it is applied after
+ this signal handler is called, providing that delayRemove is false.
*/
@@ -1530,6 +1584,228 @@ void QQuickGridView::setSnapMode(SnapMode mode)
\sa footer, headerItem
*/
+/*!
+ \qmlproperty Transition QtQuick2::GridView::populate
+ This property holds the transition to apply to items that are initially created for a
+ view.
+
+ This transition is applied to all the items that are created when:
+
+ \list
+ \o The view is first created
+ \o The view's \l model changes
+ \o The view's \l model is \l {QAbstractItemModel::reset}{reset}, if the model is a QAbstractItemModel subclass
+ \endlist
+
+ For example, here is a view that specifies such a transition:
+
+ \code
+ GridView {
+ ...
+ populate: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ When the view is initialized, the view will create all the necessary items for the view,
+ then animate them to their correct positions within the view over one second.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa add, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::GridView::add
+ This property holds the transition to apply to items that are added within the view.
+
+ The transition is applied to items that have been added to the visible area of the view. For
+ example, here is a view that specifies such a transition:
+
+ \code
+ GridView {
+ ...
+ add: Transition {
+ NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is added to the above view, the item will be animated from the position (100,100)
+ to its final x,y position within the view, over one second. The transition only applies to
+ the new items that are added to the view; it does not apply to the items below that are
+ displaced by the addition of the new items. To animate the displaced items, set the \l
+ addDisplaced property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \note This transition is not applied to the items that are created when the view is initially
+ populated, or when the view's \l model changes. In those cases, the \l populate transition is
+ applied instead.
+
+ \sa addDisplaced, populate, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::GridView::addDisplaced
+ This property holds the transition to apply to items in the view that are displaced by other
+ items that have been added to the view.
+
+ The transition is applied to items that are currently visible and have been displaced by newly
+ added items. For example, here is a view that specifies such a transition:
+
+ \code
+ GridView {
+ ...
+ addDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is added to the above view, all items beneath the new item are displaced, causing
+ them to move down (or sideways, if horizontally orientated) within the view. As this
+ displacement occurs, the items' movement to their new x,y positions within the view will be
+ animated by a NumberAnimation over one second, as specified. This transition is not applied to
+ the new item that has been added to the view; to animate the added items, set the \l add
+ property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \note This transition is not applied to the items that are created when the view is initially
+ populated, or when the view's \l model changes. In those cases, the \l populate transition is
+ applied instead.
+
+ \sa add, populate, ViewTransition
+*/
+/*!
+ \qmlproperty Transition QtQuick2::GridView::move
+ This property holds the transition to apply to items in the view that are moved by a move
+ operation.
+
+ The transition is applied to items that are moving within the view or are moving
+ into the view as a result of a move operation in the view's model. For example:
+
+ \code
+ GridView {
+ ...
+ move: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is moved within the above view, the item will be animated to its new position in
+ the view over one second. The transition only applies to the items that are the subject of the
+ move operation in the model; it does not apply to the items below them that are displaced by
+ the move operation. To animate the displaced items, set the \l moveDisplaced property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa moveDisplaced, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::GridView::moveDisplaced
+ This property holds the transition to apply to items in the view that are displaced by a
+ move operation in the view.
+
+ The transition is applied to items that are currently visible and have been displaced following
+ a move operation in the view's model. For example, here is a view that specifies such a transition:
+
+ \code
+ GridView {
+ ...
+ moveDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item moves within (or moves into) the above view, all items beneath it are
+ displaced, causing them to move upwards (or sideways, if horizontally orientated) within the
+ view. As this displacement occurs, the items' movement to their new x,y positions within the
+ view will be animated by a NumberAnimation over one second, as specified. This transition is
+ not applied to the item that are actually the subject of the move operation; to animate the
+ moved items, set the \l move property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa move, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::GridView::remove
+ This property holds the transition to apply to items that are removed from the view.
+
+ The transition is applied to items that have been removed from the visible area of the view. For
+ example:
+
+ \code
+ GridView {
+ ...
+ remove: Transition {
+ ParallelAnimation {
+ NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
+ NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
+ }
+ }
+ }
+ \endcode
+
+ Whenever an item is removed from the above view, the item will be animated to the position (100,100)
+ over one second, and in parallel will also change its opacity to 0. The transition
+ only applies to the items that are removed from the view; it does not apply to the items below
+ them that are displaced by the removal of the items. To animate the displaced items, set the \l
+ removeDisplaced property.
+
+ Note that by the time the transition is applied, the item has already been removed from the
+ model; any references to the model data for the removed index will not be valid.
+
+ Additionally, if the \l delayRemove attached property has been set for a delegate item, the
+ remove transition will not be applied until \l delayRemove becomes false again.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa removeDisplaced, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::GridView::removeDisplaced
+ This property holds the transition to apply to items in the view that are displaced by the
+ removal of other items in the view.
+
+ The transition is applied to items that are currently visible and have been displaced by
+ the removal of items. For example, here is a view that specifies such a transition:
+
+ \code
+ GridView {
+ ...
+ removeDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is removed from the above view, all items beneath it are displaced, causing
+ them to move upwards (or sideways, if horizontally orientated) within the view. As this
+ displacement occurs, the items' movement to their new x,y positions within the view will be
+ animated by a NumberAnimation over one second, as specified. This transition is not applied to
+ the item that has actually been removed from the view; to animate the removed items, set the
+ \l remove property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa remove, ViewTransition
+*/
void QQuickGridView::viewportMoved()
{
Q_D(QQuickGridView);
@@ -1583,9 +1859,9 @@ void QQuickGridView::viewportMoved()
d->updateCurrent(idx);
if (d->currentItem && static_cast<FxGridItemSG*>(d->currentItem)->colPos() != static_cast<FxGridItemSG*>(d->highlight)->colPos() && d->autoHighlight) {
if (d->flow == LeftToRight)
- d->highlightXAnimator->to = d->currentItem->item->x();
+ d->highlightXAnimator->to = d->currentItem->itemX();
else
- d->highlightYAnimator->to = d->currentItem->item->y();
+ d->highlightYAnimator->to = d->currentItem->itemY();
}
}
}
@@ -1775,7 +2051,7 @@ void QQuickGridView::moveCurrentIndexRight()
}
}
-bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems)
+bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
{
Q_Q(QQuickGridView);
@@ -1831,8 +2107,13 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
// Update the indexes of the following visible items.
for (int i = 0; i < visibleItems.count(); ++i) {
FxViewItem *item = visibleItems.at(i);
- if (item->index != -1 && item->index >= modelIndex)
+ if (item->index != -1 && item->index >= modelIndex) {
item->index += count;
+ if (change.isMove())
+ transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, false);
+ else
+ transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, false);
+ }
}
int prevVisibleCount = visibleItems.count();
@@ -1845,7 +2126,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
while (i >= 0) {
if (rowPos > from && insertionIdx < visibleIndex) {
// item won't be visible, just note the size for repositioning
- insertResult->changeBeforeVisible++;
+ insertResult->countChangeBeforeVisible++;
} else {
// item is before first visible e.g. in cache buffer
FxViewItem *item = 0;
@@ -1858,8 +2139,12 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
item->item->setVisible(true);
visibleItems.insert(insertionIdx, item);
- if (!change.isMove())
+ if (insertionIdx == 0)
+ insertResult->changedFirstItem = true;
+ if (!change.isMove()) {
addedItems->append(item);
+ transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+ }
insertResult->sizeChangesBeforeVisiblePos += rowSize();
}
@@ -1878,6 +2163,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
FxViewItem *item = 0;
if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i))))
item->index = modelIndex + i;
+ bool newItem = !item;
if (!item)
item = createItem(modelIndex + i);
if (!item)
@@ -1887,8 +2173,15 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
visibleItems.insert(index, item);
if (index == 0)
insertResult->changedFirstItem = true;
- if (!change.isMove())
+ if (change.isMove()) {
+ // we know this is a move target, since move displaced items that are
+ // shuffled into view due to a move would be added in refill()
+ if (moveTransition && newItem)
+ movingIntoView->append(MovedItem(item, change.moveKey(item->index)));
+ } else {
addedItems->append(item);
+ transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+ }
insertResult->sizeChangesAfterVisiblePos += rowSize();
if (++colNum >= columns) {
@@ -1906,6 +2199,41 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
return visibleItems.count() > prevVisibleCount;
}
+void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
+{
+ int markerItemIndex = -1;
+ for (int i=0; i<visibleItems.count(); i++) {
+ if (visibleItems[i]->index == afterModelIndex) {
+ markerItemIndex = i;
+ break;
+ }
+ }
+ if (markerItemIndex < 0)
+ return;
+
+ const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
+ int countItemsRemoved = -(removalResult.sizeChangesAfterVisiblePos / rowSize());
+
+ // account for whether first item has changed if < 1 row was removed before visible
+ int changeBeforeVisible = insertionResult.countChangeBeforeVisible - removalResult.countChangeBeforeVisible;
+ if (changeBeforeVisible != 0)
+ countItemsRemoved += (changeBeforeVisible % columns) - (columns - 1);
+
+ countItemsRemoved -= removalResult.countChangeAfterVisibleItems;
+
+ for (int i=markerItemIndex+1; i<visibleItems.count() && visibleItems.at(i)->position() < viewEndPos; i++) {
+ FxGridItemSG *gridItem = static_cast<FxGridItemSG *>(visibleItems[i]);
+ if (!gridItem->transitionScheduledOrRunning()) {
+ qreal origRowPos = gridItem->colPos();
+ qreal origColPos = gridItem->rowPos();
+ int indexDiff = gridItem->index - countItemsRemoved;
+ gridItem->setPosition((indexDiff % columns) * colSize(), (indexDiff / columns) * rowSize());
+ transitionNextReposition(gridItem, FxViewItemTransitionManager::RemoveTransition, false);
+ gridItem->setPosition(origRowPos, origColPos);
+ }
+ }
+}
+
bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const
{
// If we add or remove items before visible items, a layout may be
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index 3d1e96da84..a4cfa26205 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -184,6 +184,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
qmlRegisterUncreatableType<QQuickKeyNavigationAttached>(uri,major,minor,"KeyNavigation",QQuickKeyNavigationAttached::tr("KeyNavigation is only available via attached properties"));
qmlRegisterUncreatableType<QQuickKeysAttached>(uri,major,minor,"Keys",QQuickKeysAttached::tr("Keys is only available via attached properties"));
qmlRegisterUncreatableType<QQuickLayoutMirroringAttached>(uri,major,minor,"LayoutMirroring", QQuickLayoutMirroringAttached::tr("LayoutMirroring is only available via attached properties"));
+ qmlRegisterUncreatableType<QQuickViewTransitionAttached>(uri,major,minor,"ViewTransition",QQuickViewTransitionAttached::tr("ViewTransition is only available via attached properties"));
qmlRegisterType<QQuickPinchArea>(uri,major,minor,"PinchArea");
qmlRegisterType<QQuickPinch>(uri,major,minor,"Pinch");
diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp
index f09be2b54f..87a428a87c 100644
--- a/src/quick/items/qquickitemview.cpp
+++ b/src/quick/items/qquickitemview.cpp
@@ -40,17 +40,26 @@
****************************************************************************/
#include "qquickitemview_p_p.h"
+#include <QtQuick/private/qdeclarativetransition_p.h>
QT_BEGIN_NAMESPACE
FxViewItem::FxViewItem(QQuickItem *i, bool own)
- : item(i), ownItem(own), index(-1)
+ : item(i), ownItem(own), index(-1), releaseAfterTransition(false)
+ , transition(0)
+ , nextTransitionType(FxViewItemTransitionManager::NoTransition)
+ , isTransitionTarget(false)
+ , nextTransitionToSet(false)
{
}
FxViewItem::~FxViewItem()
{
+ if (transition)
+ transition->m_item = 0;
+ delete transition;
+
if (ownItem && item) {
item->setParentItem(0);
item->deleteLater();
@@ -58,6 +67,273 @@ FxViewItem::~FxViewItem()
}
}
+qreal FxViewItem::itemX() const
+{
+ if (nextTransitionType != FxViewItemTransitionManager::NoTransition)
+ return nextTransitionToSet ? nextTransitionTo.x() : item->x();
+ else if (transition && transition->isActive())
+ return transition->m_toPos.x();
+ else
+ return item->x();
+}
+
+qreal FxViewItem::itemY() const
+{
+ // If item is transitioning to some pos, return that dest pos.
+ // If item was redirected to some new pos before the current transition finished,
+ // return that new pos.
+ if (nextTransitionType != FxViewItemTransitionManager::NoTransition)
+ return nextTransitionToSet ? nextTransitionTo.y() : item->y();
+ else if (transition && transition->isActive())
+ return transition->m_toPos.y();
+ else
+ return item->y();
+}
+
+void FxViewItem::setVisible(bool visible)
+{
+ if (!visible && transitionScheduledOrRunning())
+ return;
+ item->setVisible(visible);
+}
+
+void FxViewItem::setNextTransition(FxViewItemTransitionManager::TransitionType type, bool isTargetItem)
+{
+ // Don't reset nextTransitionToSet - once it is set, it cannot be changed
+ // until the animation finishes since the itemX() and itemY() may be used
+ // to calculate positions for transitions for other items in the view.
+ nextTransitionType = type;
+ isTransitionTarget = isTargetItem;
+}
+
+bool FxViewItem::transitionScheduledOrRunning() const
+{
+ return (transition && transition->isActive())
+ || nextTransitionType != FxViewItemTransitionManager::NoTransition;
+}
+
+bool FxViewItem::prepareTransition(const QRectF &viewBounds)
+{
+ bool doTransition = false;
+
+ switch (nextTransitionType) {
+ case FxViewItemTransitionManager::NoTransition:
+ {
+ return false;
+ }
+ case FxViewItemTransitionManager::PopulateTransition:
+ {
+ return true;
+ }
+ case FxViewItemTransitionManager::AddTransition:
+ case FxViewItemTransitionManager::RemoveTransition:
+ // For Add targets, do transition if item is moving into visible area
+ // For Remove targets, do transition if item is currently in visible area
+ if (isTransitionTarget) {
+ doTransition = (nextTransitionType == FxViewItemTransitionManager::AddTransition)
+ ? viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))
+ : viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()));
+ if (!doTransition)
+ item->setPos(nextTransitionTo);
+ } else {
+ if (viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()))
+ || viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))) {
+ doTransition = (nextTransitionTo != item->pos());
+ } else {
+ item->setPos(nextTransitionTo);
+ }
+ }
+ break;
+ case FxViewItemTransitionManager::MoveTransition:
+ // do transition if moving from or into visible area
+ if (nextTransitionTo != item->pos()) {
+ doTransition = viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()))
+ || viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()));
+ if (!doTransition)
+ item->setPos(nextTransitionTo);
+ }
+ break;
+ }
+
+ if (!doTransition)
+ resetTransitionData();
+ return doTransition;
+}
+
+void FxViewItem::startTransition()
+{
+ if (nextTransitionType == FxViewItemTransitionManager::NoTransition)
+ return;
+
+ if (!transition || transition->m_type != nextTransitionType || transition->m_type != isTransitionTarget) {
+ delete transition;
+ transition = new FxViewItemTransitionManager;
+ }
+
+ // if item is not already moving somewhere, set it to not move anywhere
+ // so that removed items do not move to the default (0,0)
+ if (!nextTransitionToSet)
+ moveTo(item->pos());
+
+ transition->startTransition(this, nextTransitionType, nextTransitionTo, isTransitionTarget);
+ nextTransitionType = FxViewItemTransitionManager::NoTransition;
+}
+
+void FxViewItem::stopTransition()
+{
+ if (transition) {
+ transition->cancel();
+ delete transition;
+ transition = 0;
+ }
+ resetTransitionData();
+ finishedTransition();
+}
+
+void FxViewItem::finishedTransition()
+{
+ nextTransitionToSet = false;
+ nextTransitionTo = QPointF();
+
+ if (releaseAfterTransition) {
+ QQuickItemViewPrivate *vp = static_cast<QQuickItemViewPrivate*>(QObjectPrivate::get(itemView()));
+ vp->releasePendingTransition.removeOne(this);
+ vp->releaseItem(this);
+ }
+}
+
+void FxViewItem::resetTransitionData()
+{
+ nextTransitionType = FxViewItemTransitionManager::NoTransition;
+ isTransitionTarget = false;
+ nextTransitionTo = QPointF();
+ nextTransitionToSet = false;
+}
+
+bool FxViewItem::isPendingRemoval() const
+{
+ if (nextTransitionType == FxViewItemTransitionManager::RemoveTransition)
+ return isTransitionTarget;
+ if (transition && transition->isActive() && transition->m_type == FxViewItemTransitionManager::RemoveTransition)
+ return transition->m_isTarget;
+ return false;
+}
+
+void FxViewItem::moveTo(const QPointF &pos)
+{
+ if (transitionScheduledOrRunning()) {
+ nextTransitionTo = pos;
+ nextTransitionToSet = true;
+ } else {
+ item->setPos(pos);
+ }
+}
+
+
+FxViewItemTransitionManager::FxViewItemTransitionManager()
+ : m_active(false), m_item(0), m_type(FxViewItemTransitionManager::NoTransition), m_isTarget(false)
+{
+}
+
+FxViewItemTransitionManager::~FxViewItemTransitionManager()
+{
+}
+
+bool FxViewItemTransitionManager::isActive() const
+{
+ return m_active;
+}
+
+void FxViewItemTransitionManager::startTransition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, const QPointF &to, bool isTargetItem)
+{
+ if (!item) {
+ qWarning("startTransition(): invalid item");
+ return;
+ }
+
+ QQuickItemViewPrivate *vp = static_cast<QQuickItemViewPrivate*>(QObjectPrivate::get(item->itemView()));
+
+ QDeclarativeTransition *trans = 0;
+ switch (type) {
+ case NoTransition:
+ break;
+ case PopulateTransition:
+ trans = vp->populateTransition;
+ break;
+ case AddTransition:
+ trans = isTargetItem ? vp->addTransition : vp->addDisplacedTransition;
+ break;
+ case MoveTransition:
+ trans = isTargetItem ? vp->moveTransition : vp->moveDisplacedTransition;
+ break;
+ case RemoveTransition:
+ trans = isTargetItem ? vp->removeTransition : vp->removeDisplacedTransition;
+ break;
+ }
+
+ if (!trans) {
+ qWarning("QQuickItemView: invalid view transition!");
+ return;
+ }
+
+ m_active = true;
+ m_item = item;
+ m_toPos = to;
+ m_type = type;
+ m_isTarget = isTargetItem;
+
+ QQuickViewTransitionAttached *attached =
+ static_cast<QQuickViewTransitionAttached*>(qmlAttachedPropertiesObject<QQuickViewTransitionAttached>(trans));
+ if (attached) {
+ attached->m_index = item->index;
+ attached->m_item = item->item;
+ attached->m_destination = to;
+ switch (type) {
+ case NoTransition:
+ break;
+ case PopulateTransition:
+ case AddTransition:
+ attached->m_targetIndexes = vp->addTransitionIndexes;
+ attached->m_targetItems = vp->addTransitionTargets;
+ break;
+ case MoveTransition:
+ attached->m_targetIndexes = vp->moveTransitionIndexes;
+ attached->m_targetItems = vp->moveTransitionTargets;
+ break;
+ case RemoveTransition:
+ attached->m_targetIndexes = vp->removeTransitionIndexes;
+ attached->m_targetItems = vp->removeTransitionTargets;
+ break;
+ }
+ emit attached->indexChanged();
+ emit attached->itemChanged();
+ emit attached->destinationChanged();
+ emit attached->targetIndexesChanged();
+ emit attached->targetItemsChanged();
+ }
+
+ QDeclarativeStateOperation::ActionList actions;
+ actions << QDeclarativeAction(item->item, QLatin1String("x"), QVariant(to.x()));
+ actions << QDeclarativeAction(item->item, QLatin1String("y"), QVariant(to.y()));
+
+ QDeclarativeTransitionManager::transition(actions, trans, item->item);
+}
+
+void FxViewItemTransitionManager::finished()
+{
+ QDeclarativeTransitionManager::finished();
+
+ m_active = false;
+
+ if (m_item)
+ m_item->finishedTransition();
+ m_item = 0;
+ m_toPos.setX(0);
+ m_toPos.setY(0);
+ m_type = NoTransition;
+ m_isTarget = false;
+}
+
QQuickItemViewChangeSet::QQuickItemViewChangeSet()
: active(false)
@@ -137,6 +413,364 @@ void QQuickItemViewChangeSet::reset()
}
+QQuickViewTransitionAttached::QQuickViewTransitionAttached(QObject *parent)
+ : QObject(parent), m_index(-1), m_item(0)
+{
+}
+/*!
+ \qmlclass ViewTransition QQuickViewTransitionAttached
+ \inqmlmodule QtQuick 2
+ \ingroup qml-view-elements
+ \brief The ViewTransition attached property provides details on items under transition in a view.
+
+ With ListView and GridView, it is possible to specify transitions that should be applied whenever
+ the items in the view change as a result of modifications to the view's model. They both have the
+ following properties that can be set to the appropriate transitions to be run for various
+ operations:
+
+ \list
+ \o \c add and \c addDisplaced - the transitions to run when items are added to the view
+ \o \c remove and \c removeDisplaced - the transitions to run when items are removed from the view
+ \o \c move and \c moveDisplaced - the transitions to run when items are moved within the view
+ (i.e. as a result of a move operation in the model)
+ \o \c populate - the transition to run when a view is created, or when the model changes
+ \endlist
+
+ Such view transitions additionally have access to a ViewTransition attached property that
+ provides details of the items that are under transition and the operation that triggered the
+ transition. Since view transitions are run once per item, these details can be used to customise
+ each transition for each individual item.
+
+ The ViewTransition attached property provides the following properties specific to the item to
+ which the transition is applied:
+
+ \list
+ \o ViewTransition.item - the item that is under transition
+ \o ViewTransition.index - the index of this item
+ \o ViewTransition.destination - the (x,y) point to which this item is moving for the relevant view operation
+ \endlist
+
+ In addition, ViewTransition provides properties specific to the items which are the target
+ of the operation that triggered the transition:
+
+ \list
+ \o ViewTransition.targetIndexes - the indexes of the target items
+ \o ViewTransition.targetItems - the target items themselves
+ \endlist
+
+ View transitions can be written without referring to any of the attributes listed
+ above. These attributes merely provide extra details that are useful for customising view
+ transitions.
+
+ Following is an introduction to view transitions and the ways in which the ViewTransition
+ attached property can be used to augment view transitions.
+
+
+ \section2 View transitions: a simple example
+
+ Here is a basic example of the use of view transitions. The view below specifies transitions for
+ the \c add and \c addDisplaced properties, which will be run when items are added to the view:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml 0
+
+ When the space key is pressed, adding an item to the model, the new item will fade in and
+ increase in scale over 400 milliseconds as it is added to the view. Also, any item that is
+ displaced by the addition of a new item will animate to its new position in the view over
+ 400 milliseconds, as specified by the \c addDisplaced transition.
+
+ If five items were inserted in succession at index 0, the effect would be this:
+
+ \image viewtransitions-basic.gif
+
+ Notice that the NumberAnimation objects above do not need to specify a \c target to animate
+ the appropriate item. Also, the NumberAnimation in the \c addTransition does not need to specify
+ the \c to value to move the item to its correct position in the view. This is because the view
+ implicitly sets the \c target and \c to values with the correct item and final item position
+ values if these properties are not explicitly defined.
+
+ At its simplest, a view transition may just animate an item to its new position following a
+ view operation, just as the \c addDisplaced transition does above, or animate some item properties,
+ as in the \c add transition above. Additionally, a view transition may make use of the
+ ViewTransition attached property to customise animation behavior for different items. Following
+ are some examples of how this can be achieved.
+
+
+ \section2 Using the ViewTransition attached property
+
+ As stated, the various ViewTransition properties provide details specific to the individual item
+ being transitioned as well as the operation that triggered the transition. In the animation above,
+ five items are inserted in succession at index 0. When the fifth and final insertion takes place,
+ adding "Item 4" to the view, the \c add transition is run once (for the inserted item) and the
+ \c addDisplaced transition is run four times (once for each of the four existing items in the view).
+
+ At this point, if we examined the \c addDisplaced transition that was run for the bottom displaced
+ item ("Item 0"), the ViewTransition property values provided to this transition would be as follows:
+
+ \table
+ \header
+ \o Property
+ \o Value
+ \o Explanation
+ \row
+ \o ViewTransition.item
+ \o "Item 0" delegate instance
+ \o The "Item 0" \l Rectangle object itself
+ \row
+ \o ViewTransition.index
+ \o \c int value of 4
+ \o The index of "Item 0" within the model following the add operation
+ \row
+ \o ViewTransition.destination
+ \o \l point value of (0, 120)
+ \o The position that "Item 0" is moving to
+ \row
+ \o ViewTransition.targetIndexes
+ \o \c int array, just contains the integer "0" (zero)
+ \o The index of "Item 4", the new item added to the view
+ \row
+ \o ViewTransition.targetItems
+ \o object array, just contains the "Item 4" delegate instance
+ \o The "Item 4" \l Rectangle object - the new item added to the view
+ \endtable
+
+ The ViewTransition.targetIndexes and ViewTransition.targetItems lists provide the items and
+ indexes of all delegate instances that are the targets of the relevant operation. For an add
+ operation, these are all the items that are added into the view; for a remove, these are all
+ the items removed from the view, and so on. (Note these lists will only contain references to
+ items that have been created within the view or its cached items; targets that are not within
+ the visible area of the view or within the item cache will not be accessible.)
+
+ So, while the ViewTransition.item, ViewTransition.index and ViewTransition.destination values
+ vary for each individual transition that is run, the ViewTransition.targetIndexes and
+ ViewTransition.targetItems values are the same for every \c add and \c addDisplaced transition
+ that is triggered by a particular add operation.
+
+
+ \section3 Delaying animations based on index
+
+ Since each view transition is run once for each item affected by the transition, the ViewTransition
+ properties can be used within a transition to define custom behavior for each item's transition.
+ For example, the ListView in the previous example could use this information to create a ripple-type
+ effect on the movement of the displaced items.
+
+ This can be achieved by modifying the \c addDisplaced transition so that it delays the animation of
+ each displaced item based on the difference between its index (provided by ViewTransition.index)
+ and the first removed index (provided by ViewTransition.targetIndexes):
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-delayedbyindex.qml 0
+
+ Each displaced item delays its animation by an additional 100 milliseconds, producing a subtle
+ ripple-type effect when items are displaced by the add, like this:
+
+ \image viewtransitions-delayedbyindex.gif
+
+
+ \section3 Animating items to intermediate positions
+
+ The ViewTransition.item property gives a reference to the item to which the transition is being
+ applied. This can be used to access any of the item's attributes, custom \c property values,
+ and so on.
+
+ Below is a modification of the \c addDisplaced transition from the previous example. It adds a
+ ParallelAnimation with nested NumberAnimation objects that reference ViewTransition.item to access
+ each item's \c x and \c y values at the start of their transitions. This allows each item to
+ animate to an intermediate position relative to its starting point for the transition, before
+ animating to its final position in the view:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-intermediatemove.qml 0
+
+ Now, a displaced item will first move to a position of (20, 50) relative to its starting
+ position, and then to its final, correct position in the view:
+
+ \image viewtransitions-intermediatemove.gif
+
+ Since the final NumberAnimation does not specify a \c to value, the view implicitly sets this
+ value to the item's final position in the view, and so this last animation will move this item
+ to the correct place. If the transition requires the final position of the item for some calculation,
+ this is accessible through ViewTransition.destination.
+
+ Instead of using multiple NumberAnimations, you could use a PathAnimation to animate an item over
+ a curved path. For example, the \c add transition in the previous example could be augmented with
+ a PathAnimation as follows: to animate newly added items along a path:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-pathanim.qml 0
+
+ This animates newly added items along a path. Notice that each path is specified relative to
+ each item's final destination point, so that items inserted at different indexes start their
+ paths from different positions:
+
+ \image viewtransitions-pathanim.gif
+
+
+ \section2 Handling interrupted animations
+
+ A view transition may be interrupted at any time if a different view transition needs to be
+ applied while the original transition is in progress. For example, say Item A is inserted at index 0
+ and undergoes an "add" transition; then, Item B is inserted at index 0 in quick succession before
+ Item A's transition has finished. Since Item B is inserted before Item A, it will displace Item
+ A, causing the view to interrupt Item A's "add" transition mid-way and start an "addDisplaced"
+ transition on Item A instead.
+
+ For simple animations that simply animate an item's movement to its final destination, this
+ interruption is unlikely to require additional consideration. However, if a transition changes other
+ properties, this interruption may cause unwanted side effects. Consider the first example on this
+ page, repeated below for convenience:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml 0
+
+ If multiple items are added in rapid succession, without waiting for a previous transition
+ to finish, this is the result:
+
+ \image viewtransitions-interruptedbad.gif
+
+ Each newly added item undergoes an \c add transition, but before the transition can finish,
+ another item is added, displacing the previously added item. Because of this, the \c add
+ transition on the previously added item is interrupted and an \c addDisplaced transition is
+ started on the item instead. Due to the interruption, the \c opacity and \c scale animations
+ have not completed, thus producing items with opacity and scale that are below 1.0.
+
+ To fix this, the \c addDisplaced transition should additionally ensure the item properties are
+ set to the end values specified in the \c add transition, effectively resetting these values
+ whenever an item is displaced. In this case, it means setting the item opacity and scale to 1.0:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-interruptedgood.qml 0
+
+ Now, when an item's \c add transition is interrupted, its opacity and scale are animated to 1.0
+ upon displacement, avoiding the erroneous visual effects from before:
+
+ \image viewtransitions-interruptedgood.gif
+
+ The same principle applies to any combination of view transitions. An added item may be moved
+ before its add transition finishes, or a moved item may be removed before its moved transition
+ finishes, and so on; so, the rule of thumb is that every transition should handle the same set of
+ properties.
+
+
+ \section2 Restrictions regarding ScriptAction
+
+ When a view transition is initialized, any property bindings that refer to the ViewTransition
+ attached property are evaluated in preparation for the transition. Due to the nature of the
+ internal construction of a view transition, the attributes of the ViewTransition attached
+ property are only valid for the relevant item when the transition is initialized, and may not be
+ valid when the transition is actually run.
+
+ Therefore, a ScriptAction within a view transition should not refer to the ViewTransition
+ attached property, as it may not refer to the expected values at the time that the ScriptAction
+ is actually invoked. Consider the following example:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactionbad.qml 0
+
+ When the space key is pressed, three items are moved from index 5 to index 1. For each moved
+ item, the \c moveTransition sequence presumably animates the item's color to "yellow", then
+ animates it to its final position, then changes the item color back to "lightsteelblue" using a
+ ScriptAction. However, when run, the transition does not produce the intended result:
+
+ \image viewtransitions-scriptactionbad.gif
+
+ Only the last moved item is returned to the "lightsteelblue" color; the others remain yellow. This
+ is because the ScriptAction is not run until after the transition has already been initialized, by
+ which time the ViewTransition.item value has changed to refer to a different item; the item that
+ the script had intended to refer to is not the one held by ViewTransition.item at the time the
+ ScriptAction is actually invoked.
+
+ In this instance, to avoid this issue, the view could set the property using a PropertyAction
+ instead:
+
+ \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactiongood.qml 0
+
+ When the transition is initialized, the PropertyAction \c target will be set to the respective
+ ViewTransition.item for the transition and will later run with the correct item target as
+ expected.
+ */
+
+/*!
+ \qmlattachedproperty list QtQuick2::ViewTransition::index
+
+ This attached property holds the index of the item that is being
+ transitioned.
+
+ Note that if the item is being moved, this property holds the index that
+ the item is moving to, not from.
+*/
+
+/*!
+ \qmlattachedproperty list QtQuick2::ViewTransition::item
+
+ This attached property holds the the item that is being transitioned.
+
+ \warning This item should not be kept and referred to outside of the transition
+ as it may become invalid as the view changes.
+*/
+
+/*!
+ \qmlattachedproperty list QtQuick2::ViewTransition::destination
+
+ This attached property holds the final destination position for the transitioned
+ item within the view.
+
+ This property value is a \l point with \c x and \c y properties.
+*/
+
+/*!
+ \qmlattachedproperty list QtQuick2::ViewTransition::targetIndexes
+
+ This attached property holds a list of the indexes of the items in view
+ that are the target of the relevant operation.
+
+ The targets are the items that are the subject of the operation. For
+ an add operation, these are the items being added; for a remove, these
+ are the items being removed; for a move, these are the items being
+ moved.
+
+ For example, if the transition was triggered by an insert operation
+ that added two items at index 1 and 2, this targetIndexes list would
+ have the value [1,2].
+
+ \note The targetIndexes list only contains the indexes of items that are actually
+ in view, or will be in the view once the relevant operation completes.
+
+ \sa QtQuick2::ViewTransition::targetIndexes
+*/
+
+/*!
+ \qmlattachedproperty list QtQuick2::ViewTransition::targetItems
+
+ This attached property holds the list of items in view that are the
+ target of the relevant operation.
+
+ The targets are the items that are the subject of the operation. For
+ an add operation, these are the items being added; for a remove, these
+ are the items being removed; for a move, these are the items being
+ moved.
+
+ For example, if the transition was triggered by an insert operation
+ that added two items at index 1 and 2, this targetItems list would
+ contain these two items.
+
+ \note The targetItems list only contains items that are actually
+ in view, or will be in the view once the relevant operation completes.
+
+ \warning The objects in this list should not be kept and referred to
+ outside of the transition as the items may become invalid. The targetItems
+ are only valid when the Transition is initially created; this also means
+ they should not be used by ScriptAction objects in the Transition, which are
+ not evaluated until the transition is run.
+
+ \sa QtQuick2::ViewTransition::targetIndexes
+*/
+QDeclarativeListProperty<QObject> QQuickViewTransitionAttached::targetItems()
+{
+ return QDeclarativeListProperty<QObject>(this, m_targetItems);
+}
+
+QQuickViewTransitionAttached *QQuickViewTransitionAttached::qmlAttachedProperties(QObject *obj)
+{
+ return new QQuickViewTransitionAttached(obj);
+}
+
+
+//-----------------------------------
+
QQuickItemView::QQuickItemView(QQuickFlickablePrivate &dd, QQuickItem *parent)
: QQuickFlickable(dd, parent)
{
@@ -232,6 +866,12 @@ void QQuickItemView::setModel(const QVariant &model)
d->moveReason = QQuickItemViewPrivate::Other;
}
d->updateViewport();
+
+ if (d->populateTransition) {
+ d->forceLayout = true;
+ d->usePopulateTransition = true;
+ polish();
+ }
}
connect(d->model, SIGNAL(modelUpdated(QDeclarativeChangeSet,bool)),
this, SLOT(modelUpdated(QDeclarativeChangeSet,bool)));
@@ -585,6 +1225,111 @@ void QQuickItemView::setHighlightMoveDuration(int duration)
}
}
+QDeclarativeTransition *QQuickItemView::populateTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->populateTransition;
+}
+
+void QQuickItemView::setPopulateTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->populateTransition != transition) {
+ d->populateTransition = transition;
+ emit populateTransitionChanged();
+ }
+}
+
+QDeclarativeTransition *QQuickItemView::addTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->addTransition;
+}
+
+void QQuickItemView::setAddTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->addTransition != transition) {
+ d->addTransition = transition;
+ emit addTransitionChanged();
+ }
+}
+
+QDeclarativeTransition *QQuickItemView::addDisplacedTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->addDisplacedTransition;
+}
+
+void QQuickItemView::setAddDisplacedTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->addDisplacedTransition != transition) {
+ d->addDisplacedTransition = transition;
+ emit addDisplacedTransitionChanged();
+ }
+}
+
+QDeclarativeTransition *QQuickItemView::moveTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->moveTransition;
+}
+
+void QQuickItemView::setMoveTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->moveTransition != transition) {
+ d->moveTransition = transition;
+ emit moveTransitionChanged();
+ }
+}
+
+QDeclarativeTransition *QQuickItemView::moveDisplacedTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->moveDisplacedTransition;
+}
+
+void QQuickItemView::setMoveDisplacedTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->moveDisplacedTransition != transition) {
+ d->moveDisplacedTransition = transition;
+ emit moveDisplacedTransitionChanged();
+ }
+}
+
+QDeclarativeTransition *QQuickItemView::removeTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->removeTransition;
+}
+
+void QQuickItemView::setRemoveTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->removeTransition != transition) {
+ d->removeTransition = transition;
+ emit removeTransitionChanged();
+ }
+}
+
+QDeclarativeTransition *QQuickItemView::removeDisplacedTransition() const
+{
+ Q_D(const QQuickItemView);
+ return d->removeDisplacedTransition;
+}
+
+void QQuickItemView::setRemoveDisplacedTransition(QDeclarativeTransition *transition)
+{
+ Q_D(QQuickItemView);
+ if (d->removeDisplacedTransition != transition) {
+ d->removeDisplacedTransition = transition;
+ emit removeDisplacedTransitionChanged();
+ }
+}
+
void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode)
{
Q_Q(QQuickItemView);
@@ -719,6 +1464,64 @@ void QQuickItemViewPrivate::applyPendingChanges()
layout();
}
+bool QQuickItemViewPrivate::hasItemTransitions() const
+{
+ return populateTransition
+ || addTransition || addDisplacedTransition
+ || moveTransition || moveDisplacedTransition
+ || removeTransition || removeDisplacedTransition;
+}
+
+void QQuickItemViewPrivate::transitionNextReposition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, bool isTarget)
+{
+ switch (type) {
+ case FxViewItemTransitionManager::NoTransition:
+ return;
+ case FxViewItemTransitionManager::PopulateTransition:
+ if (populateTransition) {
+ item->setNextTransition(FxViewItemTransitionManager::PopulateTransition, isTarget);
+ return;
+ }
+ break;
+ case FxViewItemTransitionManager::AddTransition:
+ if (!usePopulateTransition) {
+ if ((isTarget && addTransition) || (!isTarget && addDisplacedTransition)) {
+ item->setNextTransition(type, isTarget);
+ return;
+ }
+ }
+ break;
+ case FxViewItemTransitionManager::MoveTransition:
+ if ((isTarget && moveTransition) || (!isTarget && moveDisplacedTransition)) {
+ item->setNextTransition(type, isTarget);
+ return;
+ }
+ break;
+ case FxViewItemTransitionManager::RemoveTransition:
+ if ((isTarget && removeTransition) || (!isTarget && removeDisplacedTransition)) {
+ item->setNextTransition(type, isTarget);
+ return;
+ }
+ break;
+ }
+
+ // the requested transition type is not valid, but the item is scheduled/in another
+ // transition, so cancel it to allow the item to move directly to the correct pos
+ if (item->transitionScheduledOrRunning())
+ item->stopTransition();
+}
+
+int QQuickItemViewPrivate::findMoveKeyIndex(QDeclarativeChangeSet::MoveKey key, const QVector<QDeclarativeChangeSet::Remove> &changes) const
+{
+ for (int i=0; i<changes.count(); i++) {
+ for (int j=changes[i].index; j<changes[i].index + changes[i].count; j++) {
+ if (changes[i].moveKey(j) == key)
+ return j;
+ }
+ }
+ return -1;
+}
+
// for debugging only
void QQuickItemViewPrivate::checkVisible() const
{
@@ -733,6 +1536,17 @@ void QQuickItemViewPrivate::checkVisible() const
}
}
+// for debugging only
+void QQuickItemViewPrivate::showVisibleItems() const
+{
+ qDebug() << "Visible items:";
+ for (int i = 0; i < visibleItems.count(); ++i) {
+ qDebug() << "\t" << visibleItems[i]->index
+ << visibleItems[i]->item->objectName()
+ << visibleItems[i]->position();
+ }
+}
+
void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry)
{
Q_Q(QQuickItemView);
@@ -752,8 +1566,19 @@ void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &
fixupPosition();
}
- if (currentItem && currentItem->item == item)
+ if (currentItem && currentItem->item == item) {
+ // don't allow item movement transitions to trigger a re-layout and
+ // start new transitions
+ bool prevDisableLayout = disableLayout;
+ if (!disableLayout) {
+ FxViewItem *actualItem = hasItemTransitions() ? visibleItem(currentIndex) : 0;
+ if (actualItem && actualItem->transition && actualItem->transition->isRunning())
+ disableLayout = true;
+ }
updateHighlight();
+ disableLayout = prevDisableLayout;
+ }
+
if (trackedItem && trackedItem->item == item)
q->trackedPositionChanged();
}
@@ -765,8 +1590,15 @@ void QQuickItemView::destroyRemoved()
it != d->visibleItems.end();) {
FxViewItem *item = *it;
if (item->index == -1 && item->attached->delayRemove() == false) {
- d->releaseItem(item);
- it = d->visibleItems.erase(it);
+ if (d->removeTransition) {
+ // don't remove from visibleItems until next layout()
+ d->runDelayedRemoveTransition = true;
+ QObject::disconnect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()));
+ ++it;
+ } else {
+ d->releaseItem(item);
+ it = d->visibleItems.erase(it);
+ }
} else {
++it;
}
@@ -782,6 +1614,7 @@ void QQuickItemView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
{
Q_D(QQuickItemView);
if (reset) {
+ d->usePopulateTransition = true;
d->moveReason = QQuickItemViewPrivate::SetIndex;
d->regenerate();
if (d->highlight && d->currentItem) {
@@ -790,8 +1623,11 @@ void QQuickItemView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
d->updateTrackedItem();
}
d->moveReason = QQuickItemViewPrivate::Other;
-
emit countChanged();
+ if (d->populateTransition) {
+ d->forceLayout = true;
+ polish();
+ }
} else {
d->currentChanges.prepare(d->currentIndex, d->itemCount);
d->currentChanges.applyChanges(changeSet);
@@ -1088,6 +1924,8 @@ void QQuickItemView::componentComplete()
d->updateFooter();
d->updateViewport();
d->setPosition(d->contentStartOffset());
+ d->usePopulateTransition = true;
+
if (d->isValid()) {
d->refill();
d->moveReason = QQuickItemViewPrivate::SetIndex;
@@ -1122,11 +1960,16 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
, highlightRangeStart(0), highlightRangeEnd(0)
, highlightMoveDuration(150)
, headerComponent(0), header(0), footerComponent(0), footer(0)
+ , populateTransition(0)
+ , addTransition(0), addDisplacedTransition(0)
+ , moveTransition(0), moveDisplacedTransition(0)
+ , removeTransition(0), removeDisplacedTransition(0)
, minExtent(0), maxExtent(0)
, ownModel(false), wrap(false)
- , inApplyModelChanges(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
+ , disableLayout(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
, haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false)
, fillCacheBuffer(false), inRequest(false), requestedAsync(false)
+ , usePopulateTransition(false), runDelayedRemoveTransition(false)
{
}
@@ -1193,6 +2036,8 @@ FxViewItem *QQuickItemViewPrivate::visibleItem(int modelIndex) const {
return 0;
}
+// should rename to firstItemInView() to avoid confusion with other "*visible*" methods
+// that don't look at the view position and size
FxViewItem *QQuickItemViewPrivate::firstVisibleItem() const {
const qreal pos = isContentFlowReversed() ? -position()-size() : position();
for (int i = 0; i < visibleItems.count(); ++i) {
@@ -1203,6 +2048,16 @@ FxViewItem *QQuickItemViewPrivate::firstVisibleItem() const {
return visibleItems.count() ? visibleItems.first() : 0;
}
+int QQuickItemViewPrivate::findLastIndexInView() const
+{
+ const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
+ for (int i=visibleItems.count() - 1; i>=0; i--) {
+ if (visibleItems.at(i)->position() <= viewEndPos && visibleItems.at(i)->index != -1)
+ return visibleItems.at(i)->index;
+ }
+ return -1;
+}
+
// Map a model index to visibleItems list index.
// These may differ if removed items are still present in the visible list,
// e.g. doing a removal animation
@@ -1284,6 +2139,12 @@ void QQuickItemViewPrivate::clear()
visibleItems.clear();
visibleIndex = 0;
+ for (int i = 0; i < releasePendingTransition.count(); ++i) {
+ releasePendingTransition.at(i)->releaseAfterTransition = false;
+ releaseItem(releasePendingTransition.at(i));
+ }
+ releasePendingTransition.clear();
+
releaseItem(currentItem);
currentItem = 0;
createHighlight();
@@ -1382,16 +2243,26 @@ void QQuickItemViewPrivate::updateViewport()
void QQuickItemViewPrivate::layout()
{
Q_Q(QQuickItemView);
- if (inApplyModelChanges)
+ if (disableLayout)
return;
if (!isValid() && !visibleItems.count()) {
clear();
setPosition(contentStartOffset());
+ usePopulateTransition = false;
return;
}
- if (!applyModelChanges() && !forceLayout) {
+ if (runDelayedRemoveTransition && removeDisplacedTransition) {
+ // assume that any items moving now are moving due to the remove - if they schedule
+ // a different transition, that will override this one anyway
+ for (int i=0; i<visibleItems.count(); i++)
+ transitionNextReposition(visibleItems[i], FxViewItemTransitionManager::RemoveTransition, false);
+ }
+
+ ChangeResult insertionPosChanges;
+ ChangeResult removalPosChanges;
+ if (!applyModelChanges(&insertionPosChanges, &removalPosChanges) && !forceLayout) {
if (fillCacheBuffer) {
fillCacheBuffer = false;
refill();
@@ -1400,11 +2271,15 @@ void QQuickItemViewPrivate::layout()
}
forceLayout = false;
+ if (usePopulateTransition && populateTransition) {
+ for (int i=0; i<visibleItems.count(); i++)
+ transitionNextReposition(visibleItems.at(i), FxViewItemTransitionManager::PopulateTransition, true);
+ }
layoutVisibleItems();
- refill();
+ int lastIndexInView = findLastIndexInView();
+ refill();
markExtentsDirty();
-
updateHighlight();
if (!q->isMoving() && !q->isFlicking()) {
@@ -1416,20 +2291,49 @@ void QQuickItemViewPrivate::layout()
updateFooter();
updateViewport();
updateUnrequestedPositions();
+
+ if (hasItemTransitions()) {
+ // items added in the last refill() may need to be transitioned in - e.g. a remove
+ // causes items to slide up into view
+ if (moveDisplacedTransition || removeDisplacedTransition)
+ translateAndTransitionItemsAfter(lastIndexInView, insertionPosChanges, removalPosChanges);
+
+ prepareVisibleItemTransitions();
+
+ QRectF viewBounds(0, position(), q->width(), q->height());
+ for (QList<FxViewItem*>::Iterator it = releasePendingTransition.begin();
+ it != releasePendingTransition.end(); ) {
+ FxViewItem *item = *it;
+ if ( (item->transition && item->transition->isActive())
+ || prepareNonVisibleItemTransition(item, viewBounds)) {
+ ++it;
+ } else {
+ releaseItem(item);
+ it = releasePendingTransition.erase(it);
+ }
+ }
+
+ for (int i=0; i<visibleItems.count(); i++)
+ visibleItems[i]->startTransition();
+ for (int i=0; i<releasePendingTransition.count(); i++)
+ releasePendingTransition[i]->startTransition();
+ }
+ usePopulateTransition = false;
+ runDelayedRemoveTransition = false;
}
-bool QQuickItemViewPrivate::applyModelChanges()
+bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult, ChangeResult *totalRemovalResult)
{
Q_Q(QQuickItemView);
- if (!q->isComponentComplete() || !currentChanges.hasPendingChanges() || inApplyModelChanges)
+ if (!q->isComponentComplete() || (!currentChanges.hasPendingChanges() && !runDelayedRemoveTransition) || disableLayout)
return false;
- inApplyModelChanges = true;
+ disableLayout = true;
updateUnrequestedIndexes();
moveReason = QQuickItemViewPrivate::Other;
- FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? visibleItems.first() : 0;
+ FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? *visibleItems.constBegin() : 0;
int prevItemCount = itemCount;
int prevVisibleItemsCount = visibleItems.count();
bool visibleAffected = false;
@@ -1445,10 +2349,13 @@ bool QQuickItemViewPrivate::applyModelChanges()
}
qreal prevVisibleItemsFirstPos = visibleItems.count() ? visibleItems.first()->position() : 0.0;
+ totalInsertionResult->visiblePos = prevViewPos;
+ totalRemovalResult->visiblePos = prevViewPos;
+
const QVector<QDeclarativeChangeSet::Remove> &removals = currentChanges.pendingChanges.removes();
const QVector<QDeclarativeChangeSet::Insert> &insertions = currentChanges.pendingChanges.inserts();
- ChangeResult removalResult(prevViewPos);
ChangeResult insertionResult(prevViewPos);
+ ChangeResult removalResult(prevViewPos);
int removedCount = 0;
for (int i=0; i<removals.count(); i++) {
@@ -1459,11 +2366,25 @@ bool QQuickItemViewPrivate::applyModelChanges()
visibleAffected = true;
if (prevFirstVisibleIndex >= 0 && removals[i].index < prevFirstVisibleIndex) {
if (removals[i].index + removals[i].count < prevFirstVisibleIndex)
- removalResult.changeBeforeVisible -= removals[i].count;
+ removalResult.countChangeBeforeVisible += removals[i].count;
else
- removalResult.changeBeforeVisible -= (prevFirstVisibleIndex - removals[i].index);
+ removalResult.countChangeBeforeVisible += (prevFirstVisibleIndex - removals[i].index);
}
}
+ if (runDelayedRemoveTransition) {
+ QDeclarativeChangeSet::Remove removal;
+ for (QList<FxViewItem*>::Iterator it = visibleItems.begin(); it != visibleItems.end();) {
+ FxViewItem *item = *it;
+ if (item->index == -1 && !item->attached->delayRemove()) {
+ removeItem(item, removal, &removalResult);
+ removedCount++;
+ it = visibleItems.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ *totalRemovalResult += removalResult;
if (!removals.isEmpty()) {
updateVisibleIndex();
@@ -1475,31 +2396,50 @@ bool QQuickItemViewPrivate::applyModelChanges()
}
QList<FxViewItem *> newItems;
+ QList<MovedItem> movingIntoView;
+
for (int i=0; i<insertions.count(); i++) {
bool wasEmpty = visibleItems.isEmpty();
- if (applyInsertionChange(insertions[i], &insertionResult, &newItems))
+ if (applyInsertionChange(insertions[i], &insertionResult, &newItems, &movingIntoView))
visibleAffected = true;
if (!visibleAffected && needsRefillForAddedOrRemovedIndex(insertions[i].index))
visibleAffected = true;
if (wasEmpty && !visibleItems.isEmpty())
resetFirstItemPosition();
+ *totalInsertionResult += insertionResult;
// set positions correctly for the next insertion
if (i < insertions.count() - 1) {
repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
layoutVisibleItems(insertions[i].index);
}
-
itemCount += insertions[i].count;
}
for (int i=0; i<newItems.count(); i++)
newItems.at(i)->attached->emitAdd();
+ // for each item that was moved directly into the view as a result of a move(),
+ // find the index it was moved from in order to set its initial position, so that we
+ // can transition it from this "original" position to its new position in the view
+ if (moveTransition) {
+ for (int i=0; i<movingIntoView.count(); i++) {
+ int fromIndex = findMoveKeyIndex(movingIntoView[i].moveKey, removals);
+ if (fromIndex >= 0) {
+ if (prevFirstVisibleIndex >= 0 && fromIndex < prevFirstVisibleIndex)
+ repositionItemAt(movingIntoView[i].item, fromIndex, -totalInsertionResult->sizeChangesAfterVisiblePos);
+ else
+ repositionItemAt(movingIntoView[i].item, fromIndex, totalInsertionResult->sizeChangesAfterVisiblePos);
+ transitionNextReposition(movingIntoView[i].item, FxViewItemTransitionManager::MoveTransition, true);
+ }
+ }
+ }
+
// reposition visibleItems.first() correctly so that the content y doesn't jump
if (removedCount != prevVisibleItemsCount)
repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
// Whatever removed/moved items remain are no longer visible items.
+ prepareRemoveTransitions(&currentChanges.removedItems);
for (QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *>::Iterator it = currentChanges.removedItems.begin();
it != currentChanges.removedItems.end(); ++it) {
releaseItem(it.value());
@@ -1526,7 +2466,7 @@ bool QQuickItemViewPrivate::applyModelChanges()
if (!visibleAffected && viewportChanged)
updateViewport();
- inApplyModelChanges = false;
+ disableLayout = false;
return visibleAffected;
}
@@ -1535,6 +2475,13 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
Q_Q(QQuickItemView);
bool visibleAffected = false;
+ if (visibleItems.count() && removal.index + removal.count > visibleItems.last()->index) {
+ if (removal.index > visibleItems.last()->index)
+ removeResult->countChangeAfterVisibleItems += removal.count;
+ else
+ removeResult->countChangeAfterVisibleItems += ((removal.index + removal.count - 1) - visibleItems.last()->index);
+ }
+
QList<FxViewItem*>::Iterator it = visibleItems.begin();
while (it != visibleItems.end()) {
FxViewItem *item = *it;
@@ -1546,6 +2493,10 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
} else if (item->index >= removal.index + removal.count) {
// after removed items
item->index -= removal.count;
+ if (removal.isMove())
+ transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, false);
+ else
+ transitionNextReposition(item, FxViewItemTransitionManager::RemoveTransition, false);
++it;
} else {
// removed item
@@ -1558,21 +2509,9 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
QObject::connect(item->attached, SIGNAL(delayRemoveChanged()), q, SLOT(destroyRemoved()), Qt::QueuedConnection);
++it;
} else {
- if (removeResult->visiblePos.isValid()) {
- if (item->position() < removeResult->visiblePos)
- removeResult->sizeChangesBeforeVisiblePos += item->size();
- else
- removeResult->sizeChangesAfterVisiblePos += item->size();
- }
- if (removal.isMove()) {
- currentChanges.removedItems.insert(removal.moveKey(item->index), item);
- } else {
- // track item so it is released later
- currentChanges.removedItems.insertMulti(QDeclarativeChangeSet::MoveKey(), item);
+ removeItem(item, removal, removeResult);
+ if (!removal.isMove())
(*removedCount)++;
- }
- if (!removeResult->changedFirstItem && item == visibleItems.first())
- removeResult->changedFirstItem = true;
it = visibleItems.erase(it);
}
}
@@ -1581,6 +2520,25 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
return visibleAffected;
}
+void QQuickItemViewPrivate::removeItem(FxViewItem *item, const QDeclarativeChangeSet::Remove &removal, ChangeResult *removeResult)
+{
+ if (removeResult->visiblePos.isValid()) {
+ if (item->position() < removeResult->visiblePos)
+ removeResult->sizeChangesBeforeVisiblePos += item->size();
+ else
+ removeResult->sizeChangesAfterVisiblePos += item->size();
+ }
+ if (removal.isMove()) {
+ currentChanges.removedItems.insert(removal.moveKey(item->index), item);
+ transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, true);
+ } else {
+ // track item so it is released later
+ currentChanges.removedItems.insertMulti(QDeclarativeChangeSet::MoveKey(), item);
+ }
+ if (!removeResult->changedFirstItem && item == *visibleItems.constBegin())
+ removeResult->changedFirstItem = true;
+}
+
void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirst,
qreal prevVisibleItemsFirstPos,
FxViewItem *prevFirstVisible,
@@ -1613,13 +2571,102 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs
moveForwardsBy = removalResult->sizeChangesBeforeVisiblePos;
moveBackwardsBy = insertionResult->sizeChangesBeforeVisiblePos;
}
- adjustFirstItem(moveForwardsBy, moveBackwardsBy, insertionResult->changeBeforeVisible + removalResult->changeBeforeVisible);
+ adjustFirstItem(moveForwardsBy, moveBackwardsBy, insertionResult->countChangeBeforeVisible - removalResult->countChangeBeforeVisible);
}
insertionResult->reset();
removalResult->reset();
}
}
+void QQuickItemViewPrivate::prepareVisibleItemTransitions()
+{
+ Q_Q(QQuickItemView);
+ if (!hasItemTransitions())
+ return;
+
+ addTransitionIndexes.clear();
+ addTransitionTargets.clear();
+ moveTransitionIndexes.clear();
+ moveTransitionTargets.clear();
+
+ QRectF viewBounds(0, position(), q->width(), q->height());
+ for (int i=0; i<visibleItems.count(); i++) {
+ // must call for every visible item to init or discard transitions
+ if (!visibleItems[i]->prepareTransition(viewBounds))
+ continue;
+ if (visibleItems[i]->isTransitionTarget) {
+ switch (visibleItems[i]->nextTransitionType) {
+ case FxViewItemTransitionManager::NoTransition:
+ break;
+ case FxViewItemTransitionManager::PopulateTransition:
+ case FxViewItemTransitionManager::AddTransition:
+ addTransitionIndexes.append(visibleItems[i]->index);
+ addTransitionTargets.append(visibleItems[i]->item);
+ break;
+ case FxViewItemTransitionManager::MoveTransition:
+ moveTransitionIndexes.append(visibleItems[i]->index);
+ moveTransitionTargets.append(visibleItems[i]->item);
+ break;
+ case FxViewItemTransitionManager::RemoveTransition:
+ // removed targets won't be in visibleItems, handle these
+ // in prepareNonVisibleItemTransition()
+ break;
+ }
+ }
+ }
+}
+
+void QQuickItemViewPrivate::prepareRemoveTransitions(QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *> *removedItems)
+{
+ if (!removeTransition && !removeDisplacedTransition)
+ return;
+
+ removeTransitionIndexes.clear();
+ removeTransitionTargets.clear();
+
+ if (removeTransition) {
+ for (QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *>::Iterator it = removedItems->begin();
+ it != removedItems->end(); ) {
+ bool isRemove = it.key().moveId < 0;
+ if (isRemove) {
+ FxViewItem *item = *it;
+ item->releaseAfterTransition = true;
+ releasePendingTransition.append(item);
+ transitionNextReposition(item, FxViewItemTransitionManager::RemoveTransition, true);
+ it = removedItems->erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+}
+
+bool QQuickItemViewPrivate::prepareNonVisibleItemTransition(FxViewItem *item, const QRectF &viewBounds)
+{
+ // Called for items that have been removed from visibleItems and may now be
+ // transitioned out of the view. This applies to items that are being directly
+ // removed, or moved to outside of the view, as well as those that are
+ // displaced to a position outside of the view due to an insert or move.
+
+ if (item->nextTransitionType == FxViewItemTransitionManager::MoveTransition)
+ repositionItemAt(item, item->index, 0);
+ if (!item->prepareTransition(viewBounds))
+ return false;
+
+ if (item->isTransitionTarget) {
+ if (item->nextTransitionType == FxViewItemTransitionManager::MoveTransition) {
+ moveTransitionIndexes.append(item->index);
+ moveTransitionTargets.append(item->item);
+ } else if (item->nextTransitionType == FxViewItemTransitionManager::RemoveTransition) {
+ removeTransitionIndexes.append(item->index);
+ removeTransitionTargets.append(item->item);
+ }
+ }
+
+ item->releaseAfterTransition = true;
+ return true;
+}
+
/*
This may return 0 if the item is being created asynchronously.
When the item becomes available, refill() will be called and the item
@@ -1628,6 +2675,7 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs
FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, bool asynchronous)
{
Q_Q(QQuickItemView);
+
if (requestedIndex == modelIndex && (asynchronous || requestedAsync == asynchronous))
return 0;
@@ -1638,6 +2686,14 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, bool asynchronous)
requestedItem = 0;
}
+ for (int i=0; i<releasePendingTransition.count(); i++) {
+ if (releasePendingTransition[i]->index == modelIndex
+ && !releasePendingTransition[i]->isPendingRemoval()) {
+ releasePendingTransition[i]->releaseAfterTransition = false;
+ return releasePendingTransition.takeAt(i);
+ }
+ }
+
requestedIndex = modelIndex;
requestedAsync = asynchronous;
inRequest = true;
diff --git a/src/quick/items/qquickitemview_p.h b/src/quick/items/qquickitemview_p.h
index e43f7c6d70..0d3cd1c3ce 100644
--- a/src/quick/items/qquickitemview_p.h
+++ b/src/quick/items/qquickitemview_p.h
@@ -48,6 +48,8 @@ QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
+QT_MODULE(Declarative)
+
class QDeclarativeChangeSet;
class QQuickItemViewPrivate;
@@ -74,6 +76,14 @@ class Q_AUTOTEST_EXPORT QQuickItemView : public QQuickFlickable
Q_PROPERTY(QDeclarativeComponent *footer READ footer WRITE setFooter NOTIFY footerChanged)
Q_PROPERTY(QQuickItem *footerItem READ footerItem NOTIFY footerItemChanged)
+ Q_PROPERTY(QDeclarativeTransition *populate READ populateTransition WRITE setPopulateTransition NOTIFY populateTransitionChanged)
+ Q_PROPERTY(QDeclarativeTransition *add READ addTransition WRITE setAddTransition NOTIFY addTransitionChanged)
+ Q_PROPERTY(QDeclarativeTransition *addDisplaced READ addDisplacedTransition WRITE setAddDisplacedTransition NOTIFY addDisplacedTransitionChanged)
+ Q_PROPERTY(QDeclarativeTransition *move READ moveTransition WRITE setMoveTransition NOTIFY moveTransitionChanged)
+ Q_PROPERTY(QDeclarativeTransition *moveDisplaced READ moveDisplacedTransition WRITE setMoveDisplacedTransition NOTIFY moveDisplacedTransitionChanged)
+ Q_PROPERTY(QDeclarativeTransition *remove READ removeTransition WRITE setRemoveTransition NOTIFY removeTransitionChanged)
+ Q_PROPERTY(QDeclarativeTransition *removeDisplaced READ removeDisplacedTransition WRITE setRemoveDisplacedTransition NOTIFY removeDisplacedTransitionChanged)
+
Q_PROPERTY(QDeclarativeComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged)
Q_PROPERTY(QQuickItem *highlightItem READ highlightItem NOTIFY highlightItemChanged)
Q_PROPERTY(bool highlightFollowsCurrentItem READ highlightFollowsCurrentItem WRITE setHighlightFollowsCurrentItem NOTIFY highlightFollowsCurrentItemChanged)
@@ -120,6 +130,27 @@ public:
void setHeader(QDeclarativeComponent *);
QQuickItem *headerItem() const;
+ QDeclarativeTransition *populateTransition() const;
+ void setPopulateTransition(QDeclarativeTransition *transition);
+
+ QDeclarativeTransition *addTransition() const;
+ void setAddTransition(QDeclarativeTransition *transition);
+
+ QDeclarativeTransition *addDisplacedTransition() const;
+ void setAddDisplacedTransition(QDeclarativeTransition *transition);
+
+ QDeclarativeTransition *moveTransition() const;
+ void setMoveTransition(QDeclarativeTransition *transition);
+
+ QDeclarativeTransition *moveDisplacedTransition() const;
+ void setMoveDisplacedTransition(QDeclarativeTransition *transition);
+
+ QDeclarativeTransition *removeTransition() const;
+ void setRemoveTransition(QDeclarativeTransition *transition);
+
+ QDeclarativeTransition *removeDisplacedTransition() const;
+ void setRemoveDisplacedTransition(QDeclarativeTransition *transition);
+
QDeclarativeComponent *highlight() const;
void setHighlight(QDeclarativeComponent *);
@@ -173,6 +204,14 @@ signals:
void headerItemChanged();
void footerItemChanged();
+ void populateTransitionChanged();
+ void addTransitionChanged();
+ void addDisplacedTransitionChanged();
+ void moveTransitionChanged();
+ void moveDisplacedTransitionChanged();
+ void removeTransitionChanged();
+ void removeDisplacedTransitionChanged();
+
void highlightChanged();
void highlightItemChanged();
void highlightFollowsCurrentItemChanged();
@@ -200,8 +239,6 @@ protected slots:
void animStopped();
void trackedPositionChanged();
-
-
private:
Q_DECLARE_PRIVATE(QQuickItemView)
};
@@ -287,8 +324,53 @@ public:
QString m_nextSection;
};
+class QQuickViewTransitionAttached : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int index READ index NOTIFY indexChanged)
+ Q_PROPERTY(QQuickItem* item READ item NOTIFY itemChanged)
+ Q_PROPERTY(QPointF destination READ destination NOTIFY destinationChanged)
+
+ Q_PROPERTY(QList<int> targetIndexes READ targetIndexes NOTIFY targetIndexesChanged)
+ Q_PROPERTY(QDeclarativeListProperty<QObject> targetItems READ targetItems NOTIFY targetItemsChanged)
+
+public:
+ QQuickViewTransitionAttached(QObject *parent);
+
+ int index() const { return m_index; }
+ QQuickItem *item() const { return m_item; }
+ QPointF destination() const { return m_destination; }
+
+ QList<int> targetIndexes() const { return m_targetIndexes; }
+ QDeclarativeListProperty<QObject> targetItems();
+
+ static QQuickViewTransitionAttached *qmlAttachedProperties(QObject *);
+
+signals:
+ void indexChanged();
+ void itemChanged();
+ void destinationChanged();
+
+ void targetIndexesChanged();
+ void targetItemsChanged();
+
+private:
+ friend class FxViewItemTransitionManager;
+ int m_index;
+ QQuickItem *m_item;
+ QPointF m_destination;
+
+ QList<int> m_targetIndexes;
+ QList<QObject *> m_targetItems;
+};
+
QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickViewTransitionAttached)
+QML_DECLARE_TYPEINFO(QQuickViewTransitionAttached, QML_HAS_ATTACHED_PROPERTIES)
+
QT_END_HEADER
#endif // QQUICKITEMVIEW_P_H
diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h
index f191f0683c..6768149d74 100644
--- a/src/quick/items/qquickitemview_p_p.h
+++ b/src/quick/items/qquickitemview_p_p.h
@@ -45,6 +45,8 @@
#include "qquickitemview_p.h"
#include "qquickflickable_p_p.h"
#include "qquickvisualdatamodel_p.h"
+#include "qquickvisualitemmodel_p.h"
+#include <private/qdeclarativetransitionmanager_p_p.h>
#include <private/qdeclarativechangeset_p.h>
@@ -52,12 +54,58 @@ QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
+QT_MODULE(Declarative)
+
+
+class FxViewItem;
+class FxViewItemTransitionManager : public QDeclarativeTransitionManager
+{
+public:
+ enum TransitionType {
+ NoTransition,
+ PopulateTransition,
+ AddTransition,
+ MoveTransition,
+ RemoveTransition
+ };
+
+ FxViewItemTransitionManager();
+ ~FxViewItemTransitionManager();
+
+ bool isActive() const;
+ void startTransition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, const QPointF &to, bool isTargetItem);
+
+ bool m_active;
+ FxViewItem *m_item;
+ QPointF m_toPos;
+ FxViewItemTransitionManager::TransitionType m_type;
+ bool m_isTarget;
+
+protected:
+ virtual void finished();
+};
+
+
class FxViewItem
{
public:
FxViewItem(QQuickItem *, bool own);
virtual ~FxViewItem();
+ qreal itemX() const;
+ qreal itemY() const;
+
+ void setVisible(bool visible);
+
+ void setNextTransition(FxViewItemTransitionManager::TransitionType, bool isTargetItem);
+ bool transitionScheduledOrRunning() const;
+ bool isPendingRemoval() const;
+
+ bool prepareTransition(const QRectF &viewBounds);
+ void startTransition();
+ void stopTransition();
+ void finishedTransition();
+
// these are positions and sizes along the current direction of scrolling/flicking
virtual qreal position() const = 0;
virtual qreal endPosition() const = 0;
@@ -65,13 +113,26 @@ public:
virtual qreal sectionSize() const = 0;
virtual bool contains(qreal x, qreal y) const = 0;
+ virtual QQuickItemView *itemView() const = 0;
QQuickItem *item;
bool ownItem;
int index;
+ bool releaseAfterTransition;
QQuickItemViewAttached *attached;
+
+ FxViewItemTransitionManager *transition;
+ QPointF nextTransitionTo;
+ FxViewItemTransitionManager::TransitionType nextTransitionType;
+ bool isTransitionTarget;
+ bool nextTransitionToSet;
+
+protected:
+ void moveTo(const QPointF &pos);
+ void resetTransitionData();
};
+
class QQuickItemViewChangeSet
{
public:
@@ -93,6 +154,7 @@ public:
bool currentRemoved : 1;
};
+
class QQuickItemViewPrivate : public QQuickFlickablePrivate
{
Q_DECLARE_PUBLIC(QQuickItemView)
@@ -101,20 +163,39 @@ public:
struct ChangeResult {
QDeclarativeNullableValue<qreal> visiblePos;
+ bool changedFirstItem;
qreal sizeChangesBeforeVisiblePos;
qreal sizeChangesAfterVisiblePos;
- bool changedFirstItem;
- int changeBeforeVisible;
+ int countChangeBeforeVisible;
+ int countChangeAfterVisibleItems;
+
+ ChangeResult()
+ : visiblePos(0), changedFirstItem(false),
+ sizeChangesBeforeVisiblePos(0), sizeChangesAfterVisiblePos(0),
+ countChangeBeforeVisible(0), countChangeAfterVisibleItems(0) {}
ChangeResult(const QDeclarativeNullableValue<qreal> &p)
- : visiblePos(p), sizeChangesBeforeVisiblePos(0), sizeChangesAfterVisiblePos(0),
- changedFirstItem(false), changeBeforeVisible(0) {}
+ : visiblePos(p), changedFirstItem(false),
+ sizeChangesBeforeVisiblePos(0), sizeChangesAfterVisiblePos(0),
+ countChangeBeforeVisible(0), countChangeAfterVisibleItems(0) {}
+
+ ChangeResult &operator+=(const ChangeResult &other) {
+ if (&other == this)
+ return *this;
+ changedFirstItem &= other.changedFirstItem;
+ sizeChangesBeforeVisiblePos += other.sizeChangesBeforeVisiblePos;
+ sizeChangesAfterVisiblePos += other.sizeChangesAfterVisiblePos;
+ countChangeBeforeVisible += other.countChangeBeforeVisible;
+ countChangeAfterVisibleItems += other.countChangeAfterVisibleItems;
+ return *this;
+ }
void reset() {
+ changedFirstItem = false;
sizeChangesBeforeVisiblePos = 0.0;
sizeChangesAfterVisiblePos = 0.0;
- changedFirstItem = false;
- changeBeforeVisible = 0;
+ countChangeBeforeVisible = 0;
+ countChangeAfterVisibleItems = 0;
}
};
@@ -130,6 +211,7 @@ public:
int findLastVisibleIndex(int defaultValue = -1) const;
FxViewItem *visibleItem(int modelIndex) const;
FxViewItem *firstVisibleItem() const;
+ int findLastIndexInView() const;
int mapFromModel(int modelIndex) const;
virtual void init();
@@ -155,12 +237,22 @@ public:
void updateVisibleIndex();
void positionViewAtIndex(int index, int mode);
void applyPendingChanges();
- bool applyModelChanges();
+ bool applyModelChanges(ChangeResult *insertionResult, ChangeResult *removalResult);
bool applyRemovalChange(const QDeclarativeChangeSet::Remove &removal, ChangeResult *changeResult, int *removedCount);
+ void removeItem(FxViewItem *item, const QDeclarativeChangeSet::Remove &removal, ChangeResult *removeResult);
void repositionFirstItem(FxViewItem *prevVisibleItemsFirst, qreal prevVisibleItemsFirstPos,
FxViewItem *prevFirstVisible, ChangeResult *insertionResult, ChangeResult *removalResult);
+ void prepareVisibleItemTransitions();
+ void prepareRemoveTransitions(QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *> *removedItems);
+ bool prepareNonVisibleItemTransition(FxViewItem *item, const QRectF &viewBounds);
+
+ bool hasItemTransitions() const;
+ void transitionNextReposition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, bool isTarget);
+ int findMoveKeyIndex(QDeclarativeChangeSet::MoveKey key, const QVector<QDeclarativeChangeSet::Remove> &changes) const;
+
void checkVisible() const;
+ void showVisibleItems() const;
void markExtentsDirty() {
if (layoutOrientation() == Qt::Vertical)
@@ -201,12 +293,35 @@ public:
QDeclarativeComponent *footerComponent;
FxViewItem *footer;
+ QDeclarativeTransition *populateTransition;
+ QDeclarativeTransition *addTransition;
+ QDeclarativeTransition *addDisplacedTransition;
+ QDeclarativeTransition *moveTransition;
+ QDeclarativeTransition *moveDisplacedTransition;
+ QDeclarativeTransition *removeTransition;
+ QDeclarativeTransition *removeDisplacedTransition;
+
+ QList<int> addTransitionIndexes;
+ QList<int> moveTransitionIndexes;
+ QList<int> removeTransitionIndexes;
+ QList<QObject *> addTransitionTargets;
+ QList<QObject *> moveTransitionTargets;
+ QList<QObject *> removeTransitionTargets;
+
+ struct MovedItem {
+ FxViewItem *item;
+ QDeclarativeChangeSet::MoveKey moveKey;
+ MovedItem(FxViewItem *i, QDeclarativeChangeSet::MoveKey k)
+ : item(i), moveKey(k) {}
+ };
+ QList<FxViewItem *> releasePendingTransition;
+
mutable qreal minExtent;
mutable qreal maxExtent;
bool ownModel : 1;
bool wrap : 1;
- bool inApplyModelChanges : 1;
+ bool disableLayout : 1;
bool inViewportMoved : 1;
bool forceLayout : 1;
bool currentIndexCleared : 1;
@@ -217,6 +332,8 @@ public:
bool fillCacheBuffer : 1;
bool inRequest : 1;
bool requestedAsync : 1;
+ bool usePopulateTransition : 1;
+ bool runDelayedRemoveTransition : 1;
protected:
virtual Qt::Orientation layoutOrientation() const = 0;
@@ -246,15 +363,19 @@ protected:
virtual void visibleItemsChanged() {}
virtual FxViewItem *newViewItem(int index, QQuickItem *item) = 0;
+ virtual void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) = 0;
virtual void repositionPackageItemAt(QQuickItem *item, int index) = 0;
virtual void resetFirstItemPosition(qreal pos = 0.0) = 0;
virtual void adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible) = 0;
virtual void layoutVisibleItems(int fromModelIndex = 0) = 0;
virtual void changedVisibleIndex(int newIndex) = 0;
- virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *newItems) = 0;
+
+ virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult,
+ QList<FxViewItem *> *newItems, QList<MovedItem> *movingIntoView) = 0;
virtual bool needsRefillForAddedOrRemovedIndex(int) const { return false; }
+ virtual void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) = 0;
virtual void initializeViewItem(FxViewItem *) {}
virtual void initializeCurrentItem() {}
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index 424edc5843..03be177e2c 100644
--- a/src/quick/items/qquicklistview.cpp
+++ b/src/quick/items/qquicklistview.cpp
@@ -94,6 +94,7 @@ public:
virtual FxViewItem *newViewItem(int index, QQuickItem *item);
virtual void initializeViewItem(FxViewItem *item);
virtual void releaseItem(FxViewItem *item);
+ virtual void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer);
virtual void repositionPackageItemAt(QQuickItem *item, int index);
virtual void resetFirstItemPosition(qreal pos = 0.0);
virtual void adjustFirstItem(qreal forwards, qreal backwards, int);
@@ -104,7 +105,9 @@ public:
virtual void setPosition(qreal pos);
virtual void layoutVisibleItems(int fromModelIndex = 0);
- virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems);
+
+ virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView);
+ virtual void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult);
virtual void updateSections();
QQuickItem *getSectionItem(const QString &section);
@@ -253,9 +256,9 @@ public:
}
qreal itemPosition() const {
if (view->orientation() == QQuickListView::Vertical)
- return item->y();
+ return itemY();
else
- return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-item->x() : item->x());
+ return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-itemX() : itemX());
}
qreal size() const {
if (section)
@@ -273,35 +276,26 @@ public:
}
qreal endPosition() const {
if (view->orientation() == QQuickListView::Vertical) {
- return item->y() + item->height();
+ return itemY() + item->height();
} else {
return (view->effectiveLayoutDirection() == Qt::RightToLeft
- ? -item->x()
- : item->x() + item->width());
+ ? -itemX()
+ : itemX() + item->width());
}
}
void setPosition(qreal pos) {
- if (view->orientation() == QQuickListView::Vertical) {
- if (section) {
+ // position the section immediately even if there is a transition
+ if (section) {
+ if (view->orientation() == QQuickListView::Vertical) {
section->setY(pos);
- pos += section->height();
- }
- item->setY(pos);
- } else {
- if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
- if (section) {
- section->setX(-section->width()-pos);
- pos += section->width();
- }
- item->setX(-item->width()-pos);
} else {
- if (section) {
+ if (view->effectiveLayoutDirection() == Qt::RightToLeft)
+ section->setX(-section->width()-pos);
+ else
section->setX(pos);
- pos += section->width();
- }
- item->setX(pos);
}
}
+ moveTo(pointForPosition(pos));
}
void setSize(qreal size) {
if (view->orientation() == QQuickListView::Vertical)
@@ -310,12 +304,34 @@ public:
item->setWidth(size);
}
bool contains(qreal x, qreal y) const {
- return (x >= item->x() && x < item->x() + item->width() &&
- y >= item->y() && y < item->y() + item->height());
+ return (x >= itemX() && x < itemX() + item->width() &&
+ y >= itemY() && y < itemY() + item->height());
+ }
+ QQuickItemView *itemView() const {
+ return view;
}
QQuickItem *section;
QQuickListView *view;
+
+private:
+ QPointF pointForPosition(qreal pos) const {
+ if (view->orientation() == QQuickListView::Vertical) {
+ if (section)
+ pos += section->height();
+ return QPointF(itemX(), pos);
+ } else {
+ if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
+ if (section)
+ pos += section->width();
+ return QPointF(-item->width() - pos, itemY());
+ } else {
+ if (section)
+ pos += section->width();
+ return QPointF(pos, itemY());
+ }
+ }
+ }
};
//----------------------------------------------------------------------------
@@ -401,8 +417,9 @@ qreal QQuickListViewPrivate::lastPosition() const
qreal QQuickListViewPrivate::positionAt(int modelIndex) const
{
- if (FxViewItem *item = visibleItem(modelIndex))
+ if (FxViewItem *item = visibleItem(modelIndex)) {
return item->position();
+ }
if (!visibleItems.isEmpty()) {
if (modelIndex < visibleIndex) {
int count = visibleIndex - modelIndex;
@@ -612,7 +629,8 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
#endif
if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex, doBuffer))))
break;
- item->setPosition(pos);
+ if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+ item->setPosition(pos);
item->item->setVisible(!doBuffer);
pos += item->size() + spacing;
visibleItems.append(item);
@@ -631,7 +649,8 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
break;
--visibleIndex;
visiblePos -= item->size() + spacing;
- item->setPosition(visiblePos);
+ if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+ item->setPosition(visiblePos);
item->item->setVisible(!doBuffer);
visibleItems.prepend(item);
changed = true;
@@ -654,6 +673,7 @@ bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
&& (item = visibleItems.at(index)) && item->endPosition() < bufferFrom) {
if (item->attached->delayRemove())
break;
+
if (item->size() > 0) {
#ifdef DEBUG_DELEGATE_LIFECYCLE
qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition();
@@ -663,7 +683,15 @@ bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
if (item->index != -1)
visibleIndex++;
visibleItems.removeAt(index);
- releaseItem(item);
+ if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+ qDebug() << "refill not releasing animating item" << item->index << item->item->objectName();
+#endif
+ item->releaseAfterTransition = true;
+ releasePendingTransition.append(item);
+ } else {
+ releaseItem(item);
+ }
if (index == 0)
break;
item = visibleItems.at(--index);
@@ -681,7 +709,15 @@ bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1 << item->position();
#endif
visibleItems.removeLast();
- releaseItem(item);
+ if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+ qDebug() << "refill not releasing animating item" << item->index << item->item->objectName();
+#endif
+ item->releaseAfterTransition = true;
+ releasePendingTransition.append(item);
+ } else {
+ releaseItem(item);
+ }
changed = true;
}
@@ -713,11 +749,12 @@ void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex)
qreal sum = firstItem->size();
qreal pos = firstItem->position() + firstItem->size() + spacing;
firstItem->item->setVisible(firstItem->endPosition() >= from && firstItem->position() <= to);
+
for (int i=1; i < visibleItems.count(); ++i) {
FxListItemSG *item = static_cast<FxListItemSG*>(visibleItems.at(i));
if (item->index >= fromModelIndex) {
item->setPosition(pos);
- item->item->setVisible(item->endPosition() >= from && item->position() <= to);
+ item->setVisible(item->endPosition() >= from && item->position() <= to);
}
pos += item->size() + spacing;
sum += item->size();
@@ -726,12 +763,16 @@ void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex)
averageSize = qRound(sum / visibleItems.count());
// move current item if it is not a visible item.
- if (currentIndex >= 0 && currentItem && !fixedCurrent) {
+ if (currentIndex >= 0 && currentItem && !fixedCurrent)
static_cast<FxListItemSG*>(currentItem)->setPosition(positionAt(currentIndex));
- }
}
}
+void QQuickListViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer)
+{
+ static_cast<FxListItemSG *>(item)->setPosition(positionAt(index) + sizeBuffer);
+}
+
void QQuickListViewPrivate::repositionPackageItemAt(QQuickItem *item, int index)
{
Q_Q(QQuickListView);
@@ -1132,13 +1173,17 @@ void QQuickListViewPrivate::initializeCurrentItem()
if (currentItem) {
FxListItemSG *listItem = static_cast<FxListItemSG *>(currentItem);
- if (currentIndex == visibleIndex - 1 && visibleItems.count()) {
- // We can calculate exact postion in this case
- listItem->setPosition(visibleItems.first()->position() - currentItem->size() - spacing);
- } else {
- // Create current item now and position as best we can.
- // Its position will be corrected when it becomes visible.
- listItem->setPosition(positionAt(currentIndex));
+ // don't reposition the item if it's about to be transitioned to another position
+ FxViewItem *actualItem = visibleItem(currentIndex);
+ if ((!actualItem || !actualItem->transitionScheduledOrRunning())) {
+ if (currentIndex == visibleIndex - 1 && visibleItems.count()) {
+ // We can calculate exact postion in this case
+ listItem->setPosition(visibleItems.first()->position() - currentItem->size() - spacing);
+ } else {
+ // Create current item now and position as best we can.
+ // Its position will be corrected when it becomes visible.
+ listItem->setPosition(positionAt(currentIndex));
+ }
}
// Avoid showing section delegate twice. We still need the section heading so that
@@ -1653,27 +1698,34 @@ QQuickListView::~QQuickListView()
/*!
\qmlattachedproperty bool QtQuick2::ListView::delayRemove
- This attached property holds whether the delegate may be destroyed.
- It is attached to each instance of the delegate.
+ This attached property holds whether the delegate may be destroyed. It
+ is attached to each instance of the delegate. The default value is false.
It is sometimes necessary to delay the destruction of an item
- until an animation completes.
-
- The example delegate below ensures that the animation completes before
- the item is removed from the list.
+ until an animation completes. The example delegate below ensures that the
+ animation completes before the item is removed from the list.
\snippet doc/src/snippets/declarative/listview/listview.qml delayRemove
+
+ If a \l remove transition has been specified, it will not be applied until
+ delayRemove is returned to \c false.
*/
/*!
\qmlattachedsignal QtQuick2::ListView::onAdd()
- This attached handler is called immediately after an item is added to the view.
+ This attached signal handler is called immediately after an item is added to the view.
+
+ If an \l add transition is specified, it is applied immediately after
+ this signal handler is called.
*/
/*!
\qmlattachedsignal QtQuick2::ListView::onRemove()
This attached handler is called immediately before an item is removed from the view.
+
+ If a \l remove transition has been specified, it is applied after
+ this signal handler is called, providing that delayRemove is false.
*/
/*!
@@ -2191,6 +2243,231 @@ void QQuickListView::setSnapMode(SnapMode mode)
\sa footer, headerItem
*/
+/*!
+ \qmlproperty Transition QtQuick2::ListView::populate
+ This property holds the transition to apply to items that are initially created for a
+ view.
+
+ This transition is applied to all the items that are created when:
+
+ \list
+ \o The view is first created
+ \o The view's \l model changes
+ \o The view's \l model is \l {QAbstractItemModel::reset}{reset}, if the model is a QAbstractItemModel subclass
+ \endlist
+
+ For example, here is a view that specifies such a transition:
+
+ \code
+ ListView {
+ ...
+ populate: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ When the view is initialized, the view will create all the necessary items for the view,
+ then animate them to their correct positions within the view over one second.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa add, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::ListView::add
+ This property holds the transition to apply to items that are added within the view.
+
+ The transition is applied to items that have been added to the visible area of the view. For
+ example, here is a view that specifies such a transition:
+
+ \code
+ ListView {
+ ...
+ add: Transition {
+ NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is added to the above view, the item will be animated from the position (100,100)
+ to its final x,y position within the view, over one second. The transition only applies to
+ the new items that are added to the view; it does not apply to the items below that are
+ displaced by the addition of the new items. To animate the displaced items, set the \l
+ addDisplaced property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \note This transition is not applied to the items that are created when the view is initially
+ populated, or when the view's \l model changes. In those cases, the \l populate transition is
+ applied instead.
+
+ \sa addDisplaced, populate, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::ListView::addDisplaced
+ This property holds the transition to apply to items in the view that are displaced by other
+ items that have been added to the view.
+
+ The transition is applied to items that are currently visible and have been displaced by newly
+ added items. For example, here is a view that specifies such a transition:
+
+ \code
+ ListView {
+ ...
+ addDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is added to the above view, all items beneath the new item are displaced, causing
+ them to move down (or sideways, if horizontally orientated) within the view. As this
+ displacement occurs, the items' movement to their new x,y positions within the view will be
+ animated by a NumberAnimation over one second, as specified. This transition is not applied to
+ the new item that has been added to the view; to animate the added items, set the \l add
+ property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \note This transition is not applied to the items that are created when the view is initially
+ populated, or when the view's \l model changes. In those cases, the \l populate transition is
+ applied instead.
+
+ \sa add, populate, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::ListView::move
+ This property holds the transition to apply to items in the view that are moved by a move
+ operation.
+
+ The transition is applied to items that are moving within the view or are moving
+ into the view as a result of a move operation in the view's model. For example:
+
+ \code
+ ListView {
+ ...
+ move: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is moved within the above view, the item will be animated to its new position in
+ the view over one second. The transition only applies to the items that are the subject of the
+ move operation in the model; it does not apply to the items below them that are displaced by
+ the move operation. To animate the displaced items, set the \l moveDisplaced property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa moveDisplaced, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::ListView::moveDisplaced
+ This property holds the transition to apply to items in the view that are displaced by a
+ move operation in the view.
+
+ The transition is applied to items that are currently visible and have been displaced following
+ a move operation in the view's model. For example, here is a view that specifies such a transition:
+
+ \code
+ ListView {
+ ...
+ moveDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item moves within (or moves into) the above view, all items beneath it are
+ displaced, causing them to move upwards (or sideways, if horizontally orientated) within the
+ view. As this displacement occurs, the items' movement to their new x,y positions within the
+ view will be animated by a NumberAnimation over one second, as specified. This transition is
+ not applied to the item that are actually the subject of the move operation; to animate the
+ moved items, set the \l move property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa move, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::ListView::remove
+ This property holds the transition to apply to items that are removed from the view.
+
+ The transition is applied to items that have been removed from the visible area of the view. For
+ example:
+
+ \code
+ ListView {
+ ...
+ remove: Transition {
+ ParallelAnimation {
+ NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
+ NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
+ }
+ }
+ }
+ \endcode
+
+ Whenever an item is removed from the above view, the item will be animated to the position (100,100)
+ over one second, and in parallel will also change its opacity to 0. The transition
+ only applies to the items that are removed from the view; it does not apply to the items below
+ them that are displaced by the removal of the items. To animate the displaced items, set the \l
+ removeDisplaced property.
+
+ Note that by the time the transition is applied, the item has already been removed from the
+ model; any references to the model data for the removed index will not be valid.
+
+ Additionally, if the \l delayRemove attached property has been set for a delegate item, the
+ remove transition will not be applied until \l delayRemove becomes false again.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa removeDisplaced, ViewTransition
+*/
+
+/*!
+ \qmlproperty Transition QtQuick2::ListView::removeDisplaced
+ This property holds the transition to apply to items in the view that are displaced by the
+ removal of other items in the view.
+
+ The transition is applied to items that are currently visible and have been displaced by
+ the removal of items. For example, here is a view that specifies such a transition:
+
+ \code
+ ListView {
+ ...
+ removeDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+ }
+ \endcode
+
+ Whenever an item is removed from the above view, all items beneath it are displaced, causing
+ them to move upwards (or sideways, if horizontally orientated) within the view. As this
+ displacement occurs, the items' movement to their new x,y positions within the view will be
+ animated by a NumberAnimation over one second, as specified. This transition is not applied to
+ the item that has actually been removed from the view; to animate the removed items, set the
+ \l remove property.
+
+ For more details and examples on how to use view transitions, see the ViewTransition
+ documentation.
+
+ \sa remove, ViewTransition
+*/
+
+
void QQuickListView::viewportMoved()
{
Q_D(QQuickListView);
@@ -2385,7 +2662,7 @@ void QQuickListView::updateSections()
}
}
-bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems)
+bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
{
int modelIndex = change.index;
int count = change.count;
@@ -2450,8 +2727,10 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
visibleItems.insert(insertionIdx, item);
if (insertionIdx == 0)
insertResult->changedFirstItem = true;
- if (!change.isMove())
+ if (!change.isMove()) {
addedItems->append(item);
+ transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+ }
insertResult->sizeChangesBeforeVisiblePos += item->size() + spacing;
pos -= item->size() + spacing;
}
@@ -2464,6 +2743,7 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
FxViewItem *item = 0;
if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i))))
item->index = modelIndex + i;
+ bool newItem = !item;
if (!item)
item = createItem(modelIndex + i);
if (!item)
@@ -2472,8 +2752,15 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
visibleItems.insert(index, item);
if (index == 0)
insertResult->changedFirstItem = true;
- if (!change.isMove())
+ if (change.isMove()) {
+ // we know this is a move target, since move displaced items that are
+ // shuffled into view due to a move would be added in refill()
+ if (moveTransition && newItem)
+ movingIntoView->append(MovedItem(item, change.moveKey(item->index)));
+ } else {
addedItems->append(item);
+ transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+ }
insertResult->sizeChangesAfterVisiblePos += item->size() + spacing;
pos += item->size() + spacing;
++index;
@@ -2484,6 +2771,10 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
FxViewItem *item = visibleItems.at(index);
if (item->index != -1)
item->index += count;
+ if (change.isMove())
+ transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, false);
+ else
+ transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, false);
}
updateVisibleIndex();
@@ -2491,6 +2782,34 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
return visibleItems.count() > prevVisibleCount;
}
+void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
+{
+ Q_UNUSED(insertionResult);
+
+ int markerItemIndex = -1;
+ for (int i=0; i<visibleItems.count(); i++) {
+ if (visibleItems[i]->index == afterModelIndex) {
+ markerItemIndex = i;
+ break;
+ }
+ }
+ if (markerItemIndex < 0)
+ return;
+
+ const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
+ qreal sizeRemoved = -removalResult.sizeChangesAfterVisiblePos
+ - (removalResult.countChangeAfterVisibleItems * (averageSize + spacing));
+
+ for (int i=markerItemIndex+1; i<visibleItems.count() && visibleItems.at(i)->position() < viewEndPos; i++) {
+ FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems[i]);
+ if (!listItem->transitionScheduledOrRunning()) {
+ qreal pos = listItem->position();
+ listItem->setPosition(pos - sizeRemoved);
+ transitionNextReposition(listItem, FxViewItemTransitionManager::RemoveTransition, false);
+ listItem->setPosition(pos);
+ }
+ }
+}
/*!
\qmlmethod QtQuick2::ListView::positionViewAtIndex(int index, PositionMode mode)