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
qtimeline.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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
5#include "qtimeline.h"
6
7#include <private/qproperty_p.h>
8#include <private/qobject_p.h>
9#include <QtCore/qbasictimer.h>
10#include <QtCore/qcoreevent.h>
11#include <QtCore/qmath.h>
12#include <QtCore/qelapsedtimer.h>
13
14using namespace std::chrono_literals;
15
16QT_BEGIN_NAMESPACE
17
18class QTimeLinePrivate : public QObjectPrivate
19{
20 Q_DECLARE_PUBLIC(QTimeLine)
21public:
22 QElapsedTimer timer;
23 QBasicTimer basicTimer;
24 Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QTimeLinePrivate, QEasingCurve, easingCurve,
25 QEasingCurve::InOutSine)
26
27 int startTime = 0;
28 void setDuration(int duration) { q_func()->setDuration(duration); }
29 Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QTimeLinePrivate, int, duration,
30 &QTimeLinePrivate::setDuration, 1000)
31 int startFrame = 0;
32 int endFrame = 0;
33 Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QTimeLinePrivate, int, updateInterval, 1000 / 25)
34 Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QTimeLinePrivate, int, loopCount, 1)
35 int currentLoopCount = 0;
36
37 void setCurrentTimeForwardToQ(int time) { q_func()->setCurrentTime(time); }
38 Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QTimeLinePrivate, int, currentTime,
39 &QTimeLinePrivate::setCurrentTimeForwardToQ, 0)
40
41 void setDirection(QTimeLine::Direction direction) { q_func()->setDirection(direction); }
42 Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QTimeLinePrivate, QTimeLine::Direction, direction,
43 &QTimeLinePrivate::setDirection, QTimeLine::Forward)
44 QTimeLine::State state = QTimeLine::NotRunning;
45 inline void setState(QTimeLine::State newState)
46 {
47 Q_Q(QTimeLine);
48 if (newState != state)
49 emit q->stateChanged(state = newState, QTimeLine::QPrivateSignal());
50 }
51
52 void setCurrentTime(int msecs);
53};
54
55/*!
56 \internal
57*/
58void QTimeLinePrivate::setCurrentTime(int msecs)
59{
60 Q_Q(QTimeLine);
61 currentTime.removeBindingUnlessInWrapper();
62 const auto previousCurrentTime = currentTime.valueBypassingBindings();
63
64 const qreal lastValue = q->valueForTime(previousCurrentTime);
65 const int lastFrame = q->frameForTime(previousCurrentTime);
66
67 // Determine if we are looping.
68 const int elapsed = (direction == QTimeLine::Backward) ? (-msecs + duration) : msecs;
69 const int loopCountNow = elapsed / duration;
70
71 const bool looping = (loopCountNow != currentLoopCount);
72#ifdef QTIMELINE_DEBUG
73 qDebug() << "QTimeLinePrivate::setCurrentTime:" << msecs << duration << "with loopCountNow"
74 << loopCountNow << "currentLoopCount" << currentLoopCount << "looping" << looping;
75#endif
76 if (looping)
77 currentLoopCount = loopCountNow;
78
79 // Normalize msecs to be between 0 and duration, inclusive.
80 currentTime.setValueBypassingBindings(elapsed % duration);
81 if (direction.value() == QTimeLine::Backward)
82 currentTime.setValueBypassingBindings(duration - currentTime.valueBypassingBindings());
83
84 // Check if we have reached the end of loopcount.
85 bool finished = false;
86 if (loopCount && currentLoopCount >= loopCount) {
87 finished = true;
88 currentTime.setValueBypassingBindings((direction == QTimeLine::Backward) ? 0 : duration);
89 currentLoopCount = loopCount - 1;
90 }
91
92 const int currentFrame = q->frameForTime(currentTime.valueBypassingBindings());
93#ifdef QTIMELINE_DEBUG
94 qDebug() << "QTimeLinePrivate::setCurrentTime: frameForTime"
95 << currentTime.valueBypassingBindings() << currentFrame;
96#endif
97 const qreal currentValue = q->valueForTime(currentTime.valueBypassingBindings());
98 if (!qFuzzyCompare(lastValue, currentValue))
99 emit q->valueChanged(currentValue, QTimeLine::QPrivateSignal());
100 if (lastFrame != currentFrame) {
101 const int transitionframe = (direction == QTimeLine::Forward ? endFrame : startFrame);
102 if (looping && !finished && transitionframe != currentFrame) {
103#ifdef QTIMELINE_DEBUG
104 qDebug("QTimeLinePrivate::setCurrentTime: transitionframe");
105#endif
106 emit q->frameChanged(transitionframe, QTimeLine::QPrivateSignal());
107 }
108#ifdef QTIMELINE_DEBUG
109 else {
110 QByteArray reason;
111 if (!looping)
112 reason += " not looping";
113 if (finished) {
114 if (!reason.isEmpty())
115 reason += " and";
116 reason += " finished";
117 }
118 if (transitionframe == currentFrame) {
119 if (!reason.isEmpty())
120 reason += " and";
121 reason += " transitionframe is equal to currentFrame: " + QByteArray::number(currentFrame);
122 }
123 qDebug("QTimeLinePrivate::setCurrentTime: not transitionframe because %s", reason.constData());
124 }
125#endif
126 emit q->frameChanged(currentFrame, QTimeLine::QPrivateSignal());
127 }
128 if (finished && state == QTimeLine::Running) {
129 q->stop();
130 emit q->finished(QTimeLine::QPrivateSignal());
131 }
132 if (currentTime.valueBypassingBindings() != previousCurrentTime)
133 currentTime.notify();
134}
135QBindable<int> QTimeLine::bindableCurrentTime()
136{
137 Q_D(QTimeLine);
138 return &d->currentTime;
139}
140
141/*!
142 \class QTimeLine
143 \inmodule QtCore
144 \brief The QTimeLine class provides a timeline for controlling animations.
145 \since 4.2
146 \ingroup animation
147
148 It's most commonly used to animate a GUI control by calling a slot
149 periodically. You can construct a timeline by passing its duration in
150 milliseconds to QTimeLine's constructor. The timeline's duration describes
151 for how long the animation will run. Then you set a suitable frame range
152 by calling setFrameRange(). Finally connect the frameChanged() signal to a
153 suitable slot in the widget you wish to animate (for example, \l {QProgressBar::}{setValue()}
154 in QProgressBar). When you proceed to calling start(), QTimeLine will enter
155 Running state, and start emitting frameChanged() at regular intervals,
156 causing your widget's connected property's value to grow from the lower
157 end to the upper and of your frame range, at a steady rate. You can
158 specify the update interval by calling setUpdateInterval(). When done,
159 QTimeLine enters NotRunning state, and emits finished().
160
161 Example:
162
163 \snippet code/src_corelib_tools_qtimeline.cpp 0
164
165 By default the timeline runs once, from its beginning to its end,
166 upon which you must call start() again to restart from the beginning. To
167 make the timeline loop, you can call setLoopCount(), passing the number of
168 times the timeline should run before finishing. The direction can also be
169 changed, causing the timeline to run backward, by calling
170 setDirection(). You can also pause and unpause the timeline while it's
171 running by calling setPaused(). For interactive control, the
172 setCurrentTime() function is provided, which sets the time position of the
173 time line directly. Although most useful in NotRunning state (e.g.,
174 connected to a valueChanged() signal in a QSlider), this function can be
175 called at any time.
176
177 The frame interface is useful for standard widgets, but QTimeLine can be
178 used to control any type of animation. The heart of QTimeLine lies in the
179 valueForTime() function, which generates a \e value between 0 and 1 for a
180 given time. This value is typically used to describe the steps of an
181 animation, where 0 is the first step of an animation, and 1 is the last
182 step. When running, QTimeLine generates values between 0 and 1 by calling
183 valueForTime() and emitting valueChanged(). By default, valueForTime()
184 applies an interpolation algorithm to generate these value. You can choose
185 from a set of predefined timeline algorithms by calling setEasingCurve().
186
187 Note that, by default, QTimeLine uses QEasingCurve::InOutSine, which
188 provides a value that grows slowly, then grows steadily, and finally grows
189 slowly. For a custom timeline, you can reimplement valueForTime(), in which
190 case QTimeLine's easingCurve property is ignored.
191
192 \sa QProgressBar, QProgressDialog
193*/
194
195/*!
196 \enum QTimeLine::State
197
198 This enum describes the state of the timeline.
199
200 \value NotRunning The timeline is not running. This is the initial state
201 of QTimeLine, and the state QTimeLine reenters when finished. The current
202 time, frame and value remain unchanged until either setCurrentTime() is
203 called, or the timeline is started by calling start().
204
205 \value Paused The timeline is paused (i.e., temporarily
206 suspended). Calling setPaused(false) will resume timeline activity.
207
208 \value Running The timeline is running. While control is in the event
209 loop, QTimeLine will update its current time at regular intervals,
210 emitting valueChanged() and frameChanged() when appropriate.
211
212 \sa state(), stateChanged()
213*/
214
215/*!
216 \enum QTimeLine::Direction
217
218 This enum describes the direction of the timeline when in \l Running state.
219
220 \value Forward The current time of the timeline increases with time (i.e.,
221 moves from 0 and towards the end / duration).
222
223 \value Backward The current time of the timeline decreases with time (i.e.,
224 moves from the end / duration and towards 0).
225
226 \sa setDirection()
227*/
228
229/*!
230 \fn void QTimeLine::valueChanged(qreal value)
231
232 QTimeLine emits this signal at regular intervals when in \l Running state,
233 but only if the current value changes. \a value is the current value. \a value is
234 a number between 0.0 and 1.0
235
236 \sa QTimeLine::setDuration(), QTimeLine::valueForTime(), QTimeLine::updateInterval
237*/
238
239/*!
240 \fn void QTimeLine::frameChanged(int frame)
241
242 QTimeLine emits this signal at regular intervals when in \l Running state,
243 but only if the current frame changes. \a frame is the current frame number.
244
245 \sa QTimeLine::setFrameRange(), QTimeLine::updateInterval
246*/
247
248/*!
249 \fn void QTimeLine::stateChanged(QTimeLine::State newState)
250
251 This signal is emitted whenever QTimeLine's state changes. The new state
252 is \a newState.
253*/
254
255/*!
256 \fn void QTimeLine::finished()
257
258 This signal is emitted when QTimeLine finishes (i.e., reaches the end of
259 its time line), and does not loop.
260*/
261
262/*!
263 Constructs a timeline with a duration of \a duration milliseconds. \a
264 parent is passed to QObject's constructor. The default duration is 1000
265 milliseconds.
266 */
267QTimeLine::QTimeLine(int duration, QObject *parent)
268 : QObject(*new QTimeLinePrivate, parent)
269{
270 setDuration(duration);
271}
272
273/*!
274 Destroys the timeline.
275 */
276QTimeLine::~QTimeLine()
277{
278 Q_D(QTimeLine);
279
280 if (d->state == Running)
281 stop();
282}
283
284/*!
285 Returns the state of the timeline.
286
287 \sa start(), setPaused(), stop()
288*/
289QTimeLine::State QTimeLine::state() const
290{
291 Q_D(const QTimeLine);
292 return d->state;
293}
294
295/*!
296 \property QTimeLine::loopCount
297 \brief the number of times the timeline should loop before it's finished.
298
299 A loop count of 0 means that the timeline will loop forever.
300
301 By default, this property contains a value of 1.
302*/
303int QTimeLine::loopCount() const
304{
305 Q_D(const QTimeLine);
306 return d->loopCount;
307}
308
309void QTimeLine::setLoopCount(int count)
310{
311 Q_D(QTimeLine);
312 d->loopCount = count;
313}
314
315QBindable<int> QTimeLine::bindableLoopCount()
316{
317 Q_D(QTimeLine);
318 return &d->loopCount;
319}
320
321/*!
322 \property QTimeLine::direction
323 \brief the direction of the timeline when QTimeLine is in \l Running
324 state.
325
326 This direction indicates whether the time moves from 0 towards the
327 timeline duration, or from the value of the duration and towards 0 after
328 start() has been called.
329
330 Any binding of direction will be removed not only by setDirection(),
331 but also by toggleDirection().
332
333 By default, this property is set to \l Forward.
334*/
335QTimeLine::Direction QTimeLine::direction() const
336{
337 Q_D(const QTimeLine);
338 return d->direction;
339}
340void QTimeLine::setDirection(Direction direction)
341{
342 Q_D(QTimeLine);
343 d->direction.removeBindingUnlessInWrapper();
344 const auto previousDirection = d->direction.valueBypassingBindings();
345 d->direction.setValueBypassingBindings(direction);
346 d->startTime = d->currentTime;
347 d->timer.start();
348 if (previousDirection != d->direction.valueBypassingBindings())
349 d->direction.notify();
350}
351
352QBindable<QTimeLine::Direction> QTimeLine::bindableDirection()
353{
354 Q_D(QTimeLine);
355 return &d->direction;
356}
357
358/*!
359 \property QTimeLine::duration
360 \brief the total duration of the timeline in milliseconds.
361
362 By default, this value is 1000 (i.e., 1 second), but you can change this
363 by either passing a duration to QTimeLine's constructor, or by calling
364 setDuration(). The duration must be larger than 0.
365
366 \note Changing the duration does not cause the current time to be reset
367 to zero or the new duration. You also need to call setCurrentTime() with
368 the desired value.
369*/
370int QTimeLine::duration() const
371{
372 Q_D(const QTimeLine);
373 return d->duration;
374}
375void QTimeLine::setDuration(int duration)
376{
377 Q_D(QTimeLine);
378 if (duration <= 0) {
379 qWarning("QTimeLine::setDuration: cannot set duration <= 0");
380 return;
381 }
382 d->duration.removeBindingUnlessInWrapper();
383 if (duration != d->duration.valueBypassingBindings()) {
384 d->duration.setValueBypassingBindings(duration);
385 d->duration.notify();
386 }
387}
388
389QBindable<int> QTimeLine::bindableDuration()
390{
391 Q_D(QTimeLine);
392 return &d->duration;
393}
394
395/*!
396 Returns the start frame, which is the frame corresponding to the start of
397 the timeline (i.e., the frame for which the current value is 0).
398
399 \sa setStartFrame(), setFrameRange()
400*/
401int QTimeLine::startFrame() const
402{
403 Q_D(const QTimeLine);
404 return d->startFrame;
405}
406
407/*!
408 Sets the start frame, which is the frame corresponding to the start of the
409 timeline (i.e., the frame for which the current value is 0), to \a frame.
410
411 \sa startFrame(), endFrame(), setFrameRange()
412*/
413void QTimeLine::setStartFrame(int frame)
414{
415 Q_D(QTimeLine);
416 d->startFrame = frame;
417}
418
419/*!
420 Returns the end frame, which is the frame corresponding to the end of the
421 timeline (i.e., the frame for which the current value is 1).
422
423 \sa setEndFrame(), setFrameRange()
424*/
425int QTimeLine::endFrame() const
426{
427 Q_D(const QTimeLine);
428 return d->endFrame;
429}
430
431/*!
432 Sets the end frame, which is the frame corresponding to the end of the
433 timeline (i.e., the frame for which the current value is 1), to \a frame.
434
435 \sa endFrame(), startFrame(), setFrameRange()
436*/
437void QTimeLine::setEndFrame(int frame)
438{
439 Q_D(QTimeLine);
440 d->endFrame = frame;
441}
442
443/*!
444 Sets the timeline's frame counter to start at \a startFrame, and end and
445 \a endFrame. For each time value, QTimeLine will find the corresponding
446 frame when you call currentFrame() or frameForTime() by interpolating,
447 using the return value of valueForTime().
448
449 When in Running state, QTimeLine also emits the frameChanged() signal when
450 the frame changes.
451
452 \sa startFrame(), endFrame(), start(), currentFrame()
453*/
454void QTimeLine::setFrameRange(int startFrame, int endFrame)
455{
456 Q_D(QTimeLine);
457 d->startFrame = startFrame;
458 d->endFrame = endFrame;
459}
460
461/*!
462 \property QTimeLine::updateInterval
463 \brief the time in milliseconds between each time QTimeLine updates its
464 current time.
465
466 When updating the current time, QTimeLine will emit valueChanged() if the
467 current value changed, and frameChanged() if the frame changed.
468
469 By default, the interval is 40 ms, which corresponds to a rate of 25
470 updates per second.
471*/
472int QTimeLine::updateInterval() const
473{
474 Q_D(const QTimeLine);
475 return d->updateInterval;
476}
477void QTimeLine::setUpdateInterval(int interval)
478{
479 Q_D(QTimeLine);
480 d->updateInterval = interval;
481}
482QBindable<int> QTimeLine::bindableUpdateInterval()
483{
484 Q_D(QTimeLine);
485 return &d->updateInterval;
486}
487
488/*!
489 \property QTimeLine::easingCurve
490
491 \since 4.6
492
493 Specifies the easing curve that the timeline will use.
494 If valueForTime() is reimplemented, this value is ignored.
495
496 \sa valueForTime()
497*/
498
499QEasingCurve QTimeLine::easingCurve() const
500{
501 Q_D(const QTimeLine);
502 return d->easingCurve;
503}
504
505void QTimeLine::setEasingCurve(const QEasingCurve &curve)
506{
507 Q_D(QTimeLine);
508 d->easingCurve = curve;
509}
510
511QBindable<QEasingCurve> QTimeLine::bindableEasingCurve()
512{
513 Q_D(QTimeLine);
514 return &d->easingCurve;
515}
516
517/*!
518 \property QTimeLine::currentTime
519 \brief the current time of the time line.
520
521 When QTimeLine is in Running state, this value is updated continuously as
522 a function of the duration and direction of the timeline. Otherwise, it is
523 value that was current when stop() was called last, or the value set by
524 setCurrentTime().
525
526 \note You can bind other properties to currentTime, but it is not
527 recommended setting bindings to it. As animation progresses, the currentTime
528 is updated automatically, which cancels its bindings.
529
530 By default, this property contains a value of 0.
531*/
532int QTimeLine::currentTime() const
533{
534 Q_D(const QTimeLine);
535 return d->currentTime;
536}
537void QTimeLine::setCurrentTime(int msec)
538{
539 Q_D(QTimeLine);
540 d->startTime = 0;
541 d->currentLoopCount = 0;
542 d->timer.start();
543 d->setCurrentTime(msec);
544}
545
546/*!
547 Returns the frame corresponding to the current time.
548
549 \sa currentTime(), frameForTime(), setFrameRange()
550*/
551int QTimeLine::currentFrame() const
552{
553 Q_D(const QTimeLine);
554 return frameForTime(d->currentTime);
555}
556
557/*!
558 Returns the value corresponding to the current time.
559
560 \sa valueForTime(), currentFrame()
561*/
562qreal QTimeLine::currentValue() const
563{
564 Q_D(const QTimeLine);
565 return valueForTime(d->currentTime);
566}
567
568/*!
569 Returns the frame corresponding to the time \a msec. This value is
570 calculated using a linear interpolation of the start and end frame, based
571 on the value returned by valueForTime().
572
573 \sa valueForTime(), setFrameRange()
574*/
575int QTimeLine::frameForTime(int msec) const
576{
577 Q_D(const QTimeLine);
578 if (d->direction == Forward)
579 return d->startFrame + int((d->endFrame - d->startFrame) * valueForTime(msec));
580 return d->startFrame + qCeil((d->endFrame - d->startFrame) * valueForTime(msec));
581}
582
583/*!
584 Returns the timeline value for the time \a msec. The returned value, which
585 varies depending on the curve shape, is always between 0 and 1. If \a msec
586 is 0, the default implementation always returns 0.
587
588 Reimplement this function to provide a custom curve shape for your
589 timeline.
590
591 \sa easingCurve, frameForTime()
592*/
593qreal QTimeLine::valueForTime(int msec) const
594{
595 Q_D(const QTimeLine);
596 msec = qBound(0, msec, d->duration.value());
597
598 qreal value = msec / qreal(d->duration.value());
599 return d->easingCurve.value().valueForProgress(value);
600}
601
602/*!
603 Starts the timeline. QTimeLine will enter Running state, and once it
604 enters the event loop, it will update its current time, frame and value at
605 regular intervals. The default interval is 40 ms (i.e., 25 times per
606 second). You can change the update interval by calling
607 setUpdateInterval().
608
609 The timeline will start from position 0, or the end if going backward.
610 If you want to resume a stopped timeline without restarting, you can call
611 resume() instead.
612
613 \sa resume(), updateInterval(), frameChanged(), valueChanged()
614*/
615void QTimeLine::start()
616{
617 Q_D(QTimeLine);
618 if (d->basicTimer.isActive()) {
619 qWarning("QTimeLine::start: already running");
620 return;
621 }
622 int curTime = 0;
623 if (d->direction == Backward)
624 curTime = d->duration;
625 d->basicTimer.start(d->updateInterval * 1ms, this);
626 d->startTime = curTime;
627 d->currentLoopCount = 0;
628 d->timer.start();
629 d->setState(Running);
630 d->setCurrentTime(curTime);
631}
632
633/*!
634 Resumes the timeline from the current time. QTimeLine will reenter Running
635 state, and once it enters the event loop, it will update its current time,
636 frame and value at regular intervals.
637
638 In contrast to start(), this function does not restart the timeline before
639 it resumes.
640
641 \sa start(), updateInterval(), frameChanged(), valueChanged()
642*/
643void QTimeLine::resume()
644{
645 Q_D(QTimeLine);
646 if (d->basicTimer.isActive()) {
647 qWarning("QTimeLine::resume: already running");
648 return;
649 }
650 d->basicTimer.start(d->updateInterval * 1ms, this);
651 d->startTime = d->currentTime;
652 d->timer.start();
653 d->setState(Running);
654}
655
656/*!
657 Stops the timeline, causing QTimeLine to enter NotRunning state.
658
659 \sa start()
660*/
661void QTimeLine::stop()
662{
663 Q_D(QTimeLine);
664 d->basicTimer.stop();
665 d->setState(NotRunning);
666}
667
668/*!
669 If \a paused is true, the timeline is paused, causing QTimeLine to enter
670 Paused state. No updates will be signaled until either start() or
671 setPaused(false) is called. If \a paused is false, the timeline is resumed
672 and continues where it left.
673
674 \sa state(), start()
675*/
676void QTimeLine::setPaused(bool paused)
677{
678 Q_D(QTimeLine);
679 if (d->state == NotRunning) {
680 qWarning("QTimeLine::setPaused: Not running");
681 return;
682 }
683 if (paused && d->state != Paused) {
684 d->startTime = d->currentTime;
685 d->basicTimer.stop();
686 d->setState(Paused);
687 } else if (!paused && d->state == Paused) {
688 // Same as resume()
689 d->basicTimer.start(d->updateInterval * 1ms, this);
690 d->startTime = d->currentTime;
691 d->timer.start();
692 d->setState(Running);
693 }
694}
695
696/*!
697 Toggles the direction of the timeline. If the direction was Forward, it
698 becomes Backward, and vice verca.
699
700 Existing bindings of \l direction are removed.
701
702 \sa setDirection()
703*/
704void QTimeLine::toggleDirection()
705{
706 Q_D(QTimeLine);
707 setDirection(d->direction == Forward ? Backward : Forward);
708}
709
710/*!
711 \reimp
712*/
713void QTimeLine::timerEvent(QTimerEvent *event)
714{
715 Q_D(QTimeLine);
716 if (event->id() != d->basicTimer.id()) {
717 event->ignore();
718 return;
719 }
720 event->accept();
721
722 if (d->direction == Forward) {
723 d->setCurrentTime(d->startTime + d->timer.elapsed());
724 } else {
725 d->setCurrentTime(d->startTime - d->timer.elapsed());
726 }
727}
728
729QT_END_NAMESPACE
730
731#include "moc_qtimeline.cpp"