diff --git a/autotests/libkwineffects/CMakeLists.txt b/autotests/libkwineffects/CMakeLists.txt --- a/autotests/libkwineffects/CMakeLists.txt +++ b/autotests/libkwineffects/CMakeLists.txt @@ -11,6 +11,7 @@ kwineffects_unit_tests( windowquadlisttest + timelinetest ) add_executable(kwinglplatformtest kwinglplatformtest.cpp mock_gl.cpp ../../libkwineffects/kwinglplatform.cpp) diff --git a/autotests/libkwineffects/timelinetest.cpp b/autotests/libkwineffects/timelinetest.cpp new file mode 100644 --- /dev/null +++ b/autotests/libkwineffects/timelinetest.cpp @@ -0,0 +1,256 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2018 Vlad Zagorodniy + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#include + +#include + +using namespace std::chrono_literals; + +// FIXME: Delete it in the future. +Q_DECLARE_METATYPE(std::chrono::milliseconds) + +class TimeLineTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testUpdateForward(); + void testUpdateBackward(); + void testUpdateFinished(); + void testToggleDirection(); + void testReset(); + void testSetElapsed_data(); + void testSetElapsed(); + void testSetDuration(); + void testSetDurationRetargeting(); + void testSetDurationRetargetingSmallDuration(); + void testRunning(); +}; + +void TimeLineTest::testUpdateForward() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + // 0/1000 + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); + + // 100/1000 + timeLine.update(100ms); + QCOMPARE(timeLine.value(), 0.1); + QVERIFY(!timeLine.done()); + + // 400/1000 + timeLine.update(300ms); + QCOMPARE(timeLine.value(), 0.4); + QVERIFY(!timeLine.done()); + + // 900/1000 + timeLine.update(500ms); + QCOMPARE(timeLine.value(), 0.9); + QVERIFY(!timeLine.done()); + + // 1000/1000 + timeLine.update(3000ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testUpdateBackward() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Backward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + // 0/1000 + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(!timeLine.done()); + + // 100/1000 + timeLine.update(100ms); + QCOMPARE(timeLine.value(), 0.9); + QVERIFY(!timeLine.done()); + + // 400/1000 + timeLine.update(300ms); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + // 900/1000 + timeLine.update(500ms); + QCOMPARE(timeLine.value(), 0.1); + QVERIFY(!timeLine.done()); + + // 1000/1000 + timeLine.update(3000ms); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testUpdateFinished() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(1000ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); + + timeLine.update(42ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testToggleDirection() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); + + timeLine.update(600ms); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + timeLine.toggleDirection(); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + timeLine.update(200ms); + QCOMPARE(timeLine.value(), 0.4); + QVERIFY(!timeLine.done()); + + timeLine.update(3000ms); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testReset() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(1000ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); + + timeLine.reset(); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); +} + +void TimeLineTest::testSetElapsed_data() +{ + QTest::addColumn("duration"); + QTest::addColumn("elapsed"); + QTest::addColumn("expectedElapsed"); + QTest::addColumn("expectedDone"); + QTest::addColumn("initiallyDone"); + + QTest::newRow("Less than duration, not finished") << 1000ms << 300ms << 300ms << false << false; + QTest::newRow("Less than duration, finished") << 1000ms << 300ms << 300ms << false << true; + QTest::newRow("Greater than duration, not finished") << 1000ms << 3000ms << 1000ms << true << false; + QTest::newRow("Greater than duration, finished") << 1000ms << 3000ms << 1000ms << true << true; + QTest::newRow("Equal to duration, not finished") << 1000ms << 1000ms << 1000ms << true << false; + QTest::newRow("Equal to duration, finished") << 1000ms << 1000ms << 1000ms << true << true; +} + +void TimeLineTest::testSetElapsed() +{ + QFETCH(std::chrono::milliseconds, duration); + QFETCH(std::chrono::milliseconds, elapsed); + QFETCH(std::chrono::milliseconds, expectedElapsed); + QFETCH(bool, expectedDone); + QFETCH(bool, initiallyDone); + + KWin::TimeLine timeLine(duration, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + if (initiallyDone) { + timeLine.update(duration); + QVERIFY(timeLine.done()); + } + + timeLine.setElapsed(elapsed); + QCOMPARE(timeLine.elapsed(), expectedElapsed); + QCOMPARE(timeLine.done(), expectedDone); +} + +void TimeLineTest::testSetDuration() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + QCOMPARE(timeLine.duration(), 1000ms); + + timeLine.setDuration(3000ms); + QCOMPARE(timeLine.duration(), 3000ms); +} + +void TimeLineTest::testSetDurationRetargeting() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(500ms); + QCOMPARE(timeLine.value(), 0.5); + QVERIFY(!timeLine.done()); + + timeLine.setDuration(3000ms); + QCOMPARE(timeLine.value(), 0.5); + QVERIFY(!timeLine.done()); +} + +void TimeLineTest::testSetDurationRetargetingSmallDuration() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(999ms); + QCOMPARE(timeLine.value(), 0.999); + QVERIFY(!timeLine.done()); + + timeLine.setDuration(3ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testRunning() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.update(100ms); + QVERIFY(timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.update(900ms); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); +} + +QTEST_MAIN(TimeLineTest) + +#include "timelinetest.moc" diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -5,6 +5,7 @@ Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray Copyright (C) 2010, 2011 Martin Gräßlin +Copyright (C) 2018 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,6 +28,7 @@ #include #include +#include #include #include #include @@ -186,7 +188,7 @@ #define KWIN_EFFECT_API_MAKE_VERSION( major, minor ) (( major ) << 8 | ( minor )) #define KWIN_EFFECT_API_VERSION_MAJOR 0 -#define KWIN_EFFECT_API_VERSION_MINOR 225 +#define KWIN_EFFECT_API_VERSION_MINOR 226 #define KWIN_EFFECT_API_VERSION KWIN_EFFECT_API_MAKE_VERSION( \ KWIN_EFFECT_API_VERSION_MAJOR, KWIN_EFFECT_API_VERSION_MINOR ) @@ -3283,6 +3285,200 @@ EffectFramePrivate* const d; }; +/** + * The TimeLine class is a helper for controlling animations. + **/ +class KWINEFFECTS_EXPORT TimeLine +{ +public: + /** + * Direction of the timeline. + * + * When the direction of the timeline is Forward, the progress + * value will go from 0.0 to 1.0. + * + * When the direction of the timeline is Backward, the progress + * value will go from 1.0 to 0.0. + **/ + enum Direction { + Forward, + Backward + }; + + /** + * Constructs a new instance of TimeLine. + * + * @param duration Duration of the timeline, in milliseconds + * @param direction Direction of the timeline + * @since 5.14 + **/ + explicit TimeLine(std::chrono::milliseconds duration = std::chrono::milliseconds(1000), + Direction direction = Forward); + TimeLine(const TimeLine &other); + ~TimeLine(); + + /** + * Returns the current value of the timeline. + * + * @since 5.14 + **/ + qreal value() const; + + /** + * Updates the progress of the timeline. + * + * @note The delta value should be a non-negative number, i.e. it + * should be greater or equal to 0. + * + * @param delta The number milliseconds passed since last frame + * @since 5.14 + **/ + void update(std::chrono::milliseconds delta); + + /** + * Returns the number of elapsed milliseconds. + * + * @see setElapsed + * @since 5.14 + **/ + std::chrono::milliseconds elapsed() const; + + /** + * Sets the number of elapsed milliseconds. + * + * This method overwrites previous value of elapsed milliseconds. + * If the new value of elapsed milliseconds is greater or equal + * to duration of the timeline, the timeline will be finished, i.e. + * proceeding TimeLine::done method calls will return @c true. + * Please don't use it. Instead, use TimeLine::update. + * + * @note The new number of elapsed milliseconds should be a non-negative + * number, i.e. it should be greater or equal to 0. + * + * @param elapsed The new number of elapsed milliseconds + * @see elapsed + * @since 5.14 + **/ + void setElapsed(std::chrono::milliseconds elapsed); + + /** + * Returns the duration of the timeline. + * + * @returns Duration of the timeline, in milliseconds + * @see setDuration + * @since 5.14 + **/ + std::chrono::milliseconds duration() const; + + /** + * Sets the duration of the timeline. + * + * In addition to setting new value of duration, the timeline will + * try to retarget the number of elapsed milliseconds to match + * as close as possible old progress value. If the new duration + * is much smaller than old duration, there is a big chance that + * the timeline will be finished after setting new duration. + * + * @note The new duration should be a positive number, i.e. it + * should be greater or equal to 1. + * + * @param duration The new duration of the timeline, in milliseconds + * @see duration + * @since 5.14 + **/ + void setDuration(std::chrono::milliseconds duration); + + /** + * Returns the direction of the timeline. + * + * @returns Direction of the timeline(TimeLine::Forward or TimeLine::Backward) + * @see setDirection + * @see toggleDirection + * @since 5.14 + **/ + Direction direction() const; + + /** + * Sets the direction of the timeline. + * + * @param direction The new direction of the timeline + * @see direction + * @see toggleDirection + * @since 5.14 + **/ + void setDirection(Direction direction); + + /** + * Toggles the direction of the timeline. + * + * If the direction of the timeline was TimeLine::Forward, it becomes + * TimeLine::Backward, and vice verca. + * + * @see direction + * @see setDirection + * @since 5.14 + **/ + void toggleDirection(); + + /** + * Returns the easing curve of the timeline. + * + * @see setEasingCurve + * @since 5.14 + **/ + QEasingCurve easingCurve() const; + + /** + * Sets new easing curve. + * + * @param easingCurve An easing curve to be set + * @see easingCurve + * @since 5.14 + **/ + void setEasingCurve(const QEasingCurve &easingCurve); + + /** + * Sets new easing curve by providing its type. + * + * @param type Type of the easing curve(e.g. QEasingCurve::InQuad, etc) + * @see easingCurve + * @since 5.14 + **/ + void setEasingCurve(QEasingCurve::Type type); + + /** + * Returns whether the timeline is currently in progress. + * + * @see done + * @since 5.14 + **/ + bool running() const; + + /** + * Returns whether the timeline is finished. + * + * @see reset + * @since 5.14 + **/ + bool done() const; + + /** + * Resets the timeline to initial state. + * + * @since 5.14 + **/ + void reset(); + + TimeLine &operator=(const TimeLine &other); + +private: + qreal progress() const; + +private: + class Data; + QSharedDataPointer d; +}; + /** * Pointer to the global EffectsHandler object. **/ @@ -3512,6 +3708,7 @@ } // namespace Q_DECLARE_METATYPE(KWin::EffectWindow*) Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(KWin::TimeLine) /** @} */ diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -4,6 +4,7 @@ Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray +Copyright (C) 2018 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1919,5 +1920,152 @@ d->screenProjectionMatrix = spm; } +/*************************************************************** + TimeLine +***************************************************************/ + +class Q_DECL_HIDDEN TimeLine::Data : public QSharedData +{ +public: + std::chrono::milliseconds duration; + Direction direction; + QEasingCurve easingCurve; + + std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero(); + bool done = false; +}; + +TimeLine::TimeLine(std::chrono::milliseconds duration, Direction direction) + : d(new Data) +{ + Q_ASSERT(duration > std::chrono::milliseconds::zero()); + d->duration = duration; + d->direction = direction; +} + +TimeLine::TimeLine(const TimeLine &other) + : d(other.d) +{ +} + +TimeLine::~TimeLine() = default; + +qreal TimeLine::progress() const +{ + return static_cast(d->elapsed.count()) / d->duration.count(); +} + +qreal TimeLine::value() const +{ + const qreal t = progress(); + return d->easingCurve.valueForProgress( + d->direction == Backward ? 1.0 - t : t); +} + +void TimeLine::update(std::chrono::milliseconds delta) +{ + Q_ASSERT(delta >= std::chrono::milliseconds::zero()); + if (d->done) { + return; + } + d->elapsed += delta; + if (d->elapsed >= d->duration) { + d->done = true; + d->elapsed = d->duration; + } +} + +std::chrono::milliseconds TimeLine::elapsed() const +{ + return d->elapsed; +} + +void TimeLine::setElapsed(std::chrono::milliseconds elapsed) +{ + Q_ASSERT(elapsed >= std::chrono::milliseconds::zero()); + if (elapsed == d->elapsed) { + return; + } + reset(); + update(elapsed); +} + +std::chrono::milliseconds TimeLine::duration() const +{ + return d->duration; +} + +void TimeLine::setDuration(std::chrono::milliseconds duration) +{ + Q_ASSERT(duration > std::chrono::milliseconds::zero()); + if (duration == d->duration) { + return; + } + d->elapsed = std::chrono::milliseconds(qRound(progress() * duration.count())); + d->duration = duration; + if (d->elapsed == d->duration) { + d->done = true; + } +} + +TimeLine::Direction TimeLine::direction() const +{ + return d->direction; +} + +void TimeLine::setDirection(TimeLine::Direction direction) +{ + if (d->direction == direction) { + return; + } + if (d->elapsed > std::chrono::milliseconds::zero()) { + d->elapsed = d->duration - d->elapsed; + } + d->direction = direction; +} + +void TimeLine::toggleDirection() +{ + setDirection(d->direction == Forward ? Backward : Forward); +} + +QEasingCurve TimeLine::easingCurve() const +{ + return d->easingCurve; +} + +void TimeLine::setEasingCurve(const QEasingCurve &easingCurve) +{ + d->easingCurve = easingCurve; +} + +void TimeLine::setEasingCurve(QEasingCurve::Type type) +{ + d->easingCurve.setType(type); +} + +bool TimeLine::running() const +{ + return d->elapsed != std::chrono::milliseconds::zero() + && d->elapsed != d->duration; +} + +bool TimeLine::done() const +{ + return d->done; +} + +void TimeLine::reset() +{ + d->elapsed = std::chrono::milliseconds::zero(); + d->done = false; +} + +TimeLine &TimeLine::operator=(const TimeLine &other) +{ + d = other.d; + return *this; +} + } // namespace