From 6805cad1d23a65292eb4848f7d1d8efa5e487a11 Mon Sep 17 00:00:00 2001 From: dsc Date: Sun, 28 Nov 2010 20:12:48 -0800 Subject: [PATCH] Refactors ezl to use CommonJS. --- bin/cjs.py | 208 ++++++++----- doc/notes.md | 5 +- index.php | 5 +- lib/cjs/deps.js | 3 + lib/cjs/dummy.js | 14 + lib/cjs/module.js | 12 +- lib/jquery.js | 1 + src/Y/class.cjs | 61 +---- src/Y/core.cjs | 18 +- src/Y/index.cjs | 8 +- src/Y/modules/event.js | 68 ---- src/Y/modules/old-y.event.js | 68 ++++ src/Y/modules/y.cookies.cjs | 26 ++ src/Y/modules/y.cookies.js | 28 -- src/Y/modules/y.event.cjs | 90 ++++++ src/Y/modules/y.event.js | 96 ------ src/Y/modules/y.json.cjs | 50 +++ src/Y/modules/y.json.js | 52 --- src/Y/modules/y.kv.cjs | 26 ++ src/Y/modules/y.kv.js | 28 -- src/Y/op.cjs | 9 +- src/Y/types/array.cjs | 44 ++- src/Y/types/collection.cjs | 54 +++- src/Y/types/function.cjs | 53 ++-- src/Y/types/number.cjs | 4 +- src/Y/types/object.cjs | 7 +- src/Y/types/string.cjs | 50 ++-- src/Y/utils.cjs | 68 ++++ src/Y/y.cjs | 1 + src/evt/evt.class.js | 24 +- src/ezl/index.cjs | 5 + src/ezl/layer.cjs | 519 +++++++++++++++++++++++++++++++ src/ezl/layer.js | 513 ------------------------------- src/ezl/loc/boundingbox.cjs | 74 +++++ src/ezl/loc/index.cjs | 7 + src/ezl/loc/loc.cjs | 114 +++++++ src/ezl/loc/square.cjs | 40 +++ src/ezl/loop/cooldown.cjs | 46 +++ src/ezl/loop/cooldown.js | 41 --- src/ezl/loop/eventloop.cjs | 127 ++++++++ src/ezl/loop/eventloop.js | 122 -------- src/ezl/loop/fps.cjs | 108 +++++++ src/ezl/loop/fps.js | 101 ------ src/ezl/loop/index.cjs | 7 + src/ezl/math/index.cjs | 27 ++ src/ezl/math/line.cjs | 139 +++++++++ src/ezl/math/line.js | 141 --------- src/ezl/math/math.js | 19 -- src/ezl/math/rect.cjs | 62 ++++ src/ezl/math/vec.cjs | 122 ++++++++ src/ezl/math/vec.js | 133 -------- src/ezl/shape/circle.cjs | 23 ++ src/ezl/shape/circle.js | 23 -- src/ezl/shape/index.cjs | 11 + src/ezl/shape/line.cjs | 130 ++++++++ src/ezl/shape/line.js | 130 -------- src/ezl/shape/polygon.cjs | 75 +++++ src/ezl/shape/polygon.js | 63 ---- src/ezl/shape/rect.cjs | 30 ++ src/ezl/shape/rect.js | 28 -- src/ezl/shape/shape.cjs | 23 ++ src/ezl/shape/shape.js | 23 -- src/ezl/util/astar.cjs | 137 ++++++++ src/ezl/util/astar.js | 140 --------- src/ezl/util/binaryheap.cjs | 144 +++++++++ src/ezl/util/binaryheap.js | 146 --------- src/ezl/util/graph.cjs | 19 ++ src/ezl/util/graph.js | 16 - src/ezl/util/tree/pointquadtree.cjs | 580 +++++++++++++++++++++++++++++++++++ src/ezl/util/tree/pointquadtree.js | 580 ----------------------------------- src/ezl/util/tree/quadtree.cjs | 244 +++++++++++++++ src/ezl/util/tree/quadtree.js | 241 --------------- src/ezl/util/tree/rbtree.cjs | 463 ++++++++++++++++++++++++++++ src/ezl/util/tree/rbtree.js | 467 ---------------------------- src/tanks/map/level.cjs | 2 +- src/tanks/map/loc/loc.cjs | 260 ---------------- src/tanks/ui/grid.cjs | 2 +- tanks.php | 31 ++- 78 files changed, 3950 insertions(+), 3729 deletions(-) create mode 100644 lib/cjs/deps.js create mode 100644 lib/cjs/dummy.js create mode 120000 lib/jquery.js delete mode 100644 src/Y/modules/event.js create mode 100644 src/Y/modules/old-y.event.js create mode 100644 src/Y/modules/y.cookies.cjs delete mode 100644 src/Y/modules/y.cookies.js create mode 100644 src/Y/modules/y.event.cjs delete mode 100644 src/Y/modules/y.event.js create mode 100644 src/Y/modules/y.json.cjs delete mode 100644 src/Y/modules/y.json.js create mode 100644 src/Y/modules/y.kv.cjs delete mode 100644 src/Y/modules/y.kv.js create mode 100644 src/Y/utils.cjs create mode 100644 src/ezl/index.cjs create mode 100644 src/ezl/layer.cjs delete mode 100644 src/ezl/layer.js create mode 100644 src/ezl/loc/boundingbox.cjs create mode 100644 src/ezl/loc/index.cjs create mode 100644 src/ezl/loc/loc.cjs create mode 100644 src/ezl/loc/square.cjs create mode 100644 src/ezl/loop/cooldown.cjs delete mode 100644 src/ezl/loop/cooldown.js create mode 100644 src/ezl/loop/eventloop.cjs delete mode 100644 src/ezl/loop/eventloop.js create mode 100644 src/ezl/loop/fps.cjs delete mode 100644 src/ezl/loop/fps.js create mode 100644 src/ezl/loop/index.cjs create mode 100644 src/ezl/math/index.cjs create mode 100644 src/ezl/math/line.cjs delete mode 100644 src/ezl/math/line.js delete mode 100644 src/ezl/math/math.js create mode 100644 src/ezl/math/vec.cjs delete mode 100644 src/ezl/math/vec.js create mode 100644 src/ezl/shape/circle.cjs delete mode 100644 src/ezl/shape/circle.js create mode 100644 src/ezl/shape/index.cjs create mode 100644 src/ezl/shape/line.cjs delete mode 100644 src/ezl/shape/line.js create mode 100644 src/ezl/shape/polygon.cjs delete mode 100644 src/ezl/shape/polygon.js create mode 100644 src/ezl/shape/rect.cjs delete mode 100644 src/ezl/shape/rect.js create mode 100644 src/ezl/shape/shape.cjs delete mode 100644 src/ezl/shape/shape.js create mode 100644 src/ezl/util/astar.cjs delete mode 100644 src/ezl/util/astar.js create mode 100644 src/ezl/util/binaryheap.cjs delete mode 100644 src/ezl/util/binaryheap.js create mode 100644 src/ezl/util/graph.cjs delete mode 100644 src/ezl/util/graph.js create mode 100644 src/ezl/util/tree/pointquadtree.cjs delete mode 100644 src/ezl/util/tree/pointquadtree.js create mode 100644 src/ezl/util/tree/quadtree.cjs delete mode 100644 src/ezl/util/tree/quadtree.js create mode 100644 src/ezl/util/tree/rbtree.cjs delete mode 100644 src/ezl/util/tree/rbtree.js delete mode 100644 src/tanks/map/loc/bbox.cjs delete mode 100644 src/tanks/map/loc/index.cjs delete mode 100644 src/tanks/map/loc/loc.cjs delete mode 100644 src/tanks/map/loc/rect.cjs diff --git a/bin/cjs.py b/bin/cjs.py index 250e42d..dbdbc72 100755 --- a/bin/cjs.py +++ b/bin/cjs.py @@ -7,7 +7,7 @@ __version__ = (0, 0, 1) __all__ = ('ResolutionError', 'Module', 'JSResolver',) -import sys, re +import sys, re, json from itertools import chain, repeat from collections import defaultdict from subprocess import Popen, check_output, STDOUT, PIPE @@ -18,21 +18,30 @@ from path import path import pystache +AT_REQUIRE_PAT = re.compile(r'//@require\(\s*([\'"])(.*?)\1\s*\)') LINE_COMMENT_PAT = re.compile(r'(.*?)(//.*?)(?:\n|$)') PAIR_COMMENT_PAT = re.compile(r'(.*?)(/\*.*?\*/)') REQUIRE_PAT = re.compile(r'\brequire\(\s*([\'"])(.*?)\1\s*\)') + ROOT = path(__file__).abspath().dirname().dirname() LIB = ROOT/'lib/cjs' CWD = path('.') BLANK = path('') + MODULE_TEMPLATE = '' +DUMMY_TEMPLATE = '' +DEPS_TEMPLATE = '' try: - with (LIB/'module.js').open('rU') as f : + with (LIB/'module.js').open('rU') as f: MODULE_TEMPLATE = f.read() + with (LIB/'dummy.js').open('rU') as f: + DUMMY_TEMPLATE = f.read() + with (LIB/'deps.js').open('rU') as f: + DEPS_TEMPLATE = f.read() except Exception as ex: print - print 'Error reading module template file!' + print 'Error reading template file!' print ' ROOT={ROOT}, LIB={LIB}, module.js={}'.format(LIB/'module.js', **locals()) raise @@ -86,55 +95,41 @@ def canonicalise(query, base=None): return str(id) -class Repo(object): - def __init__(self, filepath, url): - self.path = filepath - self.url = url - - def __repr__(self): - return 'Repo({}, url={})'.format(self.path, self.url) - - def __str__(self): - return repr(self) - - class Module(Bunch): DEFAULTS = { - 'id' : '', # Canonical Module ID - 'file' : None, # path - 'name' : '', # Module Name - 'uri' : '', # Module URI (TODO) - 'text' : None, # Unmodified module text - 'contents' : None, # Compiled module text - '_requires' : None, - 'outpath' : None, + 'id' : '', # Canonical Module ID + 'file' : None, # path + 'name' : '', # Module Name + 'uri' : '', # Module URI (TODO) + 'text' : None, # Unmodified module text + 'contents' : None, # Compiled module text + 'outpath' : None, # Build directory + '_requires' : None, + '_at_requires' : None, } - def __init__(self, id, file, uri='', out=None, compile=True): + def __init__(self, id, file, uri=BLANK, out=None, compile=True): self.update(Module.DEFAULTS) self.id = id self.file = path(file) - self.name = self.id.split('/').pop().capitalize() + self.name = re.subn(r'\W', '', self.id.split('/').pop().capitalize())[0] if out: - out = (path(out) / self.id) + '.js' + out = path(out) + outpath = (out / self.id) + '.js' else: - out = self.file.replace('.cjs', '.js') - self.outpath = path(out) - self.uri = uri + out = BLANK + outpath = self.file.replace('.cjs', '.js') + self.outpath = path(outpath) + self.uri = str(out/uri/self.id)+'.js' - if compile: self.compile() + self.compile() # print "new!", repr(self) def compile(self): - # if uri: self.uri = uri.format(**self) # TODO: calc uri - - if not self.file.endswith('.cjs'): - return self - outdir = self.outpath.dirname() if not outdir.exists(): outdir.makedirs() @@ -149,7 +144,8 @@ class Module(Bunch): with self.file.open('rU') as f: txt = f.read() self['text'] = txt - self['contents'] = pystache.render(MODULE_TEMPLATE, self) + template = MODULE_TEMPLATE if self.file.endswith('.cjs') else DUMMY_TEMPLATE + self['contents'] = pystache.render(template, self) return self[attr] @property @@ -160,6 +156,10 @@ class Module(Bunch): def contents(self): return self.read('contents') + def findDirectives(self, text): + for areq in AT_REQUIRE_PAT.finditer(text): + yield canonicalise(areq.group(2), self) + def findRequires(self, text): text = LINE_COMMENT_PAT.subn(r'\1 ', text)[0] text = PAIR_COMMENT_PAT.subn(r'\1 ', text)[0] @@ -167,11 +167,23 @@ class Module(Bunch): for mreq in REQUIRE_PAT.finditer(text): yield canonicalise(mreq.group(2), self) + def calcRequires(self, attr): + if self._requires is None or self._at_requires is None: + self._at_requires = list(self.findDirectives(self.text)) + self._requires = list(self.findRequires(self.text)) + return getattr(self, attr) + + @property + def atRequires(self): + return self.calcRequires('_at_requires') + + @property + def nonAtRequires(self): + return self.calcRequires('_requires') + @property def requires(self): - if self._requires is None: - self._requires = list(self.findRequires(self.text)) - return self._requires + return self.calcRequires('_at_requires') + self._requires def __hash__(self): return hash(self.id) @@ -209,22 +221,30 @@ class CommonJS(object): req = cjs.lookup(query, mod) if req not in seen: queue.append(req) + if cjs.deps_name: + cjs.genDepLoader() return cjs repos = [] modules = {} # id -> Module - _graph = None # id -> set(dependants) - - - def __init__(self, repos, out='build', clean=True): - self.modules = {} - self.repos = set( path(path(p).abspath()+'/') for p in repos) - self.out = None if out is '' else out + _graph = None # id -> set(dependants) + _at_graph = None # id -> set(dependants) + + def __init__(self, repos, out='build', deps_name=None, clean=True, **options): + self.modules = {} + self.options = options + self.deps_name = deps_name + self.repos = set( path(path(p).abspath()+'/') for p in repos) + self.out = None if out is '' else out if self.out is not None and clean: out = path(self.out) - if out.exists(): out.rmtree() + for f in out.glob('*'): + if f.isdir(): + f.rmtree() + elif f.isfile(): + f.remove() def register(self, id, file): mod = self.modules[id] = Module(id=id, file=file, out=self.out) @@ -254,32 +274,67 @@ class CommonJS(object): for k, mod in self.modules.iteritems(): yield k, mod - @property - def graph(self): + def calcGraph(self, attr): if self._graph is None: - graph = self._graph = defaultdict(set) + graph = self._graph = defaultdict(set) + atgraph = self._at_graph = defaultdict(set) for mod in self.modules.values(): - for req in mod.requires: + for req in mod.nonAtRequires: graph[req].add(mod) - return self._graph + for req in mod.atRequires: + atgraph[req].add(mod) + return getattr(self, attr) @property - def deplist(self): - return '\n'.join( '%s %s' % (id, dep.id) for (id, deps) in self.graph.iteritems() for dep in deps ) + def graph(self): + return self.calcGraph('_graph') @property - def dependencies(self): + def atGraph(self): + return self.calcGraph('_at_graph') + + def tsort(self, graph): p = Popen(['tsort'], stderr=STDOUT, stdin=PIPE, stdout=PIPE) - p.stdin.write( self.deplist ) + deps = '\n'.join( '%s %s' % (id, dep.id) for (id, deps) in graph.iteritems() for dep in deps ) + p.stdin.write(deps) p.stdin.close() if p.wait() != 0: raise ResolutionError('Cannot resolve dependencies! Requirements must be acyclic!') - return p.stdout.read().strip().split('\n') + return p.stdout.read() + + @property + def dependencies(self): + deps = (self.tsort(self.atGraph)+self.tsort(self.graph)).strip().split('\n') + for i, dep in reversed(list(enumerate(deps[:]))): + try: + idx = deps.index(dep, 0, i-1) + if i != idx: + del deps[idx] + except ValueError: pass + return deps + + def dumpDependencies(self): + column = Popen(['column', '-s', '\t', '-t'], stderr=sys.stderr, stdin=PIPE, stdout=PIPE) + mods = self.modules.values() + for mod in sorted(mods, key=lambda m: len(m.requires), reverse=True): + column.stdin.write('%s\t->\t%r\n' % (mod, sorted(mod.requires))) + # print '%s\t->\t%r' % (mod, sorted(mod.requires)) + column.stdin.close() + if column.wait() != 0: + print >> sys.stderr, 'Some sort of error has occurred!' + return column.stdout.read() - def cat(self): - for dep in self.dependencies: - print self.modules[dep].contents + def genDepLoader(self): + with (path(self.out)/self.deps_name).open('w') as deps: + deps.write( pystache.render(DEPS_TEMPLATE, uris=json.dumps(self.uris)) ) + @property + def uris(self): + return [ self.modules[d].uri for d in self.dependencies ] + + @property + def scriptTags(self): + return '\n'.join( ''.format(uri) for uri in self.uris ) @@ -292,15 +347,19 @@ def main(): usage = 'usage: %prog [options] file[...] [-- [repo_path[...]]]', description = 'Compiles CommonJS modules.', version = '%prog'+" %i.%i.%i" % __version__) - parser.add_option("-p", "--repo-paths", dest='repos', default='', + parser.add_option("-p", "--repo-paths", default='', help="Comma-seperated paths to search for unqualified modules. " "If a path contains :, the portion afterward will be taken as the repo URL. [default: .]") parser.add_option("-o", "--out", default='build', help="Root directory to write compiled JS files. Specify '' to write to module's dir. [default: build]") parser.add_option("-C", "--no-clean", dest='clean', default=True, action="store_false", help="Do not clean the out-dir of compiled files. [default: False if -o, True otherwise]") - parser.add_option("-d", "--print-deps", default=False, action="store_true", + parser.add_option("--print-deps", default=False, action="store_true", help="Prints module dependencies after compiling. [default: %default]") + parser.add_option("-g", "--gen-deps-script", dest='deps_name', default=None, + help="Generates a JS script with the given name which doc-writes tags for this set of modules.") + parser.add_option("-s", "--script-tags", default=False, action="store_true", + help="Emits script-tags for this set of modules in dependency order. [default: %default]") (options, args) = parser.parse_args() @@ -310,21 +369,24 @@ def main(): (files, sep, repos) = partition(sys.argv, '--') files = filter(lambda f: f in args, files) repos = filter(lambda f: f in args, repos) - repos.extend( filter(None, options.repos.split(',')) ) + repos.extend( filter(None, options.repo_paths.split(',')) ) # print 'files:', files, 'repos:', (repos or ['.']) - js = CommonJS.discover(files=files, repos=repos or ['.'], **options.__dict__) + try: + js = CommonJS.discover(files=files, repos=repos or ['.'], **options.__dict__) + except ResolutionError as ex: + print >> sys.stderr, str(ex) + return 1 + + if options.script_tags: + print js.scriptTags if options.print_deps: - column = Popen(['column', '-s', '\t', '-t'], stderr=sys.stderr, stdin=PIPE, stdout=PIPE) - mods = js.modules.values() - for mod in sorted(mods, key=lambda m: len(m.requires), reverse=True): - column.stdin.write('%s\t->\t%r\n' % (mod, sorted(mod.requires))) - # print '%s\t->\t%r' % (mod, sorted(mod.requires)) - - column.stdin.close() - if column.wait() != 0: print 'Some sort of error has occurred!' - print column.stdout.read() + print >> sys.stderr, 'All Dependencies:' + print >> sys.stderr, js.dumpDependencies() + print >> sys.stderr, '' + print >> sys.stderr, 'Resolution:' + print >> sys.stderr, '\n'.join(js.dependencies) return 0 diff --git a/doc/notes.md b/doc/notes.md index 3285ae5..38cd78e 100644 --- a/doc/notes.md +++ b/doc/notes.md @@ -2,9 +2,8 @@ - Tanks seem to get stuck on some corners # TODOs -- How-to overlay -- Restart level button -- AI: Line-of-sight for dodging, firing +- change @require to @ensure + - AI: Don't shoot if it'll kill you or your friends - AI: Lead shots on moving targets - Config-driven unit-types (name, stats, properties; pointers to behavior scripts, assets) diff --git a/index.php b/index.php index afcb361..3ac3f78 100644 --- a/index.php +++ b/index.php @@ -62,7 +62,10 @@
- +
diff --git a/lib/cjs/deps.js b/lib/cjs/deps.js new file mode 100644 index 0000000..3735e59 --- /dev/null +++ b/lib/cjs/deps.js @@ -0,0 +1,3 @@ +{{{uris}}}.forEach(function(uri){ + document.write(''); +}); diff --git a/lib/cjs/dummy.js b/lib/cjs/dummy.js new file mode 100644 index 0000000..71c5a6b --- /dev/null +++ b/lib/cjs/dummy.js @@ -0,0 +1,14 @@ +require.install( +// Module ID +'{{{id}}}', + +// Module metadata +{ + 'name' : '{{{name}}}', + 'id' : '{{{id}}}', + 'uri' : '{{{uri}}}' +}, +// Dummy module: setup does nothing +function setup{{{name}}}(require, exports, module){}); + +{{{text}}} diff --git a/lib/cjs/module.js b/lib/cjs/module.js index 7f225e1..167f51a 100644 --- a/lib/cjs/module.js +++ b/lib/cjs/module.js @@ -1,17 +1,17 @@ require.install( // Module ID -'{{id}}', +'{{{id}}}', // Module metadata { - 'name' : '{{name}}', - 'id' : '{{id}}', - 'uri' : '{{uri}}' + 'name' : '{{{name}}}', + 'id' : '{{{id}}}', + 'uri' : '{{{uri}}}' }, // Module Setup function -function setup{{name}}(require, exports, module){ +function setup{{{name}}}(require, exports, module){ -{{text}} +{{{text}}} }); \ No newline at end of file diff --git a/lib/jquery.js b/lib/jquery.js new file mode 120000 index 0000000..0185856 --- /dev/null +++ b/lib/jquery.js @@ -0,0 +1 @@ +jquery-1.4.3.min.js \ No newline at end of file diff --git a/src/Y/class.cjs b/src/Y/class.cjs index 9ca59ab..dc1a4b5 100644 --- a/src/Y/class.cjs +++ b/src/Y/class.cjs @@ -1,7 +1,10 @@ // Inspired by John Resig's "Simple Class Inheritence" -- http://ejohn.org/blog/simple-javascript-inheritance/ -var Y = require('Y').Y -, type = require('Y/type') +var type = require('Y/type') +, YFunction = require('Y/types/function').YFunction +, core = require('Y/core') +, slice = core.slice + , KNOWN_CLASSES = type.type.KNOWN_CLASSES , classToString = function toString(){ return this.className+"()"; } ; @@ -113,7 +116,7 @@ function Class(className, Parent, members) { prototype[k] = members[k]; } - if (prototype.init) NewClass.init = Y(prototype.init); + if (prototype.init) NewClass.init = YFunction(prototype.init); KNOWN_CLASSES[className] = NewClass; @@ -171,59 +174,9 @@ var YBase = new Class("YBase", { - -/// Other Class Utilities /// - -function bindName(v, k){ - if ( isFunction(v) ) - this[k] = Y(v).bind(this); -} -// bindName = Y(bindName).curry(); - -function bindAll(o, names){ - var names = new Y(arguments, 1); - Y(names.size() ? names.generate(Y.op.get(o)) : o).forEach( bindName, o ); - return o; -} - -// Y.chainDelegates = chainDelegates; -function chainDelegates(type, name){ - var names = Y(arguments, 1), - proto = type.prototype; - names.forEach(function(name){ - proto[name] = function(){ - var o = this._o || this; - o[name].apply(o, Y(arguments)); - return this; - }; - }); - return type; -} - -// Y.mixinNames = mixinNames; -function mixinNames(o, Donor, names, override, yWrap){ - var target = ( isFunction(o) ? o.prototype : o) - , proto = ( isFunction(Donor) ? Donor.prototype : Donor); - - // Need a closure to capture the name - names.forEach(function(name){ - if ( isFunction(proto[name]) && (override || !target[name]) ) - target[name] = function(){ - var r = proto[name].apply(this._o || target, arguments); - return (yWrap ? Y(r) : r); - }; - }); - - return o; -} - exports['Class'] = exports['subclass'] = Class; exports['instantiate'] = Class.instantiate.bind(Class); exports['fabricate'] = Class.fabricate.bind(Class); - -exports['YBase'] = YBase; -exports['bindAll'] = bindAll; -exports['chainDelegates'] = chainDelegates; -exports['mixinNames'] = mixinNames; +exports['YBase'] = YBase; diff --git a/src/Y/core.cjs b/src/Y/core.cjs index 83f1594..0725a30 100644 --- a/src/Y/core.cjs +++ b/src/Y/core.cjs @@ -1,4 +1,15 @@ // Generic Collection Functions +var undefined +, globals = (function(){ return this; })() +, _Function = globals.Function +, _Array = globals.Array +, PT = "prototype" +, slice = _Array[PT].slice +; + +// function slice(a){ +// return _asl.apply(a, _asl.call(arguments, 1)); +// } function notWrapped(fn){ var self = arguments.callee.caller; @@ -9,7 +20,7 @@ function reduce(o, fn, acc){ if ( !o ) return acc; - fn = Function.toFunction(fn); + fn = _Function.toFunction(fn); var cxt = arguments[3] || o; if ( notWrapped(o.reduce) ) return o.reduce.apply(o, [fn, acc, cxt]); @@ -24,7 +35,7 @@ function map(o, fn){ if ( !o ) return o; - fn = Function.toFunction(fn); + fn = _Function.toFunction(fn); var acc = {}, cxt = arguments[2] || o; if ( notWrapped(o.map) ) return o.map.apply(o, [fn, cxt]); @@ -44,7 +55,7 @@ function filter(o, fn){ if ( !o ) return o; - fn = Function.toFunction(fn); + fn = _Function.toFunction(fn); var acc = {}, cxt = arguments[2] || o; if ( notWrapped(o.filter) ) return o.filter.apply(o, [fn, cxt]); @@ -116,3 +127,4 @@ exports['extend'] = extend; exports['dset'] = dset; exports['dattr'] = dattr; exports['dextend'] = dextend; +exports['slice'] = slice; \ No newline at end of file diff --git a/src/Y/index.cjs b/src/Y/index.cjs index bc8097e..cdfd44e 100644 --- a/src/Y/index.cjs +++ b/src/Y/index.cjs @@ -1,7 +1,8 @@ +// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- /// Import our external deps first /// -// require('lessly/future'); -// require('functional/to-function'); +//@require('lessly/future'); +//@require('functional/to-function'); /// Set up core and utilities /// var core = require('Y/core') @@ -17,6 +18,7 @@ core.extend(type.type, type); // Attach core & type to Y core.extend(Y, core, type); +delete Y.slice; // grr // Make top-level setters refer to the delegating versions Y['core'] = core; @@ -41,7 +43,7 @@ addNames('curry methodize genericize compose chain memoize', yfn); // Curry all operators var op = require('Y/op'); core.forEach(op, YFunction); -Y['op'] = op.extend({}, core.map(yfn.curry, op)); +Y['op'] = op['curried'] = op.extend({}, core.map(yfn.curry, op)); // Y['op'] = core.reduce(op, function(Yop, fn, k){ // Yop[k] = yfn.curry(fn); // return Yop; diff --git a/src/Y/modules/event.js b/src/Y/modules/event.js deleted file mode 100644 index e211432..0000000 --- a/src/Y/modules/event.js +++ /dev/null @@ -1,68 +0,0 @@ -function YEvent(type, target, trigger, data){ - this.data = data || {}; - // Y.extend(this, this.data); - - this.type = type; - this.target = target || trigger; - this.trigger = trigger || target; -} - -function YEmitter(target, parent){ - this.queues = {}; - this.target = target; // || this; - - if (parent) { - parent = parent.__emitter__ || parent; - if (parent !== target) this.parent = parent; - } - - Y.bindAll(this); - - // target.addEventListener = addEventListener; - // target.removeEventListener = removeEventListener; - // target.fire = fire; - // target.dispatchEvent = dispatchEvent; - -} - -YEmitter.prototype.getQueue = -function(evt){ - if ( !this.queues[evt] ) - this.queues[evt] = []; - return this.queues[evt]; -}; - -YEmitter.prototype.addEventListener = -function(evt, fn){ - this.getQueue(evt).push(fn); - return this; -}; - -YEmitter.prototype.removeEventListener = -function(evt, fn){ - var q = this.getQueue(evt) - , idx = q.indexOf(fn); - if (idx !== -1) q.splice(idx, 1); - return this; -}; - -YEmitter.prototype.fire = -function(type, instance, data){ - instance = instance || this; - var evt = new YEvent(type, data, instance, target); - this.dispatchEvent(evt); - return evt; -}; - -// XXX: does not handle degenerate or recursive event dispatch -YEmitter.prototype.dispatchEvent = -function(evt){ - for (var i=0, q=this.getQueue(evt.type), L=q.length; i 1) - Y(arguments).forEach(this.remove, this); + slice.call(arguments).forEach(this.remove, this); else { var idx = this.indexOf(v); if ( idx != -1 ) @@ -62,12 +62,20 @@ Y.YCollection.subclass('YArray', function(YArray){ // }); }; + // this.zip = + // function zip(){ + // var sequences = new YArray(slice.call(arguments)).map( Y.limit(1) ).unshift(this); + // return this.map(function(_, k){ + // return sequences.invoke('attr', k); + // }).invoke('end'); + // }; + // Like map, but produces a dict // Y(["Foo?", "R&D"]).generate(encodeURIComponent) // -> Y({"Foo?":"Foo%3F", "R&D":"R%26D"}) this.generate = function generate(fn, acc){ - var args = Y(arguments), + var args = slice.call(arguments), fn = args.shift(), acc = args.shift(); return this.reduce(function(acc, v, i){ @@ -84,7 +92,7 @@ Y.YCollection.subclass('YArray', function(YArray){ this.clone = function clone(){ - return Y(this._o.slice(0)); + return new YArray(this._o.slice(0)); }; this.size = diff --git a/src/Y/types/collection.cjs b/src/Y/types/collection.cjs index 669f942..56eb0b9 100644 --- a/src/Y/types/collection.cjs +++ b/src/Y/types/collection.cjs @@ -1,25 +1,29 @@ /** YCollection is the core of Y. */ -var Y = require('Y').Y +var YBase = require('Y/class').YBase , isFunction = require('Y/type').isFunction -, extend = require('Y/core').extend -, bool = require('Y/op').bool +, core = require('Y/core') +, op = require('Y/op') +, slice = core.slice +, extend = core.extend +, attr = core.attr +, bool = op.bool ; function extendY(){ - extend.apply(this, [this._o].concat(Y(arguments))); + extend.apply(this, [this._o].concat(slice.call(arguments))); return this; } exports['YCollection'] = -Y.YBase.subclass('YCollection', { +YBase.subclass('YCollection', { 'init' : function(o){ this._o = o || {}; }, 'attr' : function attr(k, v, def){ - var r = Y.attr(this._o, k, v, def); + var r = attr(this._o, k, v, def); if (r === this._o) return this; else @@ -73,19 +77,35 @@ Y.YBase.subclass('YCollection', { }, 'clone' : function clone(){ - return Y({}).extend(this); + return new this.constructor().extend(this); }, + // 'remove' : function remove(v){ + // var o = this._o + // , toRemove = new Y(arguments); + // + // for (var k in o) { + // var v = o[k]; + // if ( toRemove.has(v) ) { + // delete o[k]; + // toRemove.remove(v); + // } + // } + // return this; + // }, + 'remove' : function remove(v){ var o = this._o - , toRemove = new Y(arguments); + , toRemove = slice.call(arguments); for (var k in o) { - var v = o[k]; - if ( toRemove.has(v) ) { + var v = o[k], idx = toRemove.indexOf(v); + if ( idx !== -1 ) { delete o[k]; - toRemove.remove(v); + toRemove.splice(idx,1); } + if (!toRemove.length) + break; } return this; }, @@ -105,10 +125,11 @@ Y.YBase.subclass('YCollection', { }, 'zip' : function zip(){ - var sequences = new Y(arguments).map( Y.limit(1) ).unshift(this); + var sequences = slice.call(arguments); + sequences.unshift(this); return this.map(function(_, k){ - return sequences.invoke('attr', k); - }).invoke('end'); + return sequences.map(op.curried.kget(k)); + }); }, 'pluck' : function pluck(key){ @@ -118,9 +139,8 @@ Y.YBase.subclass('YCollection', { }, 'invoke' : function invoke(name){ - var args = Y(arguments), - name = args.shift(); - return this.map(function(o){ + var args = slice.call(arguments,1); + return core.map(this, function(o){ return o && o[name].apply(o, args); }); }, diff --git a/src/Y/types/function.cjs b/src/Y/types/function.cjs index f354cb3..88e81ba 100644 --- a/src/Y/types/function.cjs +++ b/src/Y/types/function.cjs @@ -2,14 +2,11 @@ var undefined , WRAPS = "__wraps__" , globals = (function(){ return this; })() -, Y = require('Y').Y -, core = require('Y/core') -, type = require('Y/type') +, core = require('Y/core') +, type = require('Y/type') -, isNumber = type.isNumber , isFunction = type.isFunction -, isArray = type.isArray -, isPlainObject = type.isPlainObject +, slice = core.slice , _ = exports._ = YFunction._ = {} , YFP = YFunction.prototype @@ -48,17 +45,17 @@ function unwrap(fn){ function curry(fn){ if (fn.__curried__) - return fn.apply(this, Y(arguments,1)); + return fn.apply(this, slice.call(arguments,1)); fn = Function.toFunction(fn); - var args = Y(arguments, 1) + var args = slice.call(arguments, 1) , L = unwrap(fn).length; if ( args.length >= L ) return fn.apply(this, args); function curried(){ - var _args = args.concat( Y(arguments) ); + var _args = args.concat( slice.call(arguments) ); if ( _args.length >= L ) return fn.apply(this, _args); else @@ -80,7 +77,7 @@ function methodize(fn) { m = fn.__methodized__ = function methodized(){ - return fn.apply(this, [this].concat( Y(arguments) )); + return fn.apply(this, [this].concat( slice.call(arguments) )); }; m[WRAPS] = fn; return m; @@ -95,7 +92,7 @@ function genericize( fn ) { g = fn.__genericized__ = function genericized(){ - var args = Y(arguments); + var args = slice.call(arguments); return fn.apply(args.shift(), args); }; g[WRAPS] = fn; @@ -106,20 +103,20 @@ function genericize( fn ) { function _composer(x,fn){ return fn.call(this, x); } function compose(f,g){ - var fns = Y(arguments).map(Function.toFunction); + var fns = slice.call(arguments).map(Function.toFunction); return function(){ - return fns.reduce(_composer, Y(arguments), this); + return fns.reduce(_composer, slice.call(arguments), this); }; } function chain(f,g){ - var fns = Y(arguments).map(Function.toFunction); + var fns = slice.call(arguments).map(Function.toFunction); if ( g.__sequence__ ) fns = g.__sequence__.concat( fns.slice(1) ); function chained(){ - var args = Y(arguments) + var args = slice.call(arguments) , i = fns.length; while (i-- > 0) args = fns[i].call(this, x); return args; @@ -131,10 +128,10 @@ function chain(f,g){ // YFunction.prototype.lazy = methodize(lazy); // function lazy(fn){ -// var args = Y(arguments, 1) +// var args = slice.call(arguments, 1) // , L = unwrap(fn).length // , lazied = function(){ -// var _args = Y(arguments) +// var _args = slice.call(arguments) // , f = _args[0]; // // if ( isFunction(f) ) { @@ -158,7 +155,7 @@ function chain(f,g){ var _bind = _Function.prototype.bind; function bind(fn, context, args){ - var bound = _bind.apply(fn, Y(arguments,1)); + var bound = _bind.apply(fn, slice.call(arguments,1)); bound[WRAPS] = fn; return YFunction(bound); } @@ -166,10 +163,10 @@ function bind(fn, context, args){ // Remembers arguments but obeys current context function partial(fn){ - var args = Y(arguments,1) + var args = slice.call(arguments,1) , partially = function partially(){ - return fn.apply( this, args.concat(Y(arguments)) ); + return fn.apply( this, args.concat(slice.call(arguments)) ); }; partially[WRAPS] = fn; return YFunction(partially); @@ -192,7 +189,7 @@ function memoize(fn){ fn.__memoized__ = function memorizer(){ // toJSON would be a better alternative, but that won't work cross-browser - var key = Y(arguments).join('\0\0\0') + var key = slice.call(arguments).join('\0\0\0') , cache = arguments.callee.cache; if ( !(key in cache) ) cache[key] = fn.apply(this, arguments); @@ -215,9 +212,12 @@ var _ofArityWrapper = YFunction._ofArityWrapper = memoize(function(n, limit){ + var i = n, args = []; + while (i-- > 0) args.unshift('$'+i); + // range(n).map( op.add('$') ).join(',') return eval('(function '+(limit ? 'limited' : 'artized')+'(fn){ '+ - 'return function('+Y.range(n).map( Y.op.add('$') ).join(',')+'){ '+ - 'return fn.apply(this,' + (limit ? 'Y(arguments).slice(0,'+n+')' : 'arguments')+ + 'return function('+args.join(',')+'){ '+ + 'return fn.apply(this,' + (limit ? 'slice.call(arguments, 0,'+n+')' : 'arguments')+ '); }; })'); }); @@ -265,9 +265,9 @@ function getName( fn ){ return fn.className || fn.name || (fn+'').match( /function\s*([^\(]*)\(/ )[1] || ''; } -function splat(fn, x){ - return fn[ isArray(x) ? "apply" : "call"](this, x); -} +// function splat(fn, x){ +// return fn[ isArray(x) ? "apply" : "call"](this, x); +// } @@ -284,7 +284,6 @@ exports['memoize'] = memoize; exports['aritize'] = aritize; exports['limit'] = limit; exports['getName'] = getName; -// exports['splat'] = splat; // Methodize and then attach to YFunction's prototype YFP.extend(core.map(exports, methodize)); diff --git a/src/Y/types/number.cjs b/src/Y/types/number.cjs index 875a641..6fb9bb5 100644 --- a/src/Y/types/number.cjs +++ b/src/Y/types/number.cjs @@ -1,4 +1,4 @@ -var Y = require('Y').Y +var YCollection = require('Y/types/collection').YCollection , op = require('Y/op') ; @@ -38,7 +38,7 @@ function range(start, end, step){ exports['YNumber'] = -Y.YCollection.subclass('YNumber', { +YCollection.subclass('YNumber', { init: function(o){ this._o = o || 0; }, diff --git a/src/Y/types/object.cjs b/src/Y/types/object.cjs index 3aeec15..07db9d7 100644 --- a/src/Y/types/object.cjs +++ b/src/Y/types/object.cjs @@ -1,10 +1,11 @@ -var Y = require('Y').Y -, isArray = Y.isArray +var YCollection = require('Y/types/collection').YCollection +, type = require('Y/type') +, isArray = type.isArray ; exports['YObject'] = -Y.YCollection.subclass('YObject', { +YCollection.subclass('YObject', { 'init': function initYObject(o){ this._o = o || {}; diff --git a/src/Y/types/string.cjs b/src/Y/types/string.cjs index cff211f..f8f608f 100644 --- a/src/Y/types/string.cjs +++ b/src/Y/types/string.cjs @@ -1,18 +1,17 @@ -var Y = require('Y').Y -, mixinNames = require('Y/class').mixinNames -, op = require('Y/op') +var YCollection = require('Y/types/collection').YCollection +, mixin = require('Y/utils').mixin +, op = require('Y/op') +, core = require('Y/core') +, slice = core.slice ; exports['YString'] = -Y.YCollection.subclass('YString', function(YString){ +YCollection.subclass('YString', function(YString){ - mixinNames(YString, String, [ - 'slice', 'split', - 'substr', 'substring', - 'concat', 'replace', - 'toLowerCase', 'toUpperCase' - ], true, true); - mixinNames(YString, String, [ 'indexOf', 'lastIndexOf', 'charAt', 'charCodeAt' ], true, false); + mixin(YString, { donor:String, wrap:YString, + names:'slice split substr substring concat replace toLowerCase toUpperCase'.split(' ') }); + mixin(YString, { donor:String, + names:'indexOf lastIndexOf charAt charCodeAt'.split(' ') }); function trim(val){ var s = this._o+''; @@ -46,7 +45,7 @@ Y.YCollection.subclass('YString', function(YString){ howMany = Number(howMany > 0 ? howMany : 0); var prefix = s.slice(0, idx) - , insert = Y(arguments, 2).join('') + , insert = slice.call(arguments, 2).join('') , suffix = s.slice(idx+howMany) ; this._o = prefix + insert + suffix; @@ -60,18 +59,9 @@ Y.YCollection.subclass('YString', function(YString){ * Negative values for start and/or end are allowed. */ mutate : function mutate(start, end){ - return this.splice.apply(this, [start, end-start].concat(Y(arguments, 2)) ); - // var s = this._o, len = s.length ; - // - // if (start < 0) start += len; - // if (end < 0) end += len; - // - // var prefix = s.slice(0, start) - // , insert = Y(arguments, 2).join('') - // , suffix = s.slice(end) - // ; - // this._o = prefix + insert + suffix; - // return this; + // var args = slice.call(arguments); + arguments[1] = end-start; + return this.splice.apply(this, arguments); }, strip: function strip(){ @@ -116,9 +106,9 @@ Y.YCollection.subclass('YString', function(YString){ this.set = function set(key, value, def){ var s = this._o, _val = (value !== undefined ? value : def); - if ( Y.isNumber(key) ) + if ( isNumber(key) ) return this.splice(key, 1, _val); - if ( Y.isArray(key) ) + if ( isArray(key) ) return this.splice.apply(this, key.slice(0,2).concat([_val]) ); // TODO: Should cache the new properties and re-applied them whenever we mutate _o, @@ -131,19 +121,19 @@ Y.YCollection.subclass('YString', function(YString){ var s = this._o; // set if ( value !== undefined || def !== undefined ) - return Y.attr(this, key, value, def); + return core.dattr(this, key, value, def); // get else { - if ( Y.isNumber(key) ) + if ( isNumber(key) ) return s.charAt(key); - if ( Y.isArray(key) ) + if ( isArray(key) ) return s.slice.apply(s, key); return s[key]; } }; - this.attr.__wraps__ = Y.attr; + this.attr.__wraps__ = core.dattr; this.reduce = function reduce(fn, acc){ fn = Function.toFunction(fn); diff --git a/src/Y/utils.cjs b/src/Y/utils.cjs new file mode 100644 index 0000000..9886733 --- /dev/null +++ b/src/Y/utils.cjs @@ -0,0 +1,68 @@ +var YFunction = require('Y/types/function').YFunction +, isFunction = require('Y/type').isFunction +, core = require('Y/core') +, op = require('Y/op') +; + + +function bindName(k){ + var v = this[k]; + if ( isFunction(v) ) + this[k] = YFunction(v).bind(this); +} + +function bindAll(o, names){ + if (!o) + return o; + if (arguments.length === 1) + names = core.map(o, op.nth(1)); + else + names = slice.call(arguments, 1); + names.forEach(bindName, o); + return o; +} + + +var defaults = { + 'donor' : null, + 'names' : null, + 'override' : true, + 'wrap' : false, + 'chain' : false +}; +function mixin(target, options){ + var opt = op.extend({}, defaults, options), Donor = opt.donor + , target = ( isFunction(target) ? target.prototype : target ) + , proto = ( isFunction(Donor) ? Donor.prototype : Donor ) + , names = opt.names + ; + + // Need a closure to capture the name + names.forEach(function(name){ + var fn = proto[name]; + if ( isFunction(fn) && (opt.override || !(name in target)) ) + target[name] = function(){ + var r = fn.apply(this._o || target, arguments); + return (opt.chain ? this : (opt.wrap ? opt.wrap(r) : r)); + }; + }); + + return o; +} + + +// function chainDelegates(type, names){ +// var proto = type.prototype; +// names.forEach(function(name){ +// proto[name] = function(){ +// var o = this._o || this; +// o[name].apply(o, arguments); +// return this; +// }; +// }); +// return type; +// } +// exports['chainDelegates'] = chainDelegates; + +exports['bindAll'] = bindAll; +exports['mixin'] = mixin; diff --git a/src/Y/y.cjs b/src/Y/y.cjs index 3241611..a9018a8 100644 --- a/src/Y/y.cjs +++ b/src/Y/y.cjs @@ -1,3 +1,4 @@ +// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- var undefined , globals = (function(){ return this; })() , _Object = globals.Object diff --git a/src/evt/evt.class.js b/src/evt/evt.class.js index 07422ef..dfc3701 100644 --- a/src/evt/evt.class.js +++ b/src/evt/evt.class.js @@ -1,4 +1,3 @@ -(function(undefined){ // Inspired by John Resig's "Simple Class Inheritence" -- http://ejohn.org/blog/simple-javascript-inheritance/ /* Metaprogramming API @@ -24,11 +23,11 @@ Class Methods Note: Metaprogramming events cannot depend on Class. */ +var Y = require('Y').Y +, Emitter = require('Y/modules/y.event').Emitter - -var Evt = this.Evt = (this.Evt || {}) -, KNOWN_CLASSES = Class.KNOWN_CLASSES = {} , isFunction = Y.isFunction +, KNOWN_CLASSES = Class.KNOWN_CLASSES = exports['KNOWN_CLASSES'] = {} , getProto = Object.getPrototypeOf , hasOwn = Object.prototype.hasOwnProperty , objToString = Object.prototype.toString @@ -38,13 +37,13 @@ var Evt = this.Evt = (this.Evt || {}) // Private delegating constructor -- must be defined for every // new class to prevent shared state. All construction is // actually done in the init method. -Evt.ConstructorTemplate = ConstructorTemplate; +exports['ConstructorTemplate'] = ConstructorTemplate; function ConstructorTemplate() { var cls = arguments.callee , instance = this; // Not subclassing - if ( cls.caller !== Evt.Class.fabricate ) { + if ( cls.caller !== Class.fabricate ) { cls.fire('create', instance, { 'instance' : instance, @@ -154,7 +153,7 @@ function Class(className, Parent, members){ , init = NewClass.prototype.init ; - instance.__emitter__ = new Y.event.Emitter(instance, NewClass); + instance.__emitter__ = new Emitter(instance, NewClass); if (init) { var result = init.apply(instance, arguments); @@ -172,7 +171,7 @@ function Class(className, Parent, members){ // Add class emitter var ParentEmitter = (Parent.__emitter__ ? Parent : ClassFactory) - , ClassEmitter = NewClass.__emitter__ = new Y.event.Emitter(NewClass, ParentEmitter) + , ClassEmitter = NewClass.__emitter__ = new Emitter(NewClass, ParentEmitter) ; // Either invoke body constructor... @@ -204,7 +203,7 @@ function Class(className, Parent, members){ // Decorate with emitter methods and // add metaprogramming data to Class object Class.__super__ = Object; -Class.__emitter__ = new Y.event.Emitter(Class); +Class.__emitter__ = new Emitter(Class); Class.fn = Class.prototype; Class.fn.__class__ = Class; Class.className = Class.fn.className = "Class"; @@ -242,9 +241,6 @@ Class.fn.subclass = // Expose -Evt.Class = -Evt.subclass = Class; - - +exports['Class'] = +exports['subclass'] = Class; -})(); \ No newline at end of file diff --git a/src/ezl/index.cjs b/src/ezl/index.cjs new file mode 100644 index 0000000..955dc17 --- /dev/null +++ b/src/ezl/index.cjs @@ -0,0 +1,5 @@ +// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- +//@require('jquery') +//@require('lessly/future') +//@require('Y') + diff --git a/src/ezl/layer.cjs b/src/ezl/layer.cjs new file mode 100644 index 0000000..063a740 --- /dev/null +++ b/src/ezl/layer.cjs @@ -0,0 +1,519 @@ +//@require('jquery') + +var undefined +, Y = require('Y').Y +, Vec = require('ezl/math/vec').Vec +, loc = require('ezl/loc') +, Loc = loc.Loc +, BoundingBox = loc.BoundingBox +, +CONTEXT_ATTRS = Y([ + 'globalAlpha', 'globalCompositeOperation', + 'strokeStyle', 'fillStyle', + 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit', + 'shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor' +]) +, + +Layer = +exports['Layer'] = +Y.subclass('Layer', { + _cssClasses : 'ezl 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(){ + $('