1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qvariantanimation.h"
5#include "qvariantanimation_p.h"
6
7#include <QtCore/qrect.h>
8#include <QtCore/qline.h>
9#include <QtCore/qmutex.h>
10#include <QtCore/private/qlocking_p.h>
11
12#include <algorithm>
13
14QT_BEGIN_NAMESPACE
15
16/*!
17 \class QVariantAnimation
18 \inmodule QtCore
19 \ingroup animation
20 \brief The QVariantAnimation class provides a base class for animations.
21 \since 4.6
22
23 This class is part of \l{The Animation Framework}. It serves as a
24 base class for property and item animations, with functions for
25 shared functionality.
26
27 The class performs interpolation over
28 \l{QVariant}s, but leaves using the interpolated values to its
29 subclasses. Currently, Qt provides QPropertyAnimation, which
30 animates Qt \l{Qt's Property System}{properties}. See the
31 QPropertyAnimation class description if you wish to animate such
32 properties.
33
34 You can then set start and end values for the property by calling
35 setStartValue() and setEndValue(), and finally call start() to
36 start the animation. QVariantAnimation will interpolate the
37 property of the target object and emit valueChanged(). To react to
38 a change in the current value you have to reimplement the
39 updateCurrentValue() virtual function or connect to said signal.
40
41 It is also possible to set values at specified steps situated
42 between the start and end value. The interpolation will then
43 touch these points at the specified steps. Note that the start and
44 end values are defined as the key values at 0.0 and 1.0.
45
46 There are two ways to affect how QVariantAnimation interpolates
47 the values. You can set an easing curve by calling
48 setEasingCurve(), and configure the duration by calling
49 setDuration(). You can change how the \l{QVariant}s are interpolated
50 by creating a subclass of QVariantAnimation, and reimplementing
51 the virtual interpolated() function.
52
53 Subclassing QVariantAnimation can be an alternative if you have
54 \l{QVariant}s that you do not wish to declare as Qt properties.
55 Note, however, that you in most cases will be better off declaring
56 your QVariant as a property.
57
58 Not all QVariant types are supported. Below is a list of currently
59 supported QVariant types:
60
61 \list
62 \li \l{QMetaType::}{Int}
63 \li \l{QMetaType::}{UInt}
64 \li \l{QMetaType::}{Double}
65 \li \l{QMetaType::}{Float}
66 \li \l{QMetaType::}{QLine}
67 \li \l{QMetaType::}{QLineF}
68 \li \l{QMetaType::}{QPoint}
69 \li \l{QMetaType::}{QPointF}
70 \li \l{QMetaType::}{QSize}
71 \li \l{QMetaType::}{QSizeF}
72 \li \l{QMetaType::}{QRect}
73 \li \l{QMetaType::}{QRectF}
74 \li \l{QMetaType::}{QColor}
75 \endlist
76
77 If you need to interpolate other variant types, including custom
78 types, you have to implement interpolation for these yourself.
79 To do this, you can register an interpolator function for a given
80 type. This function takes 3 parameters: the start value, the end value,
81 and the current progress.
82
83 Example:
84 \snippet code/src_corelib_animation_qvariantanimation.cpp 0
85
86 Another option is to reimplement interpolated(), which returns
87 interpolation values for the value being interpolated.
88
89 \omit We need some snippets around here. \endomit
90
91 \sa QPropertyAnimation, QAbstractAnimation, {The Animation Framework}
92*/
93
94/*!
95 \fn void QVariantAnimation::valueChanged(const QVariant &value)
96
97 QVariantAnimation emits this signal whenever the current \a value changes.
98
99 \sa currentValue, startValue, endValue
100*/
101
102/*!
103 This virtual function is called every time the animation's current
104 value changes. The \a value argument is the new current value.
105
106 The base class implementation does nothing.
107
108 \sa currentValue
109*/
110void QVariantAnimation::updateCurrentValue(const QVariant &) {}
111
112static bool animationValueLessThan(const QVariantAnimation::KeyValue &p1, const QVariantAnimation::KeyValue &p2)
113{
114 return p1.first < p2.first;
115}
116
117static QVariant defaultInterpolator(const void *, const void *, qreal)
118{
119 return QVariant();
120}
121
122template<> Q_INLINE_TEMPLATE QRect _q_interpolate(const QRect &f, const QRect &t, qreal progress)
123{
124 QRect ret;
125 ret.setCoords(xp1: _q_interpolate(f: f.left(), t: t.left(), progress),
126 yp1: _q_interpolate(f: f.top(), t: t.top(), progress),
127 xp2: _q_interpolate(f: f.right(), t: t.right(), progress),
128 yp2: _q_interpolate(f: f.bottom(), t: t.bottom(), progress));
129 return ret;
130}
131
132template<> Q_INLINE_TEMPLATE QRectF _q_interpolate(const QRectF &f, const QRectF &t, qreal progress)
133{
134 qreal x1, y1, w1, h1;
135 f.getRect(ax: &x1, ay: &y1, aaw: &w1, aah: &h1);
136 qreal x2, y2, w2, h2;
137 t.getRect(ax: &x2, ay: &y2, aaw: &w2, aah: &h2);
138 return QRectF(_q_interpolate(f: x1, t: x2, progress), _q_interpolate(f: y1, t: y2, progress),
139 _q_interpolate(f: w1, t: w2, progress), _q_interpolate(f: h1, t: h2, progress));
140}
141
142template<> Q_INLINE_TEMPLATE QLine _q_interpolate(const QLine &f, const QLine &t, qreal progress)
143{
144 return QLine( _q_interpolate(f: f.p1(), t: t.p1(), progress), _q_interpolate(f: f.p2(), t: t.p2(), progress));
145}
146
147template<> Q_INLINE_TEMPLATE QLineF _q_interpolate(const QLineF &f, const QLineF &t, qreal progress)
148{
149 return QLineF( _q_interpolate(f: f.p1(), t: t.p1(), progress), _q_interpolate(f: f.p2(), t: t.p2(), progress));
150}
151
152QVariantAnimationPrivate::QVariantAnimationPrivate() : duration(250), interpolator(&defaultInterpolator)
153{ }
154
155void QVariantAnimationPrivate::convertValues(int t)
156{
157 auto type = QMetaType(t);
158 //this ensures that all the keyValues are of type t
159 for (int i = 0; i < keyValues.size(); ++i) {
160 QVariantAnimation::KeyValue &pair = keyValues[i];
161 pair.second.convert(type);
162 }
163 //we also need update to the current interval if needed
164 currentInterval.start.second.convert(type);
165 currentInterval.end.second.convert(type);
166
167 //... and the interpolator
168 updateInterpolator();
169}
170
171void QVariantAnimationPrivate::updateInterpolator()
172{
173 int type = currentInterval.start.second.userType();
174 if (type == currentInterval.end.second.userType())
175 interpolator = getInterpolator(interpolationType: type);
176 else
177 interpolator = nullptr;
178
179 //we make sure that the interpolator is always set to something
180 if (!interpolator)
181 interpolator = &defaultInterpolator;
182}
183
184/*!
185 \internal
186 The goal of this function is to update the currentInterval member. As a consequence, we also
187 need to update the currentValue.
188 Set \a force to true to always recalculate the interval.
189*/
190void QVariantAnimationPrivate::recalculateCurrentInterval(bool force/*=false*/)
191{
192 // can't interpolate if we don't have at least 2 values
193 if ((keyValues.size() + (defaultStartEndValue.isValid() ? 1 : 0)) < 2)
194 return;
195
196 const qreal endProgress = (direction == QAbstractAnimation::Forward) ? qreal(1) : qreal(0);
197 const qreal progress = easing.value().valueForProgress(
198 progress: duration == 0 ? endProgress : qreal(currentTime) / qreal(duration));
199
200 //0 and 1 are still the boundaries
201 if (force || (currentInterval.start.first > 0 && progress < currentInterval.start.first)
202 || (currentInterval.end.first < 1 && progress > currentInterval.end.first)) {
203 //let's update currentInterval
204 QVariantAnimation::KeyValues::const_iterator it = std::lower_bound(first: keyValues.constBegin(),
205 last: keyValues.constEnd(),
206 val: qMakePair(value1: progress, value2: QVariant()),
207 comp: animationValueLessThan);
208 if (it == keyValues.constBegin()) {
209 //the item pointed to by it is the start element in the range
210 if (it->first == 0 && keyValues.size() > 1) {
211 currentInterval.start = *it;
212 currentInterval.end = *(it+1);
213 } else {
214 currentInterval.start = qMakePair(value1: qreal(0), value2&: defaultStartEndValue);
215 currentInterval.end = *it;
216 }
217 } else if (it == keyValues.constEnd()) {
218 --it; //position the iterator on the last item
219 if (it->first == 1 && keyValues.size() > 1) {
220 //we have an end value (item with progress = 1)
221 currentInterval.start = *(it-1);
222 currentInterval.end = *it;
223 } else {
224 //we use the default end value here
225 currentInterval.start = *it;
226 currentInterval.end = qMakePair(value1: qreal(1), value2&: defaultStartEndValue);
227 }
228 } else {
229 currentInterval.start = *(it-1);
230 currentInterval.end = *it;
231 }
232
233 // update all the values of the currentInterval
234 updateInterpolator();
235 }
236 setCurrentValueForProgress(progress);
237}
238
239void QVariantAnimationPrivate::setCurrentValueForProgress(const qreal progress)
240{
241 Q_Q(QVariantAnimation);
242
243 const qreal startProgress = currentInterval.start.first;
244 const qreal endProgress = currentInterval.end.first;
245 const qreal localProgress =
246 qIsNull(d: progress - startProgress) ? 0.0 // avoid 0/0 below
247 /* else */ : (progress - startProgress) / (endProgress - startProgress);
248
249 QVariant ret = q->interpolated(from: currentInterval.start.second,
250 to: currentInterval.end.second,
251 progress: localProgress);
252 qSwap(value1&: currentValue, value2&: ret);
253 q->updateCurrentValue(currentValue);
254 Q_CONSTINIT static QBasicAtomicInt changedSignalIndex = Q_BASIC_ATOMIC_INITIALIZER(0);
255 if (!changedSignalIndex.loadRelaxed()) {
256 //we keep the mask so that we emit valueChanged only when needed (for performance reasons)
257 changedSignalIndex.testAndSetRelaxed(expectedValue: 0, newValue: signalIndex(signalName: "valueChanged(QVariant)"));
258 }
259 if (isSignalConnected(signalIdx: changedSignalIndex.loadRelaxed()) && currentValue != ret) {
260 //the value has changed
261 emit q->valueChanged(value: currentValue);
262 }
263}
264
265QVariant QVariantAnimationPrivate::valueAt(qreal step) const
266{
267 QVariantAnimation::KeyValues::const_iterator result =
268 std::lower_bound(first: keyValues.constBegin(), last: keyValues.constEnd(), val: qMakePair(value1&: step, value2: QVariant()), comp: animationValueLessThan);
269 if (result != keyValues.constEnd() && !animationValueLessThan(p1: qMakePair(value1&: step, value2: QVariant()), p2: *result))
270 return result->second;
271
272 return QVariant();
273}
274
275void QVariantAnimationPrivate::setValueAt(qreal step, const QVariant &value)
276{
277 if (step < qreal(0.0) || step > qreal(1.0)) {
278 qWarning(msg: "QVariantAnimation::setValueAt: invalid step = %f", step);
279 return;
280 }
281
282 QVariantAnimation::KeyValue pair(step, value);
283
284 QVariantAnimation::KeyValues::iterator result = std::lower_bound(first: keyValues.begin(), last: keyValues.end(), val: pair, comp: animationValueLessThan);
285 if (result == keyValues.end() || result->first != step) {
286 keyValues.insert(before: result, t: pair);
287 } else {
288 if (value.isValid())
289 result->second = value; // replaces the previous value
290 else
291 keyValues.erase(pos: result); // removes the previous value
292 }
293
294 recalculateCurrentInterval(/*force=*/true);
295}
296
297void QVariantAnimationPrivate::setDefaultStartEndValue(const QVariant &value)
298{
299 defaultStartEndValue = value;
300 recalculateCurrentInterval(/*force=*/true);
301}
302
303/*!
304 Construct a QVariantAnimation object. \a parent is passed to QAbstractAnimation's
305 constructor.
306*/
307QVariantAnimation::QVariantAnimation(QObject *parent) : QAbstractAnimation(*new QVariantAnimationPrivate, parent)
308{
309}
310
311/*!
312 \internal
313*/
314QVariantAnimation::QVariantAnimation(QVariantAnimationPrivate &dd, QObject *parent) : QAbstractAnimation(dd, parent)
315{
316}
317
318/*!
319 Destroys the animation.
320*/
321QVariantAnimation::~QVariantAnimation()
322{
323}
324
325/*!
326 \property QVariantAnimation::easingCurve
327 \brief the easing curve of the animation
328
329 This property defines the easing curve of the animation. By
330 default, a linear easing curve is used, resulting in linear
331 interpolation. Other curves are provided, for instance,
332 QEasingCurve::InCirc, which provides a circular entry curve.
333 Another example is QEasingCurve::InOutElastic, which provides an
334 elastic effect on the values of the interpolated variant.
335
336 QVariantAnimation will use the QEasingCurve::valueForProgress() to
337 transform the "normalized progress" (currentTime / totalDuration)
338 of the animation into the effective progress actually
339 used by the animation. It is this effective progress that will be
340 the progress when interpolated() is called. Also, the steps in the
341 keyValues are referring to this effective progress.
342
343 The easing curve is used with the interpolator, the interpolated()
344 virtual function, and the animation's duration to control how the
345 current value changes as the animation progresses.
346*/
347QEasingCurve QVariantAnimation::easingCurve() const
348{
349 Q_D(const QVariantAnimation);
350 return d->easing;
351}
352
353void QVariantAnimation::setEasingCurve(const QEasingCurve &easing)
354{
355 Q_D(QVariantAnimation);
356 d->easing.removeBindingUnlessInWrapper();
357 const bool valueChanged = easing != d->easing.valueBypassingBindings();
358 d->easing.setValueBypassingBindings(easing);
359 d->recalculateCurrentInterval();
360 if (valueChanged)
361 d->easing.notify();
362}
363
364QBindable<QEasingCurve> QVariantAnimation::bindableEasingCurve()
365{
366 Q_D(QVariantAnimation);
367 return &d->easing;
368}
369
370typedef QList<QVariantAnimation::Interpolator> QInterpolatorVector;
371Q_GLOBAL_STATIC(QInterpolatorVector, registeredInterpolators)
372Q_CONSTINIT static QBasicMutex registeredInterpolatorsMutex;
373
374/*!
375 \fn template <typename T> void qRegisterAnimationInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
376 \relates QVariantAnimation
377 \threadsafe
378
379 Registers a custom interpolator \a func for the template type \c{T}.
380 The interpolator has to be registered before the animation is constructed.
381 To unregister (and use the default interpolator) set \a func to \nullptr.
382 */
383
384/*!
385 \internal
386 \typedef QVariantAnimation::Interpolator
387
388 This is a typedef for a pointer to a function with the following
389 signature:
390 \snippet code/src_corelib_animation_qvariantanimation.cpp 1
391
392*/
393
394/*!
395 * \internal
396 * Registers a custom interpolator \a func for the specific \a interpolationType.
397 * The interpolator has to be registered before the animation is constructed.
398 * To unregister (and use the default interpolator) set \a func to \nullptr.
399 */
400void QVariantAnimation::registerInterpolator(QVariantAnimation::Interpolator func, int interpolationType)
401{
402 // will override any existing interpolators
403 QInterpolatorVector *interpolators = registeredInterpolators();
404 // When built on solaris with GCC, the destructors can be called
405 // in such an order that we get here with interpolators == NULL,
406 // to continue causes the app to crash on exit with a SEGV
407 if (interpolators) {
408 const auto locker = qt_scoped_lock(mutex&: registeredInterpolatorsMutex);
409 if (interpolationType >= interpolators->size())
410 interpolators->resize(size: interpolationType + 1);
411 interpolators->replace(i: interpolationType, t: func);
412 }
413}
414
415
416template<typename T> static inline QVariantAnimation::Interpolator castToInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
417{
418 return reinterpret_cast<QVariantAnimation::Interpolator>(reinterpret_cast<void(*)()>(func));
419}
420
421QVariantAnimation::Interpolator QVariantAnimationPrivate::getInterpolator(int interpolationType)
422{
423 {
424 QInterpolatorVector *interpolators = registeredInterpolators();
425 const auto locker = qt_scoped_lock(mutex&: registeredInterpolatorsMutex);
426 QVariantAnimation::Interpolator ret = nullptr;
427 if (interpolationType < interpolators->size()) {
428 ret = interpolators->at(i: interpolationType);
429 if (ret) return ret;
430 }
431 }
432
433 switch(interpolationType)
434 {
435 case QMetaType::Int:
436 return castToInterpolator(func: _q_interpolateVariant<int>);
437 case QMetaType::UInt:
438 return castToInterpolator(func: _q_interpolateVariant<uint>);
439 case QMetaType::Double:
440 return castToInterpolator(func: _q_interpolateVariant<double>);
441 case QMetaType::Float:
442 return castToInterpolator(func: _q_interpolateVariant<float>);
443 case QMetaType::QLine:
444 return castToInterpolator(func: _q_interpolateVariant<QLine>);
445 case QMetaType::QLineF:
446 return castToInterpolator(func: _q_interpolateVariant<QLineF>);
447 case QMetaType::QPoint:
448 return castToInterpolator(func: _q_interpolateVariant<QPoint>);
449 case QMetaType::QPointF:
450 return castToInterpolator(func: _q_interpolateVariant<QPointF>);
451 case QMetaType::QSize:
452 return castToInterpolator(func: _q_interpolateVariant<QSize>);
453 case QMetaType::QSizeF:
454 return castToInterpolator(func: _q_interpolateVariant<QSizeF>);
455 case QMetaType::QRect:
456 return castToInterpolator(func: _q_interpolateVariant<QRect>);
457 case QMetaType::QRectF:
458 return castToInterpolator(func: _q_interpolateVariant<QRectF>);
459 default:
460 return nullptr; //this type is not handled
461 }
462}
463
464/*!
465 \property QVariantAnimation::duration
466 \brief the duration of the animation
467
468 This property describes the duration in milliseconds of the
469 animation. The default duration is 250 milliseconds.
470
471 \sa QAbstractAnimation::duration()
472 */
473int QVariantAnimation::duration() const
474{
475 Q_D(const QVariantAnimation);
476 return d->duration;
477}
478
479void QVariantAnimation::setDuration(int msecs)
480{
481 Q_D(QVariantAnimation);
482 if (msecs < 0) {
483 qWarning(msg: "QVariantAnimation::setDuration: cannot set a negative duration");
484 return;
485 }
486 d->duration.removeBindingUnlessInWrapper();
487 if (d->duration.valueBypassingBindings() != msecs) {
488 d->duration.setValueBypassingBindings(msecs);
489 d->recalculateCurrentInterval();
490 d->duration.notify();
491 }
492}
493
494QBindable<int> QVariantAnimation::bindableDuration()
495{
496 Q_D(QVariantAnimation);
497 return &d->duration;
498}
499
500/*!
501 \property QVariantAnimation::startValue
502 \brief the optional start value of the animation
503
504 This property describes the optional start value of the animation. If
505 omitted, or if a null QVariant is assigned as the start value, the
506 animation will use the current position of the end when the animation
507 is started.
508
509 \sa endValue
510*/
511QVariant QVariantAnimation::startValue() const
512{
513 return keyValueAt(step: 0);
514}
515
516void QVariantAnimation::setStartValue(const QVariant &value)
517{
518 setKeyValueAt(step: 0, value);
519}
520
521/*!
522 \property QVariantAnimation::endValue
523 \brief the end value of the animation
524
525 This property describes the end value of the animation.
526
527 \sa startValue
528 */
529QVariant QVariantAnimation::endValue() const
530{
531 return keyValueAt(step: 1);
532}
533
534void QVariantAnimation::setEndValue(const QVariant &value)
535{
536 setKeyValueAt(step: 1, value);
537}
538
539
540/*!
541 Returns the key frame value for the given \a step. The given \a step
542 must be in the range 0 to 1. If there is no KeyValue for \a step,
543 it returns an invalid QVariant.
544
545 \sa keyValues(), setKeyValueAt()
546*/
547QVariant QVariantAnimation::keyValueAt(qreal step) const
548{
549 return d_func()->valueAt(step);
550}
551
552/*!
553 \typedef QVariantAnimation::KeyValue
554
555 This is a typedef for QPair<qreal, QVariant>.
556*/
557/*!
558 \typedef QVariantAnimation::KeyValues
559
560 This is a typedef for QList<KeyValue>
561*/
562
563/*!
564 Creates a key frame at the given \a step with the given \a value.
565 The given \a step must be in the range 0 to 1.
566
567 \sa setKeyValues(), keyValueAt()
568*/
569void QVariantAnimation::setKeyValueAt(qreal step, const QVariant &value)
570{
571 d_func()->setValueAt(step, value);
572}
573
574/*!
575 Returns the key frames of this animation.
576
577 \sa keyValueAt(), setKeyValues()
578*/
579QVariantAnimation::KeyValues QVariantAnimation::keyValues() const
580{
581 return d_func()->keyValues;
582}
583
584/*!
585 Replaces the current set of key frames with the given \a keyValues.
586 the step of the key frames must be in the range 0 to 1.
587
588 \sa keyValues(), keyValueAt()
589*/
590void QVariantAnimation::setKeyValues(const KeyValues &keyValues)
591{
592 Q_D(QVariantAnimation);
593 d->keyValues = keyValues;
594 std::sort(first: d->keyValues.begin(), last: d->keyValues.end(), comp: animationValueLessThan);
595 d->recalculateCurrentInterval(/*force=*/true);
596}
597
598/*!
599 \property QVariantAnimation::currentValue
600 \brief the current value of the animation.
601
602 This property describes the current value; an interpolated value
603 between the \l{startValue}{start value} and the \l{endValue}{end
604 value}, using the current time for progress. The value itself is
605 obtained from interpolated(), which is called repeatedly as the
606 animation is running.
607
608 QVariantAnimation calls the virtual updateCurrentValue() function
609 when the current value changes. This is particularly useful for
610 subclasses that need to track updates. For example,
611 QPropertyAnimation uses this function to animate Qt \l{Qt's
612 Property System}{properties}.
613
614 \sa startValue, endValue
615*/
616QVariant QVariantAnimation::currentValue() const
617{
618 Q_D(const QVariantAnimation);
619 if (!d->currentValue.isValid())
620 const_cast<QVariantAnimationPrivate*>(d)->recalculateCurrentInterval();
621 return d->currentValue;
622}
623
624/*!
625 \reimp
626 */
627bool QVariantAnimation::event(QEvent *event)
628{
629 return QAbstractAnimation::event(event);
630}
631
632/*!
633 \reimp
634*/
635void QVariantAnimation::updateState(QAbstractAnimation::State newState,
636 QAbstractAnimation::State oldState)
637{
638 Q_UNUSED(oldState);
639 Q_UNUSED(newState);
640}
641
642/*!
643
644 This virtual function returns the linear interpolation between
645 variants \a from and \a to, at \a progress, usually a value
646 between 0 and 1. You can reimplement this function in a subclass
647 of QVariantAnimation to provide your own interpolation algorithm.
648
649 Note that in order for the interpolation to work with a
650 QEasingCurve that return a value smaller than 0 or larger than 1
651 (such as QEasingCurve::InBack) you should make sure that it can
652 extrapolate. If the semantic of the datatype does not allow
653 extrapolation this function should handle that gracefully.
654
655 You should call the QVariantAnimation implementation of this
656 function if you want your class to handle the types already
657 supported by Qt (see class QVariantAnimation description for a
658 list of supported types).
659
660 \sa QEasingCurve
661 */
662QVariant QVariantAnimation::interpolated(const QVariant &from, const QVariant &to, qreal progress) const
663{
664 return d_func()->interpolator(from.constData(), to.constData(), progress);
665}
666
667/*!
668 \reimp
669 */
670void QVariantAnimation::updateCurrentTime(int)
671{
672 d_func()->recalculateCurrentInterval();
673}
674
675QT_END_NAMESPACE
676
677#include "moc_qvariantanimation.cpp"
678

source code of qtbase/src/corelib/animation/qvariantanimation.cpp