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 "qquicktransition_p.h"
5
6#include "qquickstate_p.h"
7#include "qquickstate_p_p.h"
8#include "qquickstatechangescript_p.h"
9#include "qquickanimation_p.h"
10#include "qquickanimation_p_p.h"
11#include "qquicktransitionmanager_p_p.h"
12
13#include <private/qquickanimatorjob_p.h>
14
15#include "private/qparallelanimationgroupjob_p.h"
16
17QT_BEGIN_NAMESPACE
18
19/*!
20 \qmltype Transition
21 \nativetype QQuickTransition
22 \inqmlmodule QtQuick
23 \ingroup qtquick-transitions-animations
24 \brief Defines animated transitions that occur on state changes.
25
26 A Transition defines the animations to be applied when a \l State change occurs.
27
28 For example, the following \l Rectangle has two states: the default state, and
29 an added "moved" state. In the "moved state, the rectangle's position changes
30 to (50, 50). The added Transition specifies that when the rectangle
31 changes between the default and the "moved" state, any changes
32 to the \c x and \c y properties should be animated, using an \c Easing.InOutQuad.
33
34 \snippet qml/transition.qml 0
35
36 Notice the example does not require \l{PropertyAnimation::}{to} and
37 \l{PropertyAnimation::}{from} values for the NumberAnimation. As a convenience,
38 these properties are automatically set to the values of \c x and \c y before
39 and after the state change; the \c from values are provided by
40 the current values of \c x and \c y, and the \c to values are provided by
41 the PropertyChanges object. If you wish, you can provide \l{PropertyAnimation::}{to} and
42 \l{PropertyAnimation::}{from} values anyway to override the default values.
43
44 By default, a Transition's animations are applied for any state change in the
45 parent item. The Transition \l {Transition::}{from} and \l {Transition::}{to}
46 values can be set to restrict the animations to only be applied when changing
47 from one particular state to another.
48
49 Top-level animations within a transition are run in parallel. To run them
50 sequentially, define them within a SequentialAnimation:
51
52 \snippet qml/transition-reversible.qml sequential animations
53
54 To define multiple Transitions, specify \l Item::transitions as a list:
55
56 \snippet qml/transitions-list.qml list of transitions
57
58 If multiple Transitions are specified, only a single (best-matching)
59 Transition will be applied for any particular state change. In the
60 example above, if the Rectangle enters a state other than \c "middleRight"
61 or \c "bottomLeft", the third Transition will be carried out, meaning the
62 icon will be moved to the starting point.
63
64 If a state change has a Transition that matches the same property as a
65 \l Behavior, the Transition animation overrides the \l Behavior for that
66 state change.
67
68 \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, {Qt Qml}
69*/
70
71//ParallelAnimationWrapper allows us to do a "callback" when the animation finishes, rather than connecting
72//and disconnecting signals and slots frequently
73class ParallelAnimationWrapper : public QParallelAnimationGroupJob
74{
75public:
76 ParallelAnimationWrapper() : QParallelAnimationGroupJob() {}
77 QQuickTransitionManager *manager;
78
79protected:
80 void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) override;
81};
82
83class QQuickTransitionPrivate : public QObjectPrivate
84{
85 Q_DECLARE_PUBLIC(QQuickTransition)
86public:
87 QQuickTransitionPrivate()
88 : fromState(QLatin1String("*")), toState(QLatin1String("*"))
89 , runningInstanceCount(0), state(QAbstractAnimationJob::Stopped)
90 , reversed(false), reversible(false), enabled(true)
91 {
92 }
93
94 static QQuickTransitionPrivate *get(QQuickTransition *q) { return q->d_func(); }
95 void animationStateChanged(QAbstractAnimationJob::State newState);
96
97 QString fromState;
98 QString toState;
99 quint32 runningInstanceCount;
100 QAbstractAnimationJob::State state;
101 bool reversed;
102 bool reversible;
103 bool enabled;
104protected:
105
106 static void append_animation(QQmlListProperty<QQuickAbstractAnimation> *list, QQuickAbstractAnimation *a);
107 static qsizetype animation_count(QQmlListProperty<QQuickAbstractAnimation> *list);
108 static QQuickAbstractAnimation* animation_at(QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos);
109 static void clear_animations(QQmlListProperty<QQuickAbstractAnimation> *list);
110 static void removeLast_animation(QQmlListProperty<QQuickAbstractAnimation> *list);
111 static void replace_animation(
112 QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos,
113 QQuickAbstractAnimation *a);
114
115 QList<QPointer<QQuickAbstractAnimation>> animations;
116};
117
118void QQuickTransitionPrivate::append_animation(QQmlListProperty<QQuickAbstractAnimation> *list, QQuickAbstractAnimation *a)
119{
120 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
121 q->d_func()->animations.append(t: a);
122 if (a)
123 a->setDisableUserControl();
124}
125
126qsizetype QQuickTransitionPrivate::animation_count(QQmlListProperty<QQuickAbstractAnimation> *list)
127{
128 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
129 return q->d_func()->animations.size();
130}
131
132QQuickAbstractAnimation* QQuickTransitionPrivate::animation_at(QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos)
133{
134 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
135 return q->d_func()->animations.at(i: pos);
136}
137
138void QQuickTransitionPrivate::clear_animations(QQmlListProperty<QQuickAbstractAnimation> *list)
139{
140 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
141 q->d_func()->animations.clear();
142}
143
144void QQuickTransitionPrivate::removeLast_animation(QQmlListProperty<QQuickAbstractAnimation> *list)
145{
146 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
147 q->d_func()->animations.removeLast();
148}
149
150void QQuickTransitionPrivate::replace_animation(
151 QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos, QQuickAbstractAnimation *a)
152{
153 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
154 QQuickTransitionPrivate *d = q->d_func();
155 if (d->animations.length() <= pos)
156 d->animations.resize(size: pos + 1, c: nullptr);
157 d->animations[pos] = a;
158 if (a)
159 a->setDisableUserControl();
160}
161
162void QQuickTransitionInstance::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
163{
164 if (!m_transition)
165 return;
166
167 QQuickTransitionPrivate *transition = QQuickTransitionPrivate::get(q: m_transition);
168 transition->animationStateChanged(newState);
169}
170
171void QQuickTransitionPrivate::animationStateChanged(QAbstractAnimationJob::State newState)
172{
173 Q_Q(QQuickTransition);
174
175 if (newState == QAbstractAnimationJob::Running) {
176 runningInstanceCount++;
177 if (runningInstanceCount == 1)
178 emit q->runningChanged();
179 } else if (newState == QAbstractAnimationJob::Stopped) {
180 runningInstanceCount--;
181 if (runningInstanceCount == 0)
182 emit q->runningChanged();
183 }
184}
185
186void ParallelAnimationWrapper::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
187{
188 QParallelAnimationGroupJob::updateState(newState, oldState);
189 if (newState == Stopped && (duration() == -1
190 || (direction() == QAbstractAnimationJob::Forward && currentLoopTime() == duration())
191 || (direction() == QAbstractAnimationJob::Backward && currentLoopTime() == 0)))
192 {
193 manager->complete();
194 }
195}
196
197QQuickTransitionInstance::QQuickTransitionInstance(QQuickTransition *transition, QAbstractAnimationJob *anim)
198 : m_transition(transition)
199 , m_anim(anim)
200{
201 anim->addAnimationChangeListener(listener: this, QAbstractAnimationJob::StateChange);
202}
203
204QQuickTransitionInstance::~QQuickTransitionInstance()
205{
206 removeStateChangeListener();
207 delete m_anim;
208}
209
210void QQuickTransitionInstance::start()
211{
212 if (m_anim)
213 m_anim->start();
214}
215
216void QQuickTransitionInstance::stop()
217{
218 if (m_anim)
219 m_anim->stop();
220}
221
222void QQuickTransitionInstance::complete()
223{
224 if (m_anim)
225 m_anim->complete();
226}
227
228bool QQuickTransitionInstance::isRunning() const
229{
230 return m_anim && m_anim->state() == QAbstractAnimationJob::Running;
231}
232
233QQuickTransition::QQuickTransition(QObject *parent)
234 : QObject(*(new QQuickTransitionPrivate), parent)
235{
236}
237
238QQuickTransition::~QQuickTransition()
239{
240}
241
242void QQuickTransition::setReversed(bool r)
243{
244 Q_D(QQuickTransition);
245 d->reversed = r;
246}
247
248QQuickTransitionInstance *QQuickTransition::prepare(QQuickStateOperation::ActionList &actions,
249 QList<QQmlProperty> &after,
250 QQuickTransitionManager *manager,
251 QObject *defaultTarget)
252{
253 Q_D(QQuickTransition);
254
255 qmlExecuteDeferred(this);
256
257 ParallelAnimationWrapper *group = new ParallelAnimationWrapper();
258 group->manager = manager;
259
260 QQuickAbstractAnimation::TransitionDirection direction = d->reversed ? QQuickAbstractAnimation::Backward : QQuickAbstractAnimation::Forward;
261 int start = d->reversed ? d->animations.size() - 1 : 0;
262 int end = d->reversed ? -1 : d->animations.size();
263
264 for (int i = start; i != end; d->reversed ? --i : ++i) {
265 QQuickAbstractAnimation *anim = d->animations.at(i);
266 if (!anim)
267 continue;
268
269 QAbstractAnimationJob *job = anim->transition(actions, modified&: after, direction, defaultTarget);
270 if (!job)
271 continue;
272
273 if (anim->threadingModel() == QQuickAbstractAnimation::RenderThread)
274 job = new QQuickAnimatorProxyJob(job, anim);
275
276 d->reversed ? group->prependAnimation(animation: job) : group->appendAnimation(animation: job);
277 }
278
279 group->setDirection(d->reversed ? QAbstractAnimationJob::Backward : QAbstractAnimationJob::Forward);
280
281 QQuickTransitionInstance *wrapper = new QQuickTransitionInstance(this, group);
282 return wrapper;
283}
284
285/*!
286 \qmlproperty string QtQuick::Transition::from
287 \qmlproperty string QtQuick::Transition::to
288
289 These properties indicate the state changes that trigger the transition.
290
291 The default values for these properties is "*" (that is, any state).
292
293 For example, the following transition has not set the \c to and \c from
294 properties, so the animation is always applied when changing between
295 the two states (i.e. when the mouse is pressed and released).
296
297 \snippet qml/transition-from-to.qml 0
298
299 If the transition was changed to this:
300
301 \snippet qml/transition-from-to-modified.qml modified transition
302
303 The animation would only be applied when changing from the default state
304 to the "brighter" state (i.e. when the mouse is pressed, but not on release).
305
306 Multiple \c to and \c from values can be set by using a comma-separated
307 string.
308
309 \sa reversible
310*/
311QString QQuickTransition::fromState() const
312{
313 Q_D(const QQuickTransition);
314 return d->fromState;
315}
316
317void QQuickTransition::setFromState(const QString &f)
318{
319 Q_D(QQuickTransition);
320 if (f == d->fromState)
321 return;
322
323 d->fromState = f;
324 emit fromChanged();
325}
326
327/*!
328 \qmlproperty bool QtQuick::Transition::reversible
329 This property holds whether the transition should be automatically
330 reversed when the conditions that triggered this transition are reversed.
331
332 The default value is false.
333
334 By default, transitions run in parallel and are applied to all state
335 changes if the \l from and \l to states have not been set. In this
336 situation, the transition is automatically applied when a state change
337 is reversed, and it is not necessary to set this property to reverse
338 the transition.
339
340 However, if a SequentialAnimation is used, or if the \l from or \l to
341 properties have been set, this property will need to be set to reverse
342 a transition when a state change is reverted. For example, the following
343 transition applies a sequential animation when the mouse is pressed,
344 and reverses the sequence of the animation when the mouse is released:
345
346 \snippet qml/transition-reversible.qml 0
347
348 If the transition did not set the \c to and \c reversible values, then
349 on the mouse release, the transition would play the PropertyAnimation
350 before the ColorAnimation instead of reversing the sequence.
351*/
352bool QQuickTransition::reversible() const
353{
354 Q_D(const QQuickTransition);
355 return d->reversible;
356}
357
358void QQuickTransition::setReversible(bool r)
359{
360 Q_D(QQuickTransition);
361 if (r == d->reversible)
362 return;
363
364 d->reversible = r;
365 emit reversibleChanged();
366}
367
368QString QQuickTransition::toState() const
369{
370 Q_D(const QQuickTransition);
371 return d->toState;
372}
373
374void QQuickTransition::setToState(const QString &t)
375{
376 Q_D(QQuickTransition);
377 if (t == d->toState)
378 return;
379
380 d->toState = t;
381 emit toChanged();
382}
383
384/*!
385 \qmlproperty bool QtQuick::Transition::enabled
386
387 This property holds whether the Transition will be run when moving
388 from the \c from state to the \c to state.
389
390 By default a Transition is enabled.
391
392 Note that in some circumstances disabling a Transition may cause an
393 alternative Transition to be used in its place. In the following
394 example, although the first Transition has been set to animate changes
395 from "state1" to "state2", this transition has been disabled by setting
396 \c enabled to \c false, so any such state change will actually be animated
397 by the second Transition instead.
398
399 \qml
400 Item {
401 states: [
402 State { name: "state1" },
403 State { name: "state2" }
404 ]
405 transitions: [
406 Transition { from: "state1"; to: "state2"; enabled: false },
407 Transition {
408 // ...
409 }
410 ]
411 }
412 \endqml
413*/
414
415bool QQuickTransition::enabled() const
416{
417 Q_D(const QQuickTransition);
418 return d->enabled;
419}
420
421void QQuickTransition::setEnabled(bool enabled)
422{
423 Q_D(QQuickTransition);
424 if (d->enabled == enabled)
425 return;
426 d->enabled = enabled;
427 emit enabledChanged();
428}
429
430/*!
431 \qmlproperty bool QtQuick::Transition::running
432 \readonly
433
434 This property holds whether the transition is currently running.
435
436 \note Unlike Animation::running, this property is read only,
437 and can not be used to control the transition.
438*/
439bool QQuickTransition::running() const
440{
441 Q_D(const QQuickTransition);
442 return d->runningInstanceCount;
443}
444
445
446/*!
447 \qmlproperty list<Animation> QtQuick::Transition::animations
448 \qmldefault
449
450 This property holds a list of the animations to be run for this transition.
451
452 \snippet qml/transition-animation.qml 0
453
454 The top-level animations are run in parallel. To run them sequentially,
455 define them within a SequentialAnimation:
456
457 \snippet qml/transition-reversible.qml sequential animations
458*/
459QQmlListProperty<QQuickAbstractAnimation> QQuickTransition::animations()
460{
461 Q_D(QQuickTransition);
462 return QQmlListProperty<QQuickAbstractAnimation>(
463 this, &d->animations,
464 QQuickTransitionPrivate::append_animation,
465 QQuickTransitionPrivate::animation_count,
466 QQuickTransitionPrivate::animation_at,
467 QQuickTransitionPrivate::clear_animations,
468 QQuickTransitionPrivate::replace_animation,
469 QQuickTransitionPrivate::removeLast_animation);
470}
471
472QT_END_NAMESPACE
473
474//#include <qquicktransition.moc>
475
476#include "moc_qquicktransition_p.cpp"
477

source code of qtdeclarative/src/quick/util/qquicktransition.cpp