Revision control

++ // 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
++ // Qt-Security score:significant reason:default
++
++ #include "qvariantanimation.h"
++ #include "qvariantanimation_p.h"
++
++ #include <QtCore/qrect.h>
++ #include <QtCore/qline.h>
++ #include <QtCore/qmutex.h>
++ #include <QtCore/private/qlocking_p.h>
++
++ #include <algorithm>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \class QVariantAnimation
++ \inmodule QtCore
++ \ingroup animation
++ \brief The QVariantAnimation class provides a base class for animations.
++ \since 4.6
++
++ This class is part of \l{The Animation Framework}. It serves as a
++ base class for property and item animations, with functions for
++ shared functionality.
++
++ The class performs interpolation over
++ \l{QVariant}s, but leaves using the interpolated values to its
++ subclasses. Currently, Qt provides QPropertyAnimation, which
++ animates Qt \l{Qt's Property System}{properties}. See the
++ QPropertyAnimation class description if you wish to animate such
++ properties.
++
++ You can then set start and end values for the property by calling
++ setStartValue() and setEndValue(), and finally call start() to
++ start the animation. QVariantAnimation will interpolate the
++ property of the target object and emit valueChanged(). To react to
++ a change in the current value you have to reimplement the
++ updateCurrentValue() virtual function or connect to said signal.
++
++ It is also possible to set values at specified steps situated
++ between the start and end value. The interpolation will then
++ touch these points at the specified steps. Note that the start and
++ end values are defined as the key values at 0.0 and 1.0.
++
++ There are two ways to affect how QVariantAnimation interpolates
++ the values. You can set an easing curve by calling
++ setEasingCurve(), and configure the duration by calling
++ setDuration(). You can change how the \l{QVariant}s are interpolated
++ by creating a subclass of QVariantAnimation, and reimplementing
++ the virtual interpolated() function.
++
++ Subclassing QVariantAnimation can be an alternative if you have
++ \l{QVariant}s that you do not wish to declare as Qt properties.
++ Note, however, that you in most cases will be better off declaring
++ your QVariant as a property.
++
++ Not all QVariant types are supported. Below is a list of currently
++ supported QVariant types:
++
++ \list
++ \li \l{QMetaType::}{Int}
++ \li \l{QMetaType::}{UInt}
++ \li \l{QMetaType::}{Double}
++ \li \l{QMetaType::}{Float}
++ \li \l{QMetaType::}{QLine}
++ \li \l{QMetaType::}{QLineF}
++ \li \l{QMetaType::}{QPoint}
++ \li \l{QMetaType::}{QPointF}
++ \li \l{QMetaType::}{QSize}
++ \li \l{QMetaType::}{QSizeF}
++ \li \l{QMetaType::}{QRect}
++ \li \l{QMetaType::}{QRectF}
++ \li \l{QMetaType::}{QColor}
++ \endlist
++
++ If you need to interpolate other variant types, including custom
++ types, you have to implement interpolation for these yourself.
++ To do this, you can register an interpolator function for a given
++ type. This function takes 3 parameters: the start value, the end value,
++ and the current progress.
++
++ Example:
++ \snippet code/src_corelib_animation_qvariantanimation.cpp 0
++
++ Another option is to reimplement interpolated(), which returns
++ interpolation values for the value being interpolated.
++
++ \omit We need some snippets around here. \endomit
++
++ \sa QPropertyAnimation, QAbstractAnimation, {The Animation Framework}
++ */
++
++ /*!
++ \fn void QVariantAnimation::valueChanged(const QVariant &value)
++
++ QVariantAnimation emits this signal whenever the current \a value changes.
++
++ \sa currentValue, startValue, endValue
++ */
++
++ /*!
++ This virtual function is called every time the animation's current
++ value changes. The \a value argument is the new current value.
++
++ The base class implementation does nothing.
++
++ \sa currentValue
++ */
++ void QVariantAnimation::updateCurrentValue(const QVariant &) {}
++
++ static bool animationValueLessThan(const QVariantAnimation::KeyValue &p1, const QVariantAnimation::KeyValue &p2)
++ {
++ return p1.first < p2.first;
++ }
++
++ static QVariant defaultInterpolator(const void *, const void *, qreal)
++ {
++ return QVariant();
++ }
++
++ template<> Q_INLINE_TEMPLATE QRect _q_interpolate(const QRect &f, const QRect &t, qreal progress)
++ {
++ QRect ret;
++ ret.setCoords(_q_interpolate(f.left(), t.left(), progress),
++ _q_interpolate(f.top(), t.top(), progress),
++ _q_interpolate(f.right(), t.right(), progress),
++ _q_interpolate(f.bottom(), t.bottom(), progress));
++ return ret;
++ }
++
++ template<> Q_INLINE_TEMPLATE QRectF _q_interpolate(const QRectF &f, const QRectF &t, qreal progress)
++ {
++ qreal x1, y1, w1, h1;
++ f.getRect(&x1, &y1, &w1, &h1);
++ qreal x2, y2, w2, h2;
++ t.getRect(&x2, &y2, &w2, &h2);
++ return QRectF(_q_interpolate(x1, x2, progress), _q_interpolate(y1, y2, progress),
++ _q_interpolate(w1, w2, progress), _q_interpolate(h1, h2, progress));
++ }
++
++ template<> Q_INLINE_TEMPLATE QLine _q_interpolate(const QLine &f, const QLine &t, qreal progress)
++ {
++ return QLine( _q_interpolate(f.p1(), t.p1(), progress), _q_interpolate(f.p2(), t.p2(), progress));
++ }
++
++ template<> Q_INLINE_TEMPLATE QLineF _q_interpolate(const QLineF &f, const QLineF &t, qreal progress)
++ {
++ return QLineF( _q_interpolate(f.p1(), t.p1(), progress), _q_interpolate(f.p2(), t.p2(), progress));
++ }
++
++ QVariantAnimationPrivate::QVariantAnimationPrivate() : duration(250), interpolator(&defaultInterpolator)
++ { }
++
++ QVariantAnimationPrivate::~QVariantAnimationPrivate()
++ = default;
++
++ void QVariantAnimationPrivate::convertValues(int t)
++ {
++ auto type = QMetaType(t);
++ //this ensures that all the keyValues are of type t
++ for (int i = 0; i < keyValues.size(); ++i) {
++ QVariantAnimation::KeyValue &pair = keyValues[i];
++ pair.second.convert(type);
++ }
++ //we also need update to the current interval if needed
++ currentInterval.start.second.convert(type);
++ currentInterval.end.second.convert(type);
++
++ //... and the interpolator
++ updateInterpolator();
++ }
++
++ void QVariantAnimationPrivate::updateInterpolator()
++ {
++ int type = currentInterval.start.second.userType();
++ if (type == currentInterval.end.second.userType())
++ interpolator = getInterpolator(type);
++ else
++ interpolator = nullptr;
++
++ //we make sure that the interpolator is always set to something
++ if (!interpolator)
++ interpolator = &defaultInterpolator;
++ }
++
++ /*!
++ \internal
++ The goal of this function is to update the currentInterval member. As a consequence, we also
++ need to update the currentValue.
++ Set \a force to true to always recalculate the interval.
++ */
++ void QVariantAnimationPrivate::recalculateCurrentInterval(bool force/*=false*/)
++ {
++ // can't interpolate if we don't have at least 2 values
++ if ((keyValues.size() + (defaultStartEndValue.isValid() ? 1 : 0)) < 2)
++ return;
++
++ const qreal endProgress = (direction == QAbstractAnimation::Forward) ? qreal(1) : qreal(0);
++ const qreal progress = easing.value().valueForProgress(
++ duration == 0 ? endProgress : qreal(currentTime) / qreal(duration));
++
++ //0 and 1 are still the boundaries
++ if (force || (currentInterval.start.first > 0 && progress < currentInterval.start.first)
++ || (currentInterval.end.first < 1 && progress > currentInterval.end.first)) {
++ //let's update currentInterval
++ QVariantAnimation::KeyValues::const_iterator it = std::lower_bound(keyValues.constBegin(),
++ keyValues.constEnd(),
++ std::pair{progress, QVariant{}},
++ animationValueLessThan);
++ if (it == keyValues.constBegin()) {
++ //the item pointed to by it is the start element in the range
++ if (it->first == 0 && keyValues.size() > 1) {
++ currentInterval.start = *it;
++ currentInterval.end = *(it+1);
++ } else {
++ currentInterval.start = {qreal(0), defaultStartEndValue};
++ currentInterval.end = *it;
++ }
++ } else if (it == keyValues.constEnd()) {
++ --it; //position the iterator on the last item
++ if (it->first == 1 && keyValues.size() > 1) {
++ //we have an end value (item with progress = 1)
++ currentInterval.start = *(it-1);
++ currentInterval.end = *it;
++ } else {
++ //we use the default end value here
++ currentInterval.start = *it;
++ currentInterval.end = {qreal(1), defaultStartEndValue};
++ }
++ } else {
++ currentInterval.start = *(it-1);
++ currentInterval.end = *it;
++ }
++
++ // update all the values of the currentInterval
++ updateInterpolator();
++ }
++ setCurrentValueForProgress(progress);
++ }
++
++ void QVariantAnimationPrivate::setCurrentValueForProgress(const qreal progress)
++ {
++ Q_Q(QVariantAnimation);
++
++ const qreal startProgress = currentInterval.start.first;
++ const qreal endProgress = currentInterval.end.first;
++ const qreal localProgress =
++ qIsNull(progress - startProgress) ? 0.0 // avoid 0/0 below
++ /* else */ : (progress - startProgress) / (endProgress - startProgress);
++
++ QVariant ret = q->interpolated(currentInterval.start.second,
++ currentInterval.end.second,
++ localProgress);
++ qSwap(currentValue, ret);
++ q->updateCurrentValue(currentValue);
++ Q_CONSTINIT static QBasicAtomicInt changedSignalIndex = Q_BASIC_ATOMIC_INITIALIZER(0);
++ if (!changedSignalIndex.loadRelaxed()) {
++ //we keep the mask so that we emit valueChanged only when needed (for performance reasons)
++ changedSignalIndex.testAndSetRelaxed(0, signalIndex("valueChanged(QVariant)"));
++ }
++ if (isSignalConnected(changedSignalIndex.loadRelaxed()) && currentValue != ret) {
++ //the value has changed
++ emit q->valueChanged(currentValue);
++ }
++ }
++
++ QVariant QVariantAnimationPrivate::valueAt(qreal step) const
++ {
++ const auto sought = std::pair{step, QVariant()};
++ const auto result = std::lower_bound(keyValues.cbegin(), keyValues.cend(), sought,
++ animationValueLessThan);
++ if (result != keyValues.cend() && !animationValueLessThan(sought, *result))
++ return result->second;
++
++ return QVariant();
++ }
++
++ void QVariantAnimationPrivate::setValueAt(qreal step, const QVariant &value)
++ {
++ if (step < qreal(0.0) || step > qreal(1.0)) {
++ qWarning("QVariantAnimation::setValueAt: invalid step = %f", step);
++ return;
++ }
++
++ QVariantAnimation::KeyValue pair(step, value);
++
++ QVariantAnimation::KeyValues::iterator result = std::lower_bound(keyValues.begin(), keyValues.end(), pair, animationValueLessThan);
++ if (result == keyValues.end() || result->first != step) {
++ keyValues.insert(result, pair);
++ } else {
++ if (value.isValid())
++ result->second = value; // replaces the previous value
++ else
++ keyValues.erase(result); // removes the previous value
++ }
++
++ recalculateCurrentInterval(/*force=*/true);
++ }
++
++ void QVariantAnimationPrivate::setDefaultStartEndValue(const QVariant &value)
++ {
++ defaultStartEndValue = value;
++ recalculateCurrentInterval(/*force=*/true);
++ }
++
++ /*!
++ Construct a QVariantAnimation object. \a parent is passed to QAbstractAnimation's
++ constructor.
++ */
++ QVariantAnimation::QVariantAnimation(QObject *parent) : QAbstractAnimation(*new QVariantAnimationPrivate, parent)
++ {
++ }
++
++ /*!
++ \internal
++ */
++ QVariantAnimation::QVariantAnimation(QVariantAnimationPrivate &dd, QObject *parent) : QAbstractAnimation(dd, parent)
++ {
++ }
++
++ /*!
++ Destroys the animation.
++ */
++ QVariantAnimation::~QVariantAnimation()
++ {
++ }
++
++ /*!
++ \property QVariantAnimation::easingCurve
++ \brief the easing curve of the animation
++
++ This property defines the easing curve of the animation. By
++ default, a linear easing curve is used, resulting in linear
++ interpolation. Other curves are provided, for instance,
++ QEasingCurve::InCirc, which provides a circular entry curve.
++ Another example is QEasingCurve::InOutElastic, which provides an
++ elastic effect on the values of the interpolated variant.
++
++ QVariantAnimation will use the QEasingCurve::valueForProgress() to
++ transform the "normalized progress" (currentTime() / totalDuration())
++ of the animation into the effective progress actually
++ used by the animation. It is this effective progress that will be
++ the progress when interpolated() is called. Also, the steps in the
++ keyValues are referring to this effective progress.
++
++ The easing curve is used with the interpolator, the interpolated()
++ virtual function, and the animation's duration to control how the
++ current value changes as the animation progresses.
++ */
++ QEasingCurve QVariantAnimation::easingCurve() const
++ {
++ Q_D(const QVariantAnimation);
++ return d->easing;
++ }
++
++ void QVariantAnimation::setEasingCurve(const QEasingCurve &easing)
++ {
++ Q_D(QVariantAnimation);
++ d->easing.removeBindingUnlessInWrapper();
++ const bool valueChanged = easing != d->easing.valueBypassingBindings();
++ d->easing.setValueBypassingBindings(easing);
++ d->recalculateCurrentInterval();
++ if (valueChanged)
++ d->easing.notify();
++ }
++
++ QBindable<QEasingCurve> QVariantAnimation::bindableEasingCurve()
++ {
++ Q_D(QVariantAnimation);
++ return &d->easing;
++ }
++
++ typedef QList<QVariantAnimation::Interpolator> QInterpolatorVector;
++ Q_GLOBAL_STATIC(QInterpolatorVector, registeredInterpolators)
++ Q_CONSTINIT static QBasicMutex registeredInterpolatorsMutex;
++
++ /*!
++ \fn template <typename T> void qRegisterAnimationInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
++ \relates QVariantAnimation
++ \threadsafe
++
++ Registers a custom interpolator \a func for the template type \c{T}.
++ The interpolator has to be registered before the animation is constructed.
++ To unregister (and use the default interpolator) set \a func to \nullptr.
++
++ \warning Registering a custom interpolator for one of the Qt built-in types
++ (e.g. \c {int}) will affect all animations on this type that will be created
++ later. This might be an undesired behavior. It is therefore recommended to
++ reset the interpolator to the default value after the desired animations
++ have been created.
++ */
++
++ /*!
++ \internal
++ \typedef QVariantAnimation::Interpolator
++
++ This is a typedef for a pointer to a function with the following
++ signature:
++ \snippet code/src_corelib_animation_qvariantanimation.cpp 1
++
++ */
++
++ /*!
++ * \internal
++ * Registers a custom interpolator \a func for the specific \a interpolationType.
++ * The interpolator has to be registered before the animation is constructed.
++ * To unregister (and use the default interpolator) set \a func to \nullptr.
++ */
++ void QVariantAnimation::registerInterpolator(QVariantAnimation::Interpolator func, int interpolationType)
++ {
++ // will override any existing interpolators
++ QInterpolatorVector *interpolators = registeredInterpolators();
++ // When built on solaris with GCC, the destructors can be called
++ // in such an order that we get here with interpolators == NULL,
++ // to continue causes the app to crash on exit with a SEGV
++ if (interpolators) {
++ const auto locker = qt_scoped_lock(registeredInterpolatorsMutex);
++ if (interpolationType >= interpolators->size())
++ interpolators->resize(interpolationType + 1);
++ interpolators->replace(interpolationType, func);
++ }
++ }
++
++
++ template<typename T> static inline QVariantAnimation::Interpolator castToInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
++ {
++ return reinterpret_cast<QVariantAnimation::Interpolator>(reinterpret_cast<void(*)()>(func));
++ }
++
++ QVariantAnimation::Interpolator QVariantAnimationPrivate::getInterpolator(int interpolationType)
++ {
++ {
++ QInterpolatorVector *interpolators = registeredInterpolators();
++ const auto locker = qt_scoped_lock(registeredInterpolatorsMutex);
++ QVariantAnimation::Interpolator ret = nullptr;
++ if (interpolationType < interpolators->size()) {
++ ret = interpolators->at(interpolationType);
++ if (ret) return ret;
++ }
++ }
++
++ switch(interpolationType)
++ {
++ case QMetaType::Int:
++ return castToInterpolator(_q_interpolateVariant<int>);
++ case QMetaType::UInt:
++ return castToInterpolator(_q_interpolateVariant<uint>);
++ case QMetaType::Double:
++ return castToInterpolator(_q_interpolateVariant<double>);
++ case QMetaType::Float:
++ return castToInterpolator(_q_interpolateVariant<float>);
++ case QMetaType::QLine:
++ return castToInterpolator(_q_interpolateVariant<QLine>);
++ case QMetaType::QLineF:
++ return castToInterpolator(_q_interpolateVariant<QLineF>);
++ case QMetaType::QPoint:
++ return castToInterpolator(_q_interpolateVariant<QPoint>);
++ case QMetaType::QPointF:
++ return castToInterpolator(_q_interpolateVariant<QPointF>);
++ case QMetaType::QSize:
++ return castToInterpolator(_q_interpolateVariant<QSize>);
++ case QMetaType::QSizeF:
++ return castToInterpolator(_q_interpolateVariant<QSizeF>);
++ case QMetaType::QRect:
++ return castToInterpolator(_q_interpolateVariant<QRect>);
++ case QMetaType::QRectF:
++ return castToInterpolator(_q_interpolateVariant<QRectF>);
++ default:
++ return nullptr; //this type is not handled
++ }
++ }
++
++ /*!
++ \property QVariantAnimation::duration
++ \brief the duration of the animation
++
++ This property describes the duration in milliseconds of the
++ animation. The default duration is 250 milliseconds.
++
++ \sa QAbstractAnimation::duration()
++ */
++ int QVariantAnimation::duration() const
++ {
++ Q_D(const QVariantAnimation);
++ return d->duration;
++ }
++
++ void QVariantAnimation::setDuration(int msecs)
++ {
++ Q_D(QVariantAnimation);
++ if (msecs < 0) {
++ qWarning("QVariantAnimation::setDuration: cannot set a negative duration");
++ return;
++ }
++ d->duration.removeBindingUnlessInWrapper();
++ if (d->duration.valueBypassingBindings() != msecs) {
++ d->duration.setValueBypassingBindings(msecs);
++ d->recalculateCurrentInterval();
++ d->duration.notify();
++ }
++ }
++
++ QBindable<int> QVariantAnimation::bindableDuration()
++ {
++ Q_D(QVariantAnimation);
++ return &d->duration;
++ }
++
++ /*!
++ \property QVariantAnimation::startValue
++ \brief the optional start value of the animation
++
++ This property describes the optional start value of the animation. If
++ omitted, or if a null QVariant is assigned as the start value, the
++ animation will use the current position of the end when the animation
++ is started.
++
++ \sa endValue
++ */
++ QVariant QVariantAnimation::startValue() const
++ {
++ return keyValueAt(0);
++ }
++
++ void QVariantAnimation::setStartValue(const QVariant &value)
++ {
++ setKeyValueAt(0, value);
++ }
++
++ /*!
++ \property QVariantAnimation::endValue
++ \brief the end value of the animation
++
++ This property describes the end value of the animation.
++
++ \sa startValue
++ */
++ QVariant QVariantAnimation::endValue() const
++ {
++ return keyValueAt(1);
++ }
++
++ void QVariantAnimation::setEndValue(const QVariant &value)
++ {
++ setKeyValueAt(1, value);
++ }
++
++
++ /*!
++ Returns the key frame value for the given \a step. The given \a step
++ must be in the range 0 to 1. If there is no KeyValue for \a step,
++ it returns an invalid QVariant.
++
++ \sa keyValues(), setKeyValueAt()
++ */
++ QVariant QVariantAnimation::keyValueAt(qreal step) const
++ {
++ return d_func()->valueAt(step);
++ }
++
++ /*!
++ \typedef QVariantAnimation::KeyValue
++
++ This is a typedef for std::pair<qreal, QVariant>.
++ */
++ /*!
++ \typedef QVariantAnimation::KeyValues
++
++ This is a typedef for QList<KeyValue>
++ */
++
++ /*!
++ Creates a key frame at the given \a step with the given \a value.
++ The given \a step must be in the range 0 to 1.
++
++ \sa setKeyValues(), keyValueAt()
++ */
++ void QVariantAnimation::setKeyValueAt(qreal step, const QVariant &value)
++ {
++ d_func()->setValueAt(step, value);
++ }
++
++ /*!
++ Returns the key frames of this animation.
++
++ \sa keyValueAt(), setKeyValues()
++ */
++ QVariantAnimation::KeyValues QVariantAnimation::keyValues() const
++ {
++ return d_func()->keyValues;
++ }
++
++ /*!
++ Replaces the current set of key frames with the given \a keyValues.
++ the step of the key frames must be in the range 0 to 1.
++
++ \sa keyValues(), keyValueAt()
++ */
++ void QVariantAnimation::setKeyValues(const KeyValues &keyValues)
++ {
++ Q_D(QVariantAnimation);
++ d->keyValues = keyValues;
++ std::sort(d->keyValues.begin(), d->keyValues.end(), animationValueLessThan);
++ d->recalculateCurrentInterval(/*force=*/true);
++ }
++
++ /*!
++ \property QVariantAnimation::currentValue
++ \brief the current value of the animation.
++
++ This property describes the current value; an interpolated value
++ between the \l{startValue}{start value} and the \l{endValue}{end
++ value}, using the current time for progress. The value itself is
++ obtained from interpolated(), which is called repeatedly as the
++ animation is running.
++
++ QVariantAnimation calls the virtual updateCurrentValue() function
++ when the current value changes. This is particularly useful for
++ subclasses that need to track updates. For example,
++ QPropertyAnimation uses this function to animate Qt \l{Qt's
++ Property System}{properties}.
++
++ \sa startValue, endValue
++ */
++ QVariant QVariantAnimation::currentValue() const
++ {
++ Q_D(const QVariantAnimation);
++ if (!d->currentValue.isValid())
++ const_cast<QVariantAnimationPrivate*>(d)->recalculateCurrentInterval();
++ return d->currentValue;
++ }
++
++ /*!
++ \reimp
++ */
++ bool QVariantAnimation::event(QEvent *event)
++ {
++ return QAbstractAnimation::event(event);
++ }
++
++ /*!
++ \reimp
++ */
++ void QVariantAnimation::updateState(QAbstractAnimation::State newState,
++ QAbstractAnimation::State oldState)
++ {
++ Q_UNUSED(oldState);
++ Q_UNUSED(newState);
++ }
++
++ /*!
++
++ This virtual function returns the linear interpolation between
++ variants \a from and \a to, at \a progress, usually a value
++ between 0 and 1. You can reimplement this function in a subclass
++ of QVariantAnimation to provide your own interpolation algorithm.
++
++ Note that in order for the interpolation to work with a
++ QEasingCurve that return a value smaller than 0 or larger than 1
++ (such as QEasingCurve::InBack) you should make sure that it can
++ extrapolate. If the semantic of the datatype does not allow
++ extrapolation this function should handle that gracefully.
++
++ You should call the QVariantAnimation implementation of this
++ function if you want your class to handle the types already
++ supported by Qt (see class QVariantAnimation description for a
++ list of supported types).
++
++ \sa QEasingCurve
++ */
++ QVariant QVariantAnimation::interpolated(const QVariant &from, const QVariant &to, qreal progress) const
++ {
++ return d_func()->interpolator(from.constData(), to.constData(), progress);
++ }
++
++ /*!
++ \reimp
++ */
++ void QVariantAnimation::updateCurrentTime(int)
++ {
++ d_func()->recalculateCurrentInterval();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qvariantanimation.cpp"