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 | |
19 | QT_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 | */ |
39 | class UntypedProxyProperty : public QUntypedPropertyData |
40 | { |
41 | QtPrivate::QPropertyBindingData m_bindingData; |
42 | QUntypedPropertyData *m_sourcePropertyData; |
43 | const QtPrivate::QBindableInterface *m_sourceInterface; |
44 | QVariant m_storage; |
45 | public: |
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 | |
111 | static 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 | |
121 | struct UntypedProxyPropertyBindable : QUntypedBindable { |
122 | UntypedProxyPropertyBindable(UntypedProxyProperty *property) |
123 | :QUntypedBindable (property, &untypedProxyPropertyBindableInterafce) |
124 | {} |
125 | }; |
126 | |
127 | QUntypedBindable UntypedProxyProperty::getBindable() |
128 | { |
129 | return UntypedProxyPropertyBindable {const_cast<UntypedProxyProperty *>(this)}; |
130 | } |
131 | |
132 | class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener, public QPropertyObserver |
133 | { |
134 | Q_DECLARE_PUBLIC(QQuickBehavior) |
135 | public: |
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 | |
152 | UntypedProxyProperty::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 | |
190 | QQuickBehavior::QQuickBehavior(QObject *parent) |
191 | : QObject(*(new QQuickBehaviorPrivate), parent) |
192 | { |
193 | } |
194 | |
195 | QQuickBehavior::~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 | |
208 | QQuickAbstractAnimation *QQuickBehavior::animation() |
209 | { |
210 | Q_D(QQuickBehavior); |
211 | return d->animation; |
212 | } |
213 | |
214 | void 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 | |
230 | void QQuickBehaviorPrivate::onProxyChanged(QPropertyObserver *observer, QUntypedPropertyData *) |
231 | { |
232 | auto This = static_cast<QQuickBehaviorPrivate *>(observer); |
233 | This->q_func()->write(value: This->propertyProxy->value()); |
234 | } |
235 | |
236 | void 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 | |
251 | bool QQuickBehavior::enabled() const |
252 | { |
253 | Q_D(const QQuickBehavior); |
254 | return d->enabled; |
255 | } |
256 | |
257 | void 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 | */ |
274 | QVariant 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 | */ |
348 | QQmlProperty QQuickBehavior::targetProperty() const |
349 | { |
350 | Q_D(const QQuickBehavior); |
351 | return d->property; |
352 | } |
353 | |
354 | void QQuickBehavior::componentFinalized() |
355 | { |
356 | Q_D(QQuickBehavior); |
357 | d->finalized = true; |
358 | } |
359 | |
360 | void 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 ¤tValue = 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 | |
427 | bool 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 | |
436 | void 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 | |
455 | QT_END_NAMESPACE |
456 | |
457 | #include "moc_qquickbehavior_p.cpp" |
458 |
Definitions
- UntypedProxyProperty
- getter
- setter
- bindingGetter
- bindingSetter
- makeBinding
- setObserver
- type
- value
- untypedProxyPropertyBindableInterafce
- UntypedProxyPropertyBindable
- UntypedProxyPropertyBindable
- getBindable
- QQuickBehaviorPrivate
- QQuickBehaviorPrivate
- UntypedProxyProperty
- QQuickBehavior
- ~QQuickBehavior
- animation
- setAnimation
- onProxyChanged
- animationStateChanged
- enabled
- setEnabled
- targetValue
- targetProperty
- componentFinalized
- write
- bindable
Learn to use CMake with our Intro Training
Find out more