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