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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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