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
155QVariantAnimationPrivate::~QVariantAnimationPrivate()
156 = default;
157
158void QVariantAnimationPrivate::convertValues(int t)
159{
160 auto type = QMetaType(t);
161 //this ensures that all the keyValues are of type t
162 for (int i = 0; i < keyValues.size(); ++i) {
163 QVariantAnimation::KeyValue &pair = keyValues[i];
164 pair.second.convert(type);
165 }
166 //we also need update to the current interval if needed
167 currentInterval.start.second.convert(type);
168 currentInterval.end.second.convert(type);
169
170 //... and the interpolator
171 updateInterpolator();
172}
173
174void QVariantAnimationPrivate::updateInterpolator()
175{
176 int type = currentInterval.start.second.userType();
177 if (type == currentInterval.end.second.userType())
178 interpolator = getInterpolator(interpolationType: type);
179 else
180 interpolator = nullptr;
181
182 //we make sure that the interpolator is always set to something
183 if (!interpolator)
184 interpolator = &defaultInterpolator;
185}
186
187/*!
188 \internal
189 The goal of this function is to update the currentInterval member. As a consequence, we also
190 need to update the currentValue.
191 Set \a force to true to always recalculate the interval.
192*/
193void QVariantAnimationPrivate::recalculateCurrentInterval(bool force/*=false*/)
194{
195 // can't interpolate if we don't have at least 2 values
196 if ((keyValues.size() + (defaultStartEndValue.isValid() ? 1 : 0)) < 2)
197 return;
198
199 const qreal endProgress = (direction == QAbstractAnimation::Forward) ? qreal(1) : qreal(0);
200 const qreal progress = easing.value().valueForProgress(
201 progress: duration == 0 ? endProgress : qreal(currentTime) / qreal(duration));
202
203 //0 and 1 are still the boundaries
204 if (force || (currentInterval.start.first > 0 && progress < currentInterval.start.first)
205 || (currentInterval.end.first < 1 && progress > currentInterval.end.first)) {
206 //let's update currentInterval
207 QVariantAnimation::KeyValues::const_iterator it = std::lower_bound(first: keyValues.constBegin(),
208 last: keyValues.constEnd(),
209 val: std::pair{progress, QVariant{}},
210 comp: animationValueLessThan);
211 if (it == keyValues.constBegin()) {
212 //the item pointed to by it is the start element in the range
213 if (it->first == 0 && keyValues.size() > 1) {
214 currentInterval.start = *it;
215 currentInterval.end = *(it+1);
216 } else {
217 currentInterval.start = {qreal(0), defaultStartEndValue};
218 currentInterval.end = *it;
219 }
220 } else if (it == keyValues.constEnd()) {
221 --it; //position the iterator on the last item
222 if (it->first == 1 && keyValues.size() > 1) {
223 //we have an end value (item with progress = 1)
224 currentInterval.start = *(it-1);
225 currentInterval.end = *it;
226 } else {
227 //we use the default end value here
228 currentInterval.start = *it;
229 currentInterval.end = {qreal(1), defaultStartEndValue};
230 }
231 } else {
232 currentInterval.start = *(it-1);
233 currentInterval.end = *it;
234 }
235
236 // update all the values of the currentInterval
237 updateInterpolator();
238 }
239 setCurrentValueForProgress(progress);
240}
241
242void QVariantAnimationPrivate::setCurrentValueForProgress(const qreal progress)
243{
244 Q_Q(QVariantAnimation);
245
246 const qreal startProgress = currentInterval.start.first;
247 const qreal endProgress = currentInterval.end.first;
248 const qreal localProgress =
249 qIsNull(d: progress - startProgress) ? 0.0 // avoid 0/0 below
250 /* else */ : (progress - startProgress) / (endProgress - startProgress);
251
252 QVariant ret = q->interpolated(from: currentInterval.start.second,
253 to: currentInterval.end.second,
254 progress: localProgress);
255 qSwap(value1&: currentValue, value2&: ret);
256 q->updateCurrentValue(currentValue);
257 Q_CONSTINIT static QBasicAtomicInt changedSignalIndex = Q_BASIC_ATOMIC_INITIALIZER(0);
258 if (!changedSignalIndex.loadRelaxed()) {
259 //we keep the mask so that we emit valueChanged only when needed (for performance reasons)
260 changedSignalIndex.testAndSetRelaxed(expectedValue: 0, newValue: signalIndex(signalName: "valueChanged(QVariant)"));
261 }
262 if (isSignalConnected(signalIdx: changedSignalIndex.loadRelaxed()) && currentValue != ret) {
263 //the value has changed
264 emit q->valueChanged(value: currentValue);
265 }
266}
267
268QVariant QVariantAnimationPrivate::valueAt(qreal step) const
269{
270 const auto sought = std::pair{step, QVariant()};
271 const auto result = std::lower_bound(first: keyValues.cbegin(), last: keyValues.cend(), val: sought,
272 comp: animationValueLessThan);
273 if (result != keyValues.cend() && !animationValueLessThan(p1: sought, p2: *result))
274 return result->second;
275
276 return QVariant();
277}
278
279void QVariantAnimationPrivate::setValueAt(qreal step, const QVariant &value)
280{
281 if (step < qreal(0.0) || step > qreal(1.0)) {
282 qWarning(msg: "QVariantAnimation::setValueAt: invalid step = %f", step);
283 return;
284 }
285
286 QVariantAnimation::KeyValue pair(step, value);
287
288 QVariantAnimation::KeyValues::iterator result = std::lower_bound(first: keyValues.begin(), last: keyValues.end(), val: pair, comp: animationValueLessThan);
289 if (result == keyValues.end() || result->first != step) {
290 keyValues.insert(before: result, t: pair);
291 } else {
292 if (value.isValid())
293 result->second = value; // replaces the previous value
294 else
295 keyValues.erase(pos: result); // removes the previous value
296 }
297
298 recalculateCurrentInterval(/*force=*/true);
299}
300
301void QVariantAnimationPrivate::setDefaultStartEndValue(const QVariant &value)
302{
303 defaultStartEndValue = value;
304 recalculateCurrentInterval(/*force=*/true);
305}
306
307/*!
308 Construct a QVariantAnimation object. \a parent is passed to QAbstractAnimation's
309 constructor.
310*/
311QVariantAnimation::QVariantAnimation(QObject *parent) : QAbstractAnimation(*new QVariantAnimationPrivate, parent)
312{
313}
314
315/*!
316 \internal
317*/
318QVariantAnimation::QVariantAnimation(QVariantAnimationPrivate &dd, QObject *parent) : QAbstractAnimation(dd, parent)
319{
320}
321
322/*!
323 Destroys the animation.
324*/
325QVariantAnimation::~QVariantAnimation()
326{
327}
328
329/*!
330 \property QVariantAnimation::easingCurve
331 \brief the easing curve of the animation
332
333 This property defines the easing curve of the animation. By
334 default, a linear easing curve is used, resulting in linear
335 interpolation. Other curves are provided, for instance,
336 QEasingCurve::InCirc, which provides a circular entry curve.
337 Another example is QEasingCurve::InOutElastic, which provides an
338 elastic effect on the values of the interpolated variant.
339
340 QVariantAnimation will use the QEasingCurve::valueForProgress() to
341 transform the "normalized progress" (currentTime / totalDuration)
342 of the animation into the effective progress actually
343 used by the animation. It is this effective progress that will be
344 the progress when interpolated() is called. Also, the steps in the
345 keyValues are referring to this effective progress.
346
347 The easing curve is used with the interpolator, the interpolated()
348 virtual function, and the animation's duration to control how the
349 current value changes as the animation progresses.
350*/
351QEasingCurve QVariantAnimation::easingCurve() const
352{
353 Q_D(const QVariantAnimation);
354 return d->easing;
355}
356
357void QVariantAnimation::setEasingCurve(const QEasingCurve &easing)
358{
359 Q_D(QVariantAnimation);
360 d->easing.removeBindingUnlessInWrapper();
361 const bool valueChanged = easing != d->easing.valueBypassingBindings();
362 d->easing.setValueBypassingBindings(easing);
363 d->recalculateCurrentInterval();
364 if (valueChanged)
365 d->easing.notify();
366}
367
368QBindable<QEasingCurve> QVariantAnimation::bindableEasingCurve()
369{
370 Q_D(QVariantAnimation);
371 return &d->easing;
372}
373
374typedef QList<QVariantAnimation::Interpolator> QInterpolatorVector;
375Q_GLOBAL_STATIC(QInterpolatorVector, registeredInterpolators)
376Q_CONSTINIT static QBasicMutex registeredInterpolatorsMutex;
377
378/*!
379 \fn template <typename T> void qRegisterAnimationInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
380 \relates QVariantAnimation
381 \threadsafe
382
383 Registers a custom interpolator \a func for the template type \c{T}.
384 The interpolator has to be registered before the animation is constructed.
385 To unregister (and use the default interpolator) set \a func to \nullptr.
386 */
387
388/*!
389 \internal
390 \typedef QVariantAnimation::Interpolator
391
392 This is a typedef for a pointer to a function with the following
393 signature:
394 \snippet code/src_corelib_animation_qvariantanimation.cpp 1
395
396*/
397
398/*!
399 * \internal
400 * Registers a custom interpolator \a func for the specific \a interpolationType.
401 * The interpolator has to be registered before the animation is constructed.
402 * To unregister (and use the default interpolator) set \a func to \nullptr.
403 */
404void QVariantAnimation::registerInterpolator(QVariantAnimation::Interpolator func, int interpolationType)
405{
406 // will override any existing interpolators
407 QInterpolatorVector *interpolators = registeredInterpolators();
408 // When built on solaris with GCC, the destructors can be called
409 // in such an order that we get here with interpolators == NULL,
410 // to continue causes the app to crash on exit with a SEGV
411 if (interpolators) {
412 const auto locker = qt_scoped_lock(mutex&: registeredInterpolatorsMutex);
413 if (interpolationType >= interpolators->size())
414 interpolators->resize(size: interpolationType + 1);
415 interpolators->replace(i: interpolationType, t: func);
416 }
417}
418
419
420template<typename T> static inline QVariantAnimation::Interpolator castToInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
421{
422 return reinterpret_cast<QVariantAnimation::Interpolator>(reinterpret_cast<void(*)()>(func));
423}
424
425QVariantAnimation::Interpolator QVariantAnimationPrivate::getInterpolator(int interpolationType)
426{
427 {
428 QInterpolatorVector *interpolators = registeredInterpolators();
429 const auto locker = qt_scoped_lock(mutex&: registeredInterpolatorsMutex);
430 QVariantAnimation::Interpolator ret = nullptr;
431 if (interpolationType < interpolators->size()) {
432 ret = interpolators->at(i: interpolationType);
433 if (ret) return ret;
434 }
435 }
436
437 switch(interpolationType)
438 {
439 case QMetaType::Int:
440 return castToInterpolator(func: _q_interpolateVariant<int>);
441 case QMetaType::UInt:
442 return castToInterpolator(func: _q_interpolateVariant<uint>);
443 case QMetaType::Double:
444 return castToInterpolator(func: _q_interpolateVariant<double>);
445 case QMetaType::Float:
446 return castToInterpolator(func: _q_interpolateVariant<float>);
447 case QMetaType::QLine:
448 return castToInterpolator(func: _q_interpolateVariant<QLine>);
449 case QMetaType::QLineF:
450 return castToInterpolator(func: _q_interpolateVariant<QLineF>);
451 case QMetaType::QPoint:
452 return castToInterpolator(func: _q_interpolateVariant<QPoint>);
453 case QMetaType::QPointF:
454 return castToInterpolator(func: _q_interpolateVariant<QPointF>);
455 case QMetaType::QSize:
456 return castToInterpolator(func: _q_interpolateVariant<QSize>);
457 case QMetaType::QSizeF:
458 return castToInterpolator(func: _q_interpolateVariant<QSizeF>);
459 case QMetaType::QRect:
460 return castToInterpolator(func: _q_interpolateVariant<QRect>);
461 case QMetaType::QRectF:
462 return castToInterpolator(func: _q_interpolateVariant<QRectF>);
463 default:
464 return nullptr; //this type is not handled
465 }
466}
467
468/*!
469 \property QVariantAnimation::duration
470 \brief the duration of the animation
471
472 This property describes the duration in milliseconds of the
473 animation. The default duration is 250 milliseconds.
474
475 \sa QAbstractAnimation::duration()
476 */
477int QVariantAnimation::duration() const
478{
479 Q_D(const QVariantAnimation);
480 return d->duration;
481}
482
483void QVariantAnimation::setDuration(int msecs)
484{
485 Q_D(QVariantAnimation);
486 if (msecs < 0) {
487 qWarning(msg: "QVariantAnimation::setDuration: cannot set a negative duration");
488 return;
489 }
490 d->duration.removeBindingUnlessInWrapper();
491 if (d->duration.valueBypassingBindings() != msecs) {
492 d->duration.setValueBypassingBindings(msecs);
493 d->recalculateCurrentInterval();
494 d->duration.notify();
495 }
496}
497
498QBindable<int> QVariantAnimation::bindableDuration()
499{
500 Q_D(QVariantAnimation);
501 return &d->duration;
502}
503
504/*!
505 \property QVariantAnimation::startValue
506 \brief the optional start value of the animation
507
508 This property describes the optional start value of the animation. If
509 omitted, or if a null QVariant is assigned as the start value, the
510 animation will use the current position of the end when the animation
511 is started.
512
513 \sa endValue
514*/
515QVariant QVariantAnimation::startValue() const
516{
517 return keyValueAt(step: 0);
518}
519
520void QVariantAnimation::setStartValue(const QVariant &value)
521{
522 setKeyValueAt(step: 0, value);
523}
524
525/*!
526 \property QVariantAnimation::endValue
527 \brief the end value of the animation
528
529 This property describes the end value of the animation.
530
531 \sa startValue
532 */
533QVariant QVariantAnimation::endValue() const
534{
535 return keyValueAt(step: 1);
536}
537
538void QVariantAnimation::setEndValue(const QVariant &value)
539{
540 setKeyValueAt(step: 1, value);
541}
542
543
544/*!
545 Returns the key frame value for the given \a step. The given \a step
546 must be in the range 0 to 1. If there is no KeyValue for \a step,
547 it returns an invalid QVariant.
548
549 \sa keyValues(), setKeyValueAt()
550*/
551QVariant QVariantAnimation::keyValueAt(qreal step) const
552{
553 return d_func()->valueAt(step);
554}
555
556/*!
557 \typedef QVariantAnimation::KeyValue
558
559 This is a typedef for std::pair<qreal, QVariant>.
560*/
561/*!
562 \typedef QVariantAnimation::KeyValues
563
564 This is a typedef for QList<KeyValue>
565*/
566
567/*!
568 Creates a key frame at the given \a step with the given \a value.
569 The given \a step must be in the range 0 to 1.
570
571 \sa setKeyValues(), keyValueAt()
572*/
573void QVariantAnimation::setKeyValueAt(qreal step, const QVariant &value)
574{
575 d_func()->setValueAt(step, value);
576}
577
578/*!
579 Returns the key frames of this animation.
580
581 \sa keyValueAt(), setKeyValues()
582*/
583QVariantAnimation::KeyValues QVariantAnimation::keyValues() const
584{
585 return d_func()->keyValues;
586}
587
588/*!
589 Replaces the current set of key frames with the given \a keyValues.
590 the step of the key frames must be in the range 0 to 1.
591
592 \sa keyValues(), keyValueAt()
593*/
594void QVariantAnimation::setKeyValues(const KeyValues &keyValues)
595{
596 Q_D(QVariantAnimation);
597 d->keyValues = keyValues;
598 std::sort(first: d->keyValues.begin(), last: d->keyValues.end(), comp: animationValueLessThan);
599 d->recalculateCurrentInterval(/*force=*/true);
600}
601
602/*!
603 \property QVariantAnimation::currentValue
604 \brief the current value of the animation.
605
606 This property describes the current value; an interpolated value
607 between the \l{startValue}{start value} and the \l{endValue}{end
608 value}, using the current time for progress. The value itself is
609 obtained from interpolated(), which is called repeatedly as the
610 animation is running.
611
612 QVariantAnimation calls the virtual updateCurrentValue() function
613 when the current value changes. This is particularly useful for
614 subclasses that need to track updates. For example,
615 QPropertyAnimation uses this function to animate Qt \l{Qt's
616 Property System}{properties}.
617
618 \sa startValue, endValue
619*/
620QVariant QVariantAnimation::currentValue() const
621{
622 Q_D(const QVariantAnimation);
623 if (!d->currentValue.isValid())
624 const_cast<QVariantAnimationPrivate*>(d)->recalculateCurrentInterval();
625 return d->currentValue;
626}
627
628/*!
629 \reimp
630 */
631bool QVariantAnimation::event(QEvent *event)
632{
633 return QAbstractAnimation::event(event);
634}
635
636/*!
637 \reimp
638*/
639void QVariantAnimation::updateState(QAbstractAnimation::State newState,
640 QAbstractAnimation::State oldState)
641{
642 Q_UNUSED(oldState);
643 Q_UNUSED(newState);
644}
645
646/*!
647
648 This virtual function returns the linear interpolation between
649 variants \a from and \a to, at \a progress, usually a value
650 between 0 and 1. You can reimplement this function in a subclass
651 of QVariantAnimation to provide your own interpolation algorithm.
652
653 Note that in order for the interpolation to work with a
654 QEasingCurve that return a value smaller than 0 or larger than 1
655 (such as QEasingCurve::InBack) you should make sure that it can
656 extrapolate. If the semantic of the datatype does not allow
657 extrapolation this function should handle that gracefully.
658
659 You should call the QVariantAnimation implementation of this
660 function if you want your class to handle the types already
661 supported by Qt (see class QVariantAnimation description for a
662 list of supported types).
663
664 \sa QEasingCurve
665 */
666QVariant QVariantAnimation::interpolated(const QVariant &from, const QVariant &to, qreal progress) const
667{
668 return d_func()->interpolator(from.constData(), to.constData(), progress);
669}
670
671/*!
672 \reimp
673 */
674void QVariantAnimation::updateCurrentTime(int)
675{
676 d_func()->recalculateCurrentInterval();
677}
678
679QT_END_NAMESPACE
680
681#include "moc_qvariantanimation.cpp"
682

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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