From 5e104fbc1245aba4fa5f6f6c5c825ce2c21a3817 Mon Sep 17 00:00:00 2001 From: Vlad Zagorodniy Date: Wed, 24 Oct 2018 22:58:29 +0300 Subject: [PATCH] [scripting] Introduce redirect function Summary: Consider current implementation of the Squash effect: if a window was minimized, an animation will be started; if the window is unminimized and the animation is still active (that can happen when user clicks on app's icon really fast), the animation will be stopped and a new one will be created. Such behavior can lead to rapid jumps in the observed "animation". A better approach would be first try to **reverse** the already active animation, and if that attempt wasn't successful, start a new animation. This patch introduces a new function to the scripted effects API that lets JavaScript effects to control direction of animations. The prototype of the function looks as follows: redirect(, , []) the first argument is an animation id or a list of animation ids, the second argument specifies the new direction of the animation or animations if a list of ids was passed as the first argument. The third argument specifies whether the animation(s) should be terminated when it(they) reaches the source position, currently it's relevant only for animations that are created with set() function. The termination policy argument is optional, by default it's Effect.TerminateAtSource. We can use this function to fix issues with rapid jumps in the Squash effect. Also, redirect() lets us to write effects for simple animations in slightly different style: first, we have to start the main animation (e.g. for the Dialog Parent effect, it would be dimming of main windows) and then change direction of the animation depending on external events, e.g. when the Desktop Cube effect is activated. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: davidedmundson, abetts, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D16449 --- .../effects/scripted_effects_test.cpp | 103 +++++++++++++++++- .../redirectAnimateDontTerminateTest.js | 18 +++ .../scripts/redirectAnimateTerminateTest.js | 18 +++ .../scripts/redirectSetDontTerminateTest.js | 19 ++++ .../scripts/redirectSetTerminateTest.js | 19 ++++ libkwineffects/anidata.cpp | 17 ++- libkwineffects/anidata_p.h | 7 +- libkwineffects/kwinanimationeffect.cpp | 45 +++++++- libkwineffects/kwinanimationeffect.h | 49 +++++++++ scripting/scriptedeffect.cpp | 64 +++++++++++ scripting/scriptedeffect.h | 1 + 11 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 autotests/integration/effects/scripts/redirectAnimateDontTerminateTest.js create mode 100644 autotests/integration/effects/scripts/redirectAnimateTerminateTest.js create mode 100644 autotests/integration/effects/scripts/redirectSetDontTerminateTest.js create mode 100644 autotests/integration/effects/scripts/redirectSetTerminateTest.js diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp index f0e81370ac..023c479a46 100644 --- a/autotests/integration/effects/scripted_effects_test.cpp +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -76,6 +76,8 @@ private Q_SLOTS: void testGrabAlreadyGrabbedWindow(); void testGrabAlreadyGrabbedWindowForced(); void testUngrab(); + void testRedirect_data(); + void testRedirect(); private: ScriptedEffect *loadEffect(const QString &name); @@ -301,13 +303,15 @@ void ScriptedEffectsTest::testAnimations() QCOMPARE(animationsForWindow[0].to, FPx2(1.4)); QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); QCOMPARE(animationsForWindow[0].timeLine.easingCurve().type(), QEasingCurve::OutQuad); - QCOMPARE(animationsForWindow[0].keepAtTarget, false); + QCOMPARE(animationsForWindow[0].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); if (animationCount == 2) { QCOMPARE(animationsForWindow[1].timeLine.duration(), 100ms); QCOMPARE(animationsForWindow[1].to, FPx2(0.0)); QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); - QCOMPARE(animationsForWindow[1].keepAtTarget, false); + QCOMPARE(animationsForWindow[1].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); } } QCOMPARE(effectOutputSpy[0].first(), "true"); @@ -323,12 +327,14 @@ void ScriptedEffectsTest::testAnimations() QCOMPARE(animationsForWindow[0].timeLine.duration(), 200ms); QCOMPARE(animationsForWindow[0].to, FPx2(1.5)); QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); - QCOMPARE(animationsForWindow[0].keepAtTarget, false); + QCOMPARE(animationsForWindow[0].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); if (animationCount == 2) { QCOMPARE(animationsForWindow[1].timeLine.duration(), 200ms); QCOMPARE(animationsForWindow[1].to, FPx2(1.5)); QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); - QCOMPARE(animationsForWindow[1].keepAtTarget, false); + QCOMPARE(animationsForWindow[1].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); } } c->setMinimized(false); @@ -619,5 +625,94 @@ void ScriptedEffectsTest::testUngrab() QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), nullptr); } +void ScriptedEffectsTest::testRedirect_data() +{ + QTest::addColumn("file"); + QTest::addColumn("shouldTerminate"); + QTest::newRow("animate/DontTerminateAtSource") << "redirectAnimateDontTerminateTest" << false; + QTest::newRow("animate/TerminateAtSource") << "redirectAnimateTerminateTest" << true; + QTest::newRow("set/DontTerminate") << "redirectSetDontTerminateTest" << false; + QTest::newRow("set/Terminate") << "redirectSetTerminateTest" << true; +} + +void ScriptedEffectsTest::testRedirect() +{ + // this test verifies that redirect() works + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QFETCH(QString, file); + QVERIFY(effect->load(file)); + + // create test client + using namespace KWayland::Client; + Surface *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); + QVERIFY(shellSurface); + ShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + auto around = [] (std::chrono::milliseconds elapsed, + std::chrono::milliseconds pivot, + std::chrono::milliseconds margin) { + return qAbs(elapsed.count() - pivot.count()) < margin.count(); + }; + + // initially, the test animation is at the source position + + { + const AnimationEffect::AniMap state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), c->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.direction(), TimeLine::Forward); + QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); + } + + // minimize the test client after 250ms, when the test effect sees that + // a window was minimized, it will try to reverse animation for it + QTest::qWait(250); + + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effectOutputSpy.isValid()); + + c->setMinimized(true); + + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + + { + const AnimationEffect::AniMap state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), c->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); + QVERIFY(around(animations[0].timeLine.elapsed(), 1000ms - 250ms, 50ms)); + } + + // wait for the animation to reach the start position, 100ms is an extra + // safety margin + QTest::qWait(250 + 100); + + QFETCH(bool, shouldTerminate); + if (shouldTerminate) { + const AnimationEffect::AniMap state = effect->state(); + QCOMPARE(state.count(), 0); + } else { + const AnimationEffect::AniMap state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), c->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); + QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); + QCOMPARE(animations[0].timeLine.value(), 0.0); + } +} + WAYLANDTEST_MAIN(ScriptedEffectsTest) #include "scripted_effects_test.moc" diff --git a/autotests/integration/effects/scripts/redirectAnimateDontTerminateTest.js b/autotests/integration/effects/scripts/redirectAnimateDontTerminateTest.js new file mode 100644 index 0000000000..492f54c767 --- /dev/null +++ b/autotests/integration/effects/scripts/redirectAnimateDontTerminateTest.js @@ -0,0 +1,18 @@ +effects.windowAdded.connect(function (window) { + window.animation = animate({ + window: window, + curve: QEasingCurve.Linear, + duration: animationTime(1000), + type: Effect.Opacity, + from: 0.0, + to: 1.0 + }) +}); + +effects.windowMinimized.connect(function (window) { + if (redirect(window.animation, Effect.Backward, Effect.DontTerminate)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/redirectAnimateTerminateTest.js b/autotests/integration/effects/scripts/redirectAnimateTerminateTest.js new file mode 100644 index 0000000000..b132c8470c --- /dev/null +++ b/autotests/integration/effects/scripts/redirectAnimateTerminateTest.js @@ -0,0 +1,18 @@ +effects.windowAdded.connect(function (window) { + window.animation = animate({ + window: window, + curve: QEasingCurve.Linear, + duration: animationTime(1000), + type: Effect.Opacity, + from: 0.0, + to: 1.0 + }) +}); + +effects.windowMinimized.connect(function (window) { + if (redirect(window.animation, Effect.Backward, Effect.TerminateAtSource)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/redirectSetDontTerminateTest.js b/autotests/integration/effects/scripts/redirectSetDontTerminateTest.js new file mode 100644 index 0000000000..661a277b15 --- /dev/null +++ b/autotests/integration/effects/scripts/redirectSetDontTerminateTest.js @@ -0,0 +1,19 @@ +effects.windowAdded.connect(function (window) { + window.animation = set({ + window: window, + curve: QEasingCurve.Linear, + duration: animationTime(1000), + type: Effect.Opacity, + from: 0.0, + to: 1.0, + keepAlive: false + }); +}); + +effects.windowMinimized.connect(function (window) { + if (redirect(window.animation, Effect.Backward, Effect.DontTerminate)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/redirectSetTerminateTest.js b/autotests/integration/effects/scripts/redirectSetTerminateTest.js new file mode 100644 index 0000000000..e6ff5f3581 --- /dev/null +++ b/autotests/integration/effects/scripts/redirectSetTerminateTest.js @@ -0,0 +1,19 @@ +effects.windowAdded.connect(function (window) { + window.animation = set({ + window: window, + curve: QEasingCurve.Linear, + duration: animationTime(1000), + type: Effect.Opacity, + from: 0.0, + to: 1.0, + keepAlive: false + }); +}); + +effects.windowMinimized.connect(function (window) { + if (redirect(window.animation, Effect.Backward, Effect.TerminateAtSource)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/libkwineffects/anidata.cpp b/libkwineffects/anidata.cpp index ceb4a81217..4278c24b3b 100644 --- a/libkwineffects/anidata.cpp +++ b/libkwineffects/anidata.cpp @@ -73,13 +73,12 @@ AniData::AniData() , meta(0) , startTime(0) , waitAtSource(false) - , keepAtTarget(false) , keepAlive(true) { } AniData::AniData(AnimationEffect::Attribute a, int meta_, const FPx2 &to_, - int delay, const FPx2 &from_, bool waitAtSource_, bool keepAtTarget_, + int delay, const FPx2 &from_, bool waitAtSource_, FullScreenEffectLockPtr fullScreenEffectLock_, bool keepAlive, PreviousWindowPixmapLockPtr previousWindowPixmapLock_) : attribute(a) @@ -89,12 +88,24 @@ AniData::AniData(AnimationEffect::Attribute a, int meta_, const FPx2 &to_, , startTime(AnimationEffect::clock() + delay) , fullScreenEffectLock(fullScreenEffectLock_) , waitAtSource(waitAtSource_) - , keepAtTarget(keepAtTarget_) , keepAlive(keepAlive) , previousWindowPixmapLock(previousWindowPixmapLock_) { } +bool AniData::isActive() const +{ + if (!timeLine.done()) { + return true; + } + + if (timeLine.direction() == TimeLine::Backward) { + return !(terminationFlags & AnimationEffect::TerminateAtSource); + } + + return !(terminationFlags & AnimationEffect::TerminateAtTarget); +} + static QString attributeString(KWin::AnimationEffect::Attribute attribute) { switch (attribute) { diff --git a/libkwineffects/anidata_p.h b/libkwineffects/anidata_p.h index 0e8dc31329..3c5d49dcfc 100644 --- a/libkwineffects/anidata_p.h +++ b/libkwineffects/anidata_p.h @@ -76,9 +76,11 @@ public: AniData(); AniData(AnimationEffect::Attribute a, int meta, const FPx2 &to, int delay, const FPx2 &from, bool waitAtSource, - bool keepAtTarget = false, FullScreenEffectLockPtr=FullScreenEffectLockPtr(), + FullScreenEffectLockPtr=FullScreenEffectLockPtr(), bool keepAlive = true, PreviousWindowPixmapLockPtr previousWindowPixmapLock = {}); + bool isActive() const; + inline bool isOneDimensional() const { return from[0] == from[1] && to[0] == to[1]; } @@ -92,10 +94,11 @@ public: uint meta; qint64 startTime; QSharedPointer fullScreenEffectLock; - bool waitAtSource, keepAtTarget; + bool waitAtSource; bool keepAlive; KeepAliveLockPtr keepAliveLock; PreviousWindowPixmapLockPtr previousWindowPixmapLock; + AnimationEffect::TerminationFlags terminationFlags; }; } // namespace diff --git a/libkwineffects/kwinanimationeffect.cpp b/libkwineffects/kwinanimationeffect.cpp index c5c5e24d59..4e99886826 100644 --- a/libkwineffects/kwinanimationeffect.cpp +++ b/libkwineffects/kwinanimationeffect.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking +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 @@ -258,7 +259,6 @@ quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int delay, // Delay from, // Source waitAtSource, // Whether the animation should be kept at source - keepAtTarget, // Whether the animation is persistent fullscreen, // Full screen effect lock keepAlive, // Keep alive flag previousPixmap // Previous window pixmap lock @@ -274,6 +274,11 @@ quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int animation.timeLine.setSourceRedirectMode(TimeLine::RedirectMode::Strict); animation.timeLine.setTargetRedirectMode(TimeLine::RedirectMode::Relaxed); + animation.terminationFlags = TerminateAtSource; + if (!keepAtTarget) { + animation.terminationFlags |= TerminateAtTarget; + } + it->second = QRect(); d->m_animationsTouched = true; @@ -315,6 +320,42 @@ bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemai return false; // no animation found } +bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags) +{ + Q_D(AnimationEffect); + + if (animationId == d->m_justEndedAnimation) { + return false; + } + + for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) { + auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(), + [animationId] (AniData &anim) { + return anim.id == animationId; + } + ); + if (animIt == entryIt->first.end()) { + continue; + } + + switch (direction) { + case Backward: + animIt->timeLine.setDirection(TimeLine::Backward); + break; + + case Forward: + animIt->timeLine.setDirection(TimeLine::Forward); + break; + } + + animIt->terminationFlags = terminationFlags & ~TerminateAtTarget; + + return true; + } + + return false; +} + bool AnimationEffect::cancel(quint64 animationId) { Q_D(AnimationEffect); @@ -364,7 +405,7 @@ void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time ) anim->timeLine.update(std::chrono::milliseconds(time)); } - if (!anim->timeLine.done() || anim->keepAtTarget) { + if (anim->isActive()) { // if (anim->attribute != Brightness && anim->attribute != Saturation && anim->attribute != Opacity) // transformed = true; d->m_animated = true; diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h index a71bf1908b..986e19e29e 100644 --- a/libkwineffects/kwinanimationeffect.h +++ b/libkwineffects/kwinanimationeffect.h @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking +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 @@ -108,6 +109,37 @@ public: }; enum MetaType { SourceAnchor, TargetAnchor, RelativeSourceX, RelativeSourceY, RelativeTargetX, RelativeTargetY, Axis }; + + /** + * This enum type is used to specify the direction of the animation. + **/ + enum Direction { + Forward, ///< The animation goes from source to target. + Backward ///< The animation goes from target to source. + }; + Q_ENUM(Direction) + + /** + * This enum type is used to specify when the animation should be terminated. + * + * @value DontTerminate Don't terminate the animation when it reaches the source + * or the target position. + * + * @value TerminateAtSource Terminate the animation when it reaches the source + * position. An animation can reach the source position if its direction was + * changed to go backward (from target to source). + * + * @value TerminateAtTarget Terminate the animation when it reaches the target + * position. If this flag is not set, then the animation will be persistent. + **/ + enum TerminationFlag { + DontTerminate = 0x00, + TerminateAtSource = 0x01, + TerminateAtTarget = 0x02 + }; + Q_FLAGS(TerminationFlag) + Q_DECLARE_FLAGS(TerminationFlags, TerminationFlag) + /** * Whenever you intend to connect to the EffectsHandler::windowClosed() signal, do so when reimplementing the constructor. * Do *not* add private slots named _windowClosed( EffectWindow* w ) or _windowDeleted( EffectWindow* w ) !! @@ -189,6 +221,21 @@ protected: */ bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime = -1); + /** + * Changes the direction of the animation. + * + * @param animationId The id of the animation. + * @param direction The new direction of the animation. + * @param terminationPolicy Whether the animation should be terminated when it + * reaches the source position after its direction was changed to go backward. + * Currently, TerminationFlag::TerminateAtTarget has no effect. + * @returns @c true if the direction of the animation was changed successfully, + * otherwise @c false. + **/ + bool redirect(quint64 animationId, + Direction direction, + TerminationFlags terminationFlags = TerminateAtSource); + /** * Called whenever an animation end, passes the transformed @class EffectWindow @enum Attribute and originally supplied @param meta * You can reimplement it to keep a constant transformation for the window (ie. keep it a this opacity or position) or to start another animation @@ -236,6 +283,8 @@ private: } // namespace QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2); + Q_DECLARE_METATYPE(KWin::FPx2) +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::AnimationEffect::TerminationFlags) #endif // ANIMATION_EFFECT_H diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp index 880c551323..74046fdd88 100644 --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -438,6 +438,60 @@ QScriptValue kwinEffectRetarget(QScriptContext *context, QScriptEngine *engine) return QScriptValue(ok); } +QScriptValue kwinEffectRedirect(QScriptContext *context, QScriptEngine *engine) +{ + if (context->argumentCount() != 2 && context->argumentCount() != 3) { + const QString errorMessage = QStringLiteral("redirect() takes either 2 or 3 arguments (%1 given)") + .arg(context->argumentCount()); + context->throwError(QScriptContext::SyntaxError, errorMessage); + return engine->undefinedValue(); + } + + bool ok = false; + QList animationIds = animations(context->argument(0).toVariant(), &ok); + if (!ok) { + context->throwError(QScriptContext::TypeError, QStringLiteral("Argument needs to be one or several quint64")); + return engine->undefinedValue(); + } + + const QScriptValue wrappedDirection = context->argument(1); + if (!wrappedDirection.isNumber()) { + context->throwError(QScriptContext::TypeError, QStringLiteral("Direction has invalid type")); + return engine->undefinedValue(); + } + + const auto direction = static_cast(wrappedDirection.toInt32()); + switch (direction) { + case AnimationEffect::Forward: + case AnimationEffect::Backward: + break; + + default: + context->throwError(QScriptContext::SyntaxError, QStringLiteral("Unknown direction")); + return engine->undefinedValue(); + } + + AnimationEffect::TerminationFlags terminationFlags = AnimationEffect::TerminateAtSource; + if (context->argumentCount() >= 3) { + const QScriptValue wrappedTerminationFlags = context->argument(2); + if (!wrappedTerminationFlags.isNumber()) { + context->throwError(QScriptContext::TypeError, QStringLiteral("Termination flags argument has invalid type")); + return engine->undefinedValue(); + } + + terminationFlags = static_cast(wrappedTerminationFlags.toInt32()); + } + + ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); + for (const quint64 &animationId : qAsConst(animationIds)) { + if (!effect->redirect(animationId, direction, terminationFlags)) { + return QScriptValue(false); + } + } + + return QScriptValue(true); +} + QScriptValue kwinEffectCancel(QScriptContext *context, QScriptEngine *engine) { ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); @@ -592,6 +646,11 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript retargetFunc.setData(m_engine->newQObject(this)); m_engine->globalObject().setProperty(QStringLiteral("retarget"), retargetFunc); + // redirect + QScriptValue redirectFunc = m_engine->newFunction(kwinEffectRedirect); + redirectFunc.setData(m_engine->newQObject(this)); + m_engine->globalObject().setProperty(QStringLiteral("redirect"), redirectFunc); + // cancel... QScriptValue cancelFunc = m_engine->newFunction(kwinEffectCancel); cancelFunc.setData(m_engine->newQObject(this)); @@ -657,6 +716,11 @@ bool ScriptedEffect::retarget(quint64 animationId, KWin::FPx2 newTarget, int new return AnimationEffect::retarget(animationId, newTarget, newRemainingTime); } +bool ScriptedEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags) +{ + return AnimationEffect::redirect(animationId, direction, terminationFlags); +} + bool ScriptedEffect::isGrabbed(EffectWindow* w, ScriptedEffect::DataRole grabRole) { void *e = w->data(static_cast(grabRole)).value(); diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h index 1ab803dbcc..a487ef198f 100644 --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -128,6 +128,7 @@ public Q_SLOTS: quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true); quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true); bool retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime = -1); + bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags = TerminateAtSource); bool cancel(quint64 animationId) { return AnimationEffect::cancel(animationId); } virtual bool borderActivated(ElectricBorder border);