1
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
4
* Copyright (C) 2009-2011 Red Hat, Inc.
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.
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.
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
21
* Authors: Dan Winship <danw@gnome.org>
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;
31
// This was imported from gnome-shell and stipped of shell-specific stuff
33
// This is a wrapper around imports.tweener.tweener that adds a bit of
34
// Clutter integration and some additional callbacks:
36
// 1. If the tweening target is a Clutter.Actor, then the tweenings
37
// will automatically be removed if the actor is destroyed
39
// 2. If target._delegate.onAnimationStart() exists, it will be
40
// called when the target starts being animated.
42
// 3. If target._delegate.onAnimationComplete() exists, it will be
43
// called once the target is no longer being animated.
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.
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
58
// ActionScript Tweener methods that imports.tweener.tweener doesn't
59
// currently implement: getTweens, getVersion, registerTransition,
60
// setTimeScale, updateTime.
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.)
68
// Called from Main.start
70
Tweener.setFrameTicker(new ClutterFrameTicker());
74
function addCaller(target, tweeningParameters) {
75
_wrapTweening(target, tweeningParameters);
76
Tweener.addCaller(target, tweeningParameters);
79
function addTween(target, tweeningParameters) {
80
_wrapTweening(target, tweeningParameters);
81
Tweener.addTween(target, tweeningParameters);
84
function _wrapTweening(target, tweeningParameters) {
85
let state = _getTweenState(target);
87
if (!state.destroyedId) {
88
if (target instanceof Clutter.Actor) {
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); });
97
_addHandler(target, tweeningParameters, 'onStart', _tweenStarted);
98
_addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
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;
109
function _resetTweenState(target) {
110
let state = target.__ShellTweenerState;
113
if (state.destroyedId)
114
state.actor.disconnect(state.destroyedId);
115
if (state.idleCompletedId)
116
Mainloop.source_remove(state.idleCompletedId);
119
target.__ShellTweenerState = {};
122
function _addHandler(target, params, name, handler) {
124
let oldHandler = params[name];
125
let oldScope = params[name + 'Scope'];
126
let oldParams = params[name + 'Params'];
127
let eventScope = oldScope ? oldScope : target;
129
params[name] = function () {
130
oldHandler.apply(eventScope, oldParams);
134
params[name] = function () { handler(target); };
137
function _actorDestroyed(target) {
138
_resetTweenState(target);
139
Tweener.removeTweens(target);
142
function _tweenStarted(target) {
143
let state = _getTweenState(target);
144
let delegate = target._delegate;
146
if (!state.running && delegate && delegate.onAnimationStart)
147
delegate.onAnimationStart();
148
state.running = true;
151
function _tweenCompleted(target) {
152
let state = _getTweenState(target);
154
if (!state.idleCompletedId)
155
state.idleCompletedId = Mainloop.idle_add(Lang.bind(null, _idleCompleted, target));
158
function _idleCompleted(target) {
159
let state = _getTweenState(target);
160
let delegate = target._delegate;
162
if (!isTweening(target)) {
163
_resetTweenState(target);
164
if (delegate && delegate.onAnimationComplete)
165
delegate.onAnimationComplete();
170
function getTweenCount(scope) {
171
return Tweener.getTweenCount(scope);
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;
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);
190
function pauseTweens() {
191
return Tweener.pauseTweens.apply(null, arguments);
194
function resumeTweens() {
195
return Tweener.resumeTweens.apply(null, arguments);
199
function registerSpecialProperty(name, getFunction, setFunction,
200
parameters, preProcessFunction) {
201
Tweener.registerSpecialProperty(name, getFunction, setFunction,
202
parameters, preProcessFunction);
205
function registerSpecialPropertyModifier(name, modifyFunction, getFunction) {
206
Tweener.registerSpecialPropertyModifier(name, modifyFunction, getFunction);
209
function registerSpecialPropertySplitter(name, splitFunction, parameters) {
210
Tweener.registerSpecialPropertySplitter(name, splitFunction, parameters);
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.
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.)
227
function ClutterFrameTicker() {
231
ClutterFrameTicker.prototype = {
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;
240
this._timeline.connect('new-frame', Lang.bind(this,
241
function(timeline, frame) {
242
this._onNewFrame(frame);
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();
255
// currentTime is in milliseconds
256
this.emit('prepare-frame');
259
getTime : function() {
260
return this._timeline.get_elapsed_time();
264
this._timeline.start();
268
this._timeline.stop();
269
this._startTime = -1;
273
Signals.addSignalMethods(ClutterFrameTicker.prototype);