~noskcaj/ubuntu/trusty/gnome-documents/3.10.2

« back to all changes in this revision

Viewing changes to src/util/tweener.js

  • Committer: Package Import Robot
  • Author(s): Andreas Henriksson, Thomas Bechtold
  • Date: 2013-04-04 13:32:08 UTC
  • mfrom: (3.1.4 experimental)
  • Revision ID: package-import@ubuntu.com-20130404133208-n19gqczi05z31ogb
Tags: 3.8.0-1
[ Thomas Bechtold ]
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
2
 
 
3
 
/*
4
 
 * Copyright (C) 2009-2011 Red Hat, Inc.
5
 
 *
6
 
 * This program is free software; you can redistribute it and/or
7
 
 * modify it under the terms of the GNU General Public License as
8
 
 * published by the Free Software Foundation; either version 2 of the
9
 
 * License, or (at your option) any later version.
10
 
 *
11
 
 * This program is distributed in the hope that it will be useful, but
12
 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 
 * General Public License for more details.
15
 
 *
16
 
 * You should have received a copy of the GNU General Public License
17
 
 * along with this program; if not, write to the Free Software
18
 
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19
 
 * 02110-1301  USA
20
 
 *
21
 
 * Authors: Dan Winship <danw@gnome.org>
22
 
 *
23
 
 */
24
 
 
25
 
const Clutter = imports.gi.Clutter;
26
 
const Lang = imports.lang;
27
 
const Mainloop = imports.mainloop;
28
 
const Tweener = imports.tweener.tweener;
29
 
const Signals = imports.signals;
30
 
 
31
 
// This was imported from gnome-shell and stipped of shell-specific stuff
32
 
 
33
 
// This is a wrapper around imports.tweener.tweener that adds a bit of
34
 
// Clutter integration and some additional callbacks:
35
 
//
36
 
//   1. If the tweening target is a Clutter.Actor, then the tweenings
37
 
//      will automatically be removed if the actor is destroyed
38
 
//
39
 
//   2. If target._delegate.onAnimationStart() exists, it will be
40
 
//      called when the target starts being animated.
41
 
//
42
 
//   3. If target._delegate.onAnimationComplete() exists, it will be
43
 
//      called once the target is no longer being animated.
44
 
//
45
 
// The onAnimationStart() and onAnimationComplete() callbacks differ
46
 
// from the tweener onStart and onComplete parameters, in that (1)
47
 
// they track whether or not the target has *any* tweens attached to
48
 
// it, as opposed to be called for *each* tween, and (2)
49
 
// onAnimationComplete() is always called when the object stops being
50
 
// animated, regardless of whether it stopped normally or abnormally.
51
 
//
52
 
// onAnimationComplete() is called at idle time, which means that if a
53
 
// tween completes and then another is added before returning to the
54
 
// main loop, the complete callback will not be called (until the new
55
 
// tween finishes).
56
 
 
57
 
 
58
 
// ActionScript Tweener methods that imports.tweener.tweener doesn't
59
 
// currently implement: getTweens, getVersion, registerTransition,
60
 
// setTimeScale, updateTime.
61
 
 
62
 
// imports.tweener.tweener methods that we don't re-export:
63
 
// pauseAllTweens, removeAllTweens, resumeAllTweens. (It would be hard
64
 
// to clean up properly after removeAllTweens, and also, any code that
65
 
// calls any of these is almost certainly wrong anyway, because they
66
 
// affect the entire application.)
67
 
 
68
 
// Called from Main.start
69
 
function init() {
70
 
    Tweener.setFrameTicker(new ClutterFrameTicker());
71
 
}
72
 
 
73
 
 
74
 
function addCaller(target, tweeningParameters) {
75
 
    _wrapTweening(target, tweeningParameters);
76
 
    Tweener.addCaller(target, tweeningParameters);
77
 
}
78
 
 
79
 
function addTween(target, tweeningParameters) {
80
 
    _wrapTweening(target, tweeningParameters);
81
 
    Tweener.addTween(target, tweeningParameters);
82
 
}
83
 
 
84
 
function _wrapTweening(target, tweeningParameters) {
85
 
    let state = _getTweenState(target);
86
 
 
87
 
    if (!state.destroyedId) {
88
 
        if (target instanceof Clutter.Actor) {
89
 
            state.actor = target;
90
 
            state.destroyedId = target.connect('destroy', _actorDestroyed);
91
 
        } else if (target.actor && target.actor instanceof Clutter.Actor) {
92
 
            state.actor = target.actor;
93
 
            state.destroyedId = target.actor.connect('destroy', function() { _actorDestroyed(target); });
94
 
        }
95
 
    }
96
 
 
97
 
    _addHandler(target, tweeningParameters, 'onStart', _tweenStarted);
98
 
    _addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
99
 
}
100
 
 
101
 
function _getTweenState(target) {
102
 
    // If we were paranoid, we could keep a plist mapping targets to
103
 
    // states... but we're not that paranoid.
104
 
    if (!target.__ShellTweenerState)
105
 
        _resetTweenState(target);
106
 
    return target.__ShellTweenerState;
107
 
}
108
 
 
109
 
function _resetTweenState(target) {
110
 
    let state = target.__ShellTweenerState;
111
 
 
112
 
    if (state) {
113
 
        if (state.destroyedId)
114
 
            state.actor.disconnect(state.destroyedId);
115
 
        if (state.idleCompletedId)
116
 
            Mainloop.source_remove(state.idleCompletedId);
117
 
    }
118
 
 
119
 
    target.__ShellTweenerState = {};
120
 
}
121
 
 
122
 
function _addHandler(target, params, name, handler) {
123
 
    if (params[name]) {
124
 
        let oldHandler = params[name];
125
 
        let oldScope = params[name + 'Scope'];
126
 
        let oldParams = params[name + 'Params'];
127
 
        let eventScope = oldScope ? oldScope : target;
128
 
 
129
 
        params[name] = function () {
130
 
            oldHandler.apply(eventScope, oldParams);
131
 
            handler(target);
132
 
        };
133
 
    } else
134
 
        params[name] = function () { handler(target); };
135
 
}
136
 
 
137
 
function _actorDestroyed(target) {
138
 
    _resetTweenState(target);
139
 
    Tweener.removeTweens(target);
140
 
}
141
 
 
142
 
function _tweenStarted(target) {
143
 
    let state = _getTweenState(target);
144
 
    let delegate = target._delegate;
145
 
 
146
 
    if (!state.running && delegate && delegate.onAnimationStart)
147
 
        delegate.onAnimationStart();
148
 
    state.running = true;
149
 
}
150
 
 
151
 
function _tweenCompleted(target) {
152
 
    let state = _getTweenState(target);
153
 
 
154
 
    if (!state.idleCompletedId)
155
 
        state.idleCompletedId = Mainloop.idle_add(Lang.bind(null, _idleCompleted, target));
156
 
}
157
 
 
158
 
function _idleCompleted(target) {
159
 
    let state = _getTweenState(target);
160
 
    let delegate = target._delegate;
161
 
 
162
 
    if (!isTweening(target)) {
163
 
        _resetTweenState(target);
164
 
        if (delegate && delegate.onAnimationComplete)
165
 
            delegate.onAnimationComplete();
166
 
    }
167
 
    return false;
168
 
}
169
 
 
170
 
function getTweenCount(scope) {
171
 
    return Tweener.getTweenCount(scope);
172
 
}
173
 
 
174
 
// imports.tweener.tweener doesn't provide this method (which exists
175
 
// in the ActionScript version) but it's easy to implement.
176
 
function isTweening(scope) {
177
 
    return Tweener.getTweenCount(scope) != 0;
178
 
}
179
 
 
180
 
function removeTweens(scope) {
181
 
    if (Tweener.removeTweens.apply(null, arguments)) {
182
 
        // If we just removed the last active tween, clean up
183
 
        if (Tweener.getTweenCount(scope) == 0)
184
 
            _tweenCompleted(scope);
185
 
        return true;
186
 
    } else
187
 
        return false;
188
 
}
189
 
 
190
 
function pauseTweens() {
191
 
    return Tweener.pauseTweens.apply(null, arguments);
192
 
}
193
 
 
194
 
function resumeTweens() {
195
 
    return Tweener.resumeTweens.apply(null, arguments);
196
 
}
197
 
 
198
 
 
199
 
function registerSpecialProperty(name, getFunction, setFunction,
200
 
                                 parameters, preProcessFunction) {
201
 
    Tweener.registerSpecialProperty(name, getFunction, setFunction,
202
 
                                    parameters, preProcessFunction);
203
 
}
204
 
 
205
 
function registerSpecialPropertyModifier(name, modifyFunction, getFunction) {
206
 
    Tweener.registerSpecialPropertyModifier(name, modifyFunction, getFunction);
207
 
}
208
 
 
209
 
function registerSpecialPropertySplitter(name, splitFunction, parameters) {
210
 
    Tweener.registerSpecialPropertySplitter(name, splitFunction, parameters);
211
 
}
212
 
 
213
 
 
214
 
// The 'FrameTicker' object is an object used to feed new frames to
215
 
// Tweener so it can update values and redraw. The default frame
216
 
// ticker for Tweener just uses a simple timeout at a fixed frame rate
217
 
// and has no idea of "catching up" by dropping frames.
218
 
//
219
 
// We substitute it with custom frame ticker here that connects
220
 
// Tweener to a Clutter.TimeLine. Now, Clutter.Timeline itself isn't a
221
 
// whole lot more sophisticated than a simple timeout at a fixed frame
222
 
// rate, but at least it knows how to drop frames. (See
223
 
// HippoAnimationManager for a more sophisticated view of continous
224
 
// time updates; even better is to pay attention to the vertical
225
 
// vblank and sync to that when possible.)
226
 
//
227
 
function ClutterFrameTicker() {
228
 
    this._init();
229
 
}
230
 
 
231
 
ClutterFrameTicker.prototype = {
232
 
    FRAME_RATE : 60,
233
 
 
234
 
    _init : function() {
235
 
        // We don't have a finite duration; tweener will tell us to stop
236
 
        // when we need to stop, so use 1000 seconds as "infinity"
237
 
        this._timeline = new Clutter.Timeline({ duration: 1000*1000 });
238
 
        this._startTime = -1;
239
 
 
240
 
        this._timeline.connect('new-frame', Lang.bind(this,
241
 
            function(timeline, frame) {
242
 
                this._onNewFrame(frame);
243
 
            }));
244
 
    },
245
 
 
246
 
    _onNewFrame : function(frame) {
247
 
        // If there is a lot of setup to start the animation, then
248
 
        // first frame number we get from clutter might be a long ways
249
 
        // into the animation (or the animation might even be done).
250
 
        // That looks bad, so we always start at the first frame of the
251
 
        // animation then only do frame dropping from there.
252
 
        if (this._startTime < 0)
253
 
            this._startTime = this._timeline.get_elapsed_time();
254
 
 
255
 
        // currentTime is in milliseconds
256
 
        this.emit('prepare-frame');
257
 
    },
258
 
 
259
 
    getTime : function() {
260
 
        return this._timeline.get_elapsed_time();
261
 
    },
262
 
 
263
 
    start : function() {
264
 
        this._timeline.start();
265
 
    },
266
 
 
267
 
    stop : function() {
268
 
        this._timeline.stop();
269
 
        this._startTime = -1;
270
 
    }
271
 
};
272
 
 
273
 
Signals.addSignalMethods(ClutterFrameTicker.prototype);