UNPKG

dreemgl

Version:

DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes

1,057 lines (934 loc) 29.5 kB
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others. Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ define.class(function(require){ // Node class provides attributes for events and values, propertybinding and constructor semantics var OneJSParser = require('$system/parse/onejsparser') var WiredWalker = require('$system/parse/wiredwalker') var RpcProxy = require('$system/rpc/rpcproxy') var ASTScanner = require('$system/parse/astscanner') // parser and walker for wired attributes var onejsparser = new OneJSParser() onejsparser.parser_cache = {} var wiredwalker = new WiredWalker() // the RPCProxy class reads these booleans to skip RPC interface creation for this prototype level this.rpcproxy = false // internal, called by the constructor this._atConstructor = function(){ // store the args for future reference //var args = this.constructor_args = Array.prototype.slice.call(arguments) this.children = this.constructor_children = [] this.initFromConstructorArgs(arguments) } this.setInterval = function(fn, mstime){ if(!this.interval_ids) this.interval_ids = [] var platform = typeof window !== 'undefined' ? window : global; var id = platform.setInterval(function(){ this.interval_ids.splice(this.interval_ids.indexOf(id), 1) fn.call(this) }.bind(this), mstime) this.interval_ids.push(id) return id } this.clearInterval = function(id){ if (id === undefined) return var idx = this.interval_ids.indexOf(id) if(idx !== -1){ this.interval_ids.splice(idx, 1) var platform = typeof window !== 'undefined' ? window : global; platform.clearInterval(id) } } this.setTimeout = function(fn, mstime){ if(!this.timeout_ids) this.timeout_ids = [] var platform = typeof window !== 'undefined' ? window : global; var id = platform.setTimeout(function(){ this.timeout_ids.splice(this.timeout_ids.indexOf(id), 1) fn.call(this) }.bind(this), mstime) this.timeout_ids.push(id) return id } this.clearTimeout = function(id){ if (id === undefined) return var idx = this.timeout_ids.indexOf(id) if(idx !== -1){ this.timeout_ids.splice(idx, 1) var platform = typeof window !== 'undefined' ? window : global; platform.clearInterval(id) } } this.requestAnimationFrame = function(fn){ if(!this.raf_ids) this.raf_ids = [] if (typeof window !== 'undefined') { var id = window.requestAnimationFrame(function(){ this.raf_ids.splice(this.raf_ids.indexOf(id), 1) fn.call(this) }.bind(this)) this.raf_ids.push(id) return id } else { return this.setTimeout(fn, 1000 / 60) } } this.cancelAnimationFrame = function(id){ if (id === undefined) return var idx = this.raf_ids.indexOf(id) if(idx !== -1){ this.raf_ids.splice(idx, 1) if (typeof window !== 'undefined') { window.cancelAnimationFrame(id) } else { this.clearTimeout(id) } } } // internal, called by the constructor this.initFromConstructorArgs = function(args){ // var off = 0 for(var i = 0; i < args.length; i++){ var arg = args[i] if(typeof arg === 'object' && Object.getPrototypeOf(arg) === Object.prototype){ this.attributes = arg //this.initFromConstructorProps(arg) continue } if(typeof arg === 'function'){ var prop = {}; prop[arg.name] = arg this.attributes = prop //this.initFromConstructorProps(prop) continue } if(typeof arg === 'string' && i === 0){ // off = 1 this.name = arg continue } if(Array.isArray(arg)){ this.initFromConstructorArgs(arg) } else if(arg !== undefined && typeof arg === 'object'){ arg.__constructorIndex = i; this.constructor_children.push(arg) // var name = arg.name //if(name !== undefined && !(name in this)) this[name] = arg } } } /* // internal, called by the constructor this.initFromConstructorProps = function(obj){ for(var key in obj){ var prop = obj[key] var tgt = this var type = 0 if(!this.constructor_props) this.constructor_props = {} this.constructor_props[key] = prop var idx = key.indexOf('.') if(idx !== -1){ tgt = this[key.slice(0,idx)] key = key.slice(idx + 1) } tgt[key] = prop } }*/ // Mixes in another class or object, just pass in any number of object or class references. They are copied on key by key this.mixin = function(){ for(var i = 0; i < arguments.length; i++){ var obj = arguments[i] if(typeof obj === 'function') obj = obj.prototype for(var key in obj){ // copy over getters and setters if(obj.__lookupGetter__(key) || obj.__lookupSetter__(key)){ // ignore it } else{ // other this[key] = obj[key] } } } } // internal, used by find and findChild this._findChild = function(name, ignore){ if(this === ignore) return if(this.name === name){ return this } if(this.children) { for(var i = 0; i < this.children.length; i ++){ var child = this.children[i] if(child === ignore) continue var ret = child._findChild(name, ignore) if(ret !== undefined){ return ret } } } } // Finds a child node by name. this.findChild = function(name){ if(!this.find_cache) this.find_cache = {} var child = this.find_cache[name] if(child && !child.destroyed) return child child = this.find_cache[name] = this._findChild(name) return child } // Finds a parent node by name. this.find = function(name){ var child = this.findChild(name) var node = this while(child === undefined && node.parent){ child = node.parent._findChild(name, node) node = node.parent } this.find_cache[name] = child return child } // internal, hide a property, pass in any set of strings this.hideProperty = function(){ for(var i = 0; i<arguments.length; i++){ var arg = arguments[i] if(Array.isArray(arg)){ for(var j = 0; j<arg.length; j++){ Object.defineProperty(this, arg[j],{enumerable:false, configurable:true, writeable:true}) } } else{ Object.defineProperty(this, arg,{enumerable:false, configurable:true, writeable:true}) } } } // internal, check if property is an attribute this.isAttribute = function(key){ var setter = this.__lookupSetter__(key) if(setter !== undefined && setter.isAttribute) return true else return false } // internal, returns the attribute config object (the one passed into this.attributes={attr:{config}} this.getAttributeConfig = function(key){ return this._attributes[key] } // internal, check if an attribute has wires connected this.hasWires = function(key){ var wiredfn_key = '_wiredfn_' + key return wiredfn_key in this } // internal, returns the wired-call for an attribute this.wiredCall = function(key){ var wiredcl_key = '_wiredcl_' + key return this[wiredcl_key] } // internal, emits an event recursively on all children this.emitRecursive = function(key, event, block){ if(block && block.indexOf(child)!== -1) return this.emit(key, event); for(var a in this.children){ var child = this.children[a] child.emitRecursive(key, event) } } this.emit_block_set = null this.emit = function(key, ievent){ // lets do a fastpass var event = ievent || {} var fast_key = '_fast_' + key // FAST OUT var callfn = this[fast_key] if(callfn && this['on'+key] === callfn){ // lets see if we have an 'on' key defined callfn.call(this, event, event.value, this, key) return } var lock_key = '_lock_' + key if(this[lock_key] || this.emit_block_set && this.emit_block_set.indexOf(key) !== -1) return this[lock_key] = true var counter = 0 // try{ var on_key = 'on' + key var listen_key = '_listen_' + key if(!this.__lookupSetter__(key)){ var fn = this[key] if(typeof fn === 'function'){ fn.call(this, event) } return } var proto = this // called after the onclicks, in reverse order (parent on up) var finals while(on_key in proto || listen_key in proto){ if(proto.hasOwnProperty(on_key)){ callfn = proto[on_key] callfn.call(this, event, event.value, this, key) if(event.stop) return if(event.final) finals = finals || [], finals.push(event.final) counter++ } if(proto.hasOwnProperty(listen_key)){ var listeners = proto[listen_key] for(var j = listeners.length - 1; j >= 0; j--){ listeners[j].call(this, event, event.value, this, key) if(event.stop) return if(event.final) finals = finals || [], finals.push(event.final) counter = -1 } } proto = Object.getPrototypeOf(proto) } if(finals) for(var i = finals.length - 1; i >=0; i--){ finals[i].call(this, event, event.value, this, key) if(event.stop) return } // } finally{ this[lock_key] = false if(counter === 1){ this[fast_key] = callfn } else if(counter === 0){ this[fast_key] = null } // } } // add a listener to an attribute this.addListener = function(key, cb){ if(!this.__lookupSetter__(key)){ this.defineAttribute(key, this[key], true) } var listen_key = '_listen_' + key var fast_key = '_fast_' + key this[fast_key] = undefined // invalidate fast cache var array if(!this.hasOwnProperty(listen_key)) array = this[listen_key] = [] else array = this[listen_key] if(array.indexOf(cb) === -1){ array.push(cb) } } // remove a listener from an attribute, uses the actual function reference to find it // if you dont pass in a function reference it removes all listeners this.removeListener = function(key, cb){ var listen_key = '_listen_' + key if(!this.hasOwnProperty(listen_key)) return var cbs = this[listen_key] if(cbs){ if(cb){ var idx = cbs.indexOf(cb) if(idx !== -1) cbs.splice(idx,1) } else{ cbs.length = 0 } } } // internal, check if an attribute has a listener with a .name property set to fnname this.hasListenerProp = function(key, prop, value){ var listen_key = '_listen_' + key if(!this.hasOwnProperty(listen_key)) return false var listeners = this[listen_key] if(!listeners) return false for(var i = 0; i < listeners.length; i++){ if(listeners[i][prop] === value) return true } return false } // internal, returns true if attribute has any listeners this.hasListeners = function(key){ var listen_key = '_listen_' + key var on_key = 'on' + key if(on_key in this || listen_key in this && this[listen_key].length) return true return false } // internal, remove all listeners from a node this.removeAllListeners = function(){ var keys = Object.keys(this) for(var i = 0; i < keys.length; i++){ var key = keys[i] if(key.indexOf('_listen_') === 0){ this[key] = undefined } } } // internal, set the wired function for an attribute this.setWiredAttribute = function(key, value){ if(!this.hasOwnProperty('_wiredfns')) this._wiredfns = this._wiredfns?Object.create(this._wiredfns):{} this._wiredfns[key] = value this['_wiredfn_'+key] = value } // internal, mark an attribute as persistent accross live reload / renders this.definePersist = function(arg){ if (!this.hasOwnProperty("_persists")){ if (this._persists){ this._persists = Object.create(this._persists) } else{ this._persists = {} } } this._persists[arg] = 1 } Object.defineProperty(this, 'style', { get:function(){ return this._style }, set:function(v){ if(!this.hasOwnProperty('_style')) this._style = Object.create(this._style) if(typeof v === 'object'){ for(var key in v){ var value = v[key] if(typeof value === 'object'){ var base = this._style[key] if(!base) this._style[key] = value else{ var obj = this._style[key] = Object.create(base) for(var subkey in value){ obj[subkey] = value[subkey] } } } else{ this._style[key] = v[key] } } } else if(typeof v === 'function'){ v.call(this._style) } } }) var Style = define.class(function(){ this.composeStyle = function(){ // lets find the highest matching level for(var i = arguments.length - 1; i >= 0; i--){ var match = arguments[i] if(!match) continue var style = this[match] if(style){ if(i === 0) return style if(style._composed) return style style = {} // lets compose a style from the match stack for(var j = 0; j <= i; j++){ var level = this[arguments[j]] if(level){ for(var key in level) style[key] = level[key] } } Object.defineProperty(style, '_composed', {value:1}) Object.defineProperty(style, '_match', {value:match}) // lets store it back this[match] = style return style } } } this.lookup = function(name, props){ // lets return a matching style return this.composeStyle( '$', '$_' + props.class, name, name + '_' + props.class, name + '_' + props.name ) } }) this._style = new Style() this.atStyleConstructor = function(original, props, where){ // lets see if we have it in _styles var name = original.name var propobj = props && Object.getPrototypeOf(props) === Object.prototype? props: {} // we need to flush this cache on livereload //var cacheid = name + '_' + propobj.class + '_' + propobj.name //var cache = this._style._cache || (this._style._cache = {}) //var found = cache[cacheid] //if(found) return found var style = this._style.lookup(name, propobj) // find the base class var base = original if(this.constructor.outer) base = this.constructor.outer.atStyleConstructor(original, propobj, 'outer') else if(this !== this.composition && this !== this.screen && this.screen){ base = this.screen.atStyleConstructor(original, propobj, 'screen') } else if(this.composition !== this && this.composition) base = this.composition.atStyleConstructor(original, propobj, 'composition') // 'quick' out var found = style && style._base && style._base[name] === base && style._class && style._class[name] if(found){ return /*cache[cacheid] =*/ found } if(!style) return /*cache[cacheid] =*/ base if(!style._class){ Object.defineProperty(style, '_class', {value:{}, configurable:true}) Object.defineProperty(style, '_base', {value:{}, configurable:true}) } // (re)define the class if(style._base[name] !== base || !style._class[name]){ var clsname = base.name + '_' +(where?where+'_':'')+ (style._match||'star') var cls = style._class[name] = base.extend(style, original.outer, clsname) style._base[name] = base return /*cache[cacheid] =*/ cls } return /*cache[cacheid] =*/ original } // pass an object such as {attrname:{type:vec2, value:0}, attrname:vec2(0,1)} to define attributes on an object Object.defineProperty(this, 'attributes', { get:function(){ throw new Error("attribute can only be assigned to") }, set:function(arg){ for(var key in arg){ this.defineAttribute(key, arg[key]) } } }) // define listeners {attrname:function(){}} Object.defineProperty(this, 'listeners', { get:function(){ throw new Error("listeners can only be assigned to") }, set:function(arg){ for(var key in arg){ this.addListener(key, arg[key]) } } }) /* // define setters {attrname:function(){}} Object.defineProperty(this, 'setters', { get:function(){ throw new Error("setter can only be assigned to") }, set:function(arg){ for(var key in arg){ this['_set_'+key] = arg[key] } } }) // define getters {attrname:function(){}} Object.defineProperty(this, 'getters', { get:function(){ throw new Error("getter can only be assigned to") }, set:function(arg){ for(var key in arg){ this['_get_'+key] = arg[key] } } }) // start animation by assigning keyframes to an attribute {attrname:{1:'red', 2:'green', 3:'blue'}} Object.defineProperty(this, 'animate', { get:function(){ return this.animateAttribute }, set:function(arg){ this.animateAttribute(arg) } })*/ // internal, animate an attribute with an animation object see animate this.animateAttribute = function(arg){ // count var arr = [] for(var key in arg){ var value = arg[key] if(typeof value === 'object'){ var resolve, reject var promise = new Promise(function(res, rej){ resolve = res, reject = rej }) promise.resolve = resolve promise.reject = reject arr.push(promise) this.startAnimation(key, undefined, value, promise) } else{ if(typeof value === 'string'){ value = value.toLowerCase() if(value === 'stop'){ this.stopAnimation(key) } else if(value === 'play'){ this.playAnimation(key) } else if(value === 'pause'){ this.pauseAnimation(key) } } resolve() } } if(arr.length <= 1) return arr[0] return Promise.all(arr) } // internal, define an attribute, use the attributes = api this.defineAttribute = function(key, config, always_define){ // lets create an attribute var is_config = config instanceof Config var is_attribute = !always_define && key in this // use normal value assign var islistener = false if(key[0] === 'o' && key[1] === 'n'){ if(this.__lookupSetter__(key.slice(2))) islistener = true } if(!always_define && (is_attribute && !is_config || islistener || typeof config === 'function' && !config.is_wired)){//|| !is_attribute && typeof config === 'function' && !config.is_wired){ this[key] = config return } // autoprocess the config if(is_config){ config = config.config } else{ // its a value config = {value: config} } // figure out the type if(!is_attribute && !config.type){ var value = config.value if(typeof value === 'object'){ if(value && typeof value.struct === 'function') config.type = value.struct else if(Array.isArray(value)) config.type = Array else config.type = Object } else if(typeof value === 'number'){ config.type = float } else if(typeof value === 'boolean'){ config.type = boolean } else if(typeof value === 'function'){ if(!value.is_wired){ config.type = Function } else{ // an undefined wire is automatically a number config.value = 0 config.type = Number } } else if(typeof value === 'string'){ config.type = String } } if(config.persist){ if(config.alias) throw new Error('Cannot define a persist property '+key+' with alias, use the alias attribute '+config.alias) this.definePersist(key) } if(!this.hasOwnProperty('_attributes')){ this._attributes = this._attributes?Object.create(this._attributes):{} } if(is_attribute){ // extend the config //if('type' in config) throw new Error('Cannot redefine type of attribute '+key) var newconfig = Object.create(this._attributes[key]) for(var prop in config){ newconfig[prop] = config[prop] } this._attributes[key] = newconfig if('value' in config) this[key] = config.value if('listeners' in config){ var listeners = config.listeners for(var i = 0; i < listeners.length; i++){ this.addListener(key, listeners[i]) } } return } var value_key = '_' + key var on_key = 'on' + key var listen_key = '_listen_' + key var animinit_key = '_animinit_' + key //var config_key = '_config_' + key var get_key = '_get_' + key var set_key = '_set_' + key if(!config.group) config.group = this.constructor.name if(config.animinit) this[animinit_key] = 0 var init_value = key in this? this[key]:config.value if(init_value !== undefined){ var type = config.type if(typeof init_value === 'function'){ if(init_value.is_wired) this.setWiredAttribute(key, init_value) else if(type !== Function){ //this.addListener(on_key, init_value) this[on_key] = init_value } else this[value_key] = init_value } else{ if(type && type !== Object && type !== Array && type !== Function){ this[value_key] = type(init_value) } else{ this[value_key] = init_value } } } this._attributes[key] = config if(config.listeners) this[listen_key] = config.listeners var setter // define attribute gettersetters // block attribute emission on objects with an environment thats (stub it) if(config.alias){ var alias_key = '_' + config.alias var aliasstore_key = '_alias_'+config.alias setter = function(value){ var mark var config = this._attributes[key] if(this[set_key] !== undefined) value = this[set_key](value) if(typeof value === 'function'){ if(value.is_wired) return this.setWiredAttribute(key, value) if(config.type !== Function){ //this.addListener(on_key, value) this['_fast_' + key] = undefined this[on_key] = value return } } if(typeof value === 'object'){ if(value instanceof Mark){ mark = value.mark value = value.value } else if(value instanceof Config){ this.defineAttribute(key, value) return } else if(value instanceof Animate){ return this.startAnimation(key, value) } } if(typeof value === 'object' && value !== null && value.atAttributeAssign){ value.atAttributeAssign(this, key) } if(!mark && config.motion){ // lets copy our value in our property this[value_key] = this[alias_key][config.index] this.startAnimation(key, value) return } var store if(!this.hasOwnProperty(alias_key)){ store = this[alias_key] store = this[alias_key] = store.struct(store) } else{ store = this[alias_key] } var old = this[value_key] this[value_key] = store[config.index] = value // emit alias this.emit(config.alias, {setter:true, via:key, key:config.alias, owner:this, value:this[alias_key], mark:mark}) if(this.atAttributeSet !== undefined) this.atAttributeSet(key, value) // emit self if(on_key in this || listen_key in this) this.emit(key, {setter:true, key:key, owner:this, old:old, value:value, mark:mark}) } // add a listener to the alias var aliasarray = this[aliasstore_key] if(!aliasarray) this[aliasstore_key] = aliasarray = [] aliasarray.push(function(value){ var old = this[value_key] var val = this[value_key] = value[config.index] if(on_key in this || listen_key in this) this.emit(key, {setter:true, key:key, owner:this, value:val, old:old}) }) // initialize value this[value_key] = this[alias_key][config.index] } else { var aliasstore_key = '_alias_'+key setter = function(value){ var mark var config = this._attributes[key] if(this[set_key] !== undefined) value = this[set_key](value) if(typeof value === 'function'){ if(value.is_wired) return this.setWiredAttribute(key, value) if(config.type !== Function){ //this.addListener(on_key, value) this['_fast_' + key] = undefined this[on_key] = value return } } if(typeof value === 'object'){ if(value instanceof Mark){ mark = value.mark value = value.value } else if(value instanceof Config){ this.defineAttribute(key, value) return } else if(value instanceof Animate){ return this.startAnimation(key, undefined, value.track) } } if(typeof value === 'object' && value !== null && value.atAttributeAssign){ value.atAttributeAssign(this, key) } var type = config.type if(type){ if(type !== Object && type !== Array && type !== Function) value = type(value) } if((!mark && (!config.animinit || this[animinit_key]++)) && config.motion && this.startAnimation(key, value)){ // store the end value return } var old = this[value_key] this[value_key] = value var aliases = this[aliasstore_key] if(aliases){ for(var i = 0; i<aliases.length;i++){ aliases[i].call(this, value) } } if(this.atAttributeSet !== undefined) this.atAttributeSet(key, value) if(on_key in this || listen_key in this) this.emit(key, {setter:true, owner:this, key:key, old:old, value:value, mark:mark}) } } setter.isAttribute = true var get if (this[get_key]) { var getter = this[get_key] get = function() { if(this.atAttributeGet) this.atAttributeGet(key) return getter() } } else { get = function() { if(this.atAttributeGet) this.atAttributeGet(key) // lets check if we need to map our stored type // if we are in motion, we should return the end value return this[value_key] } } Object.defineProperty(this, key, { configurable:true, enumerable:true, get: get, set: setter }) } // internal, connect a wired attribute up to its listeners this.connectWiredAttribute = function(key, initarray){ var wiredfn_key = '_wiredfn_' + key var wiredcl_key = '_wiredcl_' + key var wiredfn = this[wiredfn_key] var ast = onejsparser.parse(wiredfn.toString()) var state = wiredwalker.newState() wiredwalker.expand(ast, null, state) var bindcall = function(){ var deps = bindcall.deps if(deps && !bindcall.initialized){ bindcall.initialized = true for(var i = 0; i < deps.length; i++) deps[i]() } this[key] = this[wiredfn_key].call(this, this[wiredcl_key].find, this.rpc) }.bind(this) this[wiredcl_key] = bindcall bindcall.find = {} for(var j = 0; j < state.references.length; j++){ var ref = state.references[j] var obj = {'this':this,'find':bindcall.find,'rpc':this.rpc} for(var k = 0; k < ref.length; k++){ var part = ref[k] if(k === ref.length - 1){ // lets add a listener if(!obj || !obj.isAttribute || !obj.isAttribute(part)){ console.error("Attribute does not exist: "+ref.join('.') + " (at " + part + ") in wiring " + this[wiredfn_key].toString()) continue } obj.addListener(part, bindcall) if(obj.hasWires(part) && !obj.wiredCall(part)){ obj.connectWiredAttribute(part) if(!bindcall.deps) bindcall.deps = [] bindcall.deps.push(obj.wiredCall(part)) } } else{ var newobj = obj[part] if(!newobj){ if(obj === bindcall.find){ // lets make an alias on this, scan the parent chain obj = this.find(part) if(obj) bindcall.find[part] = obj /* while(obj){ if(part in obj){ if(part in this) console.log("Aliasing error with "+part) //console.log("ALIASING" + part, this) obj = this[part] = obj[part] break } obj = obj.parent }*/ } } else obj = newobj if(!obj) console.log('Cannot find part ' + part + ' in ' + ref.join('.') + ' in propertybind', this) } } } if(initarray) initarray.push(bindcall) } // internal, return a function that can be assigned as a listener to any value, and then re-emit on this as attribute key this.emitForward = function(key){ return function(value){ this.emit(key, value) }.bind(this) } // internal, connect all wires using the initarray returned by connectWiredAttribute this.connectWires = function(initarray, depth){ var immediate = false if(!initarray) { initarray = [] immediate = true } if(this._wiredfns){ for(var key in this._wiredfns){ this.connectWiredAttribute(key, initarray) } } // lets initialize bindings on all nested classes var nested = this.constructor.nested if(nested) for(var name in nested){ var nest = this[name.toLowerCase()] if(nest.connectWires){ nest.connectWires(initarray, depth) } } if(immediate === true){ for(var i = 0; i < initarray.length; i++){ initarray[i]() } } } // internal, does nothing sofar this.disconnectWires = function(){ } // internal, used by the attribute setter to start a 'motion' which is an auto-animated attribute this.startMotion = function(key, value){ if(!this.screen) return false return this.screen.startMotion(this, key, value) } // internal, create an rpc proxy this.createRpcProxy = function(parent){ return RpcProxy.createFromObject(this, parent) } // mixin setter API to easily assign mixins using an is: syntax in the constructors Object.defineProperty(this, 'is', { set:function(value){ // lets copy on value. if(Array.isArray(value)){ for(var i = 0; i<value.length; i++) this.is = value[i] return } if(typeof value === 'function') value = value.prototype if(typeof value === 'object'){ for(var key in value){ this[key] = value[key] } } } }) this.hideProperty(Object.keys(this)) // internal, always define an init and destroy this.attributes = { // the init event, not called when the object is constructed but specifically when it is being initialized by the render init:Config({type:Event}), // destroy event, called on all the objects that get dropped by the renderer on a re-render destroy:Config({type:Event}) }; })