File: tweener.js

package info (click to toggle)
cinnamon 3.2.7-4
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 11,624 kB
  • sloc: ansic: 33,269; python: 18,048; xml: 1,504; makefile: 780; sh: 90; cpp: 54
file content (482 lines) | stat: -rw-r--r-- 18,399 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Cinnamon = imports.gi.Cinnamon;
const St = imports.gi.St;
const Signals = imports.signals;
const Tweener = imports.tweener.tweener;

/**
 * FILE:tweener.js
 * @short_description: File providing tweening functions
 *
 * This is a wrapper around imports.tweener.tweener that adds a bit of
 * Clutter integration and some additional callbacks:
 *
 *   1. If the tweening target is a Clutter.Actor, then the tweenings
 *      will automatically be removed if the actor is destroyed
 *
 *   2. If target._delegate.onAnimationStart() exists, it will be
 *      called when the target starts being animated.
 *
 *   3. If target._delegate.onAnimationComplete() exists, it will be
 *      called once the target is no longer being animated.
 *
 * The onAnimationStart() and onAnimationComplete() callbacks differ
 * from the tweener onStart and onComplete parameters, in that (1)
 * they track whether or not the target has *any* tweens attached to
 * it, as opposed to be called for *each* tween, and (2)
 * onAnimationComplete() is always called when the object stops being
 * animated, regardless of whether it stopped normally or abnormally.
 *
 * onAnimationComplete() is called at idle time, which means that if a
 * tween completes and then another is added before returning to the
 * main loop, the complete callback will not be called (until the new
 * tween finishes).
 *
 *
 * ActionScript Tweener methods that imports.tweener.tweener doesn't
 * currently implement: getTweens, getVersion, registerTransition,
 * setTimeScale, updateTime.
 *
 * imports.tweener.tweener methods that we don't re-export:
 * pauseAllTweens, removeAllTweens, resumeAllTweens. (It would be hard
 * to clean up properly after removeAllTweens, and also, any code that
 * calls any of these is almost certainly wrong anyway, because they
 * affect the entire application.)
 */

/**
 * init:
 *
 * Function called by main.js when Cinnamon starts
 */
function init() {
    Tweener.setFrameTicker(new ClutterFrameTicker());
}


function addCaller(target, tweeningParameters) {
    _wrapTweening(target, tweeningParameters);
    Tweener.addCaller(target, tweeningParameters);
}

/**
 * addTween:
 * @target (Object): the object to tween
 * @tweeningParameters (param): parameters
 *
 * This makes @target tween according to the parameters of @tweeningParameters.
 *
 * @tweeningParameters contains certain tweening parameters that describe the
 * tween, and the actual things to tween. Everything that is not a tweening
 * parameter is processed as follows: If you have
 * ```
 * {
 *     ...
 *     x: 7
 *     ...
 * }
 * ```
 * In your parameters, then the property @x of the @target object will
 * be animated to the value of @7.
 *
 * The tweening parameters are (shamelessly stolen from the actual Tweener
 * documentation):
 *
 *  - @time (real): The duration of the transition in seconds
 *
 *  - @delay (real): The delay (in seconds) before the transition starts. The
 *    default of this parameter (when omitted) is 0.
 *
 *  - @skipUpdates (int):  How many updates must be skipped before an actual
 *    update is made.  This is a powerful property that allows the developer to
 *    enforce a different update rate for a given tweening, as if simulating a
 *    lower frame rate. This is useful on transitions that demand a share of
 *    the CPU that's higher than average on each new update, such as filter
 *    tweenings. A value of 1, for example, means that the tweening engine will
 *    do half of the updates on this transition, since it will update then skip
 *    one update; a value of 2 means it will do one third of the normal
 *    updates, since it will update, then skip two updates. The default value
 *    for this parameter (when omitted) is 0, meaning no update is skipped at
 *    all, and the active tweenings are updated on every frame render.
 *
 *  - @transition (string/function): The type of transition to use. Different
 *    equations can be used, producing different tweening updates based on time
 *    spent. You can specify this parameter by their internal string names
 *    (which you can find by seeing what's offered in the cinnamon-settings
 *    effects page), or use any custom function to have a customized easing
 *    (see below for examples and a more in-depth description). The default
 *    transition is "easeOutExpo".
 *    \
 *    If you want to use a custom function as the transition, the function must
 *    receive four parameters: current time on the transition, starting
 *    tweening value, change needed in that value, and total easing duration
 *    (plus an optional object, which will contain any parameter passed as the
 *    transitionParams property of new tweenings). During each tweening, the
 *    transition function will be continuously called, with the first parameter
 *    increasing until it reaches the total duration; it must return the new
 *    expected value.
 *    \
 *    An example of a custom transition is as follows:
 * ```
 * let myFunc = function(t, b, c, d) {
 *     let ts = (t/=d)*t;
 *     let tc = ts*t;
 *     return b+c*(-97.1975*tc*ts + 257.5975*ts*ts + -234.4*tc + 80*ts + -5*t);
 * };
 * Tweener.addTween(this.actor, {x: 200, time: 1, transition: myFunc});
 * ```

 *
 *  - @transitionParams (array): extra parameters to pass to the custom
 *    transition function
 *
 *  - @onStart (function): A function that is called immediately before a
 *    tweening starts. It is called once regardless of the number of properties
 *    involved on the tweening. The function scope (in which the event is
 *    executed) is the target object itself, unless specified by the
 *    onStartScope parameter.
 *
 *  - @onUpdate (function): A function that is called every time a tweening
 *    updates its properties. The function scope (in which the event is
 *    executed) is the target object itself, unless specified by the
 *    @onUpdateScope parameter.
 *
 *  - @onComplete (function): A function that is called immediately after a
 *    tweening is completed. It is called once regardless of the number of
 *    properties involved on the tweening. The function scope (in which the
 *    event is executed) is the target object itself, unless specified by the
 *    @onCompleteScope parameter.
 *
 *  - @onOverwrite (function): A function that is called when tweening is
 *    overwritten. It is called once regardless of the number of properties
 *    involved on the tweening. The function scope is the target object itself,
 *    unless specified by the @onOverwriteScope parameter.
 *
 *  - @onError (function): A function that gets called when an error occurs
 *    when trying to run a tweening. This is used to handle errors more
 *    commonly thrown by other events (that is, from code not controlled by
 *    Tweener), such as onStart, onUpdate or onComplete. The function scope (in
 *    which the event is executed) is the target object itself, unless
 *    specified by the @onErrorScope parameter.
 *
 *  - @rounded (boolean): Whether or not the values for this tweening must be
 *    rounded before being applied to their respective properties. This is
 *    useful, for example, when sliding objects that must be positioned on
 *    round pixels, like labels that use pixel fonts; its x and y properties
 *    need to be rounded all the time to avoid blurring the text. This option
 *    acts on all properties for that specific tween. The default value for
 *    this parameter (when omitted) is false.
 *
 *  - @min (real): The minimum the values of this tweening can take. This is
 *    useful, for example, when you animate the opacity of an object with a
 *    bounce transition and don't want the opacity of an object to fall below
 *    0.  Leave empty for no minimum.
 *
 *  - @max (real): The maximum the values of this tweening can take. This is
 *    useful, for example, when you animate the opacity of an object with a
 *    bounce transition and don't want the opacity of an object to go above 1.
 *    Leave empty for no maximum.
 *
 *  - @onStartParams (array): A list of parameters (of any type) to be passed
 *    to the onStart function. 
 *
 *  - @onUpdateParams (array): A list of parameters (of any type) to be passed
 *    to the onUpdate function.
 *  
 *  - @onCompleteParams (array): A list of parameters (of any type) to be
 *    passed to the onComplete function. 
 *
 *  - @onOverwriteParams (array): A list of parameters (of any type) to be
 *    passed to the onOverwrite function. 
 *
 *  - @onStartScope (object): The object in which the onStart function will
 *    be executed. This is needed if you have some specialized code inside the
 *    event function; in that case, references to this. inside the function
 *    will reference to the object defined by this parameter. If omitted, the
 *    tweened object is the scope used instead.
 *
 *  - @onUpdateScope (object): The object in which the onUpdate function will
 *    be executed. This is needed if you have some specialized code inside the
 *    event function; in that case, references to this. inside the function
 *    will reference to the object defined by this parameter. If omitted, the
 *    tweened object is the scope used instead.
 *
 *  - @onCompleteScope (object): The object in which the onComplete function
 *    will be executed. This is needed if you have some specialized code inside
 *    the event function; in that case, references to this. inside the function
 *    will reference to the object defined by this parameter. If omitted, the
 *    tweened object is the scope used instead.
 *
 *  - @onOverwriteScope (object): The object in which the onOverwrite function
 *    will be executed. This is needed if you have some specialized code inside
 *    the event function; in that case, references to this. inside the function
 *    will reference to the object defined by this parameter. If omitted, the
 *    tweened object is the scope used instead.
 *
 *  - @onErrorScope (object): The object in which the onError function will
 *    be executed. This is needed if you have some specialized code inside the
 *    event function; in that case, references to this. inside the function
 *    will reference to the object defined by this parameter. If omitted, the
 *    tweened object is the scope used instead.
 *
 */
function addTween(target, tweeningParameters) {
    _wrapTweening(target, tweeningParameters);
    Tweener.addTween(target, tweeningParameters);
}

function _wrapTweening(target, tweeningParameters) {
    let state = _getTweenState(target);

    if (!state.destroyedId) {
        if (target instanceof Clutter.Actor) {
            state.actor = target;
            state.destroyedId = target.connect('destroy', _actorDestroyed);
        } else if (target.actor && target.actor instanceof Clutter.Actor) {
            state.actor = target.actor;
            state.destroyedId = target.actor.connect('destroy', function() { _actorDestroyed(target); });
        }
    }

    _addHandler(target, tweeningParameters, 'onStart', _tweenStarted);
    _addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
}

function _getTweenState(target) {
    // If we were paranoid, we could keep a plist mapping targets to
    // states... but we're not that paranoid.
    if (!target.__CinnamonTweenerState)
        _resetTweenState(target);
    return target.__CinnamonTweenerState;
}

function _resetTweenState(target) {
    let state = target.__CinnamonTweenerState;

    if (state) {
        if (state.destroyedId)
            state.actor.disconnect(state.destroyedId);
        if (state.idleCompletedId) {
            Mainloop.source_remove(state.idleCompletedId);
            state.idleCompletedId = 0;
        }
    }

    target.__CinnamonTweenerState = {};
}

function _addHandler(target, params, name, handler) {
    if (params[name]) {
        let oldHandler = params[name];
        let oldScope = params[name + 'Scope'];
        let oldParams = params[name + 'Params'];
        let eventScope = oldScope ? oldScope : target;

        params[name] = function () {
            oldHandler.apply(eventScope, oldParams);
            handler(target);
        };
    } else
        params[name] = function () { handler(target); };
}

function _actorDestroyed(target) {
    _resetTweenState(target);
    Tweener.removeTweens(target);
}

function _tweenStarted(target) {
    let state = _getTweenState(target);
    let delegate = target._delegate;

    if (!state.running && delegate && delegate.onAnimationStart)
        delegate.onAnimationStart();
    state.running = true;
}

function _tweenCompleted(target) {
    let state = _getTweenState(target);

    if (!state.idleCompletedId)
        state.idleCompletedId = Mainloop.idle_add(Lang.bind(null, _idleCompleted, target));
}

function _idleCompleted(target) {
    let state = _getTweenState(target);
    let delegate = target._delegate;

    if (!isTweening(target)) {
        _resetTweenState(target);
        if (delegate && delegate.onAnimationComplete)
            delegate.onAnimationComplete();
    }

    state.idleCompletedId = 0;
    return false;
}

/**
 * getTweenCount:
 * @scope (Object): the object we are interested in
 *
 * Returns the number of tweens @scope currently has.
 */
function getTweenCount(scope) {
    return Tweener.getTweenCount(scope);
}

/**
 * isTweening:
 * @scope (Object): the object we are interested in
 *
 * Returns whether @scope is animating
 */
function isTweening(scope) {
    return Tweener.getTweenCount(scope) != 0;
}

/**
 * removeTween:
 * @scope (Object): the object we are interested in
 *
 * Removes all tweens running on the object.
 *
 * FIXME: removeTweens should be much more powerful, but I have no idea how it
 * works
 */
function removeTweens(scope) {
    if (Tweener.removeTweens.apply(null, arguments)) {
        // If we just removed the last active tween, clean up
        if (Tweener.getTweenCount(scope) == 0)
            _tweenCompleted(scope);
        return true;
    } else
        return false;
}

/**
 * pauseTweens:
 * @scope (Object): the object we are interested in
 *
 * Pauses all the tweens running on the object. Can be resumed with
 * Tweener.resumeTweens
 *
 * FIXME: removeTweens should be much more powerful, but I have no idea how it
 * works
 */
function pauseTweens() {
    return Tweener.pauseTweens.apply(null, arguments);
}

/**
 * resumeTweens:
 * @scope (Object): the object we are interested in
 *
 * Resumes all the tweens running on the object paused by Tweener.pauseTweens
 *
 * FIXME: removeTweens should be much more powerful, but I have no idea how it
 * works
 */
function resumeTweens() {
    return Tweener.resumeTweens.apply(null, arguments);
}


function registerSpecialProperty(name, getFunction, setFunction,
                                 parameters, preProcessFunction) {
    Tweener.registerSpecialProperty(name, getFunction, setFunction,
                                    parameters, preProcessFunction);
}

function registerSpecialPropertyModifier(name, modifyFunction, getFunction) {
    Tweener.registerSpecialPropertyModifier(name, modifyFunction, getFunction);
}

function registerSpecialPropertySplitter(name, splitFunction, parameters) {
    Tweener.registerSpecialPropertySplitter(name, splitFunction, parameters);
}


/**
 * #ClutterFrameTicker:
 * @short_description: Object used internally for clutter animations
 *
 * The 'FrameTicker' object is an object used to feed new frames to
 * Tweener so it can update values and redraw. The default frame
 * ticker for Tweener just uses a simple timeout at a fixed frame rate
 * and has no idea of "catching up" by dropping frames.
 *
 * We substitute it with custom frame ticker here that connects
 * Tweener to a Clutter.TimeLine. Now, Clutter.Timeline itself isn't a
 * whole lot more sophisticated than a simple timeout at a fixed frame
 * rate, but at least it knows how to drop frames. (See
 * HippoAnimationManager for a more sophisticated view of continuous
 * time updates; even better is to pay attention to the vertical
 * vblank and sync to that when possible.)
 */
function ClutterFrameTicker() {
    this._init();
}

ClutterFrameTicker.prototype = {
    FRAME_RATE : 60,

    _init : function() {
        // We don't have a finite duration; tweener will tell us to stop
        // when we need to stop, so use 1000 seconds as "infinity"
        this._timeline = new Clutter.Timeline({ duration: 1000*1000 });
        this._startTime = -1;

        this._timeline.connect('new-frame', Lang.bind(this,
            function(timeline, frame) {
                this._onNewFrame(frame);
            }));

        let perf_log = Cinnamon.PerfLog.get_default();
        perf_log.define_event("tweener.framePrepareStart",
                              "Start of a new animation frame",
                              "");
        perf_log.define_event("tweener.framePrepareDone",
                              "Finished preparing frame",
                              "");
    },

    _onNewFrame : function(frame) {
        // If there is a lot of setup to start the animation, then
        // first frame number we get from clutter might be a long ways
        // into the animation (or the animation might even be done).
        // That looks bad, so we always start at the first frame of the
        // animation then only do frame dropping from there.
        if (this._startTime < 0)
            this._startTime = this._timeline.get_elapsed_time();

        // currentTime is in milliseconds
        let perf_log = Cinnamon.PerfLog.get_default();
        perf_log.event("tweener.framePrepareStart");
        this.emit('prepare-frame');
        perf_log.event("tweener.framePrepareDone");
    },

    getTime : function() {
        return this._timeline.get_elapsed_time();
    },

    start : function() {
        if (St.get_slow_down_factor() > 0)
            Tweener.setTimeScale(1 / St.get_slow_down_factor());
        this._timeline.start();
        global.begin_work();
    },

    stop : function() {
        this._timeline.stop();
        this._startTime = -1;
        global.end_work();
    }
};

Signals.addSignalMethods(ClutterFrameTicker.prototype);