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