aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickbehavior.cpp
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2021-02-09 16:24:31 +0100
committerFabian Kosmale <fabian.kosmale@qt.io>2021-05-25 16:09:05 +0200
commite3c9fdfffb2f62da90938e8e5e76f1d15f30385d (patch)
treea0c929025f698b86055e3ff8ebf34beb84184455 /src/quick/util/qquickbehavior.cpp
parent14b8fc630f2a77b1382136e6943f2cafb44bd154 (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.cpp158
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)