diff --git a/src/core/Callback.js b/src/core/Callback.js new file mode 100644 index 00000000..c5b2ec3d --- /dev/null +++ b/src/core/Callback.js @@ -0,0 +1,132 @@ +/* + * Paper.js + * + * This file is part of Paper.js, a JavaScript Vector Graphics Library, + * based on Scriptographer.org and designed to be largely API compatible. + * http://paperjs.org/ + * http://scriptographer.org/ + * + * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey + * http://lehni.org/ & http://jonathanpuckey.com/ + * + * Distributed under the MIT license. See LICENSE file for details. + * + * All rights reserved. + */ + +/* +var events = { + frame: function() { + + }, + + resize: function() { + + } +}; + +view.attach(events); +view.detach(events); + +function handler() { + +} + +view.attach('frame', handler); + + +view.detach('frame', handler); +*/ + +var Callback = { + attach: function(type, func) { + var entry = this._events[type]; + // If an object literal is passed, attach all callbacks defined in it + if (!entry) { + return Base.each(type, function(value, key) { + this.attach(key, value); + }, this); + } + // Otherwise, attach the event now + var handlers = this._handlers = this._handlers || {}; + handlers = handlers[type] = handlers[type] || []; + if (handlers.indexOf(func) == -1) { // Not added yet, add it now + // See if this is the first handler that we're attaching, and + // call install if defined. + if (entry.install && !handlers.length) + entry.install.call(this); + handlers.push(func); + } + return this; + }, + + detach: function(type, func) { + var entry = this._events[type]; + // If an object literal is passed, detach all callbacks defined in it + if (!entry) { + return Base.each(type, function(value, key) { + this.detach(key, value); + }, this); + } + // Otherwise, detach the event now + var handlers = this._handlers && this._handlers[type], + index = handlers && handlers.indexOf(func) || -1; + if (index != -1) { + handlers.splice(index, 1); + // See if this is the last handler that we're detaching, and + // call uninstall if defined. + if (!handlers.length) { + delete this._handlers[type]; + if (entry.uninstall) + entry.uninstall.call(this); + } + } + return this; + }, + + detachAll: function(type) { + return Base.each(this._handlers && this._handlers[type] || [], + function(func) { + this.detach(type, func); + }, + this); + }, + + fire: function(type, param) { + // Returns true if fired, false otherwise + var handlers = this._handlers && this._handlers[type]; + if (!handlers) + return false; + Base.each(handlers, function(func) { + func.call(this, param); + }, this); + return true; + }, + + statics: { + inject: function(/* src, ... */) { + for (var i = 0, l = arguments.length; i < l; i++) { + var src = arguments[i], + events = src._events; + if (events) { + Base.each(events, function(entry, type) { + var part = Base.capitalize(type); + src['getOn' + part] = function() { + return this['_on' + part]; + }; + src['setOn' + part] = function(func) { + if (func) { + this.attach(type, func); + } else { + this.detach(type, this['_on' + part]); + } + this['_on' + part] = func; + }; + }); + } + this.base(src); + } + return this; + } + } +}; diff --git a/src/core/PaperScript.js b/src/core/PaperScript.js index 413f32ef..44c47d5b 100644 --- a/src/core/PaperScript.js +++ b/src/core/PaperScript.js @@ -191,7 +191,7 @@ var PaperScript = this.PaperScript = new function() { }); } if (view) { - view.onResize = onResize; + view.setOnResize(onResize); view.setOnFrame(onFrame); // Automatically draw view at the end. view.draw(); diff --git a/src/paper.js b/src/paper.js index aa4669ba..c0d185ed 100644 --- a/src/paper.js +++ b/src/paper.js @@ -54,6 +54,7 @@ var paper = new function() { /*#*/ include('core/Base.js'); /*#*/ include('core/PaperScope.js'); /*#*/ include('core/PaperScopeItem.js'); +/*#*/ include('core/Callback.js'); // Include Paper classes, which are later injected into PaperScope by setting // them on the 'this' object, e.g.: diff --git a/src/ui/View.js b/src/ui/View.js index 5b897937..d49755d2 100644 --- a/src/ui/View.js +++ b/src/ui/View.js @@ -23,9 +23,60 @@ * center, both useful for constructing artwork that should appear centered on * screen. */ -var View = this.View = PaperScopeItem.extend(/** @lends View# */{ +var View = this.View = PaperScopeItem.extend(Callback, /** @lends View# */{ _list: 'views', _reference: 'view', + _events: { + frame: { + install: function() { +/*#*/ if (options.browser) { + var that = this, + requested = false, + before, + time = 0, + count = 0; + this._onFrameCallback = function(param, dontRequest) { + requested = false; + // See if we need to stop due to a call to uninstall() + if (!that._onFrameCallback) + return; + // Set the global paper object to the current scope + paper = that._scope; + if (!dontRequest) { + // Request next frame already + requested = true; + DomEvent.requestAnimationFrame(that._onFrameCallback, + that._canvas); + } + var now = Date.now() / 1000, + delta = before ? now - before : 0; + // delta: Time elapsed since last redraw in seconds + // time: Time since first call of frame() in seconds + // Use Base.merge to convert into a Base object, + // for #toString() + that.fire('frame', Base.merge({ + delta: delta, + time: time += delta, + count: count++ + })); + before = now; + // Automatically draw view on each frame. + that.draw(true); + }; + // Call the onFrame handler straight away, initializing the + // sequence of onFrame calls. + if (!requested) + this._onFrameCallback(); +/*#*/ } // options.browser + }, + + uninstall: function() { + delete this._onFrameCallback; + } + }, + + resize: {} + }, /** * Creates a view object @@ -123,8 +174,8 @@ var View = this.View = PaperScopeItem.extend(/** @lends View# */{ this._zoom = 1; /*#*/ if (options.browser) { - this._events = this._createEvents(); - DomEvent.add(this._canvas, this._events); + this._domEvents = this._createEvents(); + DomEvent.add(this._canvas, this._domEvents); // Make sure the first view is focused for keyboard input straight away if (!View._focused) View._focused = this; @@ -155,9 +206,11 @@ var View = this.View = PaperScopeItem.extend(/** @lends View# */{ View._focused = null; delete View._views[this._id]; // Uninstall event handlers again for this view. - DomEvent.remove(this._canvas, this._events); - // Clearing _onFrame makes the frame handler stop automatically. - this._canvas = this._events = this._onFrame = null; + DomEvent.remove(this._canvas, this._domEvents); + this._canvas = this._domEvents = null; + // Removing all onFrame handlers makes the _onFrameCallback handler stop + // automatically through its uninstall method. + this.detachAll('frame'); return true; }, @@ -215,12 +268,10 @@ var View = this.View = PaperScopeItem.extend(/** @lends View# */{ this._bounds = null; this._redrawNeeded = true; // Call onResize handler on any size change - if (this.onResize) { - this.onResize({ - size: size, - delta: delta - }); - } + this.fire('resize', { + size: size, + delta: delta + }); this._redraw(); }, @@ -346,7 +397,7 @@ var View = this.View = PaperScopeItem.extend(/** @lends View# */{ if (!this._inverse) this._inverse = this._matrix.createInverse(); return this._inverse; - }, + } /** * {@grouptitle Event Handlers} @@ -373,57 +424,10 @@ var View = this.View = PaperScopeItem.extend(/** @lends View# */{ * path.rotate(3); * } * + * @property + * @name View#onFrame * @type Function - * @bean */ - getOnFrame: function() { - return this._onFrame; - }, - - setOnFrame: function(onFrame) { - this._onFrame = onFrame; - if (!onFrame) { - delete this._onFrameCallback; - return; - } -/*#*/ if (options.browser) { - var that = this, - requested = false, - before, - time = 0, - count = 0; - this._onFrameCallback = function(param, dontRequest) { - requested = false; - if (!that._onFrame) - return; - // Set the global paper object to the current scope - paper = that._scope; - // Request next frame already - requested = true; - if (!dontRequest) { - DomEvent.requestAnimationFrame(that._onFrameCallback, - that._canvas); - } - var now = Date.now() / 1000, - delta = before ? now - before : 0; - // Use Base.merge to convert into a Base object, for #toString() - that._onFrame(Base.merge({ - delta: delta, // Time elapsed since last redraw in seconds - time: time += delta, // Time since first call of frame() in seconds - count: count++ - })); - before = now; - // Automatically draw view on each frame. - that.draw(true); - }; - // Call the onFrame handler straight away, initializing the sequence - // of onFrame calls. - if (!requested) - this._onFrameCallback(); -/*#*/ } // options.browser - }, - - /** * Handler function that is called whenever a view is resized. @@ -440,9 +444,10 @@ var View = this.View = PaperScopeItem.extend(/** @lends View# */{ * path.position = view.center; * } * + * @property + * @name View#onResize * @type Function */ - onResize: null }, { statics: { _views: {},