diff options
Diffstat (limited to 'src/corelib/animation/qabstractanimation.cpp')
-rw-r--r-- | src/corelib/animation/qabstractanimation.cpp | 293 |
1 files changed, 160 insertions, 133 deletions
diff --git a/src/corelib/animation/qabstractanimation.cpp b/src/corelib/animation/qabstractanimation.cpp index 52adc343ab..9687af0987 100644 --- a/src/corelib/animation/qabstractanimation.cpp +++ b/src/corelib/animation/qabstractanimation.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only /*! \class QAbstractAnimation @@ -149,7 +113,6 @@ #include "qabstractanimation_p.h" #include <QtCore/qmath.h> -#include <QtCore/qthreadstorage.h> #include <QtCore/qcoreevent.h> #include <QtCore/qpointer.h> #include <QtCore/qscopedvaluerollback.h> @@ -214,12 +177,10 @@ typedef QList<QAbstractAnimation*>::ConstIterator AnimationListConstIt; QUnifiedTimer drives animations indirectly, via QAbstractAnimationTimer. */ -Q_GLOBAL_STATIC(QThreadStorage<QUnifiedTimer *>, unifiedTimer) - QUnifiedTimer::QUnifiedTimer() : QObject(), defaultDriver(this), lastTick(0), timingInterval(DEFAULT_TIMER_INTERVAL), currentAnimationIdx(0), insideTick(false), insideRestart(false), consistentTiming(false), slowMode(false), - startTimersPending(false), stopTimerPending(false), + startTimersPending(false), stopTimerPending(false), allowNegativeDelta(false), slowdownFactor(5.0f), profilerCallback(nullptr), driverStartTime(0), temporalDrift(0) { @@ -227,15 +188,18 @@ QUnifiedTimer::QUnifiedTimer() : driver = &defaultDriver; } +QUnifiedTimer::~QUnifiedTimer() + = default; QUnifiedTimer *QUnifiedTimer::instance(bool create) { QUnifiedTimer *inst; - if (create && !unifiedTimer()->hasLocalData()) { + static thread_local std::unique_ptr<QUnifiedTimer> unifiedTimer; + if (create && !unifiedTimer) { inst = new QUnifiedTimer; - unifiedTimer()->setLocalData(inst); + unifiedTimer.reset(inst); } else { - inst = unifiedTimer() ? unifiedTimer()->localData() : nullptr; + inst = unifiedTimer.get(); } return inst; } @@ -248,7 +212,7 @@ QUnifiedTimer *QUnifiedTimer::instance() void QUnifiedTimer::maybeUpdateAnimationsToCurrentTime() { if (elapsed() - lastTick > 50) - updateAnimationTimers(-1); + updateAnimationTimers(); } qint64 QUnifiedTimer::elapsed() const @@ -290,13 +254,13 @@ void QUnifiedTimer::stopAnimationDriver() driver->stop(); } -void QUnifiedTimer::updateAnimationTimers(qint64 currentTick) +void QUnifiedTimer::updateAnimationTimers() { //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations - if(insideTick) + if (insideTick) return; - qint64 totalElapsed = currentTick > 0 ? currentTick : elapsed(); + const qint64 totalElapsed = elapsed(); // ignore consistentTiming in case the pause timer is active qint64 delta = (consistentTiming && !pauseTimer.isActive()) ? @@ -315,11 +279,11 @@ void QUnifiedTimer::updateAnimationTimers(qint64 currentTick) // when the CPU load is high //* it might happen in some cases that the delta is negative because the animation driver // advances faster than time.elapsed() - if (delta > 0) { + if (delta != 0 && (allowNegativeDelta || delta > 0)) { QScopedValueRollback<bool> guard(insideTick, true); if (profilerCallback) profilerCallback(delta); - for (currentAnimationIdx = 0; currentAnimationIdx < animationTimers.count(); ++currentAnimationIdx) { + for (currentAnimationIdx = 0; currentAnimationIdx < animationTimers.size(); ++currentAnimationIdx) { QAbstractAnimationTimer *animation = animationTimers.at(currentAnimationIdx); animation->updateAnimationsTime(delta); } @@ -330,7 +294,7 @@ void QUnifiedTimer::updateAnimationTimers(qint64 currentTick) int QUnifiedTimer::runningAnimationCount() { int count = 0; - for (int i = 0; i < animationTimers.count(); ++i) + for (int i = 0; i < animationTimers.size(); ++i) count += animationTimers.at(i)->runningAnimationCount(); return count; } @@ -345,7 +309,7 @@ void QUnifiedTimer::localRestart() if (insideRestart) return; - if (!pausedAnimationTimers.isEmpty() && (animationTimers.count() + animationTimersToStart.count() == pausedAnimationTimers.count())) { + if (!pausedAnimationTimers.isEmpty() && (animationTimers.size() + animationTimersToStart.size() == pausedAnimationTimers.size())) { driver->stop(); int closestTimeToFinish = closestPausedAnimationTimerTimeToFinish(); // use a precise timer if the pause will be short @@ -363,7 +327,7 @@ void QUnifiedTimer::restart() { { QScopedValueRollback<bool> guard(insideRestart, true); - for (int i = 0; i < animationTimers.count(); ++i) + for (int i = 0; i < animationTimers.size(); ++i) animationTimers.at(i)->restartAnimationTimer(); } @@ -423,7 +387,7 @@ void QUnifiedTimer::timerEvent(QTimerEvent *event) if (event->timerId() == pauseTimer.timerId()) { // update current time on all timers - updateAnimationTimers(-1); + updateAnimationTimers(); restart(); } } @@ -517,6 +481,8 @@ void QUnifiedTimer::installAnimationDriver(QAnimationDriver *d) if (running) stopAnimationDriver(); driver = d; + if (driver) + allowNegativeDelta = driver->property("allowNegativeDelta").toBool(); if (running) startAnimationDriver(); } @@ -532,6 +498,7 @@ void QUnifiedTimer::uninstallAnimationDriver(QAnimationDriver *d) if (running) stopAnimationDriver(); driver = &defaultDriver; + allowNegativeDelta = false; if (running) startAnimationDriver(); } @@ -545,10 +512,6 @@ bool QUnifiedTimer::canUninstallAnimationDriver(QAnimationDriver *d) return d == driver && driver != &defaultDriver; } -#if QT_CONFIG(thread) -Q_GLOBAL_STATIC(QThreadStorage<QAnimationTimer *>, animationTimer) -#endif - QAnimationTimer::QAnimationTimer() : QAbstractAnimationTimer(), lastTick(0), currentAnimationIdx(0), insideTick(false), @@ -557,15 +520,19 @@ QAnimationTimer::QAnimationTimer() : { } +QAnimationTimer::~QAnimationTimer() + = default; + QAnimationTimer *QAnimationTimer::instance(bool create) { QAnimationTimer *inst; #if QT_CONFIG(thread) - if (create && !animationTimer()->hasLocalData()) { + static thread_local std::unique_ptr<QAnimationTimer> animationTimer; + if (create && !animationTimer) { inst = new QAnimationTimer; - animationTimer()->setLocalData(inst); + animationTimer.reset(inst); } else { - inst = animationTimer() ? animationTimer()->localData() : nullptr; + inst = animationTimer.get(); } #else Q_UNUSED(create); @@ -585,7 +552,7 @@ void QAnimationTimer::ensureTimerUpdate() QAnimationTimer *inst = QAnimationTimer::instance(false); QUnifiedTimer *instU = QUnifiedTimer::instance(false); if (instU && inst && inst->isPaused) - instU->updateAnimationTimers(-1); + instU->updateAnimationTimers(); } void QAnimationTimer::updateAnimationsTime(qint64 delta) @@ -601,7 +568,7 @@ void QAnimationTimer::updateAnimationsTime(qint64 delta) //when the CPU load is high if (delta) { QScopedValueRollback<bool> guard(insideTick, true); - for (currentAnimationIdx = 0; currentAnimationIdx < animations.count(); ++currentAnimationIdx) { + for (currentAnimationIdx = 0; currentAnimationIdx < animations.size(); ++currentAnimationIdx) { QAbstractAnimation *animation = animations.at(currentAnimationIdx); int elapsed = QAbstractAnimationPrivate::get(animation)->totalCurrentTime + (animation->direction() == QAbstractAnimation::Forward ? delta : -delta); @@ -772,68 +739,33 @@ QAnimationDriver::~QAnimationDriver() uninstall(); } - -#if QT_DEPRECATED_SINCE(5, 13) -/*! - Sets the time at which an animation driver should start at. - - This is to take into account that pauses can occur in running - animations which will stop the driver, but the time still - increases. - - \obsolete - - This logic is now handled internally in the animation system. - */ -void QAnimationDriver::setStartTime(qint64) -{ -} - -/*! - Returns the start time of the animation. - - \obsolete - - This logic is now handled internally in the animation system. - */ -qint64 QAnimationDriver::startTime() const -{ - return 0; -} -#endif - - /*! - Advances the animation based to the specified \a timeStep. This function should - be continuously called by the driver subclasses while the animation is running. - - If \a timeStep is positive, it will be used as the current time in the - calculations; otherwise, the current clock time will be used. + Advances the animation. This function should be continuously called by + the driver subclasses while the animation is running. - Since 5.4, the timeStep argument is ignored and elapsed() will be - used instead in combination with the internal time offsets of the - animation system. + The calculation of the new current time will use elapsed() in combination + with the internal time offsets of the animation system. */ -void QAnimationDriver::advanceAnimation(qint64 timeStep) +void QAnimationDriver::advanceAnimation() { QUnifiedTimer *instance = QUnifiedTimer::instance(); // update current time on all top level animations - instance->updateAnimationTimers(timeStep); + instance->updateAnimationTimers(); instance->restart(); } /*! - Advances the animation. This function should be continously called + Advances the animation. This function should be continuously called by the driver while the animation is running. */ void QAnimationDriver::advance() { - advanceAnimation(-1); + advanceAnimation(); } @@ -924,8 +856,14 @@ qint64 QAnimationDriver::elapsed() const QDefaultAnimationDriver::QDefaultAnimationDriver(QUnifiedTimer *timer) : QAnimationDriver(nullptr), m_unified_timer(timer) { - connect(this, SIGNAL(started()), this, SLOT(startTimer())); - connect(this, SIGNAL(stopped()), this, SLOT(stopTimer())); + connect(this, &QAnimationDriver::started, this, &QDefaultAnimationDriver::startTimer); + connect(this, &QAnimationDriver::stopped, this, &QDefaultAnimationDriver::stopTimer); +} + +QDefaultAnimationDriver::~QDefaultAnimationDriver() +{ + disconnect(this, &QAnimationDriver::started, this, &QDefaultAnimationDriver::startTimer); + disconnect(this, &QAnimationDriver::stopped, this, &QDefaultAnimationDriver::stopTimer); } void QDefaultAnimationDriver::timerEvent(QTimerEvent *e) @@ -946,18 +884,33 @@ void QDefaultAnimationDriver::stopTimer() m_timer.stop(); } +QAnimationDriverPrivate::QAnimationDriverPrivate() + = default; + +QAnimationDriverPrivate::~QAnimationDriverPrivate() + = default; + +QAbstractAnimationTimer::QAbstractAnimationTimer() + = default; + +QAbstractAnimationTimer::~QAbstractAnimationTimer() + = default; +QAbstractAnimationPrivate::QAbstractAnimationPrivate() + = default; + +QAbstractAnimationPrivate::~QAbstractAnimationPrivate() { } void QAbstractAnimationPrivate::setState(QAbstractAnimation::State newState) { Q_Q(QAbstractAnimation); - if (state == newState) + const QAbstractAnimation::State oldState = state.valueBypassingBindings(); + if (oldState == newState) return; if (loopCount == 0) return; - QAbstractAnimation::State oldState = state; int oldCurrentTime = currentTime; int oldCurrentLoop = currentLoop; QAbstractAnimation::Direction oldDirection = direction; @@ -965,14 +918,17 @@ void QAbstractAnimationPrivate::setState(QAbstractAnimation::State newState) // check if we should Rewind if ((newState == QAbstractAnimation::Paused || newState == QAbstractAnimation::Running) && oldState == QAbstractAnimation::Stopped) { + const int oldTotalCurrentTime = totalCurrentTime; //here we reset the time if needed //we don't call setCurrentTime because this might change the way the animation //behaves: changing the state or changing the current value totalCurrentTime = currentTime = (direction == QAbstractAnimation::Forward) ? 0 : (loopCount == -1 ? q->duration() : q->totalDuration()); + if (totalCurrentTime != oldTotalCurrentTime) + totalCurrentTime.notify(); } - state = newState; + state.setValueBypassingBindings(newState); QPointer<QAbstractAnimation> guard(q); //(un)registration of the animation must always happen before calls to @@ -988,12 +944,15 @@ void QAbstractAnimationPrivate::setState(QAbstractAnimation::State newState) } q->updateState(newState, oldState); - if (!guard || newState != state) //this is to be safe if updateState changes the state + //this is to be safe if updateState changes the state + if (!guard || newState != state.valueBypassingBindings()) return; // Notify state change + state.notify(); emit q->stateChanged(newState, oldState); - if (!guard || newState != state) //this is to be safe if updateState changes the state + //this is to be safe if updateState changes the state + if (!guard || newState != state.valueBypassingBindings()) return; switch (state) { @@ -1063,6 +1022,7 @@ QAbstractAnimation::~QAbstractAnimation() if (d->state != Stopped) { QAbstractAnimation::State oldState = d->state; d->state = Stopped; + d->state.notify(); emit stateChanged(d->state, oldState); if (oldState == QAbstractAnimation::Running) QAnimationTimer::unregisterAnimation(this); @@ -1078,6 +1038,11 @@ QAbstractAnimation::~QAbstractAnimation() This property describes the current state of the animation. When the animation state changes, QAbstractAnimation emits the stateChanged() signal. + + \note State updates might cause updates of the currentTime property, + which, in turn, can cancel its bindings. So be careful when setting + bindings to the currentTime property, when you expect the state of the + animation to change. */ QAbstractAnimation::State QAbstractAnimation::state() const { @@ -1085,6 +1050,12 @@ QAbstractAnimation::State QAbstractAnimation::state() const return d->state; } +QBindable<QAbstractAnimation::State> QAbstractAnimation::bindableState() const +{ + Q_D(const QAbstractAnimation); + return &d->state; +} + /*! If this animation is part of a QAnimationGroup, this function returns a pointer to the group; otherwise, it returns \nullptr. @@ -1150,9 +1121,13 @@ QAbstractAnimation::Direction QAbstractAnimation::direction() const void QAbstractAnimation::setDirection(Direction direction) { Q_D(QAbstractAnimation); - if (d->direction == direction) + if (d->direction == direction) { + d->direction.removeBindingUnlessInWrapper(); return; + } + const QScopedPropertyUpdateGroup guard; + const int oldCurrentLoop = d->currentLoop; if (state() == Stopped) { if (direction == Backward) { d->currentTime = duration(); @@ -1175,7 +1150,15 @@ void QAbstractAnimation::setDirection(Direction direction) // needed to update the timer interval in case of a pause animation QAnimationTimer::updateAnimationTimer(); - emit directionChanged(direction); + if (d->currentLoop != oldCurrentLoop) + d->currentLoop.notify(); + d->direction.notify(); +} + +QBindable<QAbstractAnimation::Direction> QAbstractAnimation::bindableDirection() +{ + Q_D(QAbstractAnimation); + return &d->direction; } /*! @@ -1210,6 +1193,12 @@ void QAbstractAnimation::setLoopCount(int loopCount) d->loopCount = loopCount; } +QBindable<int> QAbstractAnimation::bindableLoopCount() +{ + Q_D(QAbstractAnimation); + return &d->loopCount; +} + /*! \property QAbstractAnimation::currentLoop \brief the current loop of the animation @@ -1229,6 +1218,12 @@ int QAbstractAnimation::currentLoop() const return d->currentLoop; } +QBindable<int> QAbstractAnimation::bindableCurrentLoop() const +{ + Q_D(const QAbstractAnimation); + return &d->currentLoop; +} + /*! \fn virtual int QAbstractAnimation::duration() const = 0 @@ -1287,6 +1282,10 @@ int QAbstractAnimation::currentLoopTime() const The animation's current time starts at 0, and ends at totalDuration(). + \note You can bind other properties to currentTime, but it is not + recommended setting bindings to it. As animation progresses, the currentTime + is updated automatically, which cancels its bindings. + \sa loopCount, currentLoopTime() */ int QAbstractAnimation::currentTime() const @@ -1294,44 +1293,72 @@ int QAbstractAnimation::currentTime() const Q_D(const QAbstractAnimation); return d->totalCurrentTime; } + +QBindable<int> QAbstractAnimation::bindableCurrentTime() +{ + Q_D(QAbstractAnimation); + return &d->totalCurrentTime; +} + void QAbstractAnimation::setCurrentTime(int msecs) { Q_D(QAbstractAnimation); msecs = qMax(msecs, 0); // Calculate new time and loop. - int dura = duration(); - int totalDura = dura <= 0 ? dura : ((d->loopCount < 0) ? -1 : dura * d->loopCount); + const int dura = duration(); + const int totalLoopCount = d->loopCount; + const int totalDura = dura <= 0 ? dura : ((totalLoopCount < 0) ? -1 : dura * totalLoopCount); if (totalDura != -1) msecs = qMin(totalDura, msecs); - d->totalCurrentTime = msecs; + + d->totalCurrentTime.removeBindingUnlessInWrapper(); + + const int oldCurrentTime = d->totalCurrentTime.valueBypassingBindings(); + d->totalCurrentTime.setValueBypassingBindings(msecs); + + QAbstractAnimation::Direction currentDirection = d->direction; // Update new values. - int oldLoop = d->currentLoop; - d->currentLoop = ((dura <= 0) ? 0 : (msecs / dura)); - if (d->currentLoop == d->loopCount) { + const int oldLoop = d->currentLoop.valueBypassingBindings(); + int newCurrentLoop = (dura <= 0) ? 0 : (msecs / dura); + if (newCurrentLoop == totalLoopCount) { //we're at the end d->currentTime = qMax(0, dura); - d->currentLoop = qMax(0, d->loopCount - 1); + newCurrentLoop = qMax(0, totalLoopCount - 1); } else { - if (d->direction == Forward) { + if (currentDirection == Forward) { d->currentTime = (dura <= 0) ? msecs : (msecs % dura); } else { d->currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1; if (d->currentTime == dura) - --d->currentLoop; + newCurrentLoop = newCurrentLoop - 1; } } + d->currentLoop.setValueBypassingBindings(newCurrentLoop); + // this is a virtual function, so it can update the properties as well updateCurrentTime(d->currentTime); - if (d->currentLoop != oldLoop) - emit currentLoopChanged(d->currentLoop); + // read the property values again + newCurrentLoop = d->currentLoop.valueBypassingBindings(); + currentDirection = d->direction; + const int newTotalCurrentTime = d->totalCurrentTime.valueBypassingBindings(); + + if (newCurrentLoop != oldLoop) + d->currentLoop.notify(); + + /* Notify before calling stop: As seen in tst_QSequentialAnimationGroup::clear + * we might delete the animation when stop is called. Thus after stop no member + * of the object must be used anymore. + */ + if (oldCurrentTime != newTotalCurrentTime) + d->totalCurrentTime.notify(); // All animations are responsible for stopping the animation when their // own end state is reached; in this case the animation is time driven, // and has reached the end. - if ((d->direction == Forward && d->totalCurrentTime == totalDura) - || (d->direction == Backward && d->totalCurrentTime == 0)) { + if ((currentDirection == Forward && newTotalCurrentTime == totalDura) + || (currentDirection == Backward && newTotalCurrentTime == 0)) { stop(); } } @@ -1355,7 +1382,7 @@ void QAbstractAnimation::setCurrentTime(int msecs) void QAbstractAnimation::start(DeletionPolicy policy) { Q_D(QAbstractAnimation); - if (d->state == Running) + if (d->state.valueBypassingBindings() == Running) return; d->deleteWhenStopped = policy; d->setState(Running); @@ -1375,7 +1402,7 @@ void QAbstractAnimation::stop() { Q_D(QAbstractAnimation); - if (d->state == Stopped) + if (d->state.valueBypassingBindings() == Stopped) return; d->setState(Stopped); @@ -1391,7 +1418,7 @@ void QAbstractAnimation::stop() void QAbstractAnimation::pause() { Q_D(QAbstractAnimation); - if (d->state == Stopped) { + if (d->state.valueBypassingBindings() == Stopped) { qWarning("QAbstractAnimation::pause: Cannot pause a stopped animation"); return; } @@ -1409,7 +1436,7 @@ void QAbstractAnimation::pause() void QAbstractAnimation::resume() { Q_D(QAbstractAnimation); - if (d->state != Paused) { + if (d->state.valueBypassingBindings() != Paused) { qWarning("QAbstractAnimation::resume: " "Cannot resume an animation that is not paused"); return; |