[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
This commit is contained in:
parent
5e104fbc12
commit
396d528075
6 changed files with 161 additions and 0 deletions
|
@ -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"
|
||||
|
|
19
autotests/integration/effects/scripts/completeTest.js
Normal file
19
autotests/integration/effects/scripts/completeTest.js
Normal 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');
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*>();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue