[scripting] Introduce complete function

Summary:
Effects that prefer to manipulate direction of animations sometimes need
to create animations in some particular state so later on they can be
played backward (swapping from and to is not enough and it would be wrong).

The proposed complete function lets such effects to fast-forward animations to
to the target position so they can be played backwards later on.

Reviewers: #kwin, davidedmundson

Reviewed By: #kwin, davidedmundson

Subscribers: davidedmundson, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D16450
icc-effect-5.17.5
Vlad Zagorodniy 2018-10-25 21:02:36 +03:00
parent 5e104fbc12
commit 396d528075
6 changed files with 161 additions and 0 deletions

View File

@ -78,6 +78,7 @@ private Q_SLOTS:
void testUngrab();
void testRedirect_data();
void testRedirect();
void testComplete();
private:
ScriptedEffect *loadEffect(const QString &name);
@ -714,5 +715,74 @@ void ScriptedEffectsTest::testRedirect()
}
}
void ScriptedEffectsTest::testComplete()
{
// this test verifies that complete works
// load the test effect
auto effect = new ScriptedEffectWithDebugSpy;
QVERIFY(effect->load(QStringLiteral("completeTest")));
// 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 should be at the start position
{
const AnimationEffect::AniMap state = effect->state();
QCOMPARE(state.count(), 1);
QCOMPARE(state.firstKey(), c->effectWindow());
const QList<AniData> animations = state.first().first;
QCOMPARE(animations.count(), 1);
QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms));
QVERIFY(!animations[0].timeLine.done());
}
// wait for 250ms
QTest::qWait(250);
{
const AnimationEffect::AniMap state = effect->state();
QCOMPARE(state.count(), 1);
QCOMPARE(state.firstKey(), c->effectWindow());
const QList<AniData> animations = state.first().first;
QCOMPARE(animations.count(), 1);
QVERIFY(around(animations[0].timeLine.elapsed(), 250ms, 50ms));
QVERIFY(!animations[0].timeLine.done());
}
// minimize the test client, when the test effect sees that a window was
// minimized, it will try to complete animation for it
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<AniData> animations = state.first().first;
QCOMPARE(animations.count(), 1);
QCOMPARE(animations[0].timeLine.elapsed(), 1000ms);
QVERIFY(animations[0].timeLine.done());
}
}
WAYLANDTEST_MAIN(ScriptedEffectsTest)
#include "scripted_effects_test.moc"

View File

@ -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,
to: 1,
keepAlive: false
});
});
effects.windowMinimized.connect(function (window) {
if (complete(window.animation)) {
sendTestResponse('ok');
} else {
sendTestResponse('fail');
}
});

View File

@ -356,6 +356,32 @@ bool AnimationEffect::redirect(quint64 animationId, Direction direction, Termina
return false;
}
bool AnimationEffect::complete(quint64 animationId)
{
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;
}
animIt->timeLine.setElapsed(animIt->timeLine.duration());
return true;
}
return false;
}
bool AnimationEffect::cancel(quint64 animationId)
{
Q_D(AnimationEffect);

View File

@ -236,6 +236,15 @@ protected:
Direction direction,
TerminationFlags terminationFlags = TerminateAtSource);
/**
* Fast-forwards the animation to the target position.
*
* @param animationId The id of the animation.
* @returns @c true if the animation was fast-forwarded successfully, otherwise
* @c false.
**/
bool complete(quint64 animationId);
/**
* 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

View File

@ -492,6 +492,32 @@ QScriptValue kwinEffectRedirect(QScriptContext *context, QScriptEngine *engine)
return QScriptValue(true);
}
QScriptValue kwinEffectComplete(QScriptContext *context, QScriptEngine *engine)
{
if (context->argumentCount() != 1) {
const QString errorMessage = QStringLiteral("complete() takes exactly 1 arguments (%1 given)")
.arg(context->argumentCount());
context->throwError(QScriptContext::SyntaxError, errorMessage);
return engine->undefinedValue();
}
bool ok = false;
QList<quint64> 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();
}
ScriptedEffect *effect = qobject_cast<ScriptedEffect *>(context->callee().data().toQObject());
for (const quint64 &animationId : qAsConst(animationIds)) {
if (!effect->complete(animationId)) {
return QScriptValue(false);
}
}
return QScriptValue(true);
}
QScriptValue kwinEffectCancel(QScriptContext *context, QScriptEngine *engine)
{
ScriptedEffect *effect = qobject_cast<ScriptedEffect*>(context->callee().data().toQObject());
@ -651,6 +677,11 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript
redirectFunc.setData(m_engine->newQObject(this));
m_engine->globalObject().setProperty(QStringLiteral("redirect"), redirectFunc);
// complete
QScriptValue completeFunc = m_engine->newFunction(kwinEffectComplete);
completeFunc.setData(m_engine->newQObject(this));
m_engine->globalObject().setProperty(QStringLiteral("complete"), completeFunc);
// cancel...
QScriptValue cancelFunc = m_engine->newFunction(kwinEffectCancel);
cancelFunc.setData(m_engine->newQObject(this));
@ -721,6 +752,11 @@ bool ScriptedEffect::redirect(quint64 animationId, Direction direction, Terminat
return AnimationEffect::redirect(animationId, direction, terminationFlags);
}
bool ScriptedEffect::complete(quint64 animationId)
{
return AnimationEffect::complete(animationId);
}
bool ScriptedEffect::isGrabbed(EffectWindow* w, ScriptedEffect::DataRole grabRole)
{
void *e = w->data(static_cast<KWin::DataRole>(grabRole)).value<void*>();

View File

@ -129,6 +129,7 @@ public Q_SLOTS:
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 complete(quint64 animationId);
bool cancel(quint64 animationId) { return AnimationEffect::cancel(animationId); }
virtual bool borderActivated(ElectricBorder border);