/* Copyright © 2015-2016 David Valdman */
define(function(require, exports, module) {
var EventHandler = require('../../events/EventHandler');
var Stream = require('../../streams/Stream');
var ResizeStream = require('../../streams/ResizeStream');
var SizeNode = require('../SizeNode');
var LayoutNode = require('../LayoutNode');
var layoutAlgebra = require('../algebras/layout');
var sizeAlgebra = require('../algebras/size');
var SIZE_KEYS = SizeNode.KEYS;
var LAYOUT_KEYS = LayoutNode.KEYS;
/**
* A node in the render tree. As such, it wraps a layout or size node,
* providing them with an `add` method. By adding nodes, the render tree
* is constructed, the leaves of which are `Surfaces`.
*
* @constructor
* @class RenderTreeNode
* @private
* @param object {Object|SizeNode|LayoutNode|Surface|View}
*/
function RenderTreeNode(object) {
// layout and size inputs
this._layout = new EventHandler();
this._size = new EventHandler();
// layout and size streams
this.size = null;
this.layout = null;
this.root = null;
if (object) _set.call(this, object);
}
/**
* Extends the render tree with a new node. Similar to how a tree data structure
* is created, but instead of a node with an array of children, children subscribe
* to notifications from the parent.
*
* Nodes can be instances of `LayoutNode`, `SizeNode`, or Object literals with
* size and layout properties, in which case, appropriate nodes will be created.
*
* This method also takes `Views` (subtrees) and `Surfaces` (leaves).
*
* @method add
* @chainable
* @param node {Object|SizeNode|LayoutNode|Surface|View} Node
* @return {RenderTreeNode}
*/
RenderTreeNode.prototype.add = function add(node) {
var childNode;
if (node.constructor === Object){
// Object literal case
return _createNodeFromObjectLiteral.call(this, node);
}
else if (node._isView){
// View case
if (this.root)
node._node.root = this.root;
else if (this.tempRoot)
node._node.tempRoot = this.tempRoot;
childNode = node;
}
else {
// Node case
childNode = new RenderTreeNode(node);
if (this.tempRoot)
childNode.tempRoot = this.tempRoot;
else childNode.root = _getRootNode.call(this);
}
childNode._layout.subscribe(this.layout || this._layout);
childNode._size.subscribe(this.size || this._size);
return childNode;
};
function _createNodeFromObjectLiteral(object){
var sizeKeys = {};
var layoutKeys = {};
for (var key in object){
if (SIZE_KEYS[key]) sizeKeys[key] = object[key];
else if (LAYOUT_KEYS[key]) layoutKeys[key] = object[key];
}
var node = this;
var needsSize = Object.keys(sizeKeys).length > 0;
var needsLayout = Object.keys(layoutKeys).length > 0;
// create extra align node if needed
if (needsSize && layoutKeys.align){
var alignNode = new LayoutNode({
align : layoutKeys.align
});
delete layoutKeys.align;
node = node.add(alignNode);
}
// create size node first if needed
if (needsSize)
node = node.add(new SizeNode(sizeKeys));
// create layout node if needed
if (needsLayout)
node = node.add(new LayoutNode(layoutKeys));
return node;
}
function _getRootNode(){
if (this.root) return this.root;
if (this.tempRoot) return _getRootNode.call(this.tempRoot);
return this;
}
function _set(object) {
if (object instanceof SizeNode){
this.size = ResizeStream.lift(
function SGSizeAlgebra (objectSpec, parentSize){
if (!parentSize) return false;
return (objectSpec)
? sizeAlgebra(objectSpec, parentSize)
: parentSize;
},
[object, this._size]
);
return;
}
else if (object instanceof LayoutNode){
this.layout = Stream.lift(
function SGLayoutAlgebra (objectSpec, parentSpec, size){
if (!parentSpec || !size) return false;
return (objectSpec)
? layoutAlgebra(objectSpec, parentSpec, size)
: parentSpec;
},
[object, this._layout, this._size]
);
return;
}
// object is a leaf node
object._size.subscribe(this._size);
object._layout.subscribe(this._layout);
object._getRoot = _getRootNode.bind(this);
}
module.exports = RenderTreeNode;
});