From 7ad25b663e3aaa22dbd395aad5e3f3343c9ee8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Sat, 27 Oct 2012 17:43:14 +0200 Subject: [PATCH] Adding more declarative way to animate a window in JavaScript The current API call to animate a window does not feel very JavaScripty. Therefore a new method "animate" is added to the global scope, which takes a JavaScript object of the following structure: { window: EffectWindow, /* the window to animate, required */ duration: int, /* duration in msec, required */ curve: QEasingCurve.Type, /* global easing curve, optional */ type: Effect.Attribute, /* for first animation, optional */ from: FPx2, /* for first animation, optional */ to: FPx2, /* for first animation, optional */ delay: int, /* for first animation, optional */ animations: [ /* additional animations, optional */ { curve: QEasingCurve.Type, /* overrides global */ type: Effect.Attribute, from: FPx2, to: FPx2, delay: int } ] } At least one animation needs to be specified either on the root level or in the array of animations. Curve is the only property on root level which is used in the animations, if not provided. REVIEW: 107079 --- .../package/contents/code/maximize.js | 39 ++++-- scripting/documentation-effect-global.xml | 33 +++++ scripting/scriptedeffect.cpp | 126 ++++++++++++++++++ 3 files changed, 184 insertions(+), 14 deletions(-) diff --git a/effects/maximize/package/contents/code/maximize.js b/effects/maximize/package/contents/code/maximize.js index 75413b7d3f..744021b8cd 100644 --- a/effects/maximize/package/contents/code/maximize.js +++ b/effects/maximize/package/contents/code/maximize.js @@ -16,7 +16,7 @@ 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 . *********************************************************************/ -/*global effect, effects, animationTime, Effect*/ +/*global effect, effects, animate, animationTime, Effect*/ var maximizeEffect = { duration: animationTime(250), loadConfig: function () { @@ -31,19 +31,30 @@ var maximizeEffect = { var oldGeometry, newGeometry; oldGeometry = window.oldGeometry; newGeometry = window.geometry; - effect.animate(window, Effect.Scale, maximizeEffect.duration, { - value1: 1.0, - value2: 1.0 - }, { - value1: oldGeometry.width / newGeometry.width, - value2: oldGeometry.height / newGeometry.height - }); - effect.animate(window, Effect.Translation, maximizeEffect.duration, { - value1: 0, - value2: 0 - }, { - value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2), - value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2) + animate({ + window: window, + duration: maximizeEffect.duration, + animations: [{ + type: Effect.Scale, + to: { + value1: 1.0, + value2: 1.0 + }, + from: { + value1: oldGeometry.width / newGeometry.width, + value2: oldGeometry.height / newGeometry.height + } + }, { + type: Effect.Translation, + to: { + value1: 0, + value2: 0 + }, + from: { + value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2), + value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2) + } + }] }); }, geometryChange: function (window, oldGeometry) { diff --git a/scripting/documentation-effect-global.xml b/scripting/documentation-effect-global.xml index 983827fa5f..e5440e0ba5 100644 --- a/scripting/documentation-effect-global.xml +++ b/scripting/documentation-effect-global.xml @@ -41,6 +41,39 @@ + + Q_SCRIPTABLE void + void KWin::ScriptedEffect::animate + (settings) + animate + + +Schedules one or many animations for one window. The animations are defined through the settings object providing +a more declarative way to specify the animations than the animate call on the effect object. The settings object +supports the following attributes: +<syntaxhighlight lang="javascript"> +{ + window: EffectWindow, /* the window to animate, required */ + duration: int, /* duration in msec, required */ + curve: QEasingCurve.Type, /* global easing curve, optional */ + type: Effect.Attribute, /* for first animation, optional */ + from: FPx2, /* for first animation, optional */ + to: FPx2, /* for first animation, optional */ + delay: int, /* for first animation, optional */ + animations: [ /* additional animations, optional */ + { + curve: QEasingCurve.Type, /* overrides global */ + type: Effect.Attribute, + from: FPx2, + to: FPx2, + delay: int + } + ] +} +</syntaxhighlight> +At least one animation needs to be specified either with the top-level properties or in the animations list. + + Q_SCRIPTABLE void void KWin::ScriptedEffect::print diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp index 610f02a81d..4a7e8e1607 100644 --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -89,6 +89,128 @@ QScriptValue kwinScriptScreenEdge(QScriptContext *context, QScriptEngine *engine return registerScreenEdge(context, engine); } +struct AnimationSettings { + AnimationEffect::Attribute a; + QEasingCurve curve; + bool curveSet; + FPx2 from; + FPx2 to; + int delay; + bool valid; +}; + +AnimationSettings animationSettingsFromObject(QScriptValue &object) +{ + AnimationSettings settings; + settings.valid = true; + settings.curveSet = false; + + settings.to = qscriptvalue_cast(object.property("to")); + settings.from = qscriptvalue_cast(object.property("from")); + + QScriptValue delay = object.property("delay"); + if (delay.isValid() && delay.isNumber()) { + settings.delay = delay.toInt32(); + } else { + settings.delay = 0; + } + + QScriptValue curve = object.property("curve"); + if (curve.isValid() && curve.isNumber()) { + settings.curve = QEasingCurve(static_cast(curve.toInt32())); + settings.curveSet = true; + } + + QScriptValue type = object.property("type"); + if (!type.isValid() || !type.isNumber()) { + settings.valid = false; + } + settings.a = static_cast(type.toInt32()); + + return settings; +} + +QScriptValue kwinEffectAnimate(QScriptContext *context, QScriptEngine *engine) +{ + ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); + if (!effect) { + context->throwError(QScriptContext::ReferenceError, "Internal Scripted KWin Effect error"); + return engine->undefinedValue(); + } + if (context->argumentCount() != 1) { + context->throwError(QScriptContext::SyntaxError, "Exactly one argument expected"); + return engine->undefinedValue(); + } + if (!context->argument(0).isObject()) { + context->throwError(QScriptContext::TypeError, "Argument needs to be an object"); + return engine->undefinedValue(); + } + QScriptValue object = context->argument(0); + QScriptValue windowProperty = object.property("window"); + if (!windowProperty.isValid() || !windowProperty.isObject()) { + context->throwError(QScriptContext::TypeError, "Window property missing in animation options"); + return engine->undefinedValue(); + } + EffectWindow *window = qobject_cast(windowProperty.toQObject()); + if (!window) { + context->throwError(QScriptContext::TypeError, "Window property does not contain an EffectWindow"); + return engine->undefinedValue(); + } + QScriptValue durationProperty = object.property("duration"); + if (!durationProperty.isValid() || !durationProperty.isNumber()) { + context->throwError(QScriptContext::TypeError, "Duration property missing in animation options"); + return engine->undefinedValue(); + } + const int duration = durationProperty.toInt32(); + + QEasingCurve curve; + QList settings; + AnimationSettings globalSettings = animationSettingsFromObject(object); + if (globalSettings.valid) { + settings << globalSettings; + if (globalSettings.curveSet) { + curve = globalSettings.curve; + } + } + QScriptValue animations = object.property("animations"); + if (animations.isValid()) { + if (!animations.isArray()) { + context->throwError(QScriptContext::TypeError, "Animations provided but not an array"); + return engine->undefinedValue(); + } + const int length = static_cast(animations.property("length").toInteger()); + for (int i=0; ithrowError(QScriptContext::TypeError, "No animations provided"); + return engine->undefinedValue(); + } + foreach (const AnimationSettings &setting, settings) { + effect->animate(window, + setting.a, + duration, + setting.to, + setting.from, + NULL, + setting.curveSet ? setting.curve : curve, + setting.delay); + } + + return engine->newVariant(true); +} + QScriptValue effectWindowToScriptValue(QScriptEngine *eng, const KEffectWindowRef &window) { return eng->newQObject(window, QScriptEngine::QtOwnership, @@ -198,6 +320,10 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript // add global Shortcut registerGlobalShortcutFunction(this, m_engine, kwinScriptGlobalShortcut); registerScreenEdgeFunction(this, m_engine, kwinScriptScreenEdge); + // add the animate method + QScriptValue animateFunc = m_engine->newFunction(kwinEffectAnimate); + animateFunc.setData(m_engine->newQObject(this)); + m_engine->globalObject().setProperty("animate", animateFunc); QScriptValue ret = m_engine->evaluate(scriptFile.readAll());