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 \instantiates 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 QList<QQuickAbstractAnimation *> animations;
111};
112
113void QQuickTransitionPrivate::append_animation(QQmlListProperty<QQuickAbstractAnimation> *list, QQuickAbstractAnimation *a)
114{
115 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
116 q->d_func()->animations.append(t: a);
117 a->setDisableUserControl();
118}
119
120qsizetype QQuickTransitionPrivate::animation_count(QQmlListProperty<QQuickAbstractAnimation> *list)
121{
122 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
123 return q->d_func()->animations.size();
124}
125
126QQuickAbstractAnimation* QQuickTransitionPrivate::animation_at(QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos)
127{
128 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
129 return q->d_func()->animations.at(i: pos);
130}
131
132void QQuickTransitionPrivate::clear_animations(QQmlListProperty<QQuickAbstractAnimation> *list)
133{
134 QQuickTransition *q = static_cast<QQuickTransition *>(list->object);
135 while (q->d_func()->animations.size()) {
136 QQuickAbstractAnimation *firstAnim = q->d_func()->animations.at(i: 0);
137 q->d_func()->animations.removeAll(t: firstAnim);
138 }
139}
140
141void QQuickTransitionInstance::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
142{
143 if (!m_transition)
144 return;
145
146 QQuickTransitionPrivate *transition = QQuickTransitionPrivate::get(q: m_transition);
147 transition->animationStateChanged(newState);
148}
149
150void QQuickTransitionPrivate::animationStateChanged(QAbstractAnimationJob::State newState)
151{
152 Q_Q(QQuickTransition);
153
154 if (newState == QAbstractAnimationJob::Running) {
155 runningInstanceCount++;
156 if (runningInstanceCount == 1)
157 emit q->runningChanged();
158 } else if (newState == QAbstractAnimationJob::Stopped) {
159 runningInstanceCount--;
160 if (runningInstanceCount == 0)
161 emit q->runningChanged();
162 }
163}
164
165void ParallelAnimationWrapper::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
166{
167 QParallelAnimationGroupJob::updateState(newState, oldState);
168 if (newState == Stopped && (duration() == -1
169 || (direction() == QAbstractAnimationJob::Forward && currentLoopTime() == duration())
170 || (direction() == QAbstractAnimationJob::Backward && currentLoopTime() == 0)))
171 {
172 manager->complete();
173 }
174}
175
176QQuickTransitionInstance::QQuickTransitionInstance(QQuickTransition *transition, QAbstractAnimationJob *anim)
177 : m_transition(transition)
178 , m_anim(anim)
179{
180 anim->addAnimationChangeListener(listener: this, QAbstractAnimationJob::StateChange);
181}
182
183QQuickTransitionInstance::~QQuickTransitionInstance()
184{
185 removeStateChangeListener();
186 delete m_anim;
187}
188
189void QQuickTransitionInstance::start()
190{
191 if (m_anim)
192 m_anim->start();
193}
194
195void QQuickTransitionInstance::stop()
196{
197 if (m_anim)
198 m_anim->stop();
199}
200
201void QQuickTransitionInstance::complete()
202{
203 if (m_anim)
204 m_anim->complete();
205}
206
207bool QQuickTransitionInstance::isRunning() const
208{
209 return m_anim && m_anim->state() == QAbstractAnimationJob::Running;
210}
211
212QQuickTransition::QQuickTransition(QObject *parent)
213 : QObject(*(new QQuickTransitionPrivate), parent)
214{
215}
216
217QQuickTransition::~QQuickTransition()
218{
219}
220
221void QQuickTransition::setReversed(bool r)
222{
223 Q_D(QQuickTransition);
224 d->reversed = r;
225}
226
227QQuickTransitionInstance *QQuickTransition::prepare(QQuickStateOperation::ActionList &actions,
228 QList<QQmlProperty> &after,
229 QQuickTransitionManager *manager,
230 QObject *defaultTarget)
231{
232 Q_D(QQuickTransition);
233
234 qmlExecuteDeferred(this);
235
236 ParallelAnimationWrapper *group = new ParallelAnimationWrapper();
237 group->manager = manager;
238
239 QQuickAbstractAnimation::TransitionDirection direction = d->reversed ? QQuickAbstractAnimation::Backward : QQuickAbstractAnimation::Forward;
240 int start = d->reversed ? d->animations.size() - 1 : 0;
241 int end = d->reversed ? -1 : d->animations.size();
242
243 QAbstractAnimationJob *anim = nullptr;
244 for (int i = start; i != end;) {
245 anim = d->animations.at(i)->transition(actions, modified&: after, direction, defaultTarget);
246 if (anim) {
247 if (d->animations.at(i)->threadingModel() == QQuickAbstractAnimation::RenderThread)
248 anim = new QQuickAnimatorProxyJob(anim, d->animations.at(i));
249 d->reversed ? group->prependAnimation(animation: anim) : group->appendAnimation(animation: anim);
250 }
251 d->reversed ? --i : ++i;
252 }
253
254 group->setDirection(d->reversed ? QAbstractAnimationJob::Backward : QAbstractAnimationJob::Forward);
255
256 QQuickTransitionInstance *wrapper = new QQuickTransitionInstance(this, group);
257 return wrapper;
258}
259
260/*!
261 \qmlproperty string QtQuick::Transition::from
262 \qmlproperty string QtQuick::Transition::to
263
264 These properties indicate the state changes that trigger the transition.
265
266 The default values for these properties is "*" (that is, any state).
267
268 For example, the following transition has not set the \c to and \c from
269 properties, so the animation is always applied when changing between
270 the two states (i.e. when the mouse is pressed and released).
271
272 \snippet qml/transition-from-to.qml 0
273
274 If the transition was changed to this:
275
276 \snippet qml/transition-from-to-modified.qml modified transition
277
278 The animation would only be applied when changing from the default state
279 to the "brighter" state (i.e. when the mouse is pressed, but not on release).
280
281 Multiple \c to and \c from values can be set by using a comma-separated
282 string.
283
284 \sa reversible
285*/
286QString QQuickTransition::fromState() const
287{
288 Q_D(const QQuickTransition);
289 return d->fromState;
290}
291
292void QQuickTransition::setFromState(const QString &f)
293{
294 Q_D(QQuickTransition);
295 if (f == d->fromState)
296 return;
297
298 d->fromState = f;
299 emit fromChanged();
300}
301
302/*!
303 \qmlproperty bool QtQuick::Transition::reversible
304 This property holds whether the transition should be automatically
305 reversed when the conditions that triggered this transition are reversed.
306
307 The default value is false.
308
309 By default, transitions run in parallel and are applied to all state
310 changes if the \l from and \l to states have not been set. In this
311 situation, the transition is automatically applied when a state change
312 is reversed, and it is not necessary to set this property to reverse
313 the transition.
314
315 However, if a SequentialAnimation is used, or if the \l from or \l to
316 properties have been set, this property will need to be set to reverse
317 a transition when a state change is reverted. For example, the following
318 transition applies a sequential animation when the mouse is pressed,
319 and reverses the sequence of the animation when the mouse is released:
320
321 \snippet qml/transition-reversible.qml 0
322
323 If the transition did not set the \c to and \c reversible values, then
324 on the mouse release, the transition would play the PropertyAnimation
325 before the ColorAnimation instead of reversing the sequence.
326*/
327bool QQuickTransition::reversible() const
328{
329 Q_D(const QQuickTransition);
330 return d->reversible;
331}
332
333void QQuickTransition::setReversible(bool r)
334{
335 Q_D(QQuickTransition);
336 if (r == d->reversible)
337 return;
338
339 d->reversible = r;
340 emit reversibleChanged();
341}
342
343QString QQuickTransition::toState() const
344{
345 Q_D(const QQuickTransition);
346 return d->toState;
347}
348
349void QQuickTransition::setToState(const QString &t)
350{
351 Q_D(QQuickTransition);
352 if (t == d->toState)
353 return;
354
355 d->toState = t;
356 emit toChanged();
357}
358
359/*!
360 \qmlproperty bool QtQuick::Transition::enabled
361
362 This property holds whether the Transition will be run when moving
363 from the \c from state to the \c to state.
364
365 By default a Transition is enabled.
366
367 Note that in some circumstances disabling a Transition may cause an
368 alternative Transition to be used in its place. In the following
369 example, although the first Transition has been set to animate changes
370 from "state1" to "state2", this transition has been disabled by setting
371 \c enabled to \c false, so any such state change will actually be animated
372 by the second Transition instead.
373
374 \qml
375 Item {
376 states: [
377 State { name: "state1" },
378 State { name: "state2" }
379 ]
380 transitions: [
381 Transition { from: "state1"; to: "state2"; enabled: false },
382 Transition {
383 // ...
384 }
385 ]
386 }
387 \endqml
388*/
389
390bool QQuickTransition::enabled() const
391{
392 Q_D(const QQuickTransition);
393 return d->enabled;
394}
395
396void QQuickTransition::setEnabled(bool enabled)
397{
398 Q_D(QQuickTransition);
399 if (d->enabled == enabled)
400 return;
401 d->enabled = enabled;
402 emit enabledChanged();
403}
404
405/*!
406 \qmlproperty bool QtQuick::Transition::running
407 \readonly
408
409 This property holds whether the transition is currently running.
410
411 \note Unlike Animation::running, this property is read only,
412 and can not be used to control the transition.
413*/
414bool QQuickTransition::running() const
415{
416 Q_D(const QQuickTransition);
417 return d->runningInstanceCount;
418}
419
420
421/*!
422 \qmlproperty list<Animation> QtQuick::Transition::animations
423 \qmldefault
424
425 This property holds a list of the animations to be run for this transition.
426
427 \snippet ../qml/dynamicscene/dynamicscene.qml top-level transitions
428
429 The top-level animations are run in parallel. To run them sequentially,
430 define them within a SequentialAnimation:
431
432 \snippet qml/transition-reversible.qml sequential animations
433*/
434QQmlListProperty<QQuickAbstractAnimation> QQuickTransition::animations()
435{
436 Q_D(QQuickTransition);
437 return QQmlListProperty<QQuickAbstractAnimation>(this, &d->animations, QQuickTransitionPrivate::append_animation,
438 QQuickTransitionPrivate::animation_count,
439 QQuickTransitionPrivate::animation_at,
440 QQuickTransitionPrivate::clear_animations);
441}
442
443QT_END_NAMESPACE
444
445//#include <qquicktransition.moc>
446
447#include "moc_qquicktransition_p.cpp"
448

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