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 "qquickbehavior_p.h"
5
6#include "qquickanimation_p.h"
7#include <qqmlcontext.h>
8#include <qqmlinfo.h>
9#include <private/qqmlproperty_p.h>
10#include <private/qqmlengine_p.h>
11#include <private/qabstractanimationjob_p.h>
12#include <private/qquicktransition_p.h>
13
14#include <private/qquickanimatorjob_p.h>
15
16#include <private/qobject_p.h>
17
18QT_BEGIN_NAMESPACE
19
20/*!
21 \internal
22 \brief The UntypedProxyProperty class is a property used in Behavior to handle bindable properties.
23
24 Whenever a bindable property with a Behavior gets a request for its bindable interface, we instead
25 return the bindable interface of the UntypedProxyProperty. This causes all reads and writes to be
26 intercepted to use \c m_storage instead; moreover, any installed binding will also use \c m_storage
27 as the property data for the binding.
28
29 The BehaviorPrivate acts as an observer, listening to changes of the proxy property. If those occur,
30 QQuickBehavior::write is called with the new value, which will then adjust the actual property (playing
31 animations if necessary).
32
33 \warning The interception mechanism works only via the metaobject system, just like it is the case with
34 non-binadble properties and writes. Bypassing the metaobject system can thus lead to inconsistent results;
35 it is however currently safe, as we do not publically expose the classes, and the code in Quick plays
36 nicely.
37 */
38class UntypedProxyProperty : public QUntypedPropertyData
39{
40 QtPrivate::QPropertyBindingData m_bindingData;
41 QUntypedPropertyData *m_sourcePropertyData;
42 const QtPrivate::QBindableInterface *m_sourceInterface;
43 QVariant m_storage;
44public:
45 void static getter(const QUntypedPropertyData *d, void *value)
46 {
47 auto This = static_cast<const UntypedProxyProperty *>(d);
48 // multiplexing: If the flag is set, we want to receive the metatype instead
49 if (quintptr(value) & QtPrivate::QBindableInterface::MetaTypeAccessorFlag) {
50 *reinterpret_cast<QMetaType *>(quintptr(value) &
51 ~QtPrivate::QBindableInterface::MetaTypeAccessorFlag)
52 = This->type();
53 return;
54 }
55 This->type().construct(where: value, copy: This->m_storage.constData());
56 This->m_bindingData.registerWithCurrentlyEvaluatingBinding();
57 }
58
59 void static setter(QUntypedPropertyData *d, const void *value)
60 {
61 auto This = static_cast<UntypedProxyProperty *>(d);
62 This->type().construct(where: This->m_storage.data(), copy: value);
63 This->m_bindingData.notifyObservers(propertyDataPtr: reinterpret_cast<QUntypedPropertyData *>(This->m_storage.data()));
64 }
65
66 static QUntypedPropertyBinding bindingGetter(const QUntypedPropertyData *d)
67 {
68 auto This = static_cast<const UntypedProxyProperty *>(d);
69 return QUntypedPropertyBinding(This->m_bindingData.binding());
70 }
71
72 static QUntypedPropertyBinding bindingSetter(QUntypedPropertyData *d,
73 const QUntypedPropertyBinding &binding)
74 {
75 auto This = static_cast<UntypedProxyProperty *>(d);
76 const QMetaType type = This->type();
77 if (binding.valueMetaType() != type)
78 return {};
79
80 // We want to notify in any case here because the target property should be set
81 // even if our proxy binding results in the default value.
82 QPropertyBindingPrivate::get(binding)->scheduleNotify();
83 return This->m_bindingData.setBinding(newBinding: binding,
84 propertyDataPtr: reinterpret_cast<QUntypedPropertyData *>(
85 This->m_storage.data()));
86 }
87
88 static QUntypedPropertyBinding makeBinding(const QUntypedPropertyData *d,
89 const QPropertyBindingSourceLocation &location)
90 {
91 auto This = static_cast<const UntypedProxyProperty *>(d);
92 return This->m_sourceInterface->makeBinding(This->m_sourcePropertyData, location);
93 }
94
95 static void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer)
96 {
97 auto This = static_cast<const UntypedProxyProperty *>(d);
98 This->m_sourceInterface->setObserver(This->m_sourcePropertyData, observer);
99 }
100
101
102
103 UntypedProxyProperty(QUntypedBindable bindable, QQuickBehaviorPrivate *behavior);
104
105 QUntypedBindable getBindable();
106 QMetaType type() const { return m_storage.metaType(); }
107 QVariant value() const {return m_storage;}
108};
109
110static constexpr inline QtPrivate::QBindableInterface untypedProxyPropertyBindableInterafce {
111 .getter: &UntypedProxyProperty::getter,
112 .setter: &UntypedProxyProperty::setter,
113 .getBinding: &UntypedProxyProperty::bindingGetter,
114 .setBinding: &UntypedProxyProperty::bindingSetter,
115 .makeBinding: &UntypedProxyProperty::makeBinding,
116 .setObserver: &UntypedProxyProperty::setObserver,
117 /*metatype*/.metaType: nullptr
118};
119
120struct UntypedProxyPropertyBindable : QUntypedBindable {
121 UntypedProxyPropertyBindable(UntypedProxyProperty *property)
122 :QUntypedBindable (property, &untypedProxyPropertyBindableInterafce)
123 {}
124};
125
126QUntypedBindable UntypedProxyProperty::getBindable()
127{
128 return UntypedProxyPropertyBindable {const_cast<UntypedProxyProperty *>(this)};
129}
130
131class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener, public QPropertyObserver
132{
133 Q_DECLARE_PUBLIC(QQuickBehavior)
134public:
135 static void onProxyChanged(QPropertyObserver *, QUntypedPropertyData *);
136 QQuickBehaviorPrivate()
137 : QPropertyObserver(&QQuickBehaviorPrivate::onProxyChanged) {}
138 void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) override;
139
140 QQmlProperty property;
141 QVariant targetValue;
142 QPointer<QQuickAbstractAnimation> animation = nullptr;
143 QAbstractAnimationJob *animationInstance = nullptr;
144 std::unique_ptr<UntypedProxyProperty> propertyProxy;
145 bool enabled = true;
146 bool finalized = false;
147 bool blockRunningChanged = false;
148
149};
150
151UntypedProxyProperty::UntypedProxyProperty(QUntypedBindable bindable, QQuickBehaviorPrivate *behavior) :
152 m_sourcePropertyData(QUntypedBindablePrivate::getPropertyData(bindable)),
153 m_sourceInterface(QUntypedBindablePrivate::getInterface(bindable)),
154 m_storage(QVariant(bindable.metaType()))
155{
156 behavior->setSource(m_bindingData);
157}
158
159/*!
160 \qmltype Behavior
161 \instantiates QQuickBehavior
162 \inqmlmodule QtQuick
163 \ingroup qtquick-transitions-animations
164 \ingroup qtquick-interceptors
165 \brief Defines a default animation for a property change.
166
167 A Behavior defines the default animation to be applied whenever a
168 particular property value changes.
169
170 For example, the following Behavior defines a NumberAnimation to be run
171 whenever the \l Rectangle's \c width value changes. When the MouseArea
172 is clicked, the \c width is changed, triggering the behavior's animation:
173
174 \snippet qml/behavior.qml 0
175
176 Note that a property cannot have more than one assigned Behavior. To provide
177 multiple animations within a Behavior, use ParallelAnimation or
178 SequentialAnimation.
179
180 If a \l{Qt Quick States}{state change} has a \l Transition that matches the same property as a
181 Behavior, the \l Transition animation overrides the Behavior for that
182 state change. For general advice on using Behaviors to animate state changes, see
183 \l{Using Qt Quick Behaviors with States}.
184
185 \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt QML}
186*/
187
188
189QQuickBehavior::QQuickBehavior(QObject *parent)
190 : QObject(*(new QQuickBehaviorPrivate), parent)
191{
192}
193
194QQuickBehavior::~QQuickBehavior()
195{
196 Q_D(QQuickBehavior);
197 delete d->animationInstance;
198}
199
200/*!
201 \qmlproperty Animation QtQuick::Behavior::animation
202 \qmldefault
203
204 This property holds the animation to run when the behavior is triggered.
205*/
206
207QQuickAbstractAnimation *QQuickBehavior::animation()
208{
209 Q_D(QQuickBehavior);
210 return d->animation;
211}
212
213void QQuickBehavior::setAnimation(QQuickAbstractAnimation *animation)
214{
215 Q_D(QQuickBehavior);
216 if (d->animation) {
217 qmlWarning(me: this) << tr(s: "Cannot change the animation assigned to a Behavior.");
218 return;
219 }
220
221 d->animation = animation;
222 if (d->animation) {
223 d->animation->setDefaultTarget(d->property);
224 d->animation->setDisableUserControl();
225 }
226}
227
228
229void QQuickBehaviorPrivate::onProxyChanged(QPropertyObserver *observer, QUntypedPropertyData *)
230{
231 auto This = static_cast<QQuickBehaviorPrivate *>(observer);
232 This->q_func()->write(value: This->propertyProxy->value());
233}
234
235void QQuickBehaviorPrivate::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState,QAbstractAnimationJob::State)
236{
237 if (!blockRunningChanged && animation)
238 animation->notifyRunningChanged(running: newState == QAbstractAnimationJob::Running);
239}
240
241/*!
242 \qmlproperty bool QtQuick::Behavior::enabled
243
244 This property holds whether the behavior will be triggered when the tracked
245 property changes value.
246
247 By default a Behavior is enabled.
248*/
249
250bool QQuickBehavior::enabled() const
251{
252 Q_D(const QQuickBehavior);
253 return d->enabled;
254}
255
256void QQuickBehavior::setEnabled(bool enabled)
257{
258 Q_D(QQuickBehavior);
259 if (d->enabled == enabled)
260 return;
261 d->enabled = enabled;
262 emit enabledChanged();
263}
264
265/*!
266 \qmlproperty Variant QtQuick::Behavior::targetValue
267
268 This property holds the target value of the property being controlled by the Behavior.
269 This value is set by the Behavior before the animation is started.
270
271 \since QtQuick 2.13
272*/
273QVariant QQuickBehavior::targetValue() const
274{
275 Q_D(const QQuickBehavior);
276 return d->targetValue;
277}
278
279/*!
280 \readonly
281 \qmlpropertygroup QtQuick::Behavior::targetProperty
282 \qmlproperty string QtQuick::Behavior::targetProperty.name
283 \qmlproperty QtObject QtQuick::Behavior::targetProperty.object
284
285 \table
286 \header
287 \li Property
288 \li Description
289 \row
290 \li name
291 \li This property holds the name of the property being controlled by this Behavior.
292 \row
293 \li object
294 \li This property holds the object of the property being controlled by this Behavior.
295 \endtable
296
297 This property can be used to define custom behaviors based on the name or the object of
298 the property being controlled.
299
300 The following example defines a Behavior fading out and fading in its target object
301 when the property it controls changes:
302 \qml
303 // FadeBehavior.qml
304 import QtQuick 2.15
305
306 Behavior {
307 id: root
308 property Item fadeTarget: targetProperty.object
309 SequentialAnimation {
310 NumberAnimation {
311 target: root.fadeTarget
312 property: "opacity"
313 to: 0
314 easing.type: Easing.InQuad
315 }
316 PropertyAction { } // actually change the controlled property between the 2 other animations
317 NumberAnimation {
318 target: root.fadeTarget
319 property: "opacity"
320 to: 1
321 easing.type: Easing.OutQuad
322 }
323 }
324 }
325 \endqml
326
327 This can be used to animate a text when it changes:
328 \qml
329 import QtQuick 2.15
330
331 Text {
332 id: root
333 property int counter
334 text: counter
335 FadeBehavior on text {}
336 Timer {
337 running: true
338 repeat: true
339 interval: 1000
340 onTriggered: ++root.counter
341 }
342 }
343 \endqml
344
345 \since QtQuick 2.15
346*/
347QQmlProperty QQuickBehavior::targetProperty() const
348{
349 Q_D(const QQuickBehavior);
350 return d->property;
351}
352
353void QQuickBehavior::componentFinalized()
354{
355 Q_D(QQuickBehavior);
356 d->finalized = true;
357}
358
359void QQuickBehavior::write(const QVariant &value)
360{
361 Q_D(QQuickBehavior);
362 const bool targetValueHasChanged = d->targetValue != value;
363 if (targetValueHasChanged) {
364 d->targetValue = value;
365 emit targetValueChanged(); // emitting the signal here should allow
366 } // d->enabled to change if scripted by the user.
367 bool bypass = !d->enabled || !d->finalized || QQmlEnginePrivate::designerMode();
368 if (!bypass)
369 qmlExecuteDeferred(this);
370 if (QQmlData::wasDeleted(object: d->animation) || bypass) {
371 if (d->animationInstance)
372 d->animationInstance->stop();
373 QQmlPropertyPrivate::write(that: d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
374 return;
375 }
376
377 bool behaviorActive = d->animation->isRunning();
378 if (behaviorActive && !targetValueHasChanged)
379 return;
380
381 if (d->animationInstance
382 && (d->animationInstance->duration() != -1
383 || d->animationInstance->isRenderThreadProxy())
384 && !d->animationInstance->isStopped()) {
385 d->blockRunningChanged = true;
386 d->animationInstance->stop();
387 }
388 // Render thread animations use "stop" to synchronize the property back
389 // to the item, so we need to read the value after.
390 const QVariant &currentValue = d->property.read();
391
392 // Don't unnecessarily wake up the animation system if no real animation
393 // is needed (value has not changed). If the Behavior was already
394 // running, let it continue as normal to ensure correct behavior and state.
395 if (!behaviorActive && d->targetValue == currentValue) {
396 QQmlPropertyPrivate::write(that: d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
397 return;
398 }
399
400 QQuickStateOperation::ActionList actions;
401 QQuickStateAction action;
402 action.property = d->property;
403 action.fromValue = currentValue;
404 action.toValue = value;
405 actions << action;
406
407 QList<QQmlProperty> after;
408 auto *newInstance = d->animation->transition(actions, modified&: after, direction: QQuickAbstractAnimation::Forward);
409 Q_ASSERT(!newInstance || newInstance != d->animationInstance);
410 delete d->animationInstance;
411 d->animationInstance = newInstance;
412
413 if (d->animationInstance) {
414 if (d->animation->threadingModel() == QQuickAbstractAnimation::RenderThread)
415 d->animationInstance = new QQuickAnimatorProxyJob(d->animationInstance, d->animation);
416
417 d->animationInstance->addAnimationChangeListener(listener: d, QAbstractAnimationJob::StateChange);
418 d->animationInstance->start();
419 d->blockRunningChanged = false;
420 }
421
422 if (!after.contains(t: d->property))
423 QQmlPropertyPrivate::write(that: d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
424}
425
426bool QQuickBehavior::bindable(QUntypedBindable *untypedBindable, QUntypedBindable target)
427{
428 Q_D(QQuickBehavior);
429 if (!d->propertyProxy)
430 d->propertyProxy = std::make_unique<UntypedProxyProperty>(args&: target, args: d);
431 *untypedBindable = d->propertyProxy->getBindable();
432 return true;
433}
434
435void QQuickBehavior::setTarget(const QQmlProperty &property)
436{
437 Q_D(QQuickBehavior);
438 d->property = property;
439 if (d->animation)
440 d->animation->setDefaultTarget(property);
441
442 if (QMetaProperty metaProp = property.property(); metaProp.isBindable()) {
443 QUntypedBindable untypedBindable = metaProp.bindable(object: property.object());
444 d->propertyProxy = std::make_unique<UntypedProxyProperty>(args&: untypedBindable, args: d);
445 if (untypedBindable.hasBinding()) {
446 // should not happen as bindings should get initialized only after interceptors
447 UntypedProxyProperty::bindingSetter(d: d->propertyProxy.get(), binding: untypedBindable.takeBinding());
448 }
449 }
450
451 Q_EMIT targetPropertyChanged();
452}
453
454QT_END_NAMESPACE
455
456#include "moc_qquickbehavior_p.cpp"
457

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