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(me: this) << tr(s: "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(running: 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
188/*!
189 \readonly
190 \qmlpropertygroup QtQuick::Behavior::targetProperty
191 \qmlproperty string QtQuick::Behavior::targetProperty.name
192 \qmlproperty Object QtQuick::Behavior::targetProperty.object
193
194 \table
195 \header
196 \li Property
197 \li Description
198 \row
199 \li name
200 \li This property holds the name of the property being controlled by this Behavior.
201 \row
202 \li object
203 \li This property holds the object of the property being controlled by this Behavior.
204 \endtable
205
206 This property can be used to define custom behaviors based on the name or the object of
207 the property being controlled.
208
209 The following example defines a Behavior fading out and fading in its target object
210 when the property it controls changes:
211 \qml
212 // FadeBehavior.qml
213 import QtQuick 2.15
214
215 Behavior {
216 id: root
217 property Item fadeTarget: targetProperty.object
218 SequentialAnimation {
219 NumberAnimation {
220 target: root.fadeTarget
221 property: "opacity"
222 to: 0
223 easing.type: Easing.InQuad
224 }
225 PropertyAction { } // actually change the controlled property between the 2 other animations
226 NumberAnimation {
227 target: root.fadeTarget
228 property: "opacity"
229 to: 1
230 easing.type: Easing.OutQuad
231 }
232 }
233 }
234 \endqml
235
236 This can be used to animate a text when it changes:
237 \qml
238 import QtQuick 2.15
239
240 Text {
241 id: root
242 property int counter
243 text: counter
244 FadeBehavior on text {}
245 Timer {
246 running: true
247 repeat: true
248 interval: 1000
249 onTriggered: ++root.counter
250 }
251 }
252 \endqml
253
254 \since QtQuick 2.15
255*/
256QQmlProperty QQuickBehavior::targetProperty() const
257{
258 Q_D(const QQuickBehavior);
259 return d->property;
260}
261
262void QQuickBehavior::write(const QVariant &value)
263{
264 Q_D(QQuickBehavior);
265 const bool targetValueHasChanged = d->targetValue != value;
266 if (targetValueHasChanged) {
267 d->targetValue = value;
268 emit targetValueChanged(); // emitting the signal here should allow
269 } // d->enabled to change if scripted by the user.
270 bool bypass = !d->enabled || !d->finalized || QQmlEnginePrivate::designerMode();
271 if (!bypass)
272 qmlExecuteDeferred(this);
273 if (QQmlData::wasDeleted(object: d->animation) || bypass) {
274 if (d->animationInstance)
275 d->animationInstance->stop();
276 QQmlPropertyPrivate::write(that: d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
277 return;
278 }
279
280 bool behaviorActive = d->animation->isRunning();
281 if (behaviorActive && !targetValueHasChanged)
282 return;
283
284 if (d->animationInstance
285 && (d->animationInstance->duration() != -1
286 || d->animationInstance->isRenderThreadProxy())
287 && !d->animationInstance->isStopped()) {
288 d->blockRunningChanged = true;
289 d->animationInstance->stop();
290 }
291 // Render thread animations use "stop" to synchronize the property back
292 // to the item, so we need to read the value after.
293 const QVariant &currentValue = d->property.read();
294
295 // Don't unnecessarily wake up the animation system if no real animation
296 // is needed (value has not changed). If the Behavior was already
297 // running, let it continue as normal to ensure correct behavior and state.
298 if (!behaviorActive && d->targetValue == currentValue) {
299 QQmlPropertyPrivate::write(that: d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
300 return;
301 }
302
303 QQuickStateOperation::ActionList actions;
304 QQuickStateAction action;
305 action.property = d->property;
306 action.fromValue = currentValue;
307 action.toValue = value;
308 actions << action;
309
310 QList<QQmlProperty> after;
311 QAbstractAnimationJob *prev = d->animationInstance;
312 d->animationInstance = d->animation->transition(actions, modified&: after, direction: QQuickAbstractAnimation::Forward);
313
314 if (d->animationInstance && d->animation->threadingModel() == QQuickAbstractAnimation::RenderThread)
315 d->animationInstance = new QQuickAnimatorProxyJob(d->animationInstance, d->animation);
316
317 if (prev && prev != d->animationInstance)
318 delete prev;
319
320 if (d->animationInstance) {
321 if (d->animationInstance != prev)
322 d->animationInstance->addAnimationChangeListener(listener: d, QAbstractAnimationJob::StateChange);
323 d->animationInstance->start();
324 d->blockRunningChanged = false;
325 }
326 if (!after.contains(t: d->property))
327 QQmlPropertyPrivate::write(that: d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
328}
329
330void QQuickBehavior::setTarget(const QQmlProperty &property)
331{
332 Q_D(QQuickBehavior);
333 d->property = property;
334 if (d->animation)
335 d->animation->setDefaultTarget(property);
336
337 QQmlEnginePrivate *engPriv = QQmlEnginePrivate::get(e: qmlEngine(this));
338 static int finalizedIdx = -1;
339 if (finalizedIdx < 0)
340 finalizedIdx = metaObject()->indexOfSlot(slot: "componentFinalized()");
341 engPriv->registerFinalizeCallback(obj: this, index: finalizedIdx);
342
343 Q_EMIT targetPropertyChanged();
344}
345
346void QQuickBehavior::componentFinalized()
347{
348 Q_D(QQuickBehavior);
349 d->finalized = true;
350}
351
352QT_END_NAMESPACE
353
354#include "moc_qquickbehavior_p.cpp"
355

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