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