1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickitemviewtransition_p.h"
41#include <QtQuick/qquickitem.h>
42#include <QtQuick/private/qquicktransition_p.h>
43#include <QtQuick/private/qquicktransitionmanager_p_p.h>
44
45QT_BEGIN_NAMESPACE
46
47static QList<int> qquickitemviewtransition_emptyIndexes = QList<int>();
48static QList<QObject *> qquickitemviewtransition_emptyTargets = QList<QObject *>();
49
50
51class QQuickItemViewTransitionJob : public QQuickTransitionManager
52{
53public:
54 QQuickItemViewTransitionJob();
55 ~QQuickItemViewTransitionJob();
56
57 void startTransition(QQuickItemViewTransitionableItem *item, int index, QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, const QPointF &to, bool isTargetItem);
58
59 QQuickItemViewTransitioner *m_transitioner;
60 QQuickItemViewTransitionableItem *m_item;
61 QPointF m_toPos;
62 QQuickItemViewTransitioner::TransitionType m_type;
63 bool m_isTarget;
64
65protected:
66 void finished() override;
67};
68
69
70QQuickItemViewTransitionJob::QQuickItemViewTransitionJob()
71 : m_transitioner(nullptr)
72 , m_item(nullptr)
73 , m_type(QQuickItemViewTransitioner::NoTransition)
74 , m_isTarget(false)
75{
76}
77
78QQuickItemViewTransitionJob::~QQuickItemViewTransitionJob()
79{
80 if (m_transitioner)
81 m_transitioner->runningJobs.remove(value: this);
82}
83
84void QQuickItemViewTransitionJob::startTransition(QQuickItemViewTransitionableItem *item, int index, QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, const QPointF &to, bool isTargetItem)
85{
86 if (type == QQuickItemViewTransitioner::NoTransition)
87 return;
88 if (!item) {
89 qWarning(msg: "startTransition(): invalid item");
90 return;
91 }
92 if (!transitioner) {
93 qWarning(msg: "startTransition(): invalid transitioner");
94 return;
95 }
96
97 QQuickTransition *trans = transitioner->transitionObject(type, asTarget: isTargetItem);
98 if (!trans) {
99 qWarning(msg: "QQuickItemView: invalid view transition!");
100 return;
101 }
102
103 m_item = item;
104 m_transitioner = transitioner;
105 m_toPos = to;
106 m_type = type;
107 m_isTarget = isTargetItem;
108
109 QQuickViewTransitionAttached *attached =
110 static_cast<QQuickViewTransitionAttached*>(qmlAttachedPropertiesObject<QQuickViewTransitionAttached>(obj: trans));
111 if (attached) {
112 attached->m_index = index;
113 attached->m_item = item->item;
114 attached->m_destination = to;
115 attached->m_targetIndexes = m_transitioner->targetIndexes(type);
116 attached->m_targetItems = m_transitioner->targetItems(type);
117 emit attached->indexChanged();
118 emit attached->itemChanged();
119 emit attached->destinationChanged();
120 emit attached->targetIndexesChanged();
121 emit attached->targetItemsChanged();
122 }
123
124 QQuickStateOperation::ActionList actions;
125 actions << QQuickStateAction(item->item, QLatin1String("x"), QVariant(to.x()));
126 actions << QQuickStateAction(item->item, QLatin1String("y"), QVariant(to.y()));
127
128 actions[0].fromValue = item->itemX();
129 actions[1].fromValue = item->itemY();
130 m_transitioner->runningJobs << this;
131 QQuickTransitionManager::transition(actions, transition: trans, defaultTarget: item->item);
132}
133
134void QQuickItemViewTransitionJob::finished()
135{
136 QQuickTransitionManager::finished();
137
138 if (m_transitioner) {
139 RETURN_IF_DELETED(m_transitioner->finishedTransition(this, m_item));
140 m_transitioner = nullptr;
141 }
142
143 m_item = nullptr;
144 m_toPos.setX(0);
145 m_toPos.setY(0);
146 m_type = QQuickItemViewTransitioner::NoTransition;
147 m_isTarget = false;
148}
149
150
151QQuickItemViewTransitioner::QQuickItemViewTransitioner()
152 : populateTransition(nullptr)
153 , addTransition(nullptr), addDisplacedTransition(nullptr)
154 , moveTransition(nullptr), moveDisplacedTransition(nullptr)
155 , removeTransition(nullptr), removeDisplacedTransition(nullptr)
156 , displacedTransition(nullptr)
157 , changeListener(nullptr)
158 , usePopulateTransition(false)
159{
160}
161
162QQuickItemViewTransitioner::~QQuickItemViewTransitioner()
163{
164 typedef QSet<QQuickItemViewTransitionJob *>::iterator JobIt;
165
166 for (JobIt it = runningJobs.begin(), end = runningJobs.end(); it != end; ++it)
167 (*it)->m_transitioner = nullptr;
168}
169
170bool QQuickItemViewTransitioner::canTransition(QQuickItemViewTransitioner::TransitionType type, bool asTarget) const
171{
172 if (!asTarget
173 && type != NoTransition && type != PopulateTransition
174 && displacedTransition && displacedTransition->enabled()) {
175 return true;
176 }
177
178 switch (type) {
179 case NoTransition:
180 break;
181 case PopulateTransition:
182 return usePopulateTransition
183 && populateTransition && populateTransition->enabled();
184 case AddTransition:
185 if (asTarget)
186 return addTransition && addTransition->enabled();
187 else
188 return addDisplacedTransition && addDisplacedTransition->enabled();
189 case MoveTransition:
190 if (asTarget)
191 return moveTransition && moveTransition->enabled();
192 else
193 return moveDisplacedTransition && moveDisplacedTransition->enabled();
194 case RemoveTransition:
195 if (asTarget)
196 return removeTransition && removeTransition->enabled();
197 else
198 return removeDisplacedTransition && removeDisplacedTransition->enabled();
199 }
200 return false;
201}
202
203void QQuickItemViewTransitioner::transitionNextReposition(QQuickItemViewTransitionableItem *item, QQuickItemViewTransitioner::TransitionType type, bool isTarget)
204{
205 item->setNextTransition(type, isTargetItem: isTarget);
206}
207
208void QQuickItemViewTransitioner::addToTargetLists(QQuickItemViewTransitioner::TransitionType type, QQuickItemViewTransitionableItem *item, int index)
209{
210 switch (type) {
211 case NoTransition:
212 break;
213 case PopulateTransition:
214 case AddTransition:
215 addTransitionIndexes << index;
216 addTransitionTargets << item->item;
217 break;
218 case MoveTransition:
219 moveTransitionIndexes << index;
220 moveTransitionTargets << item->item;
221 break;
222 case RemoveTransition:
223 removeTransitionIndexes << index;
224 removeTransitionTargets << item->item;
225 break;
226 }
227}
228
229void QQuickItemViewTransitioner::resetTargetLists()
230{
231 addTransitionIndexes.clear();
232 addTransitionTargets.clear();
233
234 removeTransitionIndexes.clear();
235 removeTransitionTargets.clear();
236
237 moveTransitionIndexes.clear();
238 moveTransitionTargets.clear();
239}
240
241QQuickTransition *QQuickItemViewTransitioner::transitionObject(QQuickItemViewTransitioner::TransitionType type, bool asTarget) const
242{
243 if (type == QQuickItemViewTransitioner::NoTransition)
244 return nullptr;
245
246 if (type == PopulateTransition)
247 asTarget = true; // no separate displaced transition
248
249 QQuickTransition *trans = nullptr;
250 switch (type) {
251 case NoTransition:
252 break;
253 case PopulateTransition:
254 trans = populateTransition;
255 break;
256 case AddTransition:
257 trans = asTarget ? addTransition : addDisplacedTransition;
258 break;
259 case MoveTransition:
260 trans = asTarget ? moveTransition : moveDisplacedTransition;
261 break;
262 case RemoveTransition:
263 trans = asTarget ? removeTransition : removeDisplacedTransition;
264 break;
265 }
266
267 if (!asTarget && (!trans || !trans->enabled()))
268 trans = displacedTransition;
269 if (trans && trans->enabled())
270 return trans;
271 return nullptr;
272}
273
274const QList<int> &QQuickItemViewTransitioner::targetIndexes(QQuickItemViewTransitioner::TransitionType type) const
275{
276 switch (type) {
277 case NoTransition:
278 break;
279 case PopulateTransition:
280 case AddTransition:
281 return addTransitionIndexes;
282 case MoveTransition:
283 return moveTransitionIndexes;
284 case RemoveTransition:
285 return removeTransitionIndexes;
286 }
287
288 return qquickitemviewtransition_emptyIndexes;
289}
290
291const QList<QObject *> &QQuickItemViewTransitioner::targetItems(QQuickItemViewTransitioner::TransitionType type) const
292{
293 switch (type) {
294 case NoTransition:
295 break;
296 case PopulateTransition:
297 case AddTransition:
298 return addTransitionTargets;
299 case MoveTransition:
300 return moveTransitionTargets;
301 case RemoveTransition:
302 return removeTransitionTargets;
303 }
304
305 return qquickitemviewtransition_emptyTargets;
306}
307
308void QQuickItemViewTransitioner::finishedTransition(QQuickItemViewTransitionJob *job, QQuickItemViewTransitionableItem *item)
309{
310 if (!runningJobs.contains(value: job))
311 return;
312 runningJobs.remove(value: job);
313 if (item) {
314 item->finishedTransition();
315 if (changeListener)
316 changeListener->viewItemTransitionFinished(item);
317 }
318}
319
320
321QQuickItemViewTransitionableItem::QQuickItemViewTransitionableItem(QQuickItem *i)
322 : item(i)
323 , transition(nullptr)
324 , nextTransitionType(QQuickItemViewTransitioner::NoTransition)
325 , isTransitionTarget(false)
326 , nextTransitionToSet(false)
327 , nextTransitionFromSet(false)
328 , lastMovedToSet(false)
329 , prepared(false)
330{
331}
332
333QQuickItemViewTransitionableItem::~QQuickItemViewTransitionableItem()
334{
335 delete transition;
336}
337
338qreal QQuickItemViewTransitionableItem::itemX() const
339{
340 if (nextTransitionType != QQuickItemViewTransitioner::NoTransition)
341 return nextTransitionToSet ? nextTransitionTo.x() : item->x();
342 else if (transition && transition->isRunning())
343 return transition->m_toPos.x();
344 else
345 return item->x();
346}
347
348qreal QQuickItemViewTransitionableItem::itemY() const
349{
350 // If item is transitioning to some pos, return that dest pos.
351 // If item was redirected to some new pos before the current transition finished,
352 // return that new pos.
353 if (nextTransitionType != QQuickItemViewTransitioner::NoTransition)
354 return nextTransitionToSet ? nextTransitionTo.y() : item->y();
355 else if (transition && transition->isRunning())
356 return transition->m_toPos.y();
357 else
358 return item->y();
359}
360
361void QQuickItemViewTransitionableItem::moveTo(const QPointF &pos, bool immediate)
362{
363 if (!nextTransitionFromSet && nextTransitionType != QQuickItemViewTransitioner::NoTransition) {
364 nextTransitionFrom = item->position();
365 nextTransitionFromSet = true;
366 }
367
368 lastMovedTo = pos;
369 lastMovedToSet = true;
370
371 if (immediate || !transitionScheduledOrRunning()) {
372 if (immediate)
373 stopTransition();
374 item->setPosition(pos);
375 } else {
376 nextTransitionTo = pos;
377 nextTransitionToSet = true;
378 }
379}
380
381bool QQuickItemViewTransitionableItem::transitionScheduledOrRunning() const
382{
383 return (transition && transition->isRunning())
384 || nextTransitionType != QQuickItemViewTransitioner::NoTransition;
385}
386
387bool QQuickItemViewTransitionableItem::transitionRunning() const
388{
389 return (transition && transition->isRunning());
390}
391
392bool QQuickItemViewTransitionableItem::isPendingRemoval() const
393{
394 if (nextTransitionType == QQuickItemViewTransitioner::RemoveTransition)
395 return isTransitionTarget;
396 if (transition && transition->isRunning() && transition->m_type == QQuickItemViewTransitioner::RemoveTransition)
397 return transition->m_isTarget;
398 return false;
399}
400
401bool QQuickItemViewTransitionableItem::prepareTransition(QQuickItemViewTransitioner *transitioner, int index, const QRectF &viewBounds)
402{
403 if (nextTransitionType == QQuickItemViewTransitioner::NoTransition)
404 return false;
405
406 if (isTransitionTarget) {
407 // If item is not already moving somewhere, set it to not move anywhere.
408 // This ensures that removed targets don't transition to the default (0,0) and that
409 // items set for other transition types only transition if they actually move somewhere.
410 if (!nextTransitionToSet)
411 moveTo(pos: item->position());
412 } else {
413 // don't start displaced transitions that don't move anywhere
414 if (!nextTransitionToSet || (nextTransitionFromSet && nextTransitionFrom == nextTransitionTo)) {
415 clearCurrentScheduledTransition();
416 return false;
417 }
418 }
419
420 bool doTransition = false;
421
422 // For move transitions (both target and displaced) and displaced transitions of other
423 // types, only run the transition if the item is actually moving to another position.
424 switch (nextTransitionType) {
425 case QQuickItemViewTransitioner::NoTransition:
426 {
427 return false;
428 }
429 case QQuickItemViewTransitioner::PopulateTransition:
430 {
431 doTransition = viewBounds.intersects(r: QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()));
432 break;
433 }
434 case QQuickItemViewTransitioner::AddTransition:
435 case QQuickItemViewTransitioner::RemoveTransition:
436 if (viewBounds.isNull()) {
437 if (isTransitionTarget)
438 doTransition = true;
439 else
440 doTransition = transitionWillChangePosition();
441 } else if (isTransitionTarget) {
442 // For Add targets, do transition if item is moving into visible area
443 // For Remove targets, do transition if item is currently in visible area
444 doTransition = (nextTransitionType == QQuickItemViewTransitioner::AddTransition)
445 ? viewBounds.intersects(r: QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))
446 : viewBounds.intersects(r: QRectF(item->x(), item->y(), item->width(), item->height()));
447 } else {
448 // do transition if moving from or into visible area
449 if (viewBounds.intersects(r: QRectF(item->x(), item->y(), item->width(), item->height()))
450 || viewBounds.intersects(r: QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))) {
451 doTransition = transitionWillChangePosition();
452 }
453 }
454 break;
455 case QQuickItemViewTransitioner::MoveTransition:
456 // do transition if moving from or into visible area
457 if (transitionWillChangePosition()) {
458 doTransition = viewBounds.isNull()
459 || viewBounds.intersects(r: QRectF(item->x(), item->y(), item->width(), item->height()))
460 || viewBounds.intersects(r: QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()));
461 }
462 break;
463 }
464
465 if (doTransition) {
466 // add item to target lists even if canTransition() is false for a target transition,
467 // since the target lists still need to be filled for displaced transitions
468 if (isTransitionTarget)
469 transitioner->addToTargetLists(type: nextTransitionType, item: this, index);
470 doTransition = transitioner->canTransition(type: nextTransitionType, asTarget: isTransitionTarget);
471 }
472
473 if (!doTransition) {
474 // if transition type is not valid, the previous transition still has to be
475 // canceled so that the item can move immediately to the right position
476 item->setPosition(nextTransitionTo);
477 ACTION_IF_DELETED(this, stopTransition(), return false);
478 }
479
480 prepared = true;
481 return doTransition;
482}
483
484void QQuickItemViewTransitionableItem::startTransition(QQuickItemViewTransitioner *transitioner, int index)
485{
486 if (nextTransitionType == QQuickItemViewTransitioner::NoTransition)
487 return;
488
489 if (!prepared) {
490 qWarning(msg: "QQuickViewItem::prepareTransition() not called!");
491 return;
492 }
493
494 if (!transition || transition->m_type != nextTransitionType || transition->m_isTarget != isTransitionTarget) {
495 if (transition)
496 RETURN_IF_DELETED(transition->cancel());
497 delete transition;
498 transition = new QQuickItemViewTransitionJob;
499 }
500
501 RETURN_IF_DELETED(transition->startTransition(this, index, transitioner, nextTransitionType, nextTransitionTo, isTransitionTarget));
502 clearCurrentScheduledTransition();
503}
504
505void QQuickItemViewTransitionableItem::setNextTransition(QQuickItemViewTransitioner::TransitionType type, bool isTargetItem)
506{
507 // Don't reset nextTransitionToSet - once it is set, it cannot be changed
508 // until the animation finishes since the itemX() and itemY() may be used
509 // to calculate positions for transitions for other items in the view.
510 nextTransitionType = type;
511 isTransitionTarget = isTargetItem;
512
513 if (!nextTransitionFromSet && lastMovedToSet) {
514 nextTransitionFrom = lastMovedTo;
515 nextTransitionFromSet = true;
516 }
517}
518
519bool QQuickItemViewTransitionableItem::transitionWillChangePosition() const
520{
521 if (transitionRunning() && transition->m_toPos != nextTransitionTo)
522 return true;
523 if (!nextTransitionFromSet)
524 return false;
525 return nextTransitionTo != nextTransitionFrom;
526}
527
528void QQuickItemViewTransitionableItem::resetNextTransitionPos()
529{
530 nextTransitionToSet = false;
531 nextTransitionTo = QPointF();
532}
533
534void QQuickItemViewTransitionableItem::finishedTransition()
535{
536 resetNextTransitionPos();
537}
538
539void QQuickItemViewTransitionableItem::clearCurrentScheduledTransition()
540{
541 // Just clear the current scheduled transition - don't touch the nextTransitionTo
542 // which may have already been set for a previously scheduled transition
543
544 nextTransitionType = QQuickItemViewTransitioner::NoTransition;
545 isTransitionTarget = false;
546 prepared = false;
547 nextTransitionFromSet = false;
548}
549
550void QQuickItemViewTransitionableItem::stopTransition()
551{
552 if (transition)
553 RETURN_IF_DELETED(transition->cancel());
554 clearCurrentScheduledTransition();
555 resetNextTransitionPos();
556}
557
558
559QQuickViewTransitionAttached::QQuickViewTransitionAttached(QObject *parent)
560 : QObject(parent), m_index(-1)
561{
562}
563
564QQuickItem *QQuickViewTransitionAttached::item() const
565{
566 return m_item.data();
567}
568
569/*!
570 \qmltype ViewTransition
571 \instantiates QQuickViewTransitionAttached
572 \inqmlmodule QtQuick
573 \ingroup qtquick-transitions-animations
574 \brief Specifies items under transition in a view.
575
576 With ListView and GridView, it is possible to specify transitions that should be applied whenever
577 the items in the view change as a result of modifications to the view's model. They both have the
578 following properties that can be set to the appropriate transitions to be run for various
579 operations:
580
581 \list
582 \li \c populate - the transition to apply to the items created initially for the view, or when the model changes
583 \li \c add - the transition to apply to items that are added to the view after it has been created
584 \li \c remove - the transition to apply to items that are removed from the view
585 \li \c move - the transition to apply to items that are moved within the view (i.e. as a result
586 of a move operation in the model)
587 \li \c displaced - the generic transition to be applied to any items that are displaced by an
588 add, move or remove operation
589 \li \c addDisplaced, \c removeDisplaced and \c moveDisplaced - the transitions to be applied when
590 items are displaced by add, move, or remove operations, respectively (these override the
591 generic displaced transition if specified)
592 \endlist
593
594 For the \l Row, \l Column, \l Grid and \l Flow positioner types, which operate with collections of child
595 items rather than data models, the following properties are used instead:
596
597 \list
598 \li \c populate - the transition to apply to items that have been added to the positioner at the
599 time of its creation
600 \li \c add - the transition to apply to items that are added to
601 or reparented to the positioner, or items that have become \l {Item::}{visible}
602 \li \c move - the transition to apply to items that have moved within the positioner, including
603 when they are displaced due to the addition or removal of other items, or when items are otherwise
604 rearranged within the positioner, or when items are repositioned due to the resizing of other
605 items in the positioner
606 \endlist
607
608 View transitions have access to a ViewTransition attached property that
609 provides details of the items that are under transition and the operation that triggered the
610 transition. Since view transitions are run once per item, these details can be used to customize
611 each transition for each individual item.
612
613 The ViewTransition attached property provides the following properties specific to the item to
614 which the transition is applied:
615
616 \list
617 \li ViewTransition.item - the item that is under transition
618 \li ViewTransition.index - the index of this item
619 \li ViewTransition.destination - the (x,y) point to which this item is moving for the relevant view operation
620 \endlist
621
622 In addition, ViewTransition provides properties specific to the items which are the target
623 of the operation that triggered the transition:
624
625 \list
626 \li ViewTransition.targetIndexes - the indexes of the target items
627 \li ViewTransition.targetItems - the target items themselves
628 \endlist
629
630 (Note that for the \l Row, \l Column, \l Grid and \l Flow positioner types, the \c move transition only
631 provides these two additional details when the transition is triggered by the addition of items
632 to a positioner.)
633
634 View transitions can be written without referring to any of the attributes listed
635 above. These attributes merely provide extra details that are useful for customising view
636 transitions.
637
638 Following is an introduction to view transitions and the ways in which the ViewTransition
639 attached property can be used to augment view transitions.
640
641
642 \section2 View Transitions: a Simple Example
643
644 Here is a basic example of the use of view transitions. The view below specifies transitions for
645 the \c add and \c displaced properties, which will be run when items are added to the view:
646
647 \snippet qml/viewtransitions/viewtransitions-basic.qml 0
648
649 When the space key is pressed, adding an item to the model, the new item will fade in and
650 increase in scale over 400 milliseconds as it is added to the view. Also, any item that is
651 displaced by the addition of a new item will animate to its new position in the view over
652 400 milliseconds, as specified by the \c displaced transition.
653
654 If five items were inserted in succession at index 0, the effect would be this:
655
656 \image viewtransitions-basic.gif
657
658 Notice that the NumberAnimation objects above do not need to specify a \c target to animate
659 the appropriate item. Also, the NumberAnimation in the \c addTransition does not need to specify
660 the \c to value to move the item to its correct position in the view. This is because the view
661 implicitly sets the \c target and \c to values with the correct item and final item position
662 values if these properties are not explicitly defined.
663
664 At its simplest, a view transition may just animate an item to its new position following a
665 view operation, just as the \c displaced transition does above, or animate some item properties,
666 as in the \c add transition above. Additionally, a view transition may make use of the
667 ViewTransition attached property to customize animation behavior for different items. Following
668 are some examples of how this can be achieved.
669
670
671 \section2 Using the ViewTransition Attached Property
672
673 As stated, the various ViewTransition properties provide details specific to the individual item
674 being transitioned as well as the operation that triggered the transition. In the animation above,
675 five items are inserted in succession at index 0. When the fifth and final insertion takes place,
676 adding "Item 4" to the view, the \c add transition is run once (for the inserted item) and the
677 \c displaced transition is run four times (once for each of the four existing items in the view).
678
679 At this point, if we examined the \c displaced transition that was run for the bottom displaced
680 item ("Item 0"), the ViewTransition property values provided to this transition would be as follows:
681
682 \table
683 \header
684 \li Property
685 \li Value
686 \li Explanation
687 \row
688 \li ViewTransition.item
689 \li "Item 0" delegate instance
690 \li The "Item 0" \l Rectangle object itself
691 \row
692 \li ViewTransition.index
693 \li \c int value of 4
694 \li The index of "Item 0" within the model following the add operation
695 \row
696 \li ViewTransition.destination
697 \li \l point value of (0, 120)
698 \li The position that "Item 0" is moving to
699 \row
700 \li ViewTransition.targetIndexes
701 \li \c int array, just contains the integer "0" (zero)
702 \li The index of "Item 4", the new item added to the view
703 \row
704 \li ViewTransition.targetItems
705 \li object array, just contains the "Item 4" delegate instance
706 \li The "Item 4" \l Rectangle object - the new item added to the view
707 \endtable
708
709 The ViewTransition.targetIndexes and ViewTransition.targetItems lists provide the items and
710 indexes of all delegate instances that are the targets of the relevant operation. For an add
711 operation, these are all the items that are added into the view; for a remove, these are all
712 the items removed from the view, and so on. (Note these lists will only contain references to
713 items that have been created within the view or its cached items; targets that are not within
714 the visible area of the view or within the item cache will not be accessible.)
715
716 So, while the ViewTransition.item, ViewTransition.index and ViewTransition.destination values
717 vary for each individual transition that is run, the ViewTransition.targetIndexes and
718 ViewTransition.targetItems values are the same for every \c add and \c displaced transition
719 that is triggered by a particular add operation.
720
721
722 \section3 Delaying Animations Based on Index
723
724 Since each view transition is run once for each item affected by the transition, the ViewTransition
725 properties can be used within a transition to define custom behavior for each item's transition.
726 For example, the ListView in the previous example could use this information to create a ripple-type
727 effect on the movement of the displaced items.
728
729 This can be achieved by modifying the \c displaced transition so that it delays the animation of
730 each displaced item based on the difference between its index (provided by ViewTransition.index)
731 and the first removed index (provided by ViewTransition.targetIndexes):
732
733 \snippet qml/viewtransitions/viewtransitions-delayedbyindex.qml 0
734
735 Each displaced item delays its animation by an additional 100 milliseconds, producing a subtle
736 ripple-type effect when items are displaced by the add, like this:
737
738 \image viewtransitions-delayedbyindex.gif
739
740
741 \section3 Animating Items to Intermediate Positions
742
743 The ViewTransition.item property gives a reference to the item to which the transition is being
744 applied. This can be used to access any of the item's attributes, custom \c property values,
745 and so on.
746
747 Below is a modification of the \c displaced transition from the previous example. It adds a
748 ParallelAnimation with nested NumberAnimation objects that reference ViewTransition.item to access
749 each item's \c x and \c y values at the start of their transitions. This allows each item to
750 animate to an intermediate position relative to its starting point for the transition, before
751 animating to its final position in the view:
752
753 \snippet qml/viewtransitions/viewtransitions-intermediatemove.qml 0
754
755 Now, a displaced item will first move to a position of (20, 50) relative to its starting
756 position, and then to its final, correct position in the view:
757
758 \image viewtransitions-intermediatemove.gif
759
760 Since the final NumberAnimation does not specify a \c to value, the view implicitly sets this
761 value to the item's final position in the view, and so this last animation will move this item
762 to the correct place. If the transition requires the final position of the item for some calculation,
763 this is accessible through ViewTransition.destination.
764
765 Instead of using multiple NumberAnimations, you could use a PathAnimation to animate an item over
766 a curved path. For example, the \c add transition in the previous example could be augmented with
767 a PathAnimation as follows: to animate newly added items along a path:
768
769 \snippet qml/viewtransitions/viewtransitions-pathanim.qml 0
770
771 This animates newly added items along a path. Notice that each path is specified relative to
772 each item's final destination point, so that items inserted at different indexes start their
773 paths from different positions:
774
775 \image viewtransitions-pathanim.gif
776
777
778 \section2 Handling Interrupted Animations
779
780 A view transition may be interrupted at any time if a different view transition needs to be
781 applied while the original transition is in progress. For example, say Item A is inserted at index 0
782 and undergoes an "add" transition; then, Item B is inserted at index 0 in quick succession before
783 Item A's transition has finished. Since Item B is inserted before Item A, it will displace Item
784 A, causing the view to interrupt Item A's "add" transition mid-way and start a "displaced"
785 transition on Item A instead.
786
787 For simple animations that simply animate an item's movement to its final destination, this
788 interruption is unlikely to require additional consideration. However, if a transition changes other
789 properties, this interruption may cause unwanted side effects. Consider the first example on this
790 page, repeated below for convenience:
791
792 \snippet qml/viewtransitions/viewtransitions-basic.qml 0
793
794 If multiple items are added in rapid succession, without waiting for a previous transition
795 to finish, this is the result:
796
797 \image viewtransitions-interruptedbad.gif
798
799 Each newly added item undergoes an \c add transition, but before the transition can finish,
800 another item is added, displacing the previously added item. Because of this, the \c add
801 transition on the previously added item is interrupted and a \c displaced transition is
802 started on the item instead. Due to the interruption, the \c opacity and \c scale animations
803 have not completed, thus producing items with opacity and scale that are below 1.0.
804
805 To fix this, the \c displaced transition should additionally ensure the item properties are
806 set to the end values specified in the \c add transition, effectively resetting these values
807 whenever an item is displaced. In this case, it means setting the item opacity and scale to 1.0:
808
809 \snippet qml/viewtransitions/viewtransitions-interruptedgood.qml 0
810
811 Now, when an item's \c add transition is interrupted, its opacity and scale are animated to 1.0
812 upon displacement, avoiding the erroneous visual effects from before:
813
814 \image viewtransitions-interruptedgood.gif
815
816 The same principle applies to any combination of view transitions. An added item may be moved
817 before its add transition finishes, or a moved item may be removed before its moved transition
818 finishes, and so on; so, the rule of thumb is that every transition should handle the same set of
819 properties.
820
821
822 \section2 Restrictions Regarding ScriptAction
823
824 When a view transition is initialized, any property bindings that refer to the ViewTransition
825 attached property are evaluated in preparation for the transition. Due to the nature of the
826 internal construction of a view transition, the attributes of the ViewTransition attached
827 property are only valid for the relevant item when the transition is initialized, and may not be
828 valid when the transition is actually run.
829
830 Therefore, a ScriptAction within a view transition should not refer to the ViewTransition
831 attached property, as it may not refer to the expected values at the time that the ScriptAction
832 is actually invoked. Consider the following example:
833
834 \snippet qml/viewtransitions/viewtransitions-scriptactionbad.qml 0
835
836 When the space key is pressed, three items are moved from index 5 to index 1. For each moved
837 item, the \c moveTransition sequence presumably animates the item's color to "yellow", then
838 animates it to its final position, then changes the item color back to "lightsteelblue" using a
839 ScriptAction. However, when run, the transition does not produce the intended result:
840
841 \image viewtransitions-scriptactionbad.gif
842
843 Only the last moved item is returned to the "lightsteelblue" color; the others remain yellow. This
844 is because the ScriptAction is not run until after the transition has already been initialized, by
845 which time the ViewTransition.item value has changed to refer to a different item; the item that
846 the script had intended to refer to is not the one held by ViewTransition.item at the time the
847 ScriptAction is actually invoked.
848
849 In this instance, to avoid this issue, the view could set the property using a PropertyAction
850 instead:
851
852 \snippet qml/viewtransitions/viewtransitions-scriptactiongood.qml 0
853
854 When the transition is initialized, the PropertyAction \c target will be set to the respective
855 ViewTransition.item for the transition and will later run with the correct item target as
856 expected.
857 */
858
859/*!
860 \qmlattachedproperty int QtQuick::ViewTransition::index
861
862 This attached property holds the index of the item that is being
863 transitioned.
864
865 Note that if the item is being moved, this property holds the index that
866 the item is moving to, not from.
867*/
868
869/*!
870 \qmlattachedproperty item QtQuick::ViewTransition::item
871
872 This attached property holds the item that is being transitioned.
873
874 \warning This item should not be kept and referred to outside of the transition
875 as it may become invalid as the view changes.
876*/
877
878/*!
879 \qmlattachedproperty point QtQuick::ViewTransition::destination
880
881 This attached property holds the final destination position for the transitioned
882 item within the view.
883
884 This property value is a \l point with \c x and \c y properties.
885*/
886
887/*!
888 \qmlattachedproperty list QtQuick::ViewTransition::targetIndexes
889
890 This attached property holds a list of the indexes of the items in view
891 that are the target of the relevant operation.
892
893 The targets are the items that are the subject of the operation. For
894 an add operation, these are the items being added; for a remove, these
895 are the items being removed; for a move, these are the items being
896 moved.
897
898 For example, if the transition was triggered by an insert operation
899 that added two items at index 1 and 2, this targetIndexes list would
900 have the value [1,2].
901
902 \note The targetIndexes list only contains the indexes of items that are actually
903 in view, or will be in the view once the relevant operation completes.
904
905 \sa QtQuick::ViewTransition::targetItems
906*/
907
908/*!
909 \qmlattachedproperty list QtQuick::ViewTransition::targetItems
910
911 This attached property holds the list of items in view that are the
912 target of the relevant operation.
913
914 The targets are the items that are the subject of the operation. For
915 an add operation, these are the items being added; for a remove, these
916 are the items being removed; for a move, these are the items being
917 moved.
918
919 For example, if the transition was triggered by an insert operation
920 that added two items at index 1 and 2, this targetItems list would
921 contain these two items.
922
923 \note The targetItems list only contains items that are actually
924 in view, or will be in the view once the relevant operation completes.
925
926 \warning The objects in this list should not be kept and referred to
927 outside of the transition as the items may become invalid. The targetItems
928 are only valid when the Transition is initially created; this also means
929 they should not be used by ScriptAction objects in the Transition, which are
930 not evaluated until the transition is run.
931
932 \sa QtQuick::ViewTransition::targetIndexes
933*/
934QQmlListProperty<QObject> QQuickViewTransitionAttached::targetItems()
935{
936 return QQmlListProperty<QObject>(this, &m_targetItems);
937}
938
939QQuickViewTransitionAttached *QQuickViewTransitionAttached::qmlAttachedProperties(QObject *obj)
940{
941 return new QQuickViewTransitionAttached(obj);
942}
943
944QT_END_NAMESPACE
945
946#include "moc_qquickitemviewtransition_p.cpp"
947

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