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