diff --git a/core/Component.js b/core/Component.js new file mode 100644 index 00000000..f40a5283 --- /dev/null +++ b/core/Component.js @@ -0,0 +1,138 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Famous Industries Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +'use strict'; + +/** + * An abstract component meant to be subclassed. + * + * @class Component + * @constructor + * @abstract + * + * @param {Node} [node] Node to which the component should be added. + */ +function Component(node) { + /** @protected */ + this._node = null; + + /** @protected */ + this._id = null; + + /** @protected */ + this._requestingUpdate = false; + + if (node) this.onAdd(node, node.addComponent(this)); +} + +/** + * Method that will be called by the node after the component has been added + * to it. + * + * @method + * @private + * + * @param {Node} node Node to which the component has been added. + * @param {Number} id The id under which the component has been + * registered. + * @return {undefined} undefined + */ +Component.prototype.onAdd = function onAdd(node, id) { + // Safety check needed for now to ensure backwards compatibility. + if (this._node === node && this._id === id) return; + + if (this._node) throw new Error( + 'Can not add component to multiple nodes.' + ); + + this._node = node; + this._id = id; +}; + +/** + * Method that will be called by the node after the component has been added + * to it. + * + * @method + * @private + * + * @param {Node} node Node from which the component has been removed. + * @param {Number} id The under which the component has previously been + * registered before being removed. + * @return {undefined} undefined + */ +Component.prototype.onRemove = function onRemove(node, id) { + this._node = null; + this._id = null; +}; + +/** + * Requests a new update from the Node. This results into the component's + * `onUpdate` method being called on the next + * + * @method _requestUpdate + * @protected + * + * @return {undefined} undefined + */ +Component.prototype._requestUpdate = function _requestUpdate () { + if (!this._requestingUpdate && this.onUpdate) { + this._node.requestUpdate(this._id); + this._requestingUpdate = true; + } +}; + +/** + * A method to be invoked during the node's update phase if an update has been + * requested previously. + * + * @method onUpdate + * @private + * + * @return {undefined} undefined + */ +Component.prototype.onUpdate = function onUpdate () { + this._requestingUpdate = false; +}; + +/** + * @method + * + * @return {Node} Node to which the component has been added. + */ +Component.prototype.getNode = function getNode() { + return this._node; +}; + +/** + * @method + * + * @return {Number} Id assigned by the Node when the component has been + * added. + */ +Component.prototype.getId = function getId() { + return this._id; +}; + +module.exports = Component; diff --git a/core/Node.js b/core/Node.js index 84e234e7..9f3a029f 100644 --- a/core/Node.js +++ b/core/Node.js @@ -95,14 +95,14 @@ function Node () { this._transformID = null; this._sizeID = null; - if (this.constructor.INIT_DEFAULT_COMPONENTS) this._init(); + if (!this.constructor.NO_DEFAULT_COMPONENTS) this._init(); } Node.RELATIVE_SIZE = 0; Node.ABSOLUTE_SIZE = 1; Node.RENDER_SIZE = 2; Node.DEFAULT_SIZE = 0; -Node.INIT_DEFAULT_COMPONENTS = true; +Node.NO_DEFAULT_COMPONENTS = false; /** * Protected method. Initializes a node with a default Transform and Size component @@ -121,7 +121,7 @@ Node.prototype._init = function _init () { * Protected method. Sets the parent of this node such that it can be looked up. * * @method - * + * * @param {Node} parent The node to set as the parent of this * * @return {undefined} undefined; @@ -201,7 +201,7 @@ Node.prototype.getLocation = function getLocation () { Node.prototype.getId = Node.prototype.getLocation; /** - * Globally dispatches the event using the Dispatch. All descendent nodes will + * Dispatches the event using the Dispatch. All descendent nodes will * receive the dispatched event. * * @method emit @@ -234,7 +234,7 @@ Node.prototype.getValue = function getValue () { var numberOfChildren = this._children.length; var numberOfComponents = this._components.length; var i = 0; - + var value = { location: this.getId(), spec: { @@ -266,7 +266,7 @@ Node.prototype.getValue = function getValue () { components: [], children: [] }; - + if (value.location) { var transform = TransformSystem.get(this.getId()); var size = SizeSystem.get(this.getId()); @@ -370,8 +370,10 @@ Node.prototype.getParent = function getParent () { Node.prototype.requestUpdate = function requestUpdate (requester) { if (this._inUpdate || !this.isMounted()) return this.requestUpdateOnNextTick(requester); - this._updateQueue.push(requester); - if (!this._requestingUpdate) this._requestUpdate(); + if (this._updateQueue.indexOf(requester) === -1) { + this._updateQueue.push(requester); + if (!this._requestingUpdate) this._requestUpdate(); + } return this; }; @@ -390,7 +392,8 @@ Node.prototype.requestUpdate = function requestUpdate (requester) { * @return {Node} this */ Node.prototype.requestUpdateOnNextTick = function requestUpdateOnNextTick (requester) { - this._nextUpdateQueue.push(requester); + if (this._nextUpdateQueue.indexOf(requester) === -1) + this._nextUpdateQueue.push(requester); return this; }; @@ -440,7 +443,7 @@ Node.prototype.getOpacity = function getOpacity () { * @return {Float32Array} An array representing the mount point. */ Node.prototype.getMountPoint = function getMountPoint () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._transformID).getMountPoint(); else if (this.isMounted()) return TransformSystem.get(this.getLocation()).getMountPoint(); @@ -455,7 +458,7 @@ Node.prototype.getMountPoint = function getMountPoint () { * @return {Float32Array} An array representing the align. */ Node.prototype.getAlign = function getAlign () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._transformID).getAlign(); else if (this.isMounted()) return TransformSystem.get(this.getLocation()).getAlign(); @@ -470,7 +473,7 @@ Node.prototype.getAlign = function getAlign () { * @return {Float32Array} An array representing the origin. */ Node.prototype.getOrigin = function getOrigin () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._transformID).getOrigin(); else if (this.isMounted()) return TransformSystem.get(this.getLocation()).getOrigin(); @@ -485,7 +488,7 @@ Node.prototype.getOrigin = function getOrigin () { * @return {Float32Array} An array representing the position. */ Node.prototype.getPosition = function getPosition () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._transformID).getPosition(); else if (this.isMounted()) return TransformSystem.get(this.getLocation()).getPosition(); @@ -500,7 +503,7 @@ Node.prototype.getPosition = function getPosition () { * @return {Float32Array} an array of four values, showing the rotation as a quaternion */ Node.prototype.getRotation = function getRotation () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._transformID).getRotation(); else if (this.isMounted()) return TransformSystem.get(this.getLocation()).getRotation(); @@ -515,7 +518,7 @@ Node.prototype.getRotation = function getRotation () { * @return {Float32Array} an array showing the current scale vector */ Node.prototype.getScale = function getScale () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._transformID).getScale(); else if (this.isMounted()) return TransformSystem.get(this.getLocation()).getScale(); @@ -530,7 +533,7 @@ Node.prototype.getScale = function getScale () { * @return {Float32Array} an array of numbers showing the current size mode */ Node.prototype.getSizeMode = function getSizeMode () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._sizeID).getSizeMode(); else if (this.isMounted()) return SizeSystem.get(this.getLocation()).getSizeMode(); @@ -545,7 +548,7 @@ Node.prototype.getSizeMode = function getSizeMode () { * @return {Float32Array} a vector 3 showing the current proportional size */ Node.prototype.getProportionalSize = function getProportionalSize () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._sizeID).getProportional(); else if (this.isMounted()) return SizeSystem.get(this.getLocation()).getProportional(); @@ -560,7 +563,7 @@ Node.prototype.getProportionalSize = function getProportionalSize () { * @return {Float32Array} a vector 3 showing the current differential size */ Node.prototype.getDifferentialSize = function getDifferentialSize () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._sizeID).getDifferential(); else if (this.isMounted()) return SizeSystem.get(this.getLocation()).getDifferential(); @@ -575,7 +578,7 @@ Node.prototype.getDifferentialSize = function getDifferentialSize () { * @return {Float32Array} a vector 3 showing the current absolute size of the node */ Node.prototype.getAbsoluteSize = function getAbsoluteSize () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._sizeID).getAbsolute(); else if (this.isMounted()) return SizeSystem.get(this.getLocation()).getAbsolute(); @@ -592,7 +595,7 @@ Node.prototype.getAbsoluteSize = function getAbsoluteSize () { * @return {Float32Array} a vector 3 showing the current render size */ Node.prototype.getRenderSize = function getRenderSize () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._sizeID).getRender(); else if (this.isMounted()) return SizeSystem.get(this.getLocation()).getRender(); @@ -607,7 +610,7 @@ Node.prototype.getRenderSize = function getRenderSize () { * @return {Float32Array} a vector 3 of the final calculated side of the node */ Node.prototype.getSize = function getSize () { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) return this.getComponent(this._sizeID).get(); else if (this.isMounted()) return SizeSystem.get(this.getLocation()).get(); @@ -704,11 +707,8 @@ Node.prototype.addComponent = function addComponent (component) { index = this._freedComponentIndicies.length ? this._freedComponentIndicies.pop() : this._components.length; this._components[index] = component; - if (this.isMounted() && component.onMount) - component.onMount(this, index); - - if (this.isShown() && component.onShow) - component.onShow(); + if (component.onAdd) + component.onAdd(this, index); } return index; @@ -740,11 +740,9 @@ Node.prototype.removeComponent = function removeComponent (component) { var index = this._components.indexOf(component); if (index !== -1) { this._freedComponentIndicies.push(index); - if (this.isShown() && component.onHide) - component.onHide(); - if (this.isMounted() && component.onDismount) - component.onDismount(); + if (component.onRemove) + component.onRemove(this, index); this._components[index] = null; } @@ -752,7 +750,7 @@ Node.prototype.removeComponent = function removeComponent (component) { }; /** - * Removes a node's subscription to a particular UIEvent. All components + * Removes a node's subscription to a particular UIEvent. All components * on the node will have the opportunity to remove all listeners depending * on this event. * @@ -885,7 +883,7 @@ Node.prototype.hide = function hide () { * @return {Node} this */ Node.prototype.setAlign = function setAlign (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._transformID).setAlign(x, y, z); else if (this.isMounted()) TransformSystem.get(this.getLocation()).setAlign(x, y, z); @@ -906,7 +904,7 @@ Node.prototype.setAlign = function setAlign (x, y, z) { * @return {Node} this */ Node.prototype.setMountPoint = function setMountPoint (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._transformID).setMountPoint(x, y, z); else if (this.isMounted()) TransformSystem.get(this.getLocation()).setMountPoint(x, y, z); @@ -927,7 +925,7 @@ Node.prototype.setMountPoint = function setMountPoint (x, y, z) { * @return {Node} this */ Node.prototype.setOrigin = function setOrigin (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._transformID).setOrigin(x, y, z); else if (this.isMounted()) TransformSystem.get(this.getLocation()).setOrigin(x, y, z); @@ -948,7 +946,7 @@ Node.prototype.setOrigin = function setOrigin (x, y, z) { * @return {Node} this */ Node.prototype.setPosition = function setPosition (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._transformID).setPosition(x, y, z); else if (this.isMounted()) TransformSystem.get(this.getLocation()).setPosition(x, y, z); @@ -972,7 +970,7 @@ Node.prototype.setPosition = function setPosition (x, y, z) { * @return {Node} this */ Node.prototype.setRotation = function setRotation (x, y, z, w) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._transformID).setRotation(x, y, z, w); else if (this.isMounted()) TransformSystem.get(this.getLocation()).setRotation(x, y, z, w); @@ -993,7 +991,7 @@ Node.prototype.setRotation = function setRotation (x, y, z, w) { * @return {Node} this */ Node.prototype.setScale = function setScale (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._transformID).setScale(x, y, z); else if (this.isMounted()) TransformSystem.get(this.getLocation()).setScale(x, y, z); @@ -1054,7 +1052,7 @@ Node.prototype.setOpacity = function setOpacity (val) { * @return {Node} this */ Node.prototype.setSizeMode = function setSizeMode (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._sizeID).setSizeMode(x, y, z); else if (this.isMounted()) SizeSystem.get(this.getLocation()).setSizeMode(x, y, z); @@ -1076,7 +1074,7 @@ Node.prototype.setSizeMode = function setSizeMode (x, y, z) { * @return {Node} this */ Node.prototype.setProportionalSize = function setProportionalSize (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._sizeID).setProportional(x, y, z); else if (this.isMounted()) SizeSystem.get(this.getLocation()).setProportional(x, y, z); @@ -1103,7 +1101,7 @@ Node.prototype.setProportionalSize = function setProportionalSize (x, y, z) { * @return {Node} this */ Node.prototype.setDifferentialSize = function setDifferentialSize (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._sizeID).setDifferential(x, y, z); else if (this.isMounted()) SizeSystem.get(this.getLocation()).setDifferential(x, y, z); @@ -1123,7 +1121,7 @@ Node.prototype.setDifferentialSize = function setDifferentialSize (x, y, z) { * @return {Node} this */ Node.prototype.setAbsoluteSize = function setAbsoluteSize (x, y, z) { - if (this.constructor.INIT_DEFAULT_COMPONENTS) + if (!this.constructor.NO_DEFAULT_COMPONENTS) this.getComponent(this._sizeID).setAbsolute(x, y, z); else if (this.isMounted()) SizeSystem.get(this.getLocation()).setAbsolute(x, y, z); @@ -1170,8 +1168,6 @@ Node.prototype.update = function update (time){ var queue = this._updateQueue; var item; - if (this.onUpdate) this.onUpdate(); - while (nextQueue.length) queue.unshift(nextQueue.pop()); while (queue.length) { @@ -1208,7 +1204,7 @@ Node.prototype.mount = function mount (path) { if (this.isMounted()) throw new Error('Node is already mounted at: ' + this.getLocation()); - if (this.constructor.INIT_DEFAULT_COMPONENTS){ + if (!this.constructor.NO_DEFAULT_COMPONENTS){ TransformSystem.registerTransformAtPath(path, this.getComponent(this._transformID)); SizeSystem.registerSizeAtPath(path, this.getComponent(this._sizeID)); } diff --git a/core/Scene.js b/core/Scene.js index cc26f3ba..3aa4f2d8 100644 --- a/core/Scene.js +++ b/core/Scene.js @@ -74,6 +74,7 @@ function Scene (selector, updater) { // Scene inherits from node Scene.prototype = Object.create(Node.prototype); Scene.prototype.constructor = Scene; +Scene.NO_DEFAULT_COMPONENTS = true; /** * Scene getUpdater function returns the passed in updater diff --git a/core/index.js b/core/index.js index 49e98679..aab49fa2 100644 --- a/core/index.js +++ b/core/index.js @@ -28,6 +28,7 @@ module.exports = { Channel: require('./Channel'), Clock: require('./Clock'), Commands: require('./Commands'), + Component: require('./Component'), Dispatch: require('./Dispatch'), Event: require('./Event'), FamousEngine: require('./FamousEngine'), diff --git a/dom-renderables/DOMElement.js b/dom-renderables/DOMElement.js index dffeee32..ef71e675 100644 --- a/dom-renderables/DOMElement.js +++ b/dom-renderables/DOMElement.js @@ -28,6 +28,9 @@ var CallbackStore = require('../utilities/CallbackStore'); var TransformSystem = require('../core/TransformSystem'); var Commands = require('../core/Commands'); var Size = require('../core/Size'); +var Component = require('../core/Component'); + +var RENDER_SIZE = 2; /** * A DOMElement is a component that can be added to a Node with the @@ -35,6 +38,7 @@ var Size = require('../core/Size'); * to through their Nodes to the Compositor where they are acted upon. * * @class DOMElement + * @augments Component * * @param {Node} node The Node to which the `DOMElement` * renderable should be attached to. @@ -53,7 +57,11 @@ var Size = require('../core/Size'); * for DOM and WebGL layering. On by default. */ function DOMElement(node, options) { - if (!node) throw new Error('DOMElement must be instantiated on a node'); + if (!node) throw new Error( + 'DOMElement must be instantiated on a Node' + ); + + Component.call(this, node); this._changeQueue = []; @@ -75,8 +83,6 @@ function DOMElement(node, options) { this._id = node ? node.addComponent(this) : null; this._node = node; - this.onSizeModeChange.apply(this, node.getSizeMode()); - this._callbacks = new CallbackStore(); this.setProperty('display', node.isShown() ? 'block' : 'none'); @@ -104,6 +110,9 @@ function DOMElement(node, options) { if (options.cutout === false) this.setCutoutState(options.cutout); } +DOMElement.prototype = Object.create(Component.prototype); +DOMElement.prototype.constructor = DOMElement; + /** * Serializes the state of the DOMElement. * @@ -151,7 +160,7 @@ DOMElement.prototype.onUpdate = function onUpdate () { } - this._requestingUpdate = false; + Component.prototype.onUpdate.call(this); }; /** @@ -172,6 +181,7 @@ DOMElement.prototype.onMount = function onMount(node, id) { this._id = id; this._UIEvents = node.getUIEvents().slice(0); TransformSystem.makeBreakPointAt(node.getLocation()); + this.onSizeModeChange.apply(this, node.getSizeMode()); this.draw(); this.setAttribute('data-fa-path', node.getLocation()); }; @@ -441,21 +451,6 @@ DOMElement.prototype.getRenderSize = function getRenderSize() { return this._renderSize; }; -/** - * Method to have the component request an update from its Node - * - * @method - * @private - * - * @return {undefined} undefined - */ -DOMElement.prototype._requestUpdate = function _requestUpdate() { - if (!this._requestingUpdate) { - this._node.requestUpdate(this._id); - this._requestingUpdate = true; - } -}; - /** * Initializes the DOMElement by sending the `INIT_DOM` command. This creates * or reallocates a new Element in the actual DOM hierarchy.