aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickbehavior.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/util/qquickbehavior.cpp')
-rw-r--r--src/quick/util/qquickbehavior.cpp345
1 files changed, 270 insertions, 75 deletions
diff --git a/src/quick/util/qquickbehavior.cpp b/src/quick/util/qquickbehavior.cpp
index d024c0099b..dd8ddad7ca 100644
--- a/src/quick/util/qquickbehavior.cpp
+++ b/src/quick/util/qquickbehavior.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQuick module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickbehavior_p.h"
@@ -50,27 +14,149 @@
#include <private/qquickanimatorjob_p.h>
#include <private/qobject_p.h>
+#include <QtCore/qpointer.h>
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);
+ const QMetaType type = This->type();
+ if (binding.valueMetaType() != type)
+ return {};
+
+ // We want to notify in any case here because the target property should be set
+ // even if our proxy binding results in the default value.
+ QPropertyBindingPrivate::get(binding)->scheduleNotify();
+ 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(bindable.metaType()))
+{
+ behavior->setSource(m_bindingData);
+}
+
/*!
\qmltype Behavior
\instantiates QQuickBehavior
@@ -97,7 +183,7 @@ public:
state change. For general advice on using Behaviors to animate state changes, see
\l{Using Qt Quick Behaviors with States}.
- \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt QML}
+ \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt Qml}
*/
@@ -114,7 +200,7 @@ QQuickBehavior::~QQuickBehavior()
/*!
\qmlproperty Animation QtQuick::Behavior::animation
- \default
+ \qmldefault
This property holds the animation to run when the behavior is triggered.
*/
@@ -141,9 +227,15 @@ 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)
+ if (!blockRunningChanged && animation)
animation->notifyRunningChanged(newState == QAbstractAnimationJob::Running);
}
@@ -171,26 +263,122 @@ void QQuickBehavior::setEnabled(bool enabled)
emit enabledChanged();
}
+/*!
+ \qmlproperty Variant QtQuick::Behavior::targetValue
+
+ This property holds the target value of the property being controlled by the Behavior.
+ This value is set by the Behavior before the animation is started.
+
+ \since QtQuick 2.13
+*/
+QVariant QQuickBehavior::targetValue() const
+{
+ Q_D(const QQuickBehavior);
+ return d->targetValue;
+}
+
+/*!
+ \readonly
+ \qmlpropertygroup QtQuick::Behavior::targetProperty
+ \qmlproperty string QtQuick::Behavior::targetProperty.name
+ \qmlproperty QtObject QtQuick::Behavior::targetProperty.object
+
+ \table
+ \header
+ \li Property
+ \li Description
+ \row
+ \li name
+ \li This property holds the name of the property being controlled by this Behavior.
+ \row
+ \li object
+ \li This property holds the object of the property being controlled by this Behavior.
+ \endtable
+
+ This property can be used to define custom behaviors based on the name or the object of
+ the property being controlled.
+
+ The following example defines a Behavior fading out and fading in its target object
+ when the property it controls changes:
+ \qml
+ // FadeBehavior.qml
+ import QtQuick 2.15
+
+ Behavior {
+ id: root
+ property Item fadeTarget: targetProperty.object
+ SequentialAnimation {
+ NumberAnimation {
+ target: root.fadeTarget
+ property: "opacity"
+ to: 0
+ easing.type: Easing.InQuad
+ }
+ PropertyAction { } // actually change the controlled property between the 2 other animations
+ NumberAnimation {
+ target: root.fadeTarget
+ property: "opacity"
+ to: 1
+ easing.type: Easing.OutQuad
+ }
+ }
+ }
+ \endqml
+
+ This can be used to animate a text when it changes:
+ \qml
+ import QtQuick 2.15
+
+ Text {
+ id: root
+ property int counter
+ text: counter
+ FadeBehavior on text {}
+ Timer {
+ running: true
+ repeat: true
+ interval: 1000
+ onTriggered: ++root.counter
+ }
+ }
+ \endqml
+
+ \since QtQuick 2.15
+*/
+QQmlProperty QQuickBehavior::targetProperty() const
+{
+ Q_D(const QQuickBehavior);
+ return d->property;
+}
+
+void QQuickBehavior::componentFinalized()
+{
+ Q_D(QQuickBehavior);
+ d->finalized = true;
+}
+
void QQuickBehavior::write(const QVariant &value)
{
Q_D(QQuickBehavior);
+ const bool targetValueHasChanged = d->targetValue != value;
+ if (targetValueHasChanged) {
+ d->targetValue = value;
+ emit targetValueChanged(); // emitting the signal here should allow
+ } // d->enabled to change if scripted by the user.
bool bypass = !d->enabled || !d->finalized || QQmlEnginePrivate::designerMode();
if (!bypass)
qmlExecuteDeferred(this);
- if (!d->animation || bypass) {
+ if (QQmlData::wasDeleted(d->animation) || bypass) {
if (d->animationInstance)
d->animationInstance->stop();
QQmlPropertyPrivate::write(d->property, value, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
- d->targetValue = value;
return;
}
bool behaviorActive = d->animation->isRunning();
- if (behaviorActive && value == d->targetValue)
+ if (behaviorActive && !targetValueHasChanged)
return;
- d->targetValue = value;
-
if (d->animationInstance
&& (d->animationInstance->duration() != -1
|| d->animationInstance->isRenderThreadProxy())
@@ -218,25 +406,33 @@ void QQuickBehavior::write(const QVariant &value)
actions << action;
QList<QQmlProperty> after;
- QAbstractAnimationJob *prev = d->animationInstance;
- d->animationInstance = d->animation->transition(actions, after, QQuickAbstractAnimation::Forward);
-
- if (d->animationInstance && d->animation->threadingModel() == QQuickAbstractAnimation::RenderThread)
- d->animationInstance = new QQuickAnimatorProxyJob(d->animationInstance, d->animation);
-
- if (prev && prev != d->animationInstance)
- delete prev;
+ auto *newInstance = d->animation->transition(actions, after, QQuickAbstractAnimation::Forward);
+ Q_ASSERT(!newInstance || newInstance != d->animationInstance);
+ delete d->animationInstance;
+ d->animationInstance = newInstance;
if (d->animationInstance) {
- if (d->animationInstance != prev)
- d->animationInstance->addAnimationChangeListener(d, QAbstractAnimationJob::StateChange);
+ if (d->animation->threadingModel() == QQuickAbstractAnimation::RenderThread)
+ d->animationInstance = new QQuickAnimatorProxyJob(d->animationInstance, d->animation);
+
+ d->animationInstance->addAnimationChangeListener(d, QAbstractAnimationJob::StateChange);
d->animationInstance->start();
d->blockRunningChanged = false;
}
+
if (!after.contains(d->property))
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);
@@ -244,17 +440,16 @@ void QQuickBehavior::setTarget(const QQmlProperty &property)
if (d->animation)
d->animation->setDefaultTarget(property);
- QQmlEnginePrivate *engPriv = QQmlEnginePrivate::get(qmlEngine(this));
- static int finalizedIdx = -1;
- if (finalizedIdx < 0)
- finalizedIdx = metaObject()->indexOfSlot("componentFinalized()");
- engPriv->registerFinalizeCallback(this, finalizedIdx);
-}
+ 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());
+ }
+ }
-void QQuickBehavior::componentFinalized()
-{
- Q_D(QQuickBehavior);
- d->finalized = true;
+ Q_EMIT targetPropertyChanged();
}
QT_END_NAMESPACE