[gnome-shell] batch: Add mechanism for doing animation series



commit 4902a600d52d24698611b8fefe1d4787f3e59089
Author: Ray Strode <rstrode redhat com>
Date:   Wed Jun 29 22:00:35 2011 -0400

    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

 js/Makefile.am  |    1 +
 js/gdm/batch.js |  228 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 229 insertions(+), 0 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index ee44b47..671c09c 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -2,6 +2,7 @@
 jsdir = $(pkgdatadir)/js
 
 nobase_dist_js_DATA = 	\
+	gdm/batch.js		\
 	misc/config.js		\
 	misc/docInfo.js		\
 	misc/fileUtils.js	\
diff --git a/js/gdm/batch.js b/js/gdm/batch.js
new file mode 100644
index 0000000..cd310d8
--- /dev/null
+++ b/js/gdm/batch.js
@@ -0,0 +1,228 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
+ *
+ * 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);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]