aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBea Lam <bea.lam@nokia.com>2012-02-27 13:16:11 +1000
committerQt by Nokia <qt-info@nokia.com>2012-03-02 06:14:49 +0100
commit79608d6f72ea5963aed2fa161b9ef6781adbc41e (patch)
tree2837eb82b5b45a73ead1045d473359b4dcb97592
parent3b01983d4f21cbd53745bb9132b9b2fffb019077 (diff)
Improved transitions for Row, Column, Grid, Flow
The view transitions functionality for ListView and GridView has been integrated into the positioner elements. Not all of this functionality is available for positioners, though, since they don't have models (and thus cannot identify certain model operations) and they don't manage the lifetime of their children. Task-number: QTBUG-24336 Change-Id: I71588de289555d2ef5a763af11358bc0af7b31a7 Reviewed-by: Alan Alpert <alan.alpert@nokia.com>
-rw-r--r--doc/src/whatsnew.qdoc7
-rw-r--r--src/quick/items/qquickitemviewtransition.cpp34
-rw-r--r--src/quick/items/qquickpositioners.cpp370
-rw-r--r--src/quick/items/qquickpositioners_p.h16
-rw-r--r--src/quick/items/qquickpositioners_p_p.h11
-rw-r--r--tests/auto/qtquick2/qquickpositioners/data/transitions.qml196
-rw-r--r--tests/auto/qtquick2/qquickpositioners/qquickpositioners.pro1
-rw-r--r--tests/auto/qtquick2/qquickpositioners/tst_qquickpositioners.cpp455
-rw-r--r--tests/auto/qtquick2/shared/viewtestutil.h1
9 files changed, 908 insertions, 183 deletions
diff --git a/doc/src/whatsnew.qdoc b/doc/src/whatsnew.qdoc
index 88d5e91d76..1c92cdb16b 100644
--- a/doc/src/whatsnew.qdoc
+++ b/doc/src/whatsnew.qdoc
@@ -112,8 +112,11 @@ Setting Image sourceSize.width and sourceSize.height will now fit the image to t
Grid now has rowSpacing and columnSpacing properties. Spacing properties on positioners are now real numbers instead
of integers.
-Positioners now have attached properties that can be used to determine a subitem's location within a
-container such as Column or Row: Positioner.index, Positioner.isFirstItem, Positioner.isLastItem.
+Positioner (Row, Column, Grid, Flow) improvements:
+\list
+\o Transitions used for \c add and \c move now have improved features: they can access a ViewTransition attached property (see the ViewTransition documentation for examples) and can now animate arbitrary item properties (instead of being restricted to animating an item's position).
+\o Items in a positioner now have attached properties that can be used to determine a subitem's location: Positioner.index, Positioner.isFirstItem, Positioner.isLastItem.
+\endlist
Loader improvements:
- "active" property added to Loader, to allow delaying instantiation of a Loader element's item property
diff --git a/src/quick/items/qquickitemviewtransition.cpp b/src/quick/items/qquickitemviewtransition.cpp
index 9235321418..abff768ad3 100644
--- a/src/quick/items/qquickitemviewtransition.cpp
+++ b/src/quick/items/qquickitemviewtransition.cpp
@@ -377,6 +377,12 @@ bool QQuickViewItem::prepareTransition(const QRectF &viewBounds)
{
bool doTransition = false;
+ // If item is not already moving somewhere, set it to not move anywhere.
+ // This ensures that removed targets don't transition to the default (0,0) and that
+ // items set for other transition types only transition if they actually move somewhere.
+ if (nextTransitionType != QQuickItemViewTransitioner::NoTransition && !nextTransitionToSet)
+ moveTo(item->pos());
+
switch (nextTransitionType) {
case QQuickItemViewTransitioner::NoTransition:
{
@@ -390,7 +396,12 @@ bool QQuickViewItem::prepareTransition(const QRectF &viewBounds)
case QQuickItemViewTransitioner::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) {
+ if (viewBounds.isNull()) {
+ if (isTransitionTarget)
+ doTransition = true;
+ else
+ doTransition = (nextTransitionTo != item->pos());
+ } else if (isTransitionTarget) {
doTransition = (nextTransitionType == QQuickItemViewTransitioner::AddTransition)
? viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))
: viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()));
@@ -408,7 +419,8 @@ bool QQuickViewItem::prepareTransition(const QRectF &viewBounds)
case QQuickItemViewTransitioner::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()))
+ doTransition = viewBounds.isNull()
+ || 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);
@@ -503,7 +515,19 @@ QQuickViewTransitionAttached::QQuickViewTransitionAttached(QObject *parent)
generic displaced transition if specified)
\endlist
- Such view transitions additionally have access to a ViewTransition attached property that
+ For the \l Row, \l Column, \l Grid and \l Flow positioner elements, which operate with collections of child
+ items rather than data models, the following properties are used instead:
+
+ \list
+ \o \c add - the transition to apply to items that are created for the positioner, added to
+ or reparented to the positioner, or items that have become \l {Item::}{visible}
+ \o \c move - the transition to apply to items that have moved within the positioner, including
+ when they are displaced due to the addition or removal of other items, or when items are otherwise
+ rearranged within the positioner, or when items are repositioned due to the resizing of other
+ items in the positioner
+ \endlist
+
+ View transitions 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.
@@ -525,6 +549,10 @@ QQuickViewTransitionAttached::QQuickViewTransitionAttached(QObject *parent)
\o ViewTransition.targetItems - the target items themselves
\endlist
+ (Note that for the \l Row, \l Column, \l Grid and \l Flow positioner elements, the \c move transition only
+ provides these two additional details when the transition is triggered by the addition of items
+ to a positioner.)
+
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.
diff --git a/src/quick/items/qquickpositioners.cpp b/src/quick/items/qquickpositioners.cpp
index 95ee9bfb58..6ed35a9f42 100644
--- a/src/quick/items/qquickpositioners.cpp
+++ b/src/quick/items/qquickpositioners.cpp
@@ -109,6 +109,7 @@ QQuickBasePositioner::QQuickBasePositioner(QQuickBasePositionerPrivate &dd, Posi
QQuickBasePositioner::~QQuickBasePositioner()
{
Q_D(QQuickBasePositioner);
+ delete d->transitioner;
for (int i = 0; i < positionedItems.count(); ++i)
d->unwatchChanges(positionedItems.at(i).item);
for (int i = 0; i < unpositionedItems.count(); ++i)
@@ -142,31 +143,36 @@ void QQuickBasePositioner::setSpacing(qreal s)
QDeclarativeTransition *QQuickBasePositioner::move() const
{
Q_D(const QQuickBasePositioner);
- return d->moveTransition;
+ return d->transitioner ? d->transitioner->displacedTransition : 0;
}
void QQuickBasePositioner::setMove(QDeclarativeTransition *mt)
{
Q_D(QQuickBasePositioner);
- if (mt == d->moveTransition)
+ if (!d->transitioner)
+ d->transitioner = new QQuickItemViewTransitioner;
+ if (mt == d->transitioner->displacedTransition)
return;
- d->moveTransition = mt;
+
+ d->transitioner->displacedTransition = mt;
emit moveChanged();
}
QDeclarativeTransition *QQuickBasePositioner::add() const
{
Q_D(const QQuickBasePositioner);
- return d->addTransition;
+ return d->transitioner ? d->transitioner->addTransition : 0;
}
void QQuickBasePositioner::setAdd(QDeclarativeTransition *add)
{
Q_D(QQuickBasePositioner);
- if (add == d->addTransition)
+ if (!d->transitioner)
+ d->transitioner = new QQuickItemViewTransitioner;
+ if (add == d->transitioner->addTransition)
return;
- d->addTransition = add;
+ d->transitioner->addTransition = add;
emit addChanged();
}
@@ -218,6 +224,7 @@ void QQuickBasePositioner::prePositioning()
for (int ii = 0; ii < unpositionedItems.count(); ii++)
oldItems.append(unpositionedItems[ii]);
unpositionedItems.clear();
+ int addedIndex = -1;
for (int ii = 0; ii < children.count(); ++ii) {
QQuickItem *child = children.at(ii);
@@ -229,9 +236,22 @@ void QQuickBasePositioner::prePositioning()
posItem.isNew = true;
if (!childPrivate->explicitVisible || !child->width() || !child->height()) {
posItem.isVisible = false;
+ posItem.index = -1;
unpositionedItems.append(posItem);
} else {
+ posItem.index = positionedItems.count();
positionedItems.append(posItem);
+
+ if (d->transitioner) {
+ if (addedIndex < 0)
+ addedIndex = posItem.index;
+ PositionedItem *theItem = &positionedItems[positionedItems.count()-1];
+
+ d->transitioner->transitionNextReposition(theItem,
+ QQuickItemViewTransitioner::AddTransition, true);
+ d->transitioner->addTransitionIndexes << posItem.index;
+ d->transitioner->addTransitionTargets << posItem.item;
+ }
}
} else {
PositionedItem *item = &oldItems[wIdx];
@@ -239,75 +259,93 @@ void QQuickBasePositioner::prePositioning()
// i.e. their positioning is not affected if an ancestor is hidden.
if (!childPrivate->explicitVisible || !child->width() || !child->height()) {
item->isVisible = false;
+ item->index = -1;
unpositionedItems.append(*item);
} else if (!item->isVisible) {
+ // item changed from non-visible to visible, treat it as a "new" item
item->isVisible = true;
item->isNew = true;
+ item->index = positionedItems.count();
positionedItems.append(*item);
+
+ if (d->transitioner) {
+ if (addedIndex < 0)
+ addedIndex = item->index;
+ d->transitioner->transitionNextReposition(&positionedItems[positionedItems.count()-1],
+ QQuickItemViewTransitioner::AddTransition, true);
+ d->transitioner->addTransitionIndexes << item->index;
+ d->transitioner->addTransitionTargets << item->item;
+ }
} else {
item->isNew = false;
+ item->index = positionedItems.count();
positionedItems.append(*item);
}
}
}
+
+ if (d->transitioner) {
+ for (int i=0; i<positionedItems.count(); i++) {
+ if (!positionedItems[i].isNew) {
+ if (addedIndex >= 0) {
+ d->transitioner->transitionNextReposition(&positionedItems[i], QQuickItemViewTransitioner::AddTransition, false);
+ } else {
+ // just queue the item for a move-type displace - if the item hasn't
+ // moved anywhere, it won't be transitioned anyway
+ d->transitioner->transitionNextReposition(&positionedItems[i], QQuickItemViewTransitioner::MoveTransition, false);
+ }
+ }
+ }
+ }
+
QSizeF contentSize(0,0);
reportConflictingAnchors();
if (!d->anchorConflict) {
doPositioning(&contentSize);
updateAttachedProperties();
}
- if (!d->addActions.isEmpty() || !d->moveActions.isEmpty())
- finishApplyTransitions();
+
+ if (d->transitioner) {
+ QRectF viewBounds;
+ for (int i=0; i<positionedItems.count(); i++) {
+ if (positionedItems[i].prepareTransition(viewBounds))
+ positionedItems[i].startTransition(d->transitioner);
+ }
+ d->transitioner->addTransitionIndexes.clear();
+ d->transitioner->addTransitionTargets.clear();
+ }
+
d->doingPositioning = false;
+
//Set implicit size to the size of its children
setImplicitSize(contentSize.width(), contentSize.height());
}
-void QQuickBasePositioner::positionX(qreal x, const PositionedItem &target)
+void QQuickBasePositioner::positionItem(qreal x, qreal y, PositionedItem *target)
{
Q_D(QQuickBasePositioner);
- if (d->type == Horizontal || d->type == Both) {
- if (target.isNew) {
- if (!d->addTransition || !d->addTransition->enabled())
- target.item->setX(x);
- else
- d->addActions << QDeclarativeAction(target.item, QLatin1String("x"), QVariant(x));
- } else if (x != target.item->x()) {
- if (!d->moveTransition || !d->moveTransition->enabled())
- target.item->setX(x);
- else
- d->moveActions << QDeclarativeAction(target.item, QLatin1String("x"), QVariant(x));
- }
+ if ( (target->itemX() != x || target->itemY() != y)
+ && d->type == Both) {
+ target->moveTo(QPointF(x, y));
}
}
-void QQuickBasePositioner::positionY(qreal y, const PositionedItem &target)
+void QQuickBasePositioner::positionItemX(qreal x, PositionedItem *target)
{
Q_D(QQuickBasePositioner);
- if (d->type == Vertical || d->type == Both) {
- if (target.isNew) {
- if (!d->addTransition || !d->addTransition->enabled())
- target.item->setY(y);
- else
- d->addActions << QDeclarativeAction(target.item, QLatin1String("y"), QVariant(y));
- } else if (y != target.item->y()) {
- if (!d->moveTransition || !d->moveTransition->enabled())
- target.item->setY(y);
- else
- d->moveActions << QDeclarativeAction(target.item, QLatin1String("y"), QVariant(y));
- }
+ if (target->itemX() != x
+ && (d->type == Horizontal || d->type == Both)) {
+ target->moveTo(QPointF(x, target->itemY()));
}
}
-void QQuickBasePositioner::finishApplyTransitions()
+void QQuickBasePositioner::positionItemY(qreal y, PositionedItem *target)
{
Q_D(QQuickBasePositioner);
- // Note that if a transition is not set the transition manager will
- // apply the changes directly, in the case add/move aren't set
- d->addTransitionManager.transition(d->addActions, d->addTransition);
- d->moveTransitionManager.transition(d->moveActions, d->moveTransition);
- d->addActions.clear();
- d->moveActions.clear();
+ if (target->itemY() != y
+ && (d->type == Vertical || d->type == Both)) {
+ target->moveTo(QPointF(target->itemX(), y));
+ }
}
QQuickPositionerAttached *QQuickBasePositioner::qmlAttachedProperties(QObject *obj)
@@ -501,30 +539,42 @@ void QQuickPositionerAttached::setIsLastItem(bool isLastItem)
/*!
\qmlproperty Transition QtQuick2::Column::add
- This property holds the transition to be applied when adding an
- item to the positioner. The transition will only be applied to the
- added item(s). Positioner transitions will only affect the
- position (x, y) of items.
+ This property holds the transition to be run for items that are added to this
+ positioner. For a positioner, this applies to:
+
+ \list
+ \o Items that are created or reparented as a child of the positioner
+ \o Child items that change their \l visible property from false to true, and thus
+ are now visible
+ \endlist
- For a positioner, adding an item can mean that either the object
- has been created or reparented, and thus is now a child or the
- positioner, or that the object has changed its \l visible property
- from false to true, and thus is now visible.
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being added. See the \l ViewTransition documentation for more details
+ and examples on using these transitions.
- \sa move
+ \sa move, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty Transition QtQuick2::Column::move
- This property holds the transition to apply to any item that has moved
- within the positioner. Positioner transitions will only affect
- the position (x, y) of items.
+ This property holds the transition to run for items that have moved within the
+ positioner. For a positioner, this applies to:
- This transition is applied to items that are displaced as a result of the
- addition or removal of other items in the positioner, or when items move due to
- a move operation in a related model, or when items resize themselves.
+ \list
+ \o Child items that move when they are displaced due to the addition, removal or
+ rearrangement of other items in the positioner
+ \o Child items that are repositioned due to the resizing of other items in the positioner
+ \endlist
+
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being moved. Note, however, that for this move transition, the
+ ViewTransition.targetIndexes and ViewTransition.targetItems lists are only set when
+ this transition is triggered by the addition of other items in the positioner; in other
+ cases, these lists will be empty.
+
+ See the \l ViewTransition documentation for more details and examples on using these transitions.
- \sa add, {declarative/positioners}{Positioners example}
+ \sa add, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty real QtQuick2::Column::spacing
@@ -545,11 +595,8 @@ void QQuickColumn::doPositioning(QSizeF *contentSize)
qreal voffset = 0;
for (int ii = 0; ii < positionedItems.count(); ++ii) {
- const PositionedItem &child = positionedItems.at(ii);
-
- if (child.item->y() != voffset)
- positionY(voffset, child);
-
+ PositionedItem &child = positionedItems[ii];
+ positionItemY(voffset, &child);
contentSize->setWidth(qMax(contentSize->width(), child.item->width()));
voffset += child.item->height();
@@ -625,42 +672,42 @@ void QQuickColumn::reportConflictingAnchors()
/*!
\qmlproperty Transition QtQuick2::Row::add
- This property holds the transition to be applied when adding an
- item to the positioner. The transition will only be applied to the
- added item(s). Positioner transitions will only affect the
- position (x, y) of items.
+ This property holds the transition to be run for items that are added to this
+ positioner. For a positioner, this applies to:
- For a positioner, adding an item can mean that either the object
- has been created or reparented, and thus is now a child or the
- positioner, or that the object has changed its \l visible property
- from false to true, and thus is now visible.
+ \list
+ \o Items that are created or reparented as a child of the positioner
+ \o Child items that change their \l visible property from false to true, and thus
+ are now visible
+ \endlist
+
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being added. See the \l ViewTransition documentation for more details
+ and examples on using these transitions.
- \sa move
+ \sa move, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty Transition QtQuick2::Row::move
- This property holds the transition to apply to any item that has moved
- within the positioner. Positioner transitions will only affect
- the position (x, y) of items.
-
- This transition is applied to items that are displaced as a result of the
- addition or removal of other items in the positioner, or when items move due to
- a move operation in a related model, or when items resize themselves.
-
- \qml
- Row {
- id: positioner
- move: Transition {
- NumberAnimation {
- properties: "x"
- duration: 1000
- }
- }
- }
- \endqml
+ This property holds the transition to run for items that have moved within the
+ positioner. For a positioner, this applies to:
+
+ \list
+ \o Child items that move when they are displaced due to the addition, removal or
+ rearrangement of other items in the positioner
+ \o Child items that are repositioned due to the resizing of other items in the positioner
+ \endlist
+
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being moved. Note, however, that for this move transition, the
+ ViewTransition.targetIndexes and ViewTransition.targetItems lists are only set when
+ this transition is triggered by the addition of other items in the positioner; in other
+ cases, these lists will be empty.
- \sa add, {declarative/positioners}{Positioners example}
+ See the \l ViewTransition documentation for more details and examples on using these transitions.
+
+ \sa add, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty real QtQuick2::Row::spacing
@@ -736,11 +783,10 @@ void QQuickRow::doPositioning(QSizeF *contentSize)
QList<qreal> hoffsets;
for (int ii = 0; ii < positionedItems.count(); ++ii) {
- const PositionedItem &child = positionedItems.at(ii);
+ PositionedItem &child = positionedItems[ii];
if (d->isLeftToRight()) {
- if (child.item->x() != hoffset)
- positionX(hoffset, child);
+ positionItemX(hoffset, &child);
} else {
hoffsets << hoffset;
}
@@ -767,10 +813,9 @@ void QQuickRow::doPositioning(QSizeF *contentSize)
int acc = 0;
for (int ii = 0; ii < positionedItems.count(); ++ii) {
- const PositionedItem &child = positionedItems.at(ii);
+ PositionedItem &child = positionedItems[ii];
hoffset = end - hoffsets[acc++] - child.item->width();
- if (child.item->x() != hoffset)
- positionX(hoffset, child);
+ positionItemX(hoffset, &child);
}
}
@@ -839,41 +884,42 @@ void QQuickRow::reportConflictingAnchors()
/*!
\qmlproperty Transition QtQuick2::Grid::add
- This property holds the transition to be applied when adding an
- item to the positioner. The transition will only be applied to the
- added item(s). Positioner transitions will only affect the
- position (x, y) of items.
+ This property holds the transition to be run for items that are added to this
+ positioner. For a positioner, this applies to:
+
+ \list
+ \o Items that are created or reparented as a child of the positioner
+ \o Child items that change their \l visible property from false to true, and thus
+ are now visible
+ \endlist
- For a positioner, adding an item can mean that either the object
- has been created or reparented, and thus is now a child or the
- positioner, or that the object has changed its \l visible property
- from false to true, and thus is now visible.
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being added. See the \l ViewTransition documentation for more details
+ and examples on using these transitions.
- \sa move
+ \sa move, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty Transition QtQuick2::Grid::move
- This property holds the transition to apply to any item that has moved
- within the positioner. Positioner transitions will only affect
- the position (x, y) of items.
+ This property holds the transition to run for items that have moved within the
+ positioner. For a positioner, this applies to:
+
+ \list
+ \o Child items that move when they are displaced due to the addition, removal or
+ rearrangement of other items in the positioner
+ \o Child items that are repositioned due to the resizing of other items in the positioner
+ \endlist
- This transition is applied to items that are displaced as a result of the
- addition or removal of other items in the positioner, or when items move due to
- a move operation in a related model, or when items resize themselves.
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being moved. Note, however, that for this move transition, the
+ ViewTransition.targetIndexes and ViewTransition.targetItems lists are only set when
+ this transition is triggered by the addition of other items in the positioner; in other
+ cases, these lists will be empty.
- \qml
- Grid {
- move: Transition {
- NumberAnimation {
- properties: "x,y"
- duration: 1000
- }
- }
- }
- \endqml
+ See the \l ViewTransition documentation for more details and examples on using these transitions.
- \sa add, {declarative/positioners}{Positioners example}
+ \sa add, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty qreal QtQuick2::Grid::spacing
@@ -1160,14 +1206,11 @@ void QQuickGrid::doPositioning(QSizeF *contentSize)
int curRow =0;
int curCol =0;
for (int i = 0; i < positionedItems.count(); ++i) {
- const PositionedItem &child = positionedItems.at(i);
+ PositionedItem &child = positionedItems[i];
qreal childXOffset = xoffset;
if (!d->isLeftToRight())
childXOffset -= child.item->width();
- if ((child.item->x() != childXOffset) || (child.item->y() != yoffset)) {
- positionX(childXOffset, child);
- positionY(yoffset, child);
- }
+ positionItem(childXOffset, yoffset, &child);
if (m_flow == LeftToRight) {
if (d->isLeftToRight())
@@ -1254,42 +1297,42 @@ void QQuickGrid::reportConflictingAnchors()
/*!
\qmlproperty Transition QtQuick2::Flow::add
- This property holds the transition to be applied when adding an
- item to the positioner. The transition will only be applied to the
- added item(s). Positioner transitions will only affect the
- position (x, y) of items.
+ This property holds the transition to be run for items that are added to this
+ positioner. For a positioner, this applies to:
+
+ \list
+ \o Items that are created or reparented as a child of the positioner
+ \o Child items that change their \l visible property from false to true, and thus
+ are now visible
+ \endlist
- For a positioner, adding an item can mean that either the object
- has been created or reparented, and thus is now a child or the
- positioner, or that the object has changed its \l visible property
- from false to true, and thus is now visible.
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being added. See the \l ViewTransition documentation for more details
+ and examples on using these transitions.
- \sa move
+ \sa move, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty Transition QtQuick2::Flow::move
- This property holds the transition to apply to any item that has moved
- within the positioner. Positioner transitions will only affect
- the position (x, y) of items.
-
- This transition is applied to items that are displaced as a result of the
- addition or removal of other items in the positioner, or when items move due to
- a move operation in a related model, or when items resize themselves.
-
- \qml
- Flow {
- id: positioner
- move: Transition {
- NumberAnimation {
- properties: "x,y"
- ease: "easeOutBounce"
- }
- }
- }
- \endqml
+ This property holds the transition to run for items that have moved within the
+ positioner. For a positioner, this applies to:
+
+ \list
+ \o Child items that move when they are displaced due to the addition, removal or
+ rearrangement of other items in the positioner
+ \o Child items that are repositioned due to the resizing of other items in the positioner
+ \endlist
+
+ The transition can use the \l ViewTransition property to access more details about
+ the item that is being moved. Note, however, that for this move transition, the
+ ViewTransition.targetIndexes and ViewTransition.targetItems lists are only set when
+ this transition is triggered by the addition of other items in the positioner; in other
+ cases, these lists will be empty.
+
+ See the \l ViewTransition documentation for more details and examples on using these transitions.
- \sa add, {declarative/positioners}{Positioners example}
+ \sa add, ViewTransition, {declarative/positioners}{Positioners example}
*/
/*!
\qmlproperty real QtQuick2::Flow::spacing
@@ -1414,7 +1457,7 @@ void QQuickFlow::doPositioning(QSizeF *contentSize)
QList<qreal> hoffsets;
for (int i = 0; i < positionedItems.count(); ++i) {
- const PositionedItem &child = positionedItems.at(i);
+ PositionedItem &child = positionedItems[i];
if (d->flow == LeftToRight) {
if (widthValid() && hoffset && hoffset + child.item->width() > width()) {
@@ -1431,13 +1474,11 @@ void QQuickFlow::doPositioning(QSizeF *contentSize)
}
if (d->isLeftToRight()) {
- if (child.item->x() != hoffset)
- positionX(hoffset, child);
+ positionItem(hoffset, voffset, &child);
} else {
hoffsets << hoffset;
+ positionItemY(voffset, &child);
}
- if (child.item->y() != voffset)
- positionY(voffset, child);
contentSize->setWidth(qMax(contentSize->width(), hoffset + child.item->width()));
contentSize->setHeight(qMax(contentSize->height(), voffset + child.item->height()));
@@ -1462,10 +1503,9 @@ void QQuickFlow::doPositioning(QSizeF *contentSize)
end = contentSize->width();
int acc = 0;
for (int i = 0; i < positionedItems.count(); ++i) {
- const PositionedItem &child = positionedItems.at(i);
+ PositionedItem &child = positionedItems[i];
hoffset = end - hoffsets[acc++] - child.item->width();
- if (child.item->x() != hoffset)
- positionX(hoffset, child);
+ positionItemX(hoffset, &child);
}
}
diff --git a/src/quick/items/qquickpositioners_p.h b/src/quick/items/qquickpositioners_p.h
index a4f18cfc21..32dd9030fe 100644
--- a/src/quick/items/qquickpositioners_p.h
+++ b/src/quick/items/qquickpositioners_p.h
@@ -43,6 +43,7 @@
#define QQUICKPOSITIONERS_P_H
#include "qquickimplicitsizeitem_p.h"
+#include "qquickitemviewtransition_p.h"
#include <QtQuick/private/qdeclarativestate_p.h>
#include <private/qpodvector_p.h>
@@ -96,6 +97,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickBasePositioner : public QQuickImplicitSizeIte
Q_PROPERTY(QDeclarativeTransition *add READ add WRITE setAdd NOTIFY addChanged)
public:
enum PositionerType { None = 0x0, Horizontal = 0x1, Vertical = 0x2, Both = 0x3 };
+
QQuickBasePositioner(PositionerType, QQuickItem *parent);
~QQuickBasePositioner();
@@ -116,7 +118,6 @@ protected:
QQuickBasePositioner(QQuickBasePositionerPrivate &dd, PositionerType at, QQuickItem *parent);
virtual void componentComplete();
virtual void itemChange(ItemChange, const ItemChangeData &);
- void finishApplyTransitions();
virtual void updatePolish();
@@ -131,19 +132,22 @@ protected Q_SLOTS:
protected:
virtual void doPositioning(QSizeF *contentSize)=0;
virtual void reportConflictingAnchors()=0;
- class PositionedItem {
+
+ class PositionedItem : public QQuickViewItem
+ {
public :
- PositionedItem(QQuickItem *i) : item(i), isNew(false), isVisible(true) {}
+ PositionedItem(QQuickItem *i) : QQuickViewItem(i), isNew(false), isVisible(true) {}
bool operator==(const PositionedItem &other) const { return other.item == item; }
- QQuickItem *item;
+
bool isNew;
bool isVisible;
};
QPODVector<PositionedItem,8> positionedItems;
QPODVector<PositionedItem,8> unpositionedItems;//Still 'in' the positioner, just not positioned
- void positionX(qreal,const PositionedItem &target);
- void positionY(qreal,const PositionedItem &target);
+ void positionItem(qreal x, qreal y, PositionedItem *target);
+ void positionItemX(qreal, PositionedItem *target);
+ void positionItemY(qreal, PositionedItem *target);
private:
Q_DISABLE_COPY(QQuickBasePositioner)
diff --git a/src/quick/items/qquickpositioners_p_p.h b/src/quick/items/qquickpositioners_p_p.h
index d281f1a372..f1d174dc0a 100644
--- a/src/quick/items/qquickpositioners_p_p.h
+++ b/src/quick/items/qquickpositioners_p_p.h
@@ -66,6 +66,8 @@
QT_BEGIN_NAMESPACE
+class QQuickItemViewTransitioner;
+
class QQuickBasePositionerPrivate : public QQuickImplicitSizeItemPrivate, public QQuickItemChangeListener
{
Q_DECLARE_PUBLIC(QQuickBasePositioner)
@@ -73,7 +75,7 @@ class QQuickBasePositionerPrivate : public QQuickImplicitSizeItemPrivate, public
public:
QQuickBasePositionerPrivate()
: spacing(0), type(QQuickBasePositioner::None)
- , moveTransition(0), addTransition(0), positioningDirty(false)
+ , transitioner(0), positioningDirty(false)
, doingPositioning(false), anchorConflict(false), layoutDirection(Qt::LeftToRight)
{
}
@@ -87,12 +89,7 @@ public:
qreal spacing;
QQuickBasePositioner::PositionerType type;
- QDeclarativeTransition *moveTransition;
- QDeclarativeTransition *addTransition;
- QDeclarativeStateOperation::ActionList addActions;
- QDeclarativeStateOperation::ActionList moveActions;
- QDeclarativeTransitionManager addTransitionManager;
- QDeclarativeTransitionManager moveTransitionManager;
+ QQuickItemViewTransitioner *transitioner;
void watchChanges(QQuickItem *other);
void unwatchChanges(QQuickItem* other);
diff --git a/tests/auto/qtquick2/qquickpositioners/data/transitions.qml b/tests/auto/qtquick2/qquickpositioners/data/transitions.qml
new file mode 100644
index 0000000000..982e64141c
--- /dev/null
+++ b/tests/auto/qtquick2/qquickpositioners/data/transitions.qml
@@ -0,0 +1,196 @@
+import QtQuick 2.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 500
+
+ property int duration: 50
+
+ property int targetTransitionsDone
+ property int displaceTransitionsDone
+
+ property var targetTrans_items: new Object()
+ property var targetTrans_targetIndexes: new Array()
+ property var targetTrans_targetItems: new Array()
+
+ property var displacedTrans_items: new Object()
+ property var displacedTrans_targetIndexes: new Array()
+ property var displacedTrans_targetItems: new Array()
+
+ // for QDeclarativeListProperty types
+ function copyList(propList) {
+ var temp = new Array()
+ for (var i=0; i<propList.length; i++)
+ temp.push(propList[i])
+ return temp
+ }
+
+ function checkPos(x, y, name) {
+ if (Qt.point(x, y) == targetItems_transitionFrom)
+ model_targetItems_transitionFrom.addItem(name, "")
+ if (Qt.point(x, y) == displacedItems_transitionVia)
+ model_displacedItems_transitionVia.addItem(name, "")
+ }
+
+ Transition {
+ id: targetTransition
+ enabled: enableAddTransition
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ root.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+ root.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+ root.targetTrans_targetItems.push(root.copyList(targetTransition.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: targetItems_transitionFrom.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: targetItems_transitionFrom.y; duration: root.duration }
+ }
+
+ ScriptAction { script: root.targetTransitionsDone += 1 }
+ }
+ }
+
+ Transition {
+ id: displaced
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ root.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+ root.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+ root.displacedTrans_targetItems.push(root.copyList(displaced.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; duration: root.duration; to: displacedItems_transitionVia.x }
+ NumberAnimation { properties: "y"; duration: root.duration; to: displacedItems_transitionVia.y }
+ }
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+
+ ScriptAction { script: root.displaceTransitionsDone += 1 }
+ }
+
+ }
+
+ Row {
+ objectName: "row"
+
+ property int count: children.length - 1 // omit Repeater
+
+ x: 50; y: 50
+ width: 400; height: 400
+ Repeater {
+ objectName: "repeater"
+ Rectangle {
+ property string nameData: name
+ objectName: "wrapper"
+ width: 30 + index*5
+ height: 30 + index*5
+ border.width: 1
+ Column {
+ Text { text: index }
+ Text { objectName: "name"; text: name }
+ Text { text: parent.parent.y }
+ }
+ onXChanged: root.checkPos(x, y, name)
+ onYChanged: root.checkPos(x, y, name)
+ }
+ }
+
+ add: targetTransition
+ move: displaced
+ }
+
+ Column {
+ objectName: "column"
+
+ property int count: children.length - 1 // omit Repeater
+
+ x: 50; y: 50
+ width: 400; height: 400
+ Repeater {
+ objectName: "repeater"
+ Rectangle {
+ property string nameData: name
+ objectName: "wrapper"
+ width: 30 + index*5
+ height: 30 + index*5
+ border.width: 1
+ Column {
+ Text { text: index }
+ Text { objectName: "name"; text: name }
+ Text { text: parent.parent.y }
+ }
+ onXChanged: root.checkPos(x, y, name)
+ onYChanged: root.checkPos(x, y, name)
+ }
+ }
+
+ add: targetTransition
+ move: displaced
+ }
+
+ Grid {
+ objectName: "grid"
+
+ property int count: children.length - 1 // omit Repeater
+
+ x: 50; y: 50
+ width: 400; height: 400
+ Repeater {
+ objectName: "repeater"
+ Rectangle {
+ property string nameData: name
+ objectName: "wrapper"
+ width: 30 + index*5
+ height: 30 + index*5
+ border.width: 1
+ Column {
+ Text { text: index }
+ Text { objectName: "name"; text: name }
+ Text { text: parent.parent.y }
+ }
+
+ onXChanged: root.checkPos(x, y, name)
+ onYChanged: root.checkPos(x, y, name)
+ }
+ }
+
+ add: targetTransition
+ move: displaced
+ }
+
+ Flow {
+ objectName: "flow"
+
+ property int count: children.length - 1 // omit Repeater
+
+ x: 50; y: 50
+ width: 400; height: 400
+ Repeater {
+ objectName: "repeater"
+ Rectangle {
+ property string nameData: name
+ objectName: "wrapper"
+ width: 30 + index*5
+ height: 30 + index*5
+ border.width: 1
+ Column {
+ Text { text: index }
+ Text { objectName: "name"; text: name }
+ Text { text: parent.parent.x + " " + parent.parent.y }
+ }
+ onXChanged: root.checkPos(x, y, name)
+ onYChanged: root.checkPos(x, y, name)
+ }
+ }
+
+ add: targetTransition
+ move: displaced
+ }
+}
+
diff --git a/tests/auto/qtquick2/qquickpositioners/qquickpositioners.pro b/tests/auto/qtquick2/qquickpositioners/qquickpositioners.pro
index ec0056f8e9..699a54ed7b 100644
--- a/tests/auto/qtquick2/qquickpositioners/qquickpositioners.pro
+++ b/tests/auto/qtquick2/qquickpositioners/qquickpositioners.pro
@@ -2,6 +2,7 @@ CONFIG += testcase
TARGET = tst_qquickpositioners
SOURCES += tst_qquickpositioners.cpp
+include (../shared/util.pri)
include (../../shared/util.pri)
macx:CONFIG -= app_bundle
diff --git a/tests/auto/qtquick2/qquickpositioners/tst_qquickpositioners.cpp b/tests/auto/qtquick2/qquickpositioners/tst_qquickpositioners.cpp
index 4c903ab704..f7e14bee3e 100644
--- a/tests/auto/qtquick2/qquickpositioners/tst_qquickpositioners.cpp
+++ b/tests/auto/qtquick2/qquickpositioners/tst_qquickpositioners.cpp
@@ -47,8 +47,13 @@
#include <QtQuick/private/qdeclarativetransition_p.h>
#include <private/qquickitem_p.h>
#include <qdeclarativeexpression.h>
+#include "../shared/viewtestutil.h"
+#include "../shared/visualtestutil.h"
#include "../../shared/util.h"
+using namespace QQuickViewTestUtil;
+using namespace QQuickVisualTestUtil;
+
class tst_qquickpositioners : public QDeclarativeDataTest
{
Q_OBJECT
@@ -88,11 +93,145 @@ private slots:
void test_attachedproperties();
void test_attachedproperties_data();
void test_attachedproperties_dynamic();
+ void addTransitions_row();
+ void addTransitions_row_data();
+ void addTransitions_column();
+ void addTransitions_column_data();
+ void addTransitions_grid();
+ void addTransitions_grid_data();
+ void addTransitions_flow();
+ void addTransitions_flow_data();
+ void moveTransitions_row();
+ void moveTransitions_row_data();
+ void moveTransitions_column();
+ void moveTransitions_column_data();
+ void moveTransitions_grid();
+ void moveTransitions_grid_data();
+ void moveTransitions_flow();
+ void moveTransitions_flow_data();
private:
QQuickView *createView(const QString &filename, bool wait=true);
+
+ void addTransitions(const QString &positionerObjectName);
+ void addTransitions_data();
+ void moveTransitions(const QString &positionerObjectName);
+ void moveTransitions_data();
+ void matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes);
+ void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes);
+ void matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems);
+ void checkItemPositions(QQuickItem *positioner, QaimModel *model, qreal incrementalSize);
};
+void tst_qquickpositioners::addTransitions_row()
+{
+ addTransitions("row");
+}
+
+void tst_qquickpositioners::addTransitions_row_data()
+{
+ addTransitions_data();
+}
+
+void tst_qquickpositioners::addTransitions_column()
+{
+ addTransitions("column");
+}
+
+void tst_qquickpositioners::addTransitions_column_data()
+{
+ addTransitions_data();
+}
+
+void tst_qquickpositioners::addTransitions_grid()
+{
+ addTransitions("grid");
+}
+
+void tst_qquickpositioners::addTransitions_grid_data()
+{
+ // don't use addTransitions_data() because grid displaces items differently
+ // (adding items further down the grid can cause displace transitions at
+ // previous indexes, since grid is auto-resized to tightly fit all of its items)
+
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<int>("insertionIndex");
+ QTest::addColumn<int>("insertionCount");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ QTest::newRow("add one @ start") << 10 << 0 << 1 << ListRange(0, 9);
+ QTest::newRow("add one @ middle") << 10 << 5 << 1 << ListRange(3, 3) + ListRange(5, 9);
+ QTest::newRow("add one @ end") << 10 << 10 << 1 << ListRange(3, 3) + ListRange(7, 7);
+
+ QTest::newRow("add multiple @ start") << 10 << 0 << 3 << ListRange(0, 9);
+ QTest::newRow("add multiple @ middle") << 10 << 5 << 3 << ListRange(1, 3) + ListRange(5, 9);
+ QTest::newRow("add multiple @ end") << 10 << 10 << 3 << ListRange(1, 3) + ListRange(5, 7) + ListRange(9, 9);
+}
+
+void tst_qquickpositioners::addTransitions_flow()
+{
+ addTransitions("flow");
+}
+
+void tst_qquickpositioners::addTransitions_flow_data()
+{
+ addTransitions_data();
+}
+
+void tst_qquickpositioners::moveTransitions_row()
+{
+ moveTransitions("row");
+}
+
+void tst_qquickpositioners::moveTransitions_row_data()
+{
+ moveTransitions_data();
+}
+
+void tst_qquickpositioners::moveTransitions_column()
+{
+ moveTransitions("column");
+}
+
+void tst_qquickpositioners::moveTransitions_column_data()
+{
+ moveTransitions_data();
+}
+
+void tst_qquickpositioners::moveTransitions_grid()
+{
+ moveTransitions("grid");
+}
+
+void tst_qquickpositioners::moveTransitions_grid_data()
+{
+ // don't use moveTransitions_data() because grid displaces items differently
+ // (removing items further down the grid can cause displace transitions at
+ // previous indexes, since grid is auto-resized to tightly fit all of its items)
+
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<ListChange>("change");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ QTest::newRow("remove one @ start") << 10 << ListChange::remove(0, 1) << ListRange(1, 9);
+ QTest::newRow("remove one @ middle") << 10 << ListChange::remove(4, 1) << ListRange(2, 3) + ListRange(5, 9);
+ QTest::newRow("remove one @ end") << 10 << ListChange::remove(9, 1) << ListRange(2, 3) + ListRange(6, 7);
+
+ QTest::newRow("remove multiple @ start") << 10 << ListChange::remove(0, 3) << ListRange(3, 9);
+ QTest::newRow("remove multiple @ middle") << 10 << ListChange::remove(4, 3) << ListRange(1, 3) + ListRange(7, 9);
+ QTest::newRow("remove multiple @ end") << 10 << ListChange::remove(7, 3) << ListRange(1, 3) + ListRange(5, 6);
+}
+
+void tst_qquickpositioners::moveTransitions_flow()
+{
+ moveTransitions("flow");
+}
+
+void tst_qquickpositioners::moveTransitions_flow_data()
+{
+ moveTransitions_data();
+}
+
tst_qquickpositioners::tst_qquickpositioners()
{
}
@@ -370,6 +509,285 @@ void tst_qquickpositioners::test_horizontal_animated_disabled()
delete canvas;
}
+void tst_qquickpositioners::addTransitions(const QString &positionerObjectName)
+{
+ QFETCH(int, initialItemCount);
+ QFETCH(int, insertionIndex);
+ QFETCH(int, insertionCount);
+ QFETCH(ListRange, expectedDisplacedIndexes);
+
+ QPointF targetItems_transitionFrom(-50, -50);
+ QPointF displacedItems_transitionVia(100, 100);
+
+ QaimModel model;
+ for (int i = 0; i < initialItemCount; i++)
+ model.addItem("Original item" + QString::number(i), "");
+ QaimModel model_targetItems_transitionFrom;
+ QaimModel model_displacedItems_transitionVia;
+
+ QQuickView *canvas = QQuickViewTestUtil::createView();
+ QDeclarativeContext *ctxt = canvas->rootContext();
+ ctxt->setContextProperty("enableAddTransition", true);
+ ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
+ ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+ ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
+ ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+ canvas->setSource(testFile("transitions.qml"));
+ canvas->show();
+ qApp->processEvents();
+
+ QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+ QQuickItem *positioner = canvas->rootObject()->findChild<QQuickItem*>(positionerObjectName);
+ QVERIFY(positioner);
+ positioner->findChild<QQuickItem*>("repeater")->setProperty("model", QVariant::fromValue(&model));
+
+ QList<QPair<QString, QString> > targetData;
+ QList<int> targetIndexes;
+ for (int i=0; i<model.count(); i++) {
+ targetData << qMakePair(model.name(i), model.number(i));
+ targetIndexes << i;
+ }
+ QList<QQuickItem *> targetItems = findItems<QQuickItem>(positioner, "wrapper", targetIndexes);
+
+ // check initial add transition
+ // (positioners run the add transition on all items that are initially created for the view)
+ QTRY_COMPARE(canvas->rootObject()->property("targetTransitionsDone").toInt(), initialItemCount);
+ QTRY_COMPARE(canvas->rootObject()->property("displaceTransitionsDone").toInt(), 0);
+ model_targetItems_transitionFrom.matchAgainst(targetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+ matchItemsAndIndexes(canvas->rootObject()->property("targetTrans_items").toMap(), model, targetIndexes);
+ matchIndexLists(canvas->rootObject()->property("targetTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(canvas->rootObject()->property("targetTrans_targetItems").toList(), targetItems);
+
+ model_targetItems_transitionFrom.clear();
+ canvas->rootObject()->setProperty("targetTransitionsDone", 0);
+ canvas->rootObject()->setProperty("targetTrans_items", QVariantMap());
+ canvas->rootObject()->setProperty("targetTrans_targetIndexes", QVariantList());
+ canvas->rootObject()->setProperty("targetTrans_targetItems", QVariantList());
+
+ // do insertion
+ targetData.clear();
+ targetIndexes.clear();
+ for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
+ targetData << qMakePair(QString("New item %1").arg(i), QString(""));
+ targetIndexes << i;
+ }
+ model.insertItems(insertionIndex, targetData);
+ QTRY_COMPARE(model.count(), positioner->property("count").toInt());
+
+ targetItems = findItems<QQuickItem>(positioner, "wrapper", targetIndexes);
+
+ QTRY_COMPARE(canvas->rootObject()->property("targetTransitionsDone").toInt(), targetData.count());
+ QTRY_COMPARE(canvas->rootObject()->property("displaceTransitionsDone").toInt(), expectedDisplacedIndexes.count());
+
+ // check the target and displaced items were animated
+ model_targetItems_transitionFrom.matchAgainst(targetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+ model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+ // check attached properties
+ matchItemsAndIndexes(canvas->rootObject()->property("targetTrans_items").toMap(), model, targetIndexes);
+ matchIndexLists(canvas->rootObject()->property("targetTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(canvas->rootObject()->property("targetTrans_targetItems").toList(), targetItems);
+ if (expectedDisplacedIndexes.isValid()) {
+ // adjust expectedDisplacedIndexes to their final values after the move
+ QList<int> displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount);
+ matchItemsAndIndexes(canvas->rootObject()->property("displacedTrans_items").toMap(), model, displacedIndexes);
+ matchIndexLists(canvas->rootObject()->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(canvas->rootObject()->property("displacedTrans_targetItems").toList(), targetItems);
+ }
+
+ checkItemPositions(positioner, &model, 5.0); // XXX fetch from qml?
+
+ delete canvas;
+}
+
+void tst_qquickpositioners::addTransitions_data()
+{
+ // If this data changes, update addTransitions_grid_data() also
+
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<int>("insertionIndex");
+ QTest::addColumn<int>("insertionCount");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ QTest::newRow("add one @ start") << 10 << 0 << 1 << ListRange(0, 9);
+ QTest::newRow("add one @ middle") << 10 << 5 << 1 << ListRange(5, 9);
+ QTest::newRow("add one @ end") << 10 << 10 << 1 << ListRange();
+
+ QTest::newRow("add multiple @ start") << 10 << 0 << 3 << ListRange(0, 9);
+ QTest::newRow("add multiple @ middle") << 10 << 5 << 3 << ListRange(5, 9);
+ QTest::newRow("add multiple @ end") << 10 << 10 << 3 << ListRange();
+}
+
+void tst_qquickpositioners::moveTransitions(const QString &positionerObjectName)
+{
+ QFETCH(int, initialItemCount);
+ QFETCH(ListChange, change);
+ QFETCH(ListRange, expectedDisplacedIndexes);
+
+ QPointF targetItems_transitionFrom(-50, -50);
+ QPointF displacedItems_transitionVia(100, 100);
+
+ QaimModel model;
+ for (int i = 0; i < initialItemCount; i++)
+ model.addItem("Item" + QString::number(i), "");
+ QaimModel model_targetItems_transitionFrom;
+ QaimModel model_displacedItems_transitionVia;
+
+ QQuickView *canvas = QQuickViewTestUtil::createView();
+ QDeclarativeContext *ctxt = canvas->rootContext();
+ ctxt->setContextProperty("enableAddTransition", false);
+ ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
+ ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+ ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
+ ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+ canvas->setSource(testFile("transitions.qml"));
+ canvas->show();
+ qApp->processEvents();
+
+ QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+ QQuickItem *positioner = canvas->rootObject()->findChild<QQuickItem*>(positionerObjectName);
+ QVERIFY(positioner);
+ positioner->findChild<QQuickItem*>("repeater")->setProperty("model", QVariant::fromValue(&model));
+ QTRY_COMPARE(QQuickItemPrivate::get(positioner)->polishScheduled, false);
+
+ switch (change.type) {
+ case ListChange::Removed:
+ model.removeItems(change.index, change.count);
+ QTRY_COMPARE(model.count(), positioner->property("count").toInt());
+ break;
+ case ListChange::Moved:
+ model.moveItems(change.index, change.to, change.count);
+ QTRY_COMPARE(QQuickItemPrivate::get(positioner)->polishScheduled, false);
+ break;
+ case ListChange::Inserted:
+ case ListChange::SetCurrent:
+ case ListChange::SetContentY:
+ QVERIFY(false);
+ break;
+ }
+
+ QTRY_COMPARE(canvas->rootObject()->property("displaceTransitionsDone").toInt(), expectedDisplacedIndexes.count());
+ QCOMPARE(canvas->rootObject()->property("targetTransitionsDone").toInt(), 0);
+
+ // check the target and displaced items were animated
+ QCOMPARE(model_targetItems_transitionFrom.count(), 0);
+ model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+ // check attached properties
+ QCOMPARE(canvas->rootObject()->property("targetTrans_items").toMap().count(), 0);
+ QCOMPARE(canvas->rootObject()->property("targetTrans_targetIndexes").toList().count(), 0);
+ QCOMPARE(canvas->rootObject()->property("targetTrans_targetItems").toList().count(), 0);
+ if (expectedDisplacedIndexes.isValid()) {
+ // adjust expectedDisplacedIndexes to their final values after the move
+ QList<int> displacedIndexes;
+ if (change.type == ListChange::Inserted)
+ displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, change.index, change.count);
+ else if (change.type == ListChange::Moved)
+ displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, change.index, change.to, change.count);
+ else if (change.type == ListChange::Removed)
+ displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, change.index, change.count);
+ else
+ QVERIFY(false);
+ matchItemsAndIndexes(canvas->rootObject()->property("displacedTrans_items").toMap(), model, displacedIndexes);
+
+ QVariantList listOfEmptyIntLists;
+ for (int i=0; i<displacedIndexes.count(); i++)
+ listOfEmptyIntLists << QVariant::fromValue(QList<int>());
+ QCOMPARE(canvas->rootObject()->property("displacedTrans_targetIndexes").toList(), listOfEmptyIntLists);
+ QVariantList listOfEmptyObjectLists;
+ for (int i=0; i<displacedIndexes.count(); i++)
+ listOfEmptyObjectLists.insert(listOfEmptyObjectLists.count(), QVariantList());
+ QCOMPARE(canvas->rootObject()->property("displacedTrans_targetItems").toList(), listOfEmptyObjectLists);
+ }
+
+ checkItemPositions(positioner, &model, 5.0);
+
+ delete canvas;
+}
+
+void tst_qquickpositioners::moveTransitions_data()
+{
+ // If this data changes, update moveTransitions_grid_data() also
+
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<ListChange>("change");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ QTest::newRow("remove one @ start") << 10 << ListChange::remove(0, 1) << ListRange(1, 9);
+ QTest::newRow("remove one @ middle") << 10 << ListChange::remove(4, 1) << ListRange(5, 9);
+ QTest::newRow("remove one @ end") << 10 << ListChange::remove(9, 1) << ListRange();
+
+ QTest::newRow("remove multiple @ start") << 10 << ListChange::remove(0, 3) << ListRange(3, 9);
+ QTest::newRow("remove multiple @ middle") << 10 << ListChange::remove(4, 3) << ListRange(7, 9);
+ QTest::newRow("remove multiple @ end") << 10 << ListChange::remove(7, 3) << ListRange();
+}
+
+
+void tst_qquickpositioners::checkItemPositions(QQuickItem *positioner, QaimModel *model, qreal incrementalSize)
+{
+ QVERIFY(model->count() > 0);
+ qreal padding = 0;
+ qreal currentSize = 30;
+ qreal rowX = 0;
+ qreal rowY = 0;
+
+ for (int i=0; i<model->count(); ++i) {
+ QQuickItem *item = findItem<QQuickItem>(positioner, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+
+ QCOMPARE(item->width(), currentSize);
+ QCOMPARE(item->height(), currentSize);
+
+ if (qobject_cast<QQuickRow*>(positioner)) {
+ QCOMPARE(item->x(), (i * 30.0) + padding);
+ QCOMPARE(item->y(), 0.0);
+ } else if (qobject_cast<QQuickColumn*>(positioner)) {
+ QCOMPARE(item->x(), 0.0);
+ QCOMPARE(item->y(), (i * 30.0) + padding);
+ } else if (qobject_cast<QQuickGrid*>(positioner)) {
+ int columns = 4;
+ int rows = qCeil(model->count() / qreal(columns));
+ int lastMatchingRowIndex = (rows * columns) - (columns - i%columns);
+ if (lastMatchingRowIndex >= model->count())
+ lastMatchingRowIndex -= columns;
+ if (i % columns > 0) {
+ QQuickItem *finalAlignedRowItem = findItem<QQuickItem>(positioner, "wrapper", lastMatchingRowIndex);
+ QVERIFY(finalAlignedRowItem);
+ QCOMPARE(item->x(), finalAlignedRowItem->x());
+ } else {
+ QCOMPARE(item->x(), 0.0);
+ }
+ if (i / columns > 0) {
+ QQuickItem *prevRowLastItem = findItem<QQuickItem>(positioner, "wrapper", (i/columns * columns) - 1);
+ QVERIFY(prevRowLastItem);
+ QCOMPARE(item->y(), prevRowLastItem->y() + prevRowLastItem->height());
+ } else {
+ QCOMPARE(item->y(), 0.0);
+ }
+ } else if (qobject_cast<QQuickFlow*>(positioner)) {
+ if (rowX + item->width() > positioner->width()) {
+ QQuickItem *prevItem = findItem<QQuickItem>(positioner, "wrapper", i-1);
+ QVERIFY(prevItem);
+ rowX = 0;
+ rowY = prevItem->y() + prevItem->height();
+ }
+ QCOMPARE(item->x(), rowX);
+ QCOMPARE(item->y(), rowY);
+ rowX += item->width();
+ } else {
+ QVERIFY2(false, "Unknown positioner type");
+ }
+ QQuickText *name = findItem<QQuickText>(positioner, "name", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model->name(i));
+
+ padding += i * incrementalSize;
+ currentSize += incrementalSize;
+ }
+}
+
void tst_qquickpositioners::test_vertical()
{
QQuickView *canvas = createView(testFile("vertical.qml"));
@@ -1466,6 +1884,43 @@ QQuickView *tst_qquickpositioners::createView(const QString &filename, bool wait
return canvas;
}
+void tst_qquickpositioners::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
+{
+ for (int i=0; i<indexLists.count(); i++) {
+ QSet<int> current = indexLists[i].value<QList<int> >().toSet();
+ if (current != expectedIndexes.toSet())
+ qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
+ QCOMPARE(current, expectedIndexes.toSet());
+ }
+}
+
+void tst_qquickpositioners::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
+{
+ for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
+ QVERIFY(it.value().type() == QVariant::Int);
+ QString name = it.key();
+ int itemIndex = it.value().toInt();
+ QVERIFY2(expectedIndexes.contains(itemIndex), QTest::toString(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
+ if (model.name(itemIndex) != name)
+ qDebug() << itemIndex;
+ QCOMPARE(model.name(itemIndex), name);
+ }
+ QCOMPARE(items.count(), expectedIndexes.count());
+}
+
+void tst_qquickpositioners::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
+{
+ for (int i=0; i<itemLists.count(); i++) {
+ QVERIFY(itemLists[i].type() == QVariant::List);
+ QVariantList current = itemLists[i].toList();
+ for (int j=0; j<current.count(); j++) {
+ QQuickItem *o = qobject_cast<QQuickItem*>(current[j].value<QObject*>());
+ QVERIFY2(o, QTest::toString(QString("Invalid actual item at %1").arg(j)));
+ QVERIFY2(expectedItems.contains(o), QTest::toString(QString("Cannot match item %1").arg(j)));
+ }
+ QCOMPARE(current.count(), expectedItems.count());
+ }
+}
QTEST_MAIN(tst_qquickpositioners)
diff --git a/tests/auto/qtquick2/shared/viewtestutil.h b/tests/auto/qtquick2/shared/viewtestutil.h
index ebee1787d3..9833fcd5cc 100644
--- a/tests/auto/qtquick2/shared/viewtestutil.h
+++ b/tests/auto/qtquick2/shared/viewtestutil.h
@@ -174,6 +174,7 @@ namespace QQuickViewTestUtil
};
}
+Q_DECLARE_METATYPE(QQuickViewTestUtil::QaimModel*)
Q_DECLARE_METATYPE(QQuickViewTestUtil::ListChange)
Q_DECLARE_METATYPE(QList<QQuickViewTestUtil::ListChange>)
Q_DECLARE_METATYPE(QQuickViewTestUtil::ListRange)