diff options
Diffstat (limited to 'src/quick/util/qquickbehavior.cpp')
-rw-r--r-- | src/quick/util/qquickbehavior.cpp | 345 |
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 |