2011-09-28 09:16:26 -04:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
/*
|
batch: Add mechanism for doing animation series
In order for transformation animations to look good, they need to be
incremental and have some order to them (e.g., fade out hidden items,
then shrink to close the void left over).
Chaining animations in this way can be error prone and wordy using just
Tweener callbacks.
This commit adds a new set of classes to help:
- Task. encapsulates schedulable work to be run in a specific scope.
- ConsecutiveBatch. runs a series of tasks in order and completes
when the last in the series finishes.
- ConcurrentBatch. runs a set of tasks at the same time and completes
when the last to finish completes.
- Hold. prevents a batch from completing the pending task until
the hold is released.
The tasks associated with a batch are specified in a list at batch
construction time as either task objects or plain functions.
Batches are task objects, themselves, so they can be nested.
For now, these APIs are temporarily getting staged in a gdm/ specific
subdirectory so they will be available for use by GDM. They aren't
specific to GDM, or even to doing animations, though, so the API may eventually
move in some form or another to a more general location. Alternatively, the
APIs may ultimately get dropped entirely and replaced by something else.
https://bugzilla.gnome.org/show_bug.cgi?id=657082
2011-06-29 22:00:35 -04:00
|
|
|
* Copyright 2011 Red Hat, Inc
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* 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, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
|
|
* 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const Lang = imports.lang;
|
|
|
|
const Signals = imports.signals;
|
|
|
|
|
|
|
|
function Task() {
|
|
|
|
this._init.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
Task.prototype = {
|
|
|
|
_init: function(scope, handler) {
|
|
|
|
if (scope)
|
|
|
|
this.scope = scope;
|
|
|
|
else
|
|
|
|
this.scope = this;
|
|
|
|
|
|
|
|
this.handler = handler;
|
|
|
|
},
|
|
|
|
|
|
|
|
run: function() {
|
|
|
|
if (this.handler)
|
|
|
|
return this.handler.call(this.scope);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(Task.prototype);
|
|
|
|
|
|
|
|
function Hold() {
|
|
|
|
this._init.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
Hold.prototype = {
|
|
|
|
__proto__: Task.prototype,
|
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
Task.prototype._init.call(this,
|
|
|
|
this,
|
|
|
|
function () {
|
|
|
|
return this;
|
|
|
|
});
|
|
|
|
|
|
|
|
this._acquisitions = 1;
|
|
|
|
},
|
|
|
|
|
|
|
|
acquire: function() {
|
|
|
|
if (this._acquisitions <= 0)
|
|
|
|
throw new Error("Cannot acquire hold after it's been released");
|
|
|
|
this._acquisitions++;
|
|
|
|
},
|
|
|
|
|
|
|
|
acquireUntilAfter: function(hold) {
|
|
|
|
if (!hold.isAcquired())
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.acquire();
|
|
|
|
let signalId = hold.connect('release', Lang.bind(this, function() {
|
|
|
|
hold.disconnect(signalId);
|
|
|
|
this.release();
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
release: function() {
|
|
|
|
this._acquisitions--;
|
|
|
|
|
|
|
|
if (this._acquisitions == 0)
|
|
|
|
this.emit('release');
|
|
|
|
},
|
|
|
|
|
|
|
|
isAcquired: function() {
|
|
|
|
return this._acquisitions > 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Signals.addSignalMethods(Hold.prototype);
|
|
|
|
|
|
|
|
function Batch() {
|
|
|
|
this._init.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
Batch.prototype = {
|
|
|
|
__proto__: Task.prototype,
|
|
|
|
|
|
|
|
_init: function(scope, tasks) {
|
|
|
|
Task.prototype._init.call(this);
|
|
|
|
|
|
|
|
this.tasks = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < tasks.length; i++) {
|
|
|
|
let task;
|
|
|
|
|
|
|
|
if (tasks[i] instanceof Task) {
|
|
|
|
task = tasks[i];
|
|
|
|
} else if (typeof tasks[i] == 'function') {
|
|
|
|
task = new Task(scope, tasks[i]);
|
|
|
|
} else {
|
|
|
|
throw new Error('Batch tasks must be functions or Task, Hold or Batch objects');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.tasks.push(task);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
process: function() {
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
},
|
|
|
|
|
|
|
|
runTask: function() {
|
|
|
|
if (!(this._currentTaskIndex in this.tasks)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.tasks[this._currentTaskIndex].run();
|
|
|
|
},
|
|
|
|
|
|
|
|
_finish: function() {
|
|
|
|
this.hold.release();
|
|
|
|
},
|
|
|
|
|
|
|
|
nextTask: function() {
|
|
|
|
this._currentTaskIndex++;
|
|
|
|
|
|
|
|
// if the entire batch of tasks is finished, release
|
|
|
|
// the hold and notify anyone waiting on the batch
|
|
|
|
if (this._currentTaskIndex >= this.tasks.length) {
|
|
|
|
this._finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.process();
|
|
|
|
},
|
|
|
|
|
|
|
|
_start: function() {
|
|
|
|
// acquire a hold to get released when the entire
|
|
|
|
// batch of tasks is finished
|
|
|
|
this.hold = new Hold();
|
|
|
|
this._currentTaskIndex = 0;
|
|
|
|
this.process();
|
|
|
|
},
|
|
|
|
|
|
|
|
run: function() {
|
|
|
|
this._start();
|
|
|
|
|
|
|
|
// hold may be destroyed at this point
|
|
|
|
// if we're already done running
|
|
|
|
return this.hold;
|
|
|
|
},
|
|
|
|
|
|
|
|
cancel: function() {
|
|
|
|
this.tasks = this.tasks.splice(0, this._currentTaskIndex + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(Batch.prototype);
|
|
|
|
|
|
|
|
function ConcurrentBatch() {
|
|
|
|
this._init.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
ConcurrentBatch.prototype = {
|
|
|
|
__proto__: Batch.prototype,
|
|
|
|
|
|
|
|
_init: function(scope, tasks) {
|
|
|
|
Batch.prototype._init.call(this, scope, tasks);
|
|
|
|
},
|
|
|
|
|
|
|
|
process: function() {
|
|
|
|
let hold = this.runTask();
|
|
|
|
|
|
|
|
if (hold) {
|
|
|
|
this.hold.acquireUntilAfter(hold);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regardless of the state of the just run task,
|
|
|
|
// fire off the next one, so all the tasks can run
|
|
|
|
// concurrently.
|
|
|
|
this.nextTask();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(ConcurrentBatch.prototype);
|
|
|
|
|
|
|
|
function ConsecutiveBatch() {
|
|
|
|
this._init.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
ConsecutiveBatch.prototype = {
|
|
|
|
__proto__: Batch.prototype,
|
|
|
|
|
|
|
|
_init: function(scope, tasks) {
|
|
|
|
Batch.prototype._init.call(this, scope, tasks);
|
|
|
|
},
|
|
|
|
|
|
|
|
process: function() {
|
|
|
|
let hold = this.runTask();
|
|
|
|
|
|
|
|
if (hold && hold.isAcquired()) {
|
|
|
|
// This task is inhibiting the batch. Wait on it
|
|
|
|
// before processing the next one.
|
|
|
|
let signalId = hold.connect('release',
|
|
|
|
Lang.bind(this, function() {
|
|
|
|
hold.disconnect(signalId);
|
|
|
|
this.nextTask();
|
|
|
|
}));
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
// This task finished, process the next one
|
|
|
|
this.nextTask();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(ConsecutiveBatch.prototype);
|