diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2021-02-09 16:24:31 +0100 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2021-05-25 16:09:05 +0200 |
commit | e3c9fdfffb2f62da90938e8e5e76f1d15f30385d (patch) | |
tree | a0c929025f698b86055e3ff8ebf34beb84184455 /src/quick/util/qquickbehavior.cpp | |
parent | 14b8fc630f2a77b1382136e6943f2cafb44bd154 (diff) |
Behavior: intercept bindings
Use the new API in QQmlPropertyValueInterceptor and
QQmlInterceptorMetaObject to acutally intercept bindable properties in
Behavior.
This works as follows:
We intercept the bindable metacall, and construct an untyped proxy
property. The proxy property has storage for the property data
(currently via constructing a QVariant for it). It also has its own
binding data. We install an observer on the proxy binding data which
notifies us whenever the proxy changes. That observer is actually the
QQuickBehaviorPrivate (which now inherits QPropertyObserver). Whenever
the observer triggers, we read the current value of the proxy property
and call QQuickBehavior::write with it. That's how Behavior can now
track updates. As binding updates end up calling the write method of the
Behavior, we get support for toggling enabled for free.
The final part of the puzzle is how to get the property system to use
the proxy property instead of the real property. This is done when we
intercept the Bindable metacall: Instead of returning the source's
QUntypedBindable, we construct a custom one. Its functions do the
following:
- setting/getting values and bindings do not affect the source, but
instead operate on the proxy
- observers are still installed on the source; that way, they see all
writes done by the interceptor, instead of only the direct writes to
the source property
- makeBinding forwards to the source
- We make use of the metatype multiplexing hack in the getter
Task-number: QTBUG-90999
Change-Id: Ib91a12b05975f1257026ba4d2b64ec14852d4a14
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Diffstat (limited to 'src/quick/util/qquickbehavior.cpp')
-rw-r--r-- | src/quick/util/qquickbehavior.cpp | 158 |
1 files changed, 149 insertions, 9 deletions
diff --git a/src/quick/util/qquickbehavior.cpp b/src/quick/util/qquickbehavior.cpp index 1126a75962..eeda609816 100644 --- a/src/quick/util/qquickbehavior.cpp +++ b/src/quick/util/qquickbehavior.cpp @@ -53,24 +53,140 @@ QT_BEGIN_NAMESPACE -class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener +/*! + \internal + \brief The UntypedProxyProperty class is a property used in Behavior to handle bindable properties. + + Whenever a bindable property with a Behavior gets a request for its bindable interface, we instead + return the bindable interface of the UntypedProxyProperty. This causes all reads and writes to be + intercepted to use \c m_storage instead; moreover, any installed binding will also use \c m_storage + as the property data for the binding. + + The BehaviorPrivate acts as an observer, listening to changes of the proxy property. If those occur, + QQuickBehavior::write is called with the new value, which will then adjust the actual property (playing + animations if necessary). + + \warning The interception mechanism works only via the metaobject system, just like it is the case with + non-binadble properties and writes. Bypassing the metaobject system can thus lead to inconsistent results; + it is however currently safe, as we do not publically expose the classes, and the code in Quick plays + nicely. + */ +class UntypedProxyProperty : public QUntypedPropertyData { - Q_DECLARE_PUBLIC(QQuickBehavior) + QtPrivate::QPropertyBindingData m_bindingData; + QUntypedPropertyData *m_sourcePropertyData; + const QtPrivate::QBindableInterface *m_sourceInterface; + QVariant m_storage; public: - QQuickBehaviorPrivate() : animation(nullptr), animationInstance(nullptr), enabled(true), finalized(false) - , blockRunningChanged(false) {} + void static getter(const QUntypedPropertyData *d, void *value) + { + auto This = static_cast<const UntypedProxyProperty *>(d); + // multiplexing: If the flag is set, we want to receive the metatype instead + if (quintptr(value) & QtPrivate::QBindableInterface::MetaTypeAccessorFlag) { + *reinterpret_cast<QMetaType *>(quintptr(value) & + ~QtPrivate::QBindableInterface::MetaTypeAccessorFlag) + = This->type(); + return; + } + This->type().construct(value, This->m_storage.constData()); + This->m_bindingData.registerWithCurrentlyEvaluatingBinding(); + } + + void static setter(QUntypedPropertyData *d, const void *value) + { + auto This = static_cast<UntypedProxyProperty *>(d); + This->type().construct(This->m_storage.data(), value); + This->m_bindingData.notifyObservers(reinterpret_cast<QUntypedPropertyData *>(This->m_storage.data())); + } + + static QUntypedPropertyBinding bindingGetter(const QUntypedPropertyData *d) + { + auto This = static_cast<const UntypedProxyProperty *>(d); + return QUntypedPropertyBinding(This->m_bindingData.binding()); + } + + static QUntypedPropertyBinding bindingSetter(QUntypedPropertyData *d, + const QUntypedPropertyBinding &binding) + { + auto This = static_cast<UntypedProxyProperty *>(d); + if (binding.valueMetaType() != This->type()) + return {}; + return This->m_bindingData.setBinding(binding, + reinterpret_cast<QUntypedPropertyData *>( + This->m_storage.data())); + } + + static QUntypedPropertyBinding makeBinding(const QUntypedPropertyData *d, + const QPropertyBindingSourceLocation &location) + { + auto This = static_cast<const UntypedProxyProperty *>(d); + return This->m_sourceInterface->makeBinding(This->m_sourcePropertyData, location); + } + + static void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer) + { + auto This = static_cast<const UntypedProxyProperty *>(d); + This->m_sourceInterface->setObserver(This->m_sourcePropertyData, observer); + } + + + + UntypedProxyProperty(QUntypedBindable bindable, QQuickBehaviorPrivate *behavior); + QUntypedBindable getBindable(); + QMetaType type() const { return m_storage.metaType(); } + QVariant value() const {return m_storage;} +}; + +static constexpr inline QtPrivate::QBindableInterface untypedProxyPropertyBindableInterafce { + &UntypedProxyProperty::getter, + &UntypedProxyProperty::setter, + &UntypedProxyProperty::bindingGetter, + &UntypedProxyProperty::bindingSetter, + &UntypedProxyProperty::makeBinding, + &UntypedProxyProperty::setObserver, + /*metatype*/nullptr +}; + +struct UntypedProxyPropertyBindable : QUntypedBindable { + UntypedProxyPropertyBindable(UntypedProxyProperty *property) + :QUntypedBindable (property, &untypedProxyPropertyBindableInterafce) + {} +}; + +QUntypedBindable UntypedProxyProperty::getBindable() +{ + return UntypedProxyPropertyBindable {const_cast<UntypedProxyProperty *>(this)}; +} + +class QQuickBehaviorPrivate : public QObjectPrivate, public QAnimationJobChangeListener, public QPropertyObserver +{ + Q_DECLARE_PUBLIC(QQuickBehavior) +public: + static void onProxyChanged(QPropertyObserver *, QUntypedPropertyData *); + QQuickBehaviorPrivate() + : QPropertyObserver(&QQuickBehaviorPrivate::onProxyChanged) {} void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) override; QQmlProperty property; QVariant targetValue; - QPointer<QQuickAbstractAnimation> animation; - QAbstractAnimationJob *animationInstance; - bool enabled; - bool finalized; - bool blockRunningChanged; + QPointer<QQuickAbstractAnimation> animation = nullptr; + QAbstractAnimationJob *animationInstance = nullptr; + std::unique_ptr<UntypedProxyProperty> propertyProxy; + bool enabled = true; + bool finalized = false; + bool blockRunningChanged = false; + }; +UntypedProxyProperty::UntypedProxyProperty(QUntypedBindable bindable, QQuickBehaviorPrivate *behavior) : + m_sourcePropertyData(QUntypedBindablePrivate::getPropertyData(bindable)), + m_sourceInterface(QUntypedBindablePrivate::getInterface(bindable)), + m_storage(QVariant(QUntypedBindablePrivate::getInterface(bindable)->metaType())) +{ + behavior->setSource(m_bindingData); +} + /*! \qmltype Behavior \instantiates QQuickBehavior @@ -141,6 +257,12 @@ void QQuickBehavior::setAnimation(QQuickAbstractAnimation *animation) } +void QQuickBehaviorPrivate::onProxyChanged(QPropertyObserver *observer, QUntypedPropertyData *) +{ + auto This = static_cast<QQuickBehaviorPrivate *>(observer); + This->q_func()->write(This->propertyProxy->value()); +} + void QQuickBehaviorPrivate::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState,QAbstractAnimationJob::State) { if (!blockRunningChanged && animation) @@ -326,6 +448,15 @@ void QQuickBehavior::write(const QVariant &value) QQmlPropertyPrivate::write(d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); } +bool QQuickBehavior::bindable(QUntypedBindable *untypedBindable, QUntypedBindable target) +{ + Q_D(QQuickBehavior); + if (!d->propertyProxy) + d->propertyProxy = std::make_unique<UntypedProxyProperty>(target, d); + *untypedBindable = d->propertyProxy->getBindable(); + return true; +} + void QQuickBehavior::setTarget(const QQmlProperty &property) { Q_D(QQuickBehavior); @@ -333,6 +464,15 @@ void QQuickBehavior::setTarget(const QQmlProperty &property) if (d->animation) d->animation->setDefaultTarget(property); + if (QMetaProperty metaProp = property.property(); metaProp.isBindable()) { + QUntypedBindable untypedBindable = metaProp.bindable(property.object()); + d->propertyProxy = std::make_unique<UntypedProxyProperty>(untypedBindable, d); + if (untypedBindable.hasBinding()) { + // should not happen as bindings should get initialized only after interceptors + UntypedProxyProperty::bindingSetter(d->propertyProxy.get(), untypedBindable.takeBinding()); + } + } + QQmlEnginePrivate *engPriv = QQmlEnginePrivate::get(qmlEngine(this)); static int finalizedIdx = -1; if (finalizedIdx < 0) |