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 "qquickbehavior_p.h"
41
42#include "qquickanimation_p.h"
43#include <qqmlcontext.h>
44#include <qqmlinfo.h>
45#include <private/qqmlproperty_p.h>
46#include <private/qqmlengine_p.h>
47#include <private/qabstractanimationjob_p.h>
48#include <private/qquicktransition_p.h>
49
50#include <private/qquickanimatorjob_p.h>
51
52#include <private/qobject_p.h>
53
54QT_BEGIN_NAMESPACE
55
56class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener
57{
58 Q_DECLARE_PUBLIC(QQuickBehavior)
59public:
60 QQuickBehaviorPrivate() : animation(nullptr), animationInstance(nullptr), enabled(true), finalized(false)
61 , blockRunningChanged(false) {}
62
63 void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) override;
64
65 QQmlProperty property;
66 QVariant targetValue;
67 QPointer<QQuickAbstractAnimation> animation;
68 QAbstractAnimationJob *animationInstance;
69 bool enabled;
70 bool finalized;
71 bool blockRunningChanged;
72};
73
74/*!
75 \qmltype Behavior
76 \instantiates QQuickBehavior
77 \inqmlmodule QtQuick
78 \ingroup qtquick-transitions-animations
79 \ingroup qtquick-interceptors
80 \brief Defines a default animation for a property change.
81
82 A Behavior defines the default animation to be applied whenever a
83 particular property value changes.
84
85 For example, the following Behavior defines a NumberAnimation to be run
86 whenever the \l Rectangle's \c width value changes. When the MouseArea
87 is clicked, the \c width is changed, triggering the behavior's animation:
88
89 \snippet qml/behavior.qml 0
90
91 Note that a property cannot have more than one assigned Behavior. To provide
92 multiple animations within a Behavior, use ParallelAnimation or
93 SequentialAnimation.
94
95 If a \l{Qt Quick States}{state change} has a \l Transition that matches the same property as a
96 Behavior, the \l Transition animation overrides the Behavior for that
97 state change. For general advice on using Behaviors to animate state changes, see
98 \l{Using Qt Quick Behaviors with States}.
99
100 \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt QML}
101*/
102
103
104QQuickBehavior::QQuickBehavior(QObject *parent)
105 : QObject(*(new QQuickBehaviorPrivate), parent)
106{
107}
108
109QQuickBehavior::~QQuickBehavior()
110{
111 Q_D(QQuickBehavior);
112 delete d->animationInstance;
113}
114
115/*!
116 \qmlproperty Animation QtQuick::Behavior::animation
117 \default
118
119 This property holds the animation to run when the behavior is triggered.
120*/
121
122QQuickAbstractAnimation *QQuickBehavior::animation()
123{
124 Q_D(QQuickBehavior);
125 return d->animation;
126}
127
128void QQuickBehavior::setAnimation(QQuickAbstractAnimation *animation)
129{
130 Q_D(QQuickBehavior);
131 if (d->animation) {
132 qmlWarning(this) << tr("Cannot change the animation assigned to a Behavior.");
133 return;
134 }
135
136 d->animation = animation;
137 if (d->animation) {
138 d->animation->setDefaultTarget(d->property);
139 d->animation->setDisableUserControl();
140 }
141}
142
143
144void QQuickBehaviorPrivate::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState,QAbstractAnimationJob::State)
145{
146 if (!blockRunningChanged && animation)
147 animation->notifyRunningChanged(newState == QAbstractAnimationJob::Running);
148}
149
150/*!
151 \qmlproperty bool QtQuick::Behavior::enabled
152
153 This property holds whether the behavior will be triggered when the tracked
154 property changes value.
155
156 By default a Behavior is enabled.
157*/
158
159bool QQuickBehavior::enabled() const
160{
161 Q_D(const QQuickBehavior);
162 return d->enabled;
163}
164
165void QQuickBehavior::setEnabled(bool enabled)
166{
167 Q_D(QQuickBehavior);
168 if (d->enabled == enabled)
169 return;
170 d->enabled = enabled;
171 emit enabledChanged();
172}
173
174/*!
175 \qmlproperty Variant QtQuick::Behavior::targetValue
176
177 This property holds the target value of the property being controlled by the Behavior.
178 This value is set by the Behavior before the animation is started.
179
180 \since QtQuick 2.13
181*/
182QVariant QQuickBehavior::targetValue() const
183{
184 Q_D(const QQuickBehavior);
185 return d->targetValue;
186}
187
188void QQuickBehavior::write(const QVariant &value)
189{
190 Q_D(QQuickBehavior);
191 const bool targetValueHasChanged = d->targetValue != value;
192 if (targetValueHasChanged) {
193 d->targetValue = value;
194 emit targetValueChanged(); // emitting the signal here should allow
195 } // d->enabled to change if scripted by the user.
196 bool bypass = !d->enabled || !d->finalized || QQmlEnginePrivate::designerMode();
197 if (!bypass)
198 qmlExecuteDeferred(this);
199 if (!d->animation || bypass) {
200 if (d->animationInstance)
201 d->animationInstance->stop();
202 QQmlPropertyPrivate::write(d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
203 return;
204 }
205
206 bool behaviorActive = d->animation->isRunning();
207 if (behaviorActive && !targetValueHasChanged)
208 return;
209
210 if (d->animationInstance
211 && (d->animationInstance->duration() != -1
212 || d->animationInstance->isRenderThreadProxy())
213 && !d->animationInstance->isStopped()) {
214 d->blockRunningChanged = true;
215 d->animationInstance->stop();
216 }
217 // Render thread animations use "stop" to synchronize the property back
218 // to the item, so we need to read the value after.
219 const QVariant &currentValue = d->property.read();
220
221 // Don't unnecessarily wake up the animation system if no real animation
222 // is needed (value has not changed). If the Behavior was already
223 // running, let it continue as normal to ensure correct behavior and state.
224 if (!behaviorActive && d->targetValue == currentValue) {
225 QQmlPropertyPrivate::write(d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
226 return;
227 }
228
229 QQuickStateOperation::ActionList actions;
230 QQuickStateAction action;
231 action.property = d->property;
232 action.fromValue = currentValue;
233 action.toValue = value;
234 actions << action;
235
236 QList<QQmlProperty> after;
237 QAbstractAnimationJob *prev = d->animationInstance;
238 d->animationInstance = d->animation->transition(actions, after, QQuickAbstractAnimation::Forward);
239
240 if (d->animationInstance && d->animation->threadingModel() == QQuickAbstractAnimation::RenderThread)
241 d->animationInstance = new QQuickAnimatorProxyJob(d->animationInstance, d->animation);
242
243 if (prev && prev != d->animationInstance)
244 delete prev;
245
246 if (d->animationInstance) {
247 if (d->animationInstance != prev)
248 d->animationInstance->addAnimationChangeListener(d, QAbstractAnimationJob::StateChange);
249 d->animationInstance->start();
250 d->blockRunningChanged = false;
251 }
252 if (!after.contains(d->property))
253 QQmlPropertyPrivate::write(d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
254}
255
256void QQuickBehavior::setTarget(const QQmlProperty &property)
257{
258 Q_D(QQuickBehavior);
259 d->property = property;
260 if (d->animation)
261 d->animation->setDefaultTarget(property);
262
263 QQmlEnginePrivate *engPriv = QQmlEnginePrivate::get(qmlEngine(this));
264 static int finalizedIdx = -1;
265 if (finalizedIdx < 0)
266 finalizedIdx = metaObject()->indexOfSlot("componentFinalized()");
267 engPriv->registerFinalizeCallback(this, finalizedIdx);
268}
269
270void QQuickBehavior::componentFinalized()
271{
272 Q_D(QQuickBehavior);
273 d->finalized = true;
274}
275
276QT_END_NAMESPACE
277
278#include "moc_qquickbehavior_p.cpp"
279