From 135699021c2556cefbc582d0a80d20fd2be7d075 Mon Sep 17 00:00:00 2001 From: dsc Date: Fri, 26 Nov 2010 13:16:36 -0800 Subject: [PATCH] Trying out friendly AI. Some code reorganization. --- bin/check-css.sh | 3 + css/reset-old.css | 2 + css/reset.css | 49 +++- css/reset.min.css | 1 + src/Y/y-object.js | 10 +- src/easel/layer.js | 513 +++++++++++++++++++++++++++++ src/easel/loop/cooldown.js | 41 +++ src/easel/loop/eventloop.js | 122 +++++++ src/easel/loop/fps.js | 101 ++++++ src/easel/math/line.js | 141 ++++++++ src/easel/math/math.js | 19 + src/easel/math/vec.js | 133 ++++++++ src/easel/shape/circle.js | 23 ++ src/easel/shape/line.js | 130 ++++++++ src/easel/shape/polygon.js | 63 ++++ src/easel/shape/rect.js | 28 ++ src/easel/shape/shape.js | 23 ++ src/easel/util/astar.js | 140 ++++++++ src/easel/util/binaryheap.js | 146 +++++++++ src/easel/util/graph.js | 16 + src/easel/util/tree/pointquadtree.js | 580 +++++++++++++++++++++++++++++++++ src/easel/util/tree/quadtree.js | 241 ++++++++++++++ src/easel/util/tree/rbtree.js | 467 ++++++++++++++++++++++++++ src/portal/layer.js | 513 ----------------------------- src/portal/loop/cooldown.js | 41 --- src/portal/loop/eventloop.js | 122 ------- src/portal/loop/fps.js | 101 ------ src/portal/math/line.js | 141 -------- src/portal/math/math.js | 19 - src/portal/math/vec.js | 133 -------- src/portal/shape/circle.js | 23 -- src/portal/shape/line.js | 130 -------- src/portal/shape/polygon.js | 63 ---- src/portal/shape/rect.js | 28 -- src/portal/shape/shape.js | 23 -- src/portal/util/astar.js | 140 -------- src/portal/util/binaryheap.js | 146 --------- src/portal/util/graph.js | 16 - src/portal/util/tree/pointquadtree.js | 580 --------------------------------- src/portal/util/tree/quadtree.js | 241 -------------- src/portal/util/tree/rbtree.js | 467 -------------------------- src/tanks/game.js | 8 +- src/tanks/map/level.js | 1 + src/tanks/map/pathmap.js | 2 +- src/tanks/thing/bullet.js | 2 +- src/tanks/ui/config.js | 60 ---- src/tanks/ui/main.js | 2 +- src/tanks/ui/ui-config.js | 90 +++++ src/tanks/util/config.js | 92 +++++- tanks.php | 36 +- test/jsc/bar.js | 3 + test/jsc/baz.js | 2 + test/jsc/foo.js | 2 + test/jsc/jsc.py | 198 +++++++++++ test/jsc/lol.js | 1 + test/math/index.php | 3 - test/uki/index.php | 79 +++++ 57 files changed, 3479 insertions(+), 3021 deletions(-) create mode 100755 bin/check-css.sh create mode 100644 css/reset-old.css create mode 100644 css/reset.min.css create mode 100644 src/easel/layer.js create mode 100644 src/easel/loop/cooldown.js create mode 100644 src/easel/loop/eventloop.js create mode 100644 src/easel/loop/fps.js create mode 100644 src/easel/math/line.js create mode 100644 src/easel/math/math.js create mode 100644 src/easel/math/vec.js create mode 100644 src/easel/shape/circle.js create mode 100644 src/easel/shape/line.js create mode 100644 src/easel/shape/polygon.js create mode 100644 src/easel/shape/rect.js create mode 100644 src/easel/shape/shape.js create mode 100644 src/easel/util/astar.js create mode 100644 src/easel/util/binaryheap.js create mode 100644 src/easel/util/graph.js create mode 100644 src/easel/util/tree/pointquadtree.js create mode 100644 src/easel/util/tree/quadtree.js create mode 100644 src/easel/util/tree/rbtree.js delete mode 100644 src/portal/layer.js delete mode 100644 src/portal/loop/cooldown.js delete mode 100644 src/portal/loop/eventloop.js delete mode 100644 src/portal/loop/fps.js delete mode 100644 src/portal/math/line.js delete mode 100644 src/portal/math/math.js delete mode 100644 src/portal/math/vec.js delete mode 100644 src/portal/shape/circle.js delete mode 100644 src/portal/shape/line.js delete mode 100644 src/portal/shape/polygon.js delete mode 100644 src/portal/shape/rect.js delete mode 100644 src/portal/shape/shape.js delete mode 100644 src/portal/util/astar.js delete mode 100644 src/portal/util/binaryheap.js delete mode 100644 src/portal/util/graph.js delete mode 100644 src/portal/util/tree/pointquadtree.js delete mode 100644 src/portal/util/tree/quadtree.js delete mode 100644 src/portal/util/tree/rbtree.js delete mode 100644 src/tanks/ui/config.js create mode 100644 src/tanks/ui/ui-config.js create mode 100644 test/jsc/bar.js create mode 100644 test/jsc/baz.js create mode 100644 test/jsc/foo.js create mode 100755 test/jsc/jsc.py create mode 100644 test/jsc/lol.js create mode 100644 test/uki/index.php create mode 100644 test/uki/test-uki.js diff --git a/bin/check-css.sh b/bin/check-css.sh new file mode 100755 index 0000000..4bc5456 --- /dev/null +++ b/bin/check-css.sh @@ -0,0 +1,3 @@ +#!/usr/local/bin/fish +echo "$_" (dirname "$_") +# ls -lia ~/dev/lang/css/reset* (dirname $_)/css/reset* | sort diff --git a/css/reset-old.css b/css/reset-old.css new file mode 100644 index 0000000..dd5886b --- /dev/null +++ b/css/reset-old.css @@ -0,0 +1,2 @@ +html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;} +h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;} \ No newline at end of file diff --git a/css/reset.css b/css/reset.css index dd5886b..0fbb22a 100644 --- a/css/reset.css +++ b/css/reset.css @@ -1,2 +1,47 @@ -html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;} -h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;} \ No newline at end of file +html { color:#000; background:#fff; width:100%; height:100%; } +body { position:absolute; width:100%; top:0; left:0; } +html,body,div, + dl,dt,dd,ul,ol,li, + h1,h2,h3,h4,h5,h6, + pre,code, + form,fieldset,legend,input,button,textarea, + p,blockquote, + th,td { margin:0; padding:0; } + +address,caption,cite,code,dfn,em,strong,th,var,optgroup { font-style:inherit; font-weight:inherit; } +fieldset,img { border:0; } +caption,th { text-align:left; } +caption { margin-bottom:.5em; text-align:center; } + +h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:normal; } +h1,h2,h3,h4,h5,h6,strong { font-weight:bold; } +h1 { font-size:138.5%; } +h2 { font-size:123.1%; } +h3 { font-size:108%; } +h1,h2,h3 { margin:0.5em 0 1em; } + +q:before,q:after { content:''; } +del,ins { text-decoration:none; } +abbr,acronym { border:0; font-variant:normal; border-bottom:1px dotted #000; cursor:help; } +sup, sub { vertical-align:baseline; } +em { font-style:italic; } + +input,button,textarea,select,optgroup,option { font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; } +input,button,textarea,select { *font-size:100%; } +input[type=text],input[type=password],textarea { width:12.25em; *width:11.9em; } +legend { color:#000; } + +ul,ol,dl,blockquote { margin:1em; } +ol,ul,dl { margin-left:2em; } +li { list-style:none; } +ol li { list-style:decimal outside; } +ul li { list-style:disc outside; } +dl dd { margin-left:1em; } + +table { border-collapse:collapse; border-spacing:0; } +th,td { padding:.5em; } +th { font-weight:bold; text-align:center; } +p,fieldset,table,pre { margin-bottom:1em; } + +.clearer { clear:both !important; float:none !important; margin:0 !important; padding:0 !important; } +.rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; } \ No newline at end of file diff --git a/css/reset.min.css b/css/reset.min.css new file mode 100644 index 0000000..3c93c02 --- /dev/null +++ b/css/reset.min.css @@ -0,0 +1 @@ +html{color:#000;background:#fff;width:100%;height:100%;}body{position:absolute;width:100%;top:0;left:0;}html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}fieldset,img{border:0;}caption,th{text-align:left;}caption{margin-bottom:.5em;text-align:center;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:.5em 0 1em;}q:before,q:after{content:'';}del,ins{text-decoration:none;}abbr,acronym{border:0;font-variant:normal;border-bottom:1px dotted #000;cursor:help;}sup,sub{vertical-align:baseline;}em{font-style:italic;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}legend{color:#000;}ul,ol,dl,blockquote{margin:1em;}ol,ul,dl{margin-left:2em;}li{list-style:none;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}table{border-collapse:collapse;border-spacing:0;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}.clearer{clear:both!important;float:none!important;margin:0!important;padding:0!important;}.rounded{border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em;} \ No newline at end of file diff --git a/src/Y/y-object.js b/src/Y/y-object.js index b286350..2d6b497 100644 --- a/src/Y/y-object.js +++ b/src/Y/y-object.js @@ -27,12 +27,13 @@ YCollection.subclass('YObject', { /** * Searches a heirarchical object for a given subkey specified in dotted-property syntax. * @param {Array|String} chain The property-chain to lookup. + * @param {Any} [def] Default value should the key be `undefined`. * @param {Boolean} [meta] If supplied return an object of the form * `{ key: Qualified key name, obj: Parent object of key, value: Value at obj[key] }` * if chain is found (and `undefined` otherwise). - * @return {Any} The value at the path, or `undefined`. + * @return {Any} The value at the path, or `def` if `undefined`, otherwise `undefined`. */ - getNested : function getNested(chain, meta){ + getNested : function getNested(chain, def, meta){ if ( !isArray(chain) ) chain = chain.toString().split('.'); @@ -48,7 +49,10 @@ YCollection.subclass('YObject', { }; }, { value:this._o }); - return (ret && !meta ? ret.value : ret); + if (ret !== undefined) + return (meta ? ret : ret.value); + else + return (meta ? { value:def } : def); }, /** diff --git a/src/easel/layer.js b/src/easel/layer.js new file mode 100644 index 0000000..b6cd536 --- /dev/null +++ b/src/easel/layer.js @@ -0,0 +1,513 @@ +(function($, undefined){ + +var CONTEXT_ATTRS = Y([ + 'globalAlpha', 'globalCompositeOperation', + 'strokeStyle', 'fillStyle', + 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit', + 'shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor' +]); + + + +Layer = Y.subclass('Layer', { + _cssClasses : 'portal layer', + + canvas : null, + + parent : null, + children : null, + + ctx : null, + dirty : true, + + canvasWidth : 0, layerWidth : 0, + canvasHeight : 0, layerHeight : 0, + + x: 0, y: 0, loc : null, // Position relative to parent + + // Bleeds are marks outside the declared layer-size + negBleed : null, // Loc + posBleed : null, // Loc + + // Transforms + transform : null, // Object + useCanvasScaling : false, // default to CSS3 scaling + + + /// Setup /// + + init : function init(){ + this.children = new Y.YArray(); + + this.loc = new Loc(0,0); + this.negBleed = new Loc(0,0); + this.posBleed = new Loc(0,0); + + this.boundingBox = new Loc.BoundingBox(0,0, 0,0); + + this.transform = { + origin : new Loc('50%','50%'), // rotational origin + rotate : 0, + scale : new Loc(1.0,1.0), + translate : new Loc(0,0) // translates canvas + }; + + this.canvas = jQuery(''); + this.ctx = this.canvas[0].getContext('2d'); + + this.layer = jQuery('
') + .addClass(this._cssClasses) + .append(this.canvas); + + this.layer[0].layer = + this.canvas[0].layer = this; + }, + + /// Scene Graph Heirarchy /// + + /** @param {Layer} child */ + append : function append(child){ + new Y(arguments).invoke('appendTo', this); + return this; + }, + + /** @param {Layer} parent */ + appendTo : function appendTo(parent){ + if (!parent) return this; + + // Always ensure we detach from the DOM and redraw this node + this.remove(); + this.dirty = true; + + // Layer? Add self as new child, fix DOM + if ( parent instanceof Layer ){ + this.parent = parent; + parent.children.push(this); + parent.layer.append(this.layer); + + // Otherwise: Attach to a DOM node as a new root layer node, leave parent null + } else + $(parent).append(this.layer); + + return this; + }, + + /** + * Removes this layer from its parent and the DOM. + */ + remove : function remove(){ + if (this.parent) + this.parent.children.remove(this); + this.parent = null; + this.layer.remove(); + }, + + /** + * Clears this layer and destroys all children. + */ + empty : function empty(ctx){ + this.children.invoke('remove'); + this.clear(); + return this; + }, + + /** + * @returns The root of the scene graph. + */ + root : function root(){ + if (this.parent) + return this.parent.root(); + else + return this; + }, + + + + + + /// Attributes /// + + + attr : function attr(key, value, def){ + if (!key) return this; + + if ( Y.isPlainObject(key) ) { + for (var k in key) + this.attr(k, key[k]); + return this; + + // } else if ( CONTEXT_ATTRS.has(key) ) { + // var r = Y.attr(this.ctx, key, value, def); + // + // if (r === this.ctx) { + // // This implies we set a property + // this.dirty = true; + // return this; + // } else + // return r; + // + } else + return Y.op.attr(this, key, value, def); + }, + + /** + * Changes the layer's width and then updates the canvas. + */ + width : function width(w){ + if (w === undefined) + return this.layerWidth; + + this.layerWidth = w; + this.boundingBox = this.boundingBox.resize(w, this.layerHeight); + + var nb = this.negBleed.x + , v = this.canvasWidth = Math.ceil(w + nb + this.posBleed.x); + this.layer.width(w).css('margin-left', (-nb)+'px') + this.canvas.width(v); + // this.canvas.css({ + // 'width' : v+'px', + // 'margin-left' : (-nb)+'px' + // }); + this.canvas[0].width = v; + + + return this; + }, + + height : function height(h){ + if (h === undefined) + return this.layerHeight; + + this.layerHeight = h; + this.boundingBox = this.boundingBox.resize(this.layerWidth, h); + + var nb = this.negBleed.y + , v = this.canvasHeight = Math.ceil(h + nb + this.posBleed.y); + this.layer.height(h).css('margin-top', (-nb)+'px') + this.canvas.height(v); + // this.canvas.css({ + // 'height' : v+'px', + // 'margin-top' : (-nb)+'px' + // }); + this.canvas[0].height = v; + + return this; + }, + + /** + * position() -> object + * Gets position of the layer relative to the parent. + */ + /** + * position(top, left) -> this + * Sets the position of this node, and then returns it. + * @param {Number|String|undefined} top If omitted, this method must be invoked with `undefined` as the first argument. + * @param {Number|String|undefined} left + */ + /** + * position(pos) -> this + * Sets the position of this node, and then returns it. + * @param {Object} pos An object with "top" and/or "left" properties as in `position(top, left)`. + */ + position : function position(left, top){ + if (top === undefined && left === undefined) + return this.layer.position(); + + if (top && Y.isPlainObject(top)) + var pos = top; + else + var pos = { 'top': top, 'left':left }; + + // if (pos.left !== undefined) pos.left -= this.offsetX; + // if (pos.top !== undefined) pos.top -= this.offsetY; + + this.boundingBox = this.boundingBox.add(pos.left,pos.top); + this.loc = this.boundingBox.p1; + this.css(pos); + return this; + }, + + stroke : function stroke(style, width){ + if (style === undefined && width === undefined) + return this.strokeStyle; + + if (width !== undefined) + this.lineWidth = width; + + this.dirty = true; + this.strokeStyle = style; + return this; + }, + + fill : function fill(style){ + if (style === undefined) + return this.fillStyle; + + this.dirty = true; + this.fillStyle = style; + return this; + }, + + hide : makeDelegate('hide'), + show : makeDelegate('show'), + + /** CSS properties */ + css : makeDelegate('css'), + + /** Position relative to document. */ + offset : makeDelegate('offset'), + + + + /// Transformations /// + + /** + * Gets and sets the transformation origin. + */ + origin : function origin(x,y){ + var o = this.transform.origin; + if (arguments.length === 0) + return o.absolute(this.layerWidth, this.layerHeight); + + o.x = x; + o.y = y; + return this._applyTransforms(); + }, + + /** + * Rotates this layer by r radians. + */ + rotate : function rotate(r){ + var t = this.transform; + if (r === undefined) + return t.rotate; + + t.rotate = r; + return this._applyTransforms(); + }, + + /** + * Scales this layer by (sx,sy), and then applies scaling relatively + * to all sublayers (preserving knowledge of their individual scaling). + */ + scale : function scale(sx,sy){ + var o = this.transform.scale; + if (arguments.length === 0) + return o.absolute(this.layerWidth, this.layerHeight); + + o.x = sx; + o.y = sy; + this.dirty = true; + return this._applyTransforms(); + }, + + /** + * Translates draw calls by (x,y) within this layer only. This allows you to + * functionally move the coordinate system of the layer. + */ + translate : function translate(x,y){ + var o = this.transform.translate; + if (arguments.length === 0) + return o.absolute(this.layerWidth, this.layerHeight); + + o.x = x; + o.y = y; + this.dirty = true; + return this; + }, + + _applyTransforms : function _applyTransforms(){ + var t = this.transform, tfns = []; + + if (t.rotate !== 0) + tfns.push('rotate('+t.rotate+'rad)'); + + if (!this.useCanvasScaling && (t.scale.x !== 1 || t.scale.y !== 1)) + tfns.push('scale('+t.scale.x+','+t.scale.y+')'); + + var trans = (tfns.length ? tfns.join(' ') : 'none') + , o = t.origin.toUnits() + , origin = o.x+' '+o.y ; + this.layer.css( + ['', '-moz-', '-webkit-'].reduce( + function(values, prefix){ + values[prefix+'transform'] = trans; + values[prefix+'transform-origin'] = origin; + return values; + }, {}) ); + return this; + }, + + + + + + + + /// Iterators /// + + invoke : function invoke(name){ + var args = Y(arguments,1); + // this[name].apply(this, args); + // this.children.invoke.apply(this.children, ['invoke', name].concat(args)); + return this._invoke(name, args); + }, + _invoke : function _invoke(name, args){ + this[name].apply(this, args); + this.children.invoke('_invoke', name, args); + return this; + }, + + setAll : function setAll(k,v){ + this[k] = v; + this.children.invoke('setAll', k,v); + return this; + }, + + /** + * Reduce "up", across this and parents, inner to outer: + * acc = fn.call(context || node, acc, node) + */ + reduceup : function reduceup(acc, fn, context){ + // if ( Y.isFunction(fn) ) + // acc = fn.call(context || this, acc, this); + // else + // acc = this[fn].call(context || this, acc, this); + acc = fn.call(context || this, acc, this); + return ( this.parent ? this.parent.reduceup(acc, fn, context) : acc ); + }, + + /** + * Reduce "down", across this and children, depth-first: + * acc = fn.call(context || node, acc, node) + */ + reduce : function reduce(acc, fn, context){ + acc = fn.call(context || this, acc, this); + return this.children.reduce(acc, fn, context); + }, + + + + + + + /// Drawing Functions /// + + /** + * @param {CanvasDrawingContext2D} [ctx=this.ctx] Forces context to use rather than the layer's own. + * @param {Boolean} [force=false] Forces redraw. + */ + draw : function draw(ctx, force){ + if ( this.dirty || force ){ + var _ctx = ctx || this.ctx; + this._openPath(_ctx); + this.drawShape(_ctx); + this._closePath(_ctx); + } + + this.dirty = false; + this.children.invoke('draw', ctx, force); + return this; + }, + + _openPath : function _openPath(ctx){ + var self = this + , alwaysClear = this.alwaysClear + , neg = this.negBleed + , t = this.transform + , w = this.canvasWidth, h = this.canvasHeight ; + + ctx.beginPath(); + ctx.setTransform(1,0,0,1,0,0); + ctx.clearRect(-w,-h, 2*w,2*h); + + if (this.useCanvasScaling && (t.scale.x !== 1 || t.scale.y !== 1)) + ctx.scale(t.scale.x,t.scale.y); + + ctx.translate(neg.x, neg.y); + ctx.translate(t.translate.x, t.translate.y); + + // Set context attributes + CONTEXT_ATTRS.forEach(function(name){ + if (self[name] !== undefined) + ctx[name] = self[name]; + else if (alwaysClear) + delete ctx[name]; + }); + + return this; + }, + + /** To be implemented by subclasses. */ + drawShape : function drawShape(ctx){ return this; }, + + _closePath : function _closePath(ctx){ + ctx.closePath(); + return this; + }, + + /** + * Clears this layer and all children. + */ + clear : function clear(ctx){ + var w = this.canvas.width() + , h = this.canvas.height(); + ctx = ctx || this.ctx; + ctx.beginPath(); + ctx.setTransform(1,0,0,1,0,0); + ctx.clearRect(-w,-h, 2*w,2*h); + ctx.closePath(); + this.children.invoke('clear'); + return this; + }, + + // for debugging + point : function point(x,y, color){ + var ctx = this.ctx; + // this._openPath(ctx); + + var r = 2; + ctx.beginPath(); + ctx.arc(x,y, r, 0, Math.PI*2, false); + ctx.fillStyle = color || '#FFFFFF'; + ctx.fill(); + ctx.closePath(); + + // this._closePath(ctx); + return this; + }, + + + + /// Misc /// + toString : function toString(){ + var pos = (this.layer ? this.position() : {top:NaN, left:NaN}); + return this.className+'['+pos.left+','+pos.top+']( children='+this.children.size()+' )'; + } +}); + +function makeDelegate(name, dirties, prop){ + prop = prop || 'layer'; + return function(){ + if (dirties && arguments.length) + this.dirty = true; + + var target = this[prop] + , result = target[name].apply(target, arguments); + + return (result !== target ? result : this); + }; +} + +// Install CSS styles +$(function(){ + $('