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 | |
18 | QT_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 | */ |
38 | class UntypedProxyProperty : public QUntypedPropertyData |
39 | { |
40 | QtPrivate::QPropertyBindingData m_bindingData; |
41 | QUntypedPropertyData *m_sourcePropertyData; |
42 | const QtPrivate::QBindableInterface *m_sourceInterface; |
43 | QVariant m_storage; |
44 | public: |
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 | |
110 | static 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 | |
120 | struct UntypedProxyPropertyBindable : QUntypedBindable { |
121 | UntypedProxyPropertyBindable(UntypedProxyProperty *property) |
122 | :QUntypedBindable (property, &untypedProxyPropertyBindableInterafce) |
123 | {} |
124 | }; |
125 | |
126 | QUntypedBindable UntypedProxyProperty::getBindable() |
127 | { |
128 | return UntypedProxyPropertyBindable {const_cast<UntypedProxyProperty *>(this)}; |
129 | } |
130 | |
131 | class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener, public QPropertyObserver |
132 | { |
133 | Q_DECLARE_PUBLIC(QQuickBehavior) |
134 | public: |
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 | |
151 | UntypedProxyProperty::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 | |
189 | QQuickBehavior::QQuickBehavior(QObject *parent) |
190 | : QObject(*(new QQuickBehaviorPrivate), parent) |
191 | { |
192 | } |
193 | |
194 | QQuickBehavior::~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 | |
207 | QQuickAbstractAnimation *QQuickBehavior::animation() |
208 | { |
209 | Q_D(QQuickBehavior); |
210 | return d->animation; |
211 | } |
212 | |
213 | void 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 | |
229 | void QQuickBehaviorPrivate::onProxyChanged(QPropertyObserver *observer, QUntypedPropertyData *) |
230 | { |
231 | auto This = static_cast<QQuickBehaviorPrivate *>(observer); |
232 | This->q_func()->write(value: This->propertyProxy->value()); |
233 | } |
234 | |
235 | void 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 | |
250 | bool QQuickBehavior::enabled() const |
251 | { |
252 | Q_D(const QQuickBehavior); |
253 | return d->enabled; |
254 | } |
255 | |
256 | void 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 | */ |
273 | QVariant 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 | */ |
347 | QQmlProperty QQuickBehavior::targetProperty() const |
348 | { |
349 | Q_D(const QQuickBehavior); |
350 | return d->property; |
351 | } |
352 | |
353 | void QQuickBehavior::componentFinalized() |
354 | { |
355 | Q_D(QQuickBehavior); |
356 | d->finalized = true; |
357 | } |
358 | |
359 | void 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 ¤tValue = 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 | |
426 | bool 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 | |
435 | void 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 | |
454 | QT_END_NAMESPACE |
455 | |
456 | #include "moc_qquickbehavior_p.cpp" |
457 | |