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 
54 QT_BEGIN_NAMESPACE
55 
56 class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener
57 {
58     Q_DECLARE_PUBLIC(QQuickBehavior)
59 public:
QQuickBehaviorPrivate()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 
QQuickBehavior(QObject * parent)104 QQuickBehavior::QQuickBehavior(QObject *parent)
105     : QObject(*(new QQuickBehaviorPrivate), parent)
106 {
107 }
108 
~QQuickBehavior()109 QQuickBehavior::~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 
animation()122 QQuickAbstractAnimation *QQuickBehavior::animation()
123 {
124     Q_D(QQuickBehavior);
125     return d->animation;
126 }
127 
setAnimation(QQuickAbstractAnimation * animation)128 void 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 
animationStateChanged(QAbstractAnimationJob *,QAbstractAnimationJob::State newState,QAbstractAnimationJob::State)144 void 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 
enabled() const159 bool QQuickBehavior::enabled() const
160 {
161     Q_D(const QQuickBehavior);
162     return d->enabled;
163 }
164 
setEnabled(bool enabled)165 void 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 */
targetValue() const182 QVariant 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 */
targetProperty() const256 QQmlProperty QQuickBehavior::targetProperty() const
257 {
258     Q_D(const QQuickBehavior);
259     return d->property;
260 }
261 
write(const QVariant & value)262 void 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(d->animation) || bypass) {
274         if (d->animationInstance)
275             d->animationInstance->stop();
276         QQmlPropertyPrivate::write(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(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, after, 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(d, QAbstractAnimationJob::StateChange);
323         d->animationInstance->start();
324         d->blockRunningChanged = false;
325     }
326     if (!after.contains(d->property))
327         QQmlPropertyPrivate::write(d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
328 }
329 
setTarget(const QQmlProperty & property)330 void 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(qmlEngine(this));
338     static int finalizedIdx = -1;
339     if (finalizedIdx < 0)
340         finalizedIdx = metaObject()->indexOfSlot("componentFinalized()");
341     engPriv->registerFinalizeCallback(this, finalizedIdx);
342 
343     Q_EMIT targetPropertyChanged();
344 }
345 
componentFinalized()346 void QQuickBehavior::componentFinalized()
347 {
348     Q_D(QQuickBehavior);
349     d->finalized = true;
350 }
351 
352 QT_END_NAMESPACE
353 
354 #include "moc_qquickbehavior_p.cpp"
355