Merge branch 'refs/heads/paperscript-refactoring'

Conflicts:
	src/core/PaperScope.js
This commit is contained in:
Jürg Lehni 2014-01-03 12:34:07 +01:00
commit 57f1763abe
10 changed files with 192 additions and 166 deletions

View file

@ -1,7 +1,7 @@
require('paper'); var paper = require('paper');
var paper = require('./Tadpoles.pjs'); var scope = require('./Tadpoles.pjs')(new paper.Size(1024, 768));
paper.view.exportFrames({ scope.view.exportFrames({
amount: 400, amount: 400,
directory: __dirname, directory: __dirname,
onComplete: function() { onComplete: function() {

View file

@ -1,5 +1,3 @@
paper.setup(new Canvas(1024, 768));
// Adapted from Flocking Processing example by Daniel Schiffman: // Adapted from Flocking Processing example by Daniel Schiffman:
// http://processing.org/learning/topics/flocking.html // http://processing.org/learning/topics/flocking.html

View file

@ -26,7 +26,7 @@
}, },
"devDependencies": { "devDependencies": {
"uglify-js": "~2.3.6", "uglify-js": "~2.3.6",
"prepro": "~0.8.0", "prepro": "~0.8.1",
"grunt": "~0.4.1", "grunt": "~0.4.1",
"grunt-contrib-uglify": "~0.2.2" "grunt-contrib-uglify": "~0.2.2"
}, },

View file

@ -102,16 +102,9 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
/** /**
* The reference to the active tool. * The reference to the active tool.
* @name PaperScope#tool
* @type Tool * @type Tool
* @bean
*/ */
getTool: function() {
// If no tool exists yet but one is requested, produce it now on the fly
// so it can be used in PaperScript.
if (!this._tool)
this._tool = new Tool();
return this._tool;
},
/** /**
* The list of available tools. * The list of available tools.
@ -131,10 +124,9 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
return this; return this;
}, },
evaluate: function(code) { execute: function(code) {
var res = paper.PaperScript.evaluate(code, this); paper.PaperScript.execute(code, this);
View.updateFocus(); View.updateFocus();
return res;
}, },
/** /**
@ -166,10 +158,10 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
// Copy over all fields from this scope to the destination. // Copy over all fields from this scope to the destination.
// Do not use Base.each, since we also want to enumerate over // Do not use Base.each, since we also want to enumerate over
// fields on PaperScope.prototype, e.g. all classes // fields on PaperScope.prototype, e.g. all classes
for (var key in this) { for (var key in this)
if (!/^(version|_id)/.test(key)) // Exclude all 'hidden' fields
if (!/^_/.test(key))
scope[key] = this[key]; scope[key] = this[key];
}
}, },
/** /**
@ -228,14 +220,14 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
_id: 0, _id: 0,
/** /**
* Retrieves a PaperScope object with the given id or associated with * Retrieves a PaperScope object with the given id or associated
* the passed canvas element. * with the passed canvas element.
* *
* @param id * @param id
*/ */
get: function(id) { get: function(id) {
// If a script tag is passed, get the id from it. // If a script tag is passed, get the id from it.
if (typeof id === 'object') if (id && id.getAttribute)
id = id.getAttribute('id'); id = id.getAttribute('id');
return this._scopes[id] || null; return this._scopes[id] || null;
}, },

View file

@ -14,19 +14,10 @@
* @name PaperScript * @name PaperScript
* @namespace * @namespace
*/ */
// Note that due to the use of with(), PaperScript gets compiled outside the var PaperScript = Base.exports.PaperScript = (function(root) {
// main paper scope, and is added to the PaperScope class. This allows for // Locally turn of exports and define for inlined acorn / esprima.
// better minification and the future use of strict mode once it makes sense // Just declaring the local vars is enough, as they will be undefined.
// in terms of performance. var exports, define,
paper.PaperScope.prototype.PaperScript = (function(root) {
var Base = paper.Base,
PaperScope = paper.PaperScope,
// For local reference, for now only when setting lineNumberBase on
// Firefox.
PaperScript,
// Locally turn of exports and define for inlined acorn / esprima.
// Just declaring the local vars is enough, as they will be undefined.
exports, define,
// The scope into which the library is loaded. // The scope into which the library is loaded.
scope = this; scope = this;
/*#*/ if (__options.version == 'dev') { /*#*/ if (__options.version == 'dev') {
@ -69,9 +60,9 @@ paper.PaperScope.prototype.PaperScript = (function(root) {
}, },
{} {}
); );
paper.Point.inject(fields); Point.inject(fields);
paper.Size.inject(fields); Size.inject(fields);
paper.Color.inject(fields); Color.inject(fields);
// Use very short name for the binary operator (_$_) as well as the // Use very short name for the binary operator (_$_) as well as the
// unary operator ($_), as operations will be replaced with then. // unary operator ($_), as operations will be replaced with then.
@ -238,97 +229,117 @@ paper.PaperScope.prototype.PaperScript = (function(root) {
} }
/** /**
* Evaluates parsed PaperScript code in the passed {@link PaperScope} * Executes the parsed PaperScript code in a compiled function that receives
* object. It also installs handlers automatically for us. * all properties of the passed {@link PaperScope} as arguments, to emulate
* a global scope with unaffected performance. It also installs global view
* and tool handlers automatically for you.
* *
* @name PaperScript.evaluate * @name PaperScript.execute
* @function * @function
* @param {String} code The PaperScript code * @param {String} code The PaperScript code
* @param {PaperScript} scope The scope in which the code is executed * @param {PaperScript} scope The scope for which the code is executed
* @return {Object} the result of the code evaluation
*/ */
function evaluate(code, scope) { function execute(code, scope) {
// Set currently active scope. // Set currently active scope.
paper = scope; paper = scope;
var view = scope.project && scope.project.view, var view = scope.getView(),
// Only create a tool object if something resembling a tool handler
// definition is contained in the code.
tool = /\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)
? new Tool()
: null,
toolHandlers = tool ? tool._events : [],
// Compile a list of all handlers that can be defined globally
// inside the PaperScript. These are passed on to the function as
// undefined arguments, so that their name exists, rather than
// injecting a code line that defines them as variables.
// They are exported again at the end of the function.
handlers = ['onFrame', 'onResize'].concat(toolHandlers),
res; res;
// Define variables for potential handlers, so eval() calls below to code = compile(code);
// fetch their values do not require try-catch around them. // compile a list of paramter names for all variables that need to
// Use with() {} in order to make the scope the current 'global' scope // appear as globals inside the script. At the same time, also collect
// instead of window. // their values, so we can pass them on as arguments in the function
with (scope) { // call.
// Within this, use a function scope, so local variables to not try var params = ['_$_', '$_', 'view', 'tool'],
// and set themselves on the scope object. args = [_$_, $_ , view, tool];
(function() { // Look through all enumerable properties on the scope and expose these
var onActivate, onDeactivate, onEditOptions, // too as pseudo-globals.
onMouseDown, onMouseUp, onMouseDrag, onMouseMove, for (var key in scope) {
onKeyDown, onKeyUp, onFrame, onResize; if (!/^_/.test(key)) {
code = compile(code); params.push(key);
/*#*/ if (__options.environment == 'browser') { args.push(scope[key]);
if (root.InstallTrigger) { // Firefox }
// On Firefox, all error numbers inside evaled code are }
// relative to the line where the eval happened. Totally // Finally define the handler variable names as parameters and compose
// silly, but that's how it is. So we're calculating the // the string describing the properties for the returned object at the
// base of lineNumbers, to remove it again from reported // end of the code execution, so we can retrieve their values from the
// errors. Luckily, Firefox is the only browser where we can // function call.
// define the lineNumber for exceptions. handlers = Base.each(handlers, function(key) {
var handle = PaperScript.handleException; params.push(key);
if (!handle) { this.push(key + ': ' + key);
handle = PaperScript.handleException = function(e) { }, []).join(', ');
throw e.lineNumber >= lineNumber // We need an additional line that returns the handlers in one object.
? new Error(e.message, e.fileName, code += '\nreturn { ' + handlers + ' };';
e.lineNumber - lineNumber) /*#*/ if (__options.environment == 'browser') {
: e; if (root.InstallTrigger) { // Firefox
} // Add a semi-colon at the start so Firefox doesn't swallow empty
// We're using a crazy hack to detect wether the library // lines and shift error messages.
// is minified or not: By generating a second error on code = ';' + code;
// the 2nd line and using the difference in line numbers // On Firefox, all error numbers inside evaled code are relative to
// to calculate the offset to the eval, it works in both // the line where the eval happened. Totally silly, but that's how
// casees. // it is. So we're calculating the base of lineNumbers, to remove it
var lineNumber = new Error().lineNumber; // again from reported errors. Luckily, Firefox is the only browser
lineNumber += (new Error().lineNumber - lineNumber) * 3; // where we can define the lineNumber for exceptions.
} var handle = PaperScript.handleException;
try { if (!handle) {
// Add a semi-colon at the start so Firefox doesn't handle = PaperScript.handleException = function(e) {
// swallow empty lines and shift error messages. throw e.lineNumber >= lineNumber
res = eval(';' + code); ? new Error(e.message, e.fileName,
} catch (e) { e.lineNumber - lineNumber)
handle(e); : e;
} };
} else { // We're using a crazy hack to detect wether the library is
res = eval(code); // minified or not: By generating a second error on the 2nd line
} // and using the difference in line numbers to calculate the
/*#*/ } else { // !__options.environment == 'browser' // offset to the eval, it works in both casees.
res = eval(code); var lineNumber = new Error().lineNumber;
/*#*/ } // !__options.environment == 'browser' lineNumber += (new Error().lineNumber - lineNumber) * 3;
// Only look for tool handlers if something resembling their }
// name is contained in the code. try {
if (/on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)) { res = new Function(params, code).apply(scope, args);
Base.each(paper.Tool.prototype._events, function(key) { // NOTE: in order for the calculation of the above lineNumber
var value = eval(key); // offset to work, we cannot add any statements before the above
if (value) { // line of code, nor can we put it into a separate function.
// Use the getTool accessor that handles auto tool } catch (e) {
// creation for us: handle(e);
scope.getTool()[key] = value; }
} } else {
}); res = new Function(params, code).apply(scope, args);
} }
if (view) { /*#*/ } else { // !__options.environment == 'browser'
view.setOnResize(onResize); res = new Function(params, code).apply(scope, args);
// Fire resize event directly, so any user /*#*/ } // !__options.environment == 'browser'
// defined resize handlers are called. // Now install the 'global' tool and view handlers, and we're done!
view.fire('resize', { Base.each(toolHandlers, function(key) {
size: view.size, var value = res[key];
delta: new Point() if (value)
}); tool[key] = value;
if (onFrame) });
view.setOnFrame(onFrame); if (view) {
// Automatically update view at the end. if (res.onResize)
view.update(); view.setOnResize(res.onResize);
} // Fire resize event directly, so any user
}).call(scope); // defined resize handlers are called.
view.fire('resize', {
size: view.size,
delta: new Point()
});
if (res.onFrame)
view.setOnFrame(res.onFrame);
// Automatically update view at the end.
view.update();
} }
return res;
} }
/*#*/ if (__options.environment == 'browser') { /*#*/ if (__options.environment == 'browser') {
@ -356,12 +367,12 @@ paper.PaperScope.prototype.PaperScript = (function(root) {
if (src) { if (src) {
// If we're loading from a source, request that first and // If we're loading from a source, request that first and
// then run later. // then run later.
paper.Http.request('get', src, function(code) { Http.request('get', src, function(code) {
evaluate(code, scope); execute(code, scope);
}); });
} else { } else {
// We can simply get the code form the script tag. // We can simply get the code form the script tag.
evaluate(script.innerHTML, scope); execute(script.innerHTML, scope);
} }
// Mark script as loaded now. // Mark script as loaded now.
script.setAttribute('data-paper-ignore', true); script.setAttribute('data-paper-ignore', true);
@ -375,12 +386,12 @@ paper.PaperScope.prototype.PaperScript = (function(root) {
// Handle it asynchronously // Handle it asynchronously
setTimeout(load); setTimeout(load);
} else { } else {
paper.DomEvent.add(window, { load: load }); DomEvent.add(window, { load: load });
} }
return PaperScript = { return {
compile: compile, compile: compile,
evaluate: evaluate, execute: execute,
load: load, load: load,
lineNumberBase: 0 lineNumberBase: 0
}; };
@ -393,22 +404,28 @@ paper.PaperScope.prototype.PaperScript = (function(root) {
path = require('path'); path = require('path');
require.extensions['.pjs'] = function(module, uri) { require.extensions['.pjs'] = function(module, uri) {
var source = compile(fs.readFileSync(uri, 'utf8')), // Requiring a PaperScript on Node.js returns an initialize method which
scope = new PaperScope(); // needs to receive a Canvas object when called and returns the
scope.__filename = uri; // PaperScope.
scope.__dirname = path.dirname(uri); module.exports = function(canvas) {
// Expose core methods and values var source = compile(fs.readFileSync(uri, 'utf8')),
scope.require = require; scope = new PaperScope();
scope.console = console; scope.setup(canvas);
evaluate(source, scope); scope.__filename = uri;
module.exports = scope; scope.__dirname = path.dirname(uri);
// Expose core methods and values
scope.require = require;
scope.console = console;
execute(source, scope);
return scope;
};
}; };
/*#*/ } // __options.environment == 'node' /*#*/ } // __options.environment == 'node'
return PaperScript = { return {
compile: compile, compile: compile,
evaluate: evaluate execute: execute
}; };
/*#*/ } // !__options.environment == 'browser' /*#*/ } // !__options.environment == 'browser'

View file

@ -10,16 +10,19 @@
* All rights reserved. * All rights reserved.
*/ */
/** @scope _global_ */ { /**
* @name _global_
// DOCS: Find a way to put this description into _global_ * @namespace
*
/** * When code is executed as PaperScript, the script's scope is populated with
* In a PaperScript context, the global scope is populated with all * all fields of the currently active {@link PaperScope} object, which within
* fields of the currently active {@link PaperScope} object. In a JavaScript * the script appear to be global.
* context, it only contains the {@link #paper} reference to the currently *
* active {@link PaperScope} object, which also exposes all Paper classes. * In a JavaScript context, only the {@link paper} variable is added to the
*/ * global scope, referencing the currently active {@link PaperScope} object,
* through which all properties and Paper.js classes can be accessed.
*/
/** @scope _global_ */{
/** /**
* A reference to the currently active {@link PaperScope} object. * A reference to the currently active {@link PaperScope} object.
@ -32,40 +35,58 @@
// DOCS: This does not work: @borrows PaperScope#version as _global_#version, // DOCS: This does not work: @borrows PaperScope#version as _global_#version,
// so we're repeating documentation here form PaperScope: // so we're repeating documentation here form PaperScope:
/** /**
* {@grouptitle Global PaperScope Properties (for PaperScript)} * {@grouptitle Global PaperScript Properties}
*
* The project for which the PaperScript is executed.
*
* Note that when working with mulitple projects, this does not necessarily
* reflect the currently active project. For this, use
* {@link PaperScope#project} instead.
* *
* The currently active project.
* @name project * @name project
* @type Project * @type Project
*/ */
/** /**
* The list of all open projects within the current Paper.js context. * The list of all open projects within the current Paper.js context.
*
* @name projects * @name projects
* @type Project[] * @type Project[]
*/ */
/** /**
* The reference to the active project's view. * The reference to the project's view.
*
* Note that when working with mulitple projects, this does not necessarily
* reflect the view of the currently active project. For this, use
* {@link PaperScope#view} instead.
*
* @name view * @name view
* @type View * @type View
*/ */
/** /**
* The reference to the active tool. * The reference to the tool object which is automatically created when global
* tool event handlers are defined.
*
* Note that when working with mulitple tools, this does not necessarily
* reflect the currently active tool. For this, use {@link PaperScope#tool}
* instead.
*
* @name tool * @name tool
* @type Tool * @type Tool
*/ */
/** /**
* The list of available tools. * The list of available tools.
*
* @name tools * @name tools
* @type Tool[] * @type Tool[]
*/ */
/** /**
* {@grouptitle View Event Handlers (for PaperScript)} * {@grouptitle PaperScript View Event Handlers}
* A reference to the {@link View#onFrame} handler function. * A global reference to the {@link View#onFrame} handler function.
* *
* @name onFrame * @name onFrame
* @property * @property
@ -81,7 +102,7 @@
*/ */
/** /**
* {@grouptitle Mouse Event Handlers (for PaperScript)} * {@grouptitle PaperScript Tool Event Handlers}
* A reference to the {@link Tool#onMouseDown} handler function. * A reference to the {@link Tool#onMouseDown} handler function.
* @name onMouseDown * @name onMouseDown
* @property * @property

View file

@ -138,12 +138,10 @@ var paper = new function(undefined) {
/*#*/ include('svg/SVGImport.js'); /*#*/ include('svg/SVGImport.js');
/*#*/ } // __options.svg /*#*/ } // __options.svg
/*#*/ include('export.js');
return paper;
};
// include PaperScript separately outside the main paper scope, due to its use
// of with(). This also simplifies making its inclusion optional.
/*#*/ if (__options.paperscript) { /*#*/ if (__options.paperscript) {
/*#*/ include('core/PaperScript.js'); /*#*/ include('core/PaperScript.js');
/*#*/ } // __options.paperscript /*#*/ } // __options.paperscript
/*#*/ include('export.js');
return paper;
};

View file

@ -45,7 +45,7 @@
var Tool = PaperScopeItem.extend(/** @lends Tool# */{ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
_class: 'Tool', _class: 'Tool',
_list: 'tools', _list: 'tools',
_reference: '_tool', // PaperScope has accessor for #tool _reference: 'tool',
_events: [ 'onActivate', 'onDeactivate', 'onEditOptions', _events: [ 'onActivate', 'onDeactivate', 'onEditOptions',
'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove', 'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
'onKeyDown', 'onKeyUp' ], 'onKeyDown', 'onKeyUp' ],

View file

@ -74,7 +74,7 @@ var Key = new function() {
type = down ? 'keydown' : 'keyup', type = down ? 'keydown' : 'keyup',
view = View._focused, view = View._focused,
scope = view && view.isVisible() && view._scope, scope = view && view.isVisible() && view._scope,
tool = scope && scope._tool, tool = scope && scope.tool,
name; name;
keyMap[key] = down; keyMap[key] = down;
// Detect modifiers and mark them as pressed / released // Detect modifiers and mark them as pressed / released

View file

@ -664,7 +664,7 @@ var View = Base.extend(Callback, /** @lends View# */{
// Always first call the view's mouse handlers, as required by // Always first call the view's mouse handlers, as required by
// CanvasView, and then handle the active tool, if any. // CanvasView, and then handle the active tool, if any.
view._handleEvent('mousedown', point, event); view._handleEvent('mousedown', point, event);
if (tool = view._scope._tool) if (tool = view._scope.tool)
tool._handleEvent('mousedown', point, event); tool._handleEvent('mousedown', point, event);
// In the end we always call update(), which only updates the view if // In the end we always call update(), which only updates the view if
// anything has changed in the above calls. // anything has changed in the above calls.
@ -673,7 +673,7 @@ var View = Base.extend(Callback, /** @lends View# */{
function handleMouseMove(view, point, event) { function handleMouseMove(view, point, event) {
view._handleEvent('mousemove', point, event); view._handleEvent('mousemove', point, event);
var tool = view._scope._tool; var tool = view._scope.tool;
if (tool) { if (tool) {
// If there's no onMouseDrag, fire onMouseMove while dragging. // If there's no onMouseDrag, fire onMouseMove while dragging.
tool._handleEvent(dragging && tool.responds('mousedrag') tool._handleEvent(dragging && tool.responds('mousedrag')