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 | |
45 | QT_BEGIN_NAMESPACE |
46 | |
47 | static QList<int> qquickitemviewtransition_emptyIndexes = QList<int>(); |
48 | static QList<QObject *> qquickitemviewtransition_emptyTargets = QList<QObject *>(); |
49 | |
50 | |
51 | class QQuickItemViewTransitionJob : public QQuickTransitionManager |
52 | { |
53 | public: |
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 | |
65 | protected: |
66 | void finished() override; |
67 | }; |
68 | |
69 | |
70 | QQuickItemViewTransitionJob::QQuickItemViewTransitionJob() |
71 | : m_transitioner(nullptr) |
72 | , m_item(nullptr) |
73 | , m_type(QQuickItemViewTransitioner::NoTransition) |
74 | , m_isTarget(false) |
75 | { |
76 | } |
77 | |
78 | QQuickItemViewTransitionJob::~QQuickItemViewTransitionJob() |
79 | { |
80 | if (m_transitioner) |
81 | m_transitioner->runningJobs.remove(value: this); |
82 | } |
83 | |
84 | void 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 | |
134 | void 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 | |
151 | QQuickItemViewTransitioner::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 | |
162 | QQuickItemViewTransitioner::~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 | |
170 | bool 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 | |
203 | void QQuickItemViewTransitioner::transitionNextReposition(QQuickItemViewTransitionableItem *item, QQuickItemViewTransitioner::TransitionType type, bool isTarget) |
204 | { |
205 | item->setNextTransition(type, isTargetItem: isTarget); |
206 | } |
207 | |
208 | void 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 | |
229 | void 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 | |
241 | QQuickTransition *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 | |
274 | const 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 | |
291 | const 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 | |
308 | void 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 | |
321 | QQuickItemViewTransitionableItem::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 | |
333 | QQuickItemViewTransitionableItem::~QQuickItemViewTransitionableItem() |
334 | { |
335 | delete transition; |
336 | } |
337 | |
338 | qreal 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 | |
348 | qreal 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 | |
361 | void 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 | |
381 | bool QQuickItemViewTransitionableItem::transitionScheduledOrRunning() const |
382 | { |
383 | return (transition && transition->isRunning()) |
384 | || nextTransitionType != QQuickItemViewTransitioner::NoTransition; |
385 | } |
386 | |
387 | bool QQuickItemViewTransitionableItem::transitionRunning() const |
388 | { |
389 | return (transition && transition->isRunning()); |
390 | } |
391 | |
392 | bool 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 | |
401 | bool 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 | |
484 | void 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 | |
505 | void 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 | |
519 | bool 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 | |
528 | void QQuickItemViewTransitionableItem::resetNextTransitionPos() |
529 | { |
530 | nextTransitionToSet = false; |
531 | nextTransitionTo = QPointF(); |
532 | } |
533 | |
534 | void QQuickItemViewTransitionableItem::finishedTransition() |
535 | { |
536 | resetNextTransitionPos(); |
537 | } |
538 | |
539 | void 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 | |
550 | void QQuickItemViewTransitionableItem::stopTransition() |
551 | { |
552 | if (transition) |
553 | RETURN_IF_DELETED(transition->cancel()); |
554 | clearCurrentScheduledTransition(); |
555 | resetNextTransitionPos(); |
556 | } |
557 | |
558 | |
559 | QQuickViewTransitionAttached::QQuickViewTransitionAttached(QObject *parent) |
560 | : QObject(parent), m_index(-1) |
561 | { |
562 | } |
563 | |
564 | QQuickItem *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 | */ |
934 | QQmlListProperty<QObject> QQuickViewTransitionAttached::targetItems() |
935 | { |
936 | return QQmlListProperty<QObject>(this, &m_targetItems); |
937 | } |
938 | |
939 | QQuickViewTransitionAttached *QQuickViewTransitionAttached::qmlAttachedProperties(QObject *obj) |
940 | { |
941 | return new QQuickViewTransitionAttached(obj); |
942 | } |
943 | |
944 | QT_END_NAMESPACE |
945 | |
946 | #include "moc_qquickitemviewtransition_p.cpp" |
947 | |