Define Callback code for callback based event handling (#atach() / #detach() #fire()) and start implementing it in View.

This commit is contained in:
Jürg Lehni 2011-11-11 18:29:28 +01:00
parent 6713d6aaa8
commit 256e48b511
4 changed files with 202 additions and 64 deletions

132
src/core/Callback.js Normal file
View file

@ -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;
}
}
};

View file

@ -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();

View file

@ -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.:

View file

@ -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: {},