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,233 @@ +/******************************************************************** + 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 + +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 TimeLineTest::testUpdateForward() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + // 0/1000 + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); + + // 100/1000 + timeLine.update(100); + QCOMPARE(timeLine.value(), 0.1); + QVERIFY(!timeLine.done()); + + // 400/1000 + timeLine.update(300); + QCOMPARE(timeLine.value(), 0.4); + QVERIFY(!timeLine.done()); + + // 900/1000 + timeLine.update(500); + QCOMPARE(timeLine.value(), 0.9); + QVERIFY(!timeLine.done()); + + // 1000/1000 + timeLine.update(3000); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testUpdateBackward() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Backward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + // 0/1000 + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(!timeLine.done()); + + // 100/1000 + timeLine.update(100); + QCOMPARE(timeLine.value(), 0.9); + QVERIFY(!timeLine.done()); + + // 400/1000 + timeLine.update(300); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + // 900/1000 + timeLine.update(500); + QCOMPARE(timeLine.value(), 0.1); + QVERIFY(!timeLine.done()); + + // 1000/1000 + timeLine.update(3000); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testUpdateFinished() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(1000); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); + + timeLine.update(42); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testToggleDirection() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); + + timeLine.update(600); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + timeLine.toggleDirection(); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + timeLine.update(200); + QCOMPARE(timeLine.value(), 0.4); + QVERIFY(!timeLine.done()); + + timeLine.update(3000); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testReset() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(1000); + 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") << 1000 << 300 << 300 << false << false; + QTest::newRow("Less than duration, finished") << 1000 << 300 << 300 << false << true; + QTest::newRow("Greater than duration, not finished") << 1000 << 3000 << 1000 << true << false; + QTest::newRow("Greater than duration, finished") << 1000 << 3000 << 1000 << true << true; + QTest::newRow("Equal to duration, not finished") << 1000 << 1000 << 1000 << true << false; + QTest::newRow("Equal to duration, finished") << 1000 << 1000 << 1000 << true << true; +} + +void TimeLineTest::testSetElapsed() +{ + QFETCH(int, duration); + QFETCH(int, elapsed); + QFETCH(int, 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(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + QCOMPARE(timeLine.duration(), 1000); + + timeLine.setDuration(3000); + QCOMPARE(timeLine.duration(), 3000); +} + +void TimeLineTest::testSetDurationRetargeting() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(500); + QCOMPARE(timeLine.value(), 0.5); + QVERIFY(!timeLine.done()); + + timeLine.setDuration(3000); + QCOMPARE(timeLine.value(), 0.5); + QVERIFY(!timeLine.done()); +} + +void TimeLineTest::testSetDurationRetargetingSmallDuration() +{ + KWin::TimeLine timeLine(1000, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.update(999); + QCOMPARE(timeLine.value(), 0.999); + QVERIFY(!timeLine.done()); + + timeLine.setDuration(3); + QCOMPARE(timeLine.value(), 1.0); + 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,198 @@ 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(int duration = 1000, Direction direction = Forward); + + /** + * Returns the current value of the timeline. + * + * @since 5.14 + **/ + qreal value() const; + + /** + * Returns the reverse value of the timeline. + * + * @since 5.14 + **/ + qreal rvalue() 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(int delta); + + /** + * Returns the number of elapsed milliseconds. + * + * @see setElapsed + * @since 5.14 + **/ + int 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(int elapsed); + + /** + * Returns the duration of the timeline. + * + * @returns Duration of the timeline, in milliseconds + * @see setDuration + * @since 5.14 + **/ + int 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(int 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 finished. + * + * @see reset + * @since 5.14 + **/ + bool done() const; + + /** + * Resets the timeline to initial state. + * + * @since 5.14 + **/ + void reset(); + +private: + qreal progress() const; + +private: + int m_duration; + Direction m_direction; + QEasingCurve m_easingCurve; + + int m_elapsed = 0; + bool m_done = false; +}; + /** * Pointer to the global EffectsHandler object. **/ @@ -3512,6 +3706,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,128 @@ d->screenProjectionMatrix = spm; } +/*************************************************************** + TimeLine +***************************************************************/ + +TimeLine::TimeLine(int duration, Direction direction) + : m_duration(duration) + , m_direction(direction) +{ + Q_ASSERT(m_duration > 0); +} + +qreal TimeLine::progress() const +{ + return static_cast(m_elapsed) / m_duration; +} + +qreal TimeLine::value() const +{ + const qreal t = progress(); + return m_easingCurve.valueForProgress( + m_direction == Backward ? 1.0 - t : t); +} + +qreal TimeLine::rvalue() const +{ + const qreal t = progress(); + return m_easingCurve.valueForProgress( + m_direction == Forward ? 1.0 - t : t); +} + +void TimeLine::update(int delta) +{ + Q_ASSERT(delta >= 0); + if (m_done) { + return; + } + m_elapsed += delta; + if (m_elapsed >= m_duration) { + m_done = true; + m_elapsed = m_duration; + } +} + +int TimeLine::elapsed() const +{ + return m_elapsed; +} + +void TimeLine::setElapsed(int elapsed) +{ + Q_ASSERT(elapsed >= 0); + if (elapsed == m_elapsed) { + return; + } + reset(); + update(elapsed); +} + +int TimeLine::duration() const +{ + return m_duration; +} + +void TimeLine::setDuration(int duration) +{ + Q_ASSERT(duration > 0); + if (duration == m_duration) { + return; + } + m_elapsed = qRound(progress() * duration); + m_duration = duration; + if (m_elapsed == m_duration) { + m_done = true; + } +} + +TimeLine::Direction TimeLine::direction() const +{ + return m_direction; +} + +void TimeLine::setDirection(TimeLine::Direction direction) +{ + if (m_direction == direction) { + return; + } + if (m_elapsed > 0) { + m_elapsed = m_duration - m_elapsed; + } + m_direction = direction; +} + +void TimeLine::toggleDirection() +{ + setDirection(m_direction == Forward ? Backward : Forward); +} + +QEasingCurve TimeLine::easingCurve() const +{ + return m_easingCurve; +} + +void TimeLine::setEasingCurve(const QEasingCurve &easingCurve) +{ + m_easingCurve = easingCurve; +} + +void TimeLine::setEasingCurve(QEasingCurve::Type type) +{ + m_easingCurve.setType(type); +} + +bool TimeLine::done() const +{ + return m_done; +} + +void TimeLine::reset() +{ + m_elapsed = 0; + m_done = false; +} + } // namespace