/* Copyright © 2015-2016 David Valdman */
define(function(require, exports, module) {
var Controller = require('./Controller');
var RenderTreeNode = require('./nodes/RenderTreeNode');
var SizeNode = require('./nodes/SizeNode');
var LayoutNode = require('./nodes/LayoutNode');
/**
* A View provides encapsulation for a subtree of the render tree. You can build
* complicated visual components and add them to a render tree as you would a `Surface`.
*
* Custom `Views` are created by calling `extend` on the `View` constructor.
*
* In addition to what a `Controller` provides, a View provides:
*
* Render Tree method: `.add`
* Size methods: `setSize`, `setProportions`
* Layout methods: `setOpacity`, `setOrigin`
*
* @example
*
* var MyView = View.extend({
* defaults : {
* defaultOption1 : '',
* defaultOption2 : 42
* },
* initialize : function(options){
* // this method called on instantiation
* // options are passed in after being patched by the specified defaults
*
* var surface = new Surface({
* content : options.defaultOption1,
* size : [options.defaultOption2,100],
* properties : {background : 'red'}
* });
*
* this.add(surface);
* }
* });
*
* var myView = new myView({defaultOption1 : 'hello'});
*
* var context = Context();
* context.add(myView);
*
* context.mount(document.body);
*
* @class View
* @constructor
* @extends Core.Controller
* @uses Core.SizeNode
* @uses Core.LayoutNode
* @uses Core.SimpleStream
*/
var View = Controller.extend({
_isView : true,
defaults : {
size : null,
origin : null,
opacity : 1
},
events : {
change : setOptions
},
constructor : function View(options){
this._sizeNode = new SizeNode();
this._layoutNode = new LayoutNode();
this._node = new RenderTreeNode();
this._addNode = this._node.add(this._sizeNode).add(this._layoutNode);
this.size = this._addNode.size; // actual size
this._size = this._node.size; // incoming parent size
this._cachedSize = [0, 0];
this.size.on('start', updateSize.bind(this));
this.size.on('update', updateSize.bind(this));
this.size.on('end', updateSize.bind(this));
Controller.call(this, options);
if (this.options) setOptions.call(this, this.options);
},
/**
* Extends the render tree subtree with a new node.
*
* @method add
* @param object {SizeNode|LayoutNode|Surface} Node
* @return {RenderTreeNode}
*/
add : function add(){
return RenderTreeNode.prototype.add.apply(this._addNode, arguments);
},
/**
* Remove the View from the RenderTree. All Surfaces added to the View
* will also be removed. The View can be added back at a later time and
* all of its data and Surfaces will be restored.
*
* @method remove
*/
remove : function remove(){
RenderTreeNode.prototype.remove.apply(this._node, arguments);
},
/**
* Getter for size.
*
* @method getSize
* @return size {Number[]}
*/
getSize : function(){
return this._cachedSize;
},
/**
* Setter for size.
*
* @method setSize
* @param size {Number[]|Stream} Size as [width, height] in pixels, or a stream.
*/
setSize : function setSize(size){
this._cachedSize = size;
this._sizeNode.set({size : size});
},
/**
* Setter for proportions.
*
* @method setProportions
* @param proportions {Number[]|Stream} Proportions as [x,y], or a stream.
*/
setProportions : function setProportions(proportions){
this._sizeNode.set({proportions : proportions});
},
/**
* Setter for margins.
*
* @method setMargins
* @param margins {Number[]|Stream} Margins as [x,y], or a stream.
*/
setMargins : function setMargins(margins){
this._sizeNode.set({margins : margins});
},
/**
* Setter for aspect ratio.
*
* @method setAspectRatio
* @deprecated Use size functions instead
* @param aspectRatio {Number|Stream} Aspect ratio, or a stream.
*/
setAspectRatio: function setAspectRatio(aspectRatio) {
this._sizeNode.set({aspectRatio: aspectRatio});
},
/**
* Setter for origin.
*
* @method setOrigin
* @param origin {Number[]|Stream} Origin as [x,y], or a stream.
*/
setOrigin : function setOrigin(origin){
this._layoutNode.set({origin : origin});
},
/**
* Setter for opacity.
*
* @method setOpacity
* @param opacity {Number|Stream} Opacity
*/
setOpacity : function setOpacity(opacity){
this._layoutNode.set({opacity : opacity});
}
});
function updateSize(size){
if (this._cachedSize[0] === size[0] && this._cachedSize[1] === size[1]) return;
this._cachedSize = size;
this.emit('resize', size);
}
function setOptions(options){
for (var key in options){
var value = options[key];
switch (key){
case 'size':
this.setSize(value);
break;
case 'proportions':
this.setProportions(value);
break;
case 'margins':
this.setMargins(value);
break;
case 'aspectRatio':
this.setAspectRatio(value);
break;
case 'origin':
this.setOrigin(value);
break;
case 'opacity':
this.setOpacity(value);
break;
}
}
}
module.exports = View;
});