<commit_before>pc.extend(pc, function () {
    pc.ELEMENTTYPE_GROUP = 'group';
    pc.ELEMENTTYPE_IMAGE = 'image';
    pc.ELEMENTTYPE_TEXT = 'text';

    var _warning = false;

    var ElementComponent = function ElementComponent (system, entity) {
        this._anchor = new pc.Vec4();
        this._worldAnchor = new pc.Vec4();

        this._pivot = new pc.Vec2();

        this._width = 32;
        this._height = 32;

        // the world transform in the 2D space
        this._worldTransform = new pc.Mat4();
        // the model transform used to render
        this._modelTransform = new pc.Mat4();
        // transform that updates local position according to anchor values
        this._anchorTransform = new pc.Mat4();

        this._anchorDirty = true;

        this.entity.on('insert', this._onInsert, this);

        this.screen = null;

        this._type = pc.ELEMENTTYPE_GROUP;

        // element types
        this.image = null;
        this.text = null;
        this.group = null;

        this._findScreen();
        this._patch();

        if (!_warning) {
            console.warn("Message from PlayCanvas: The element component is currently in Beta. APIs may change without notice.");
            _warning = true;
        }
    };
    ElementComponent = pc.inherits(ElementComponent, pc.Component);

    pc.extend(ElementComponent.prototype, {
        // internal used to get the element world transform
        // and ensure it is synced before returning
        _getWorldTransform: function () {
            var syncList = [];

            return function () {
                var current = this.entity;
                syncList.length = 0;

                while (current !== null) {
                    syncList.push(current);
                    current = current._parent;
                }

                for (var i = syncList.length - 1; i >= 0; i--) {
                    syncList[i].sync();
                }

                return this._worldTransform;
            };
        }(),

        _patch: function () {
            this.entity.sync = this._sync;
            this.entity.setPosition = this._setPosition;
            this.entity.getPosition = this._getPosition;
            this.entity.getRotation = this._getRotation;
        },

        _unpatch: function () {
            this.entity.sync = pc.Entity.prototype.sync;
            this.entity.setPosition = pc.Entity.prototype.setPosition;
            this.entity.getPosition = pc.Entity.prototype.getPosition;
            this.entity.getRotation = pc.Entity.prototype.getRotation;
        },

        _getPosition: function () {
            this.element._getWorldTransform().getTranslation(this.position);
            return this.position;
        },

        _setPosition: function () {
            var position = new pc.Vec3();
            var invParentWtm = new pc.Mat4();

            return function () {
                if (arguments.length === 1) {
                    position.copy(arguments[0]);
                } else {
                    position.set(arguments[0], arguments[1], arguments[2]);
                }

                if (this._parent === null || this._parent && !this._parent.element) {
                    this.localPosition.copy(position);
                } else {
                    invParentWtm.copy(this._parent.element._getWorldTransform()).invert();
                    invParentWtm.transformPoint(position, this.localPosition);
                }
                this.dirtyLocal = true;
            };
        }(),

        _getRotation: function () {
            this.rotation.setFromMat4(this.element._getWorldTransform());
            return this.rotation;
        },

        _sync: function () {
            if (this.dirtyLocal) {
                this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);

                this.dirtyLocal = false;
                this.dirtyWorld = true;
                this._aabbVer++;
            }

            var resx = 0;
            var resy = 0;
            var screen = this.element.screen;

            if (this.element._anchorDirty) {
                var px = 0;
                var py = 1;
                if (this._parent && this._parent.element) {
                    // use parent rect
                    resx = this._parent.element.width;
                    resy = this._parent.element.height;
                    px = this._parent.element.pivot.x;
                    py = this._parent.element.pivot.y;
                } else if (screen) {
                    // use screen rect
                    var resolution = screen.screen.resolution;
                    resx = resolution.x * screen.screen.scale;
                    resy = resolution.y * screen.screen.scale;
                }
                this.element._anchorTransform.setTranslate((resx*(this.element.anchor.x - px)), -(resy * (py-this.element.anchor.y)), 0);
                this.element._anchorDirty = false;
            }

            if (this.dirtyWorld) {
                if (this._parent === null) {
                    this.worldTransform.copy(this.localTransform);
                } else {
                    // transform element hierarchy
                    if (this._parent.element) {
                        this.element._worldTransform.mul2(this._parent.element._worldTransform, this.localTransform);

                        this.element._modelTransform.mul2(this.element._anchorTransform, this.localTransform);
                        this.element._modelTransform.mul2(this._parent.element._modelTransform, this.element._modelTransform);
                        // this.element._worldTransform.mul2(this.element._anchorTransform, this.localTransform);
                        // this.element._worldTransform.mul2(this._parent.element._worldTransform, this.element._worldTransform);
                    } else {
                        this.element._worldTransform.copy(this.localTransform);
                        this.element._modelTransform.mul2(this.element._anchorTransform, this.localTransform);

                        // this.element._worldTransform.mul2(this.element._anchorTransform, this.localTransform);
                    }

                    if (screen) {
                        this.worldTransform.mul2(screen.screen._screenMatrix, this.element._modelTransform);
                        // this.element._modelTransform.mul2(screen.screen._screenMatrix, this.element._worldTransform);

                        if (!screen.screen.screenSpace) {
                            this.worldTransform.mul2(screen.worldTransform, this.worldTransform);
                            // this.worldTransform.mul2(screen.worldTransform, this.element._modelTransform);
                        } else {
                            // this.worldTransform.copy(this.element._modelTransform);
                        }
                    } else {
                        this.worldTransform.copy(this.element._modelTransform);
                    }
                }

                this.dirtyWorld = false;
                var child;

                for (var i = 0, len = this._children.length; i < len; i++) {
                    child = this._children[i];
                    child.dirtyWorld = true;
                    child._aabbVer++;

                }
            }
        },

        _onInsert: function (parent) {
            // when the entity is reparented find a possible new screen
            var screen = this._findScreen();

            if (screen) {
                this._updateScreen(screen);
            }
        },

        _updateScreen: function (screen) {
            if (this.screen && this.screen !== screen) {
                this.screen.screen.off('set:resolution', this._onScreenResize, this);
                this.screen.screen.off('set:screenspace', this._onScreenSpaceChange, this);
            }
            this.screen = screen;
            if (this.screen) {
                this.screen.screen.on('set:resolution', this._onScreenResize, this);
                this.screen.screen.on('set:screenspace', this._onScreenSpaceChange, this);

                this._setAnchors();
                this.fire('set:screen', this.screen);
            }

            this._anchorDirty = true;
            this.entity.dirtyWorld = true;

            // update all child screens
            var children = this.entity.getChildren();
            for (var i = 0, l = children.length; i < l; i++) {
                if (children[i].element) children[i].element._updateScreen(screen);
            }

            // calculate draw order
            this.screen.screen.syncDrawOrder();
        },

        _findScreen: function () {
            var screen = this.entity._parent;
            while(screen && !screen.screen) {
                screen = screen._parent;
            }
            return screen;
        },

        _onScreenResize: function (res) {
            this._anchorDirty = true;
            this.entity.dirtyWorld = true;

            var minx = this._worldAnchor.x;
            var miny = this._worldAnchor.y;
            var maxx = this._worldAnchor.z;
            var maxy = this._worldAnchor.w;
            var oldWidth = this.width;
            var oldHeight = this.height;
            var px = this.pivot.x;
            var py = this.pivot.y;

            this._setAnchors();

            var p = this.entity.getLocalPosition();

            // calculate new width
            var l = minx + p.x - this.pivot.x * this.width;
            var r = minx + p.x + (1 - this.pivot.x) * this.width;
            var ldist = l - minx;
            var rdist = r - maxx;
            var newl = this._worldAnchor.x + ldist;
            var newr = this._worldAnchor.z + rdist;
            var newleft = newl - this._worldAnchor.x;
            var newright = newr - this._worldAnchor.x;
            this.width = newright - newleft;

            // calculate new height
            var b = miny + p.y - (1 - this.pivot.y) * this.height;
            var t = miny + p.y + this.pivot.y*this.height;
            var bdist = b - miny;
            var tdist = t - maxy;
            var newb = this._worldAnchor.y + bdist;
            var newt = this._worldAnchor.w + tdist;
            var newbottom = newb - this._worldAnchor.y;
            var newtop = newt - this._worldAnchor.y;
            this.height = newtop - newbottom;

            // shift position based on pivot
            p.x = p.x - px*oldWidth + px*this.width;
            p.y = p.y - py*oldHeight + py*this.height;
            this.entity.setLocalPosition(p);

            this.fire('screen:set:resolution', res);
        },

        _onScreenSpaceChange: function () {
            this.entity.dirtyWorld = true;
            this.fire('screen:set:screenspace', this.screen.screen.screenSpace);
        },

        // override regular entity.sync method with this one

        // store pixel positions of anchor relative to current parent resolution
        _setAnchors: function () {
            var resx = 0;
            var resy = 0;
            if (this._parent && this._parent.element) {
                resx = this._parent.element.width;
                resy = this._parent.element.height;
            } else if (this.screen) {
                var res = this.screen.screen.resolution
                resx = res.x;
                resy = res.y;
            }

            this._worldAnchor.set(
                this._anchor.x*resx,
                this._anchor.y*resy,
                this._anchor.z*resx,
                this._anchor.w*resy
            );
        },

        onEnable: function () {
            ElementComponent._super.onEnable.call(this);
            if (this.image) this.image.onEnable();
            if (this.text) this.text.onEnable();
            if (this.group) this.group.onEnable();
        },

        onDisable: function () {
            ElementComponent._super.onDisable.call(this);
            if (this.image) this.image.onDisable();
            if (this.text) this.text.onDisable();
            if (this.group) this.group.onDisable();
        }
    });

    Object.defineProperty(ElementComponent.prototype, "type", {
        get: function () {
            return this._type;
        },

        set: function (value) {
            if (value !== this._type) {
                if (this.image) {
                    this.image.destroy();
                }
                if (this.text) {
                    this.text.destroy();
                }

                if (value === pc.ELEMENTTYPE_IMAGE) {
                    this.image = new pc.ImageElement(this);
                } else if (value === pc.ELEMENTTYPE_TEXT) {
                    this.text = new pc.TextElement(this);
                }

            }
            this._type = value;
        }
    });

    Object.defineProperty(ElementComponent.prototype, "drawOrder", {
        get: function () {
            return this._drawOrder;
        },

        set: function (value) {
            this._drawOrder = value;
            this.fire('set:draworder', this._drawOrder);
        }
    });

    // Object.defineProperty(ElementComponent.prototype, "left", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();
    //         return p.x - this.pivot.x * this.width;
    //     },

    //     set: function (value) {
    //         var change = value - this.left;
    //         this.width = this.right - value;
    //         this.entity.translateLocal(change/2,0,0);

    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    // Object.defineProperty(ElementComponent.prototype, "right", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();

    //         return p.x + (1 - this.pivot.x) * this.width;
    //     },

    //     set: function (value) {
    //         var change = value - this.right;
    //         this.width = value - this.left;
    //         this.entity.translateLocal(change/2, 0, 0);
    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    // Object.defineProperty(ElementComponent.prototype, "top", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();
    //         return p.y + this.pivot.y*this.height;
    //     },

    //     set: function (value) {
    //         var change = value - this.top;
    //         this.height = value - this.bottom;
    //         this.entity.translateLocal(0, change/2, 0);
    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    // Object.defineProperty(ElementComponent.prototype, "bottom", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();
    //         return p.y - (1 - this.pivot.y) * this.height;
    //     },

    //     set: function (value) {
    //         var change = value - this.bottom;
    //         this.height = this.top - value;
    //         this.entity.translateLocal(0, change/2, 0);
    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    Object.defineProperty(ElementComponent.prototype, "width", {
        get: function () {
            return this._width;
        },

        set: function (value) {
            this._width = value;
            this.fire('set:width', this._width);
            this.fire('resize', this._width, this._height);
        }
    });

    Object.defineProperty(ElementComponent.prototype, "height", {
        get: function () {
            return this._height;
        },

        set: function (value) {
            this._height = value;
            this.fire('set:height', this._height);
            this.fire('resize', this._width, this._height);
        }
    });

    Object.defineProperty(ElementComponent.prototype, "pivot", {
        get: function () {
            return this._pivot;
        },

        set: function (value) {
            if (value instanceof pc.Vec2) {
                this._pivot.set(value.x, value.y);
            } else {
                this._pivot.set(value[0], value[1]);
            }
            this.fire('set:pivot', this._pivot);
        }
    });

    Object.defineProperty(ElementComponent.prototype, "anchor", {
        get: function () {
            return this._anchor;
        },

        set: function (value) {
            if (value instanceof pc.Vec4) {
                this._anchor.set(value.x, value.y, value.z, value.w);
            } else {
                this._anchor.set(value[0], value[1], value[2], value[3]);
            }

            this._setAnchors();

            this._anchorDirty = true;
            this.fire('set:anchor', this._anchor);
        }
    });

    return {
        ElementComponent: ElementComponent
    };
}());

<commit_msg>Initialise screen<commit_after>pc.extend(pc, function () {
    pc.ELEMENTTYPE_GROUP = 'group';
    pc.ELEMENTTYPE_IMAGE = 'image';
    pc.ELEMENTTYPE_TEXT = 'text';

    var _warning = false;

    var ElementComponent = function ElementComponent (system, entity) {
        this._anchor = new pc.Vec4();
        this._worldAnchor = new pc.Vec4();

        this._pivot = new pc.Vec2();

        this._width = 32;
        this._height = 32;

        // the world transform in the 2D space
        this._worldTransform = new pc.Mat4();
        // the model transform used to render
        this._modelTransform = new pc.Mat4();
        // transform that updates local position according to anchor values
        this._anchorTransform = new pc.Mat4();

        this._anchorDirty = true;

        this.entity.on('insert', this._onInsert, this);

        this.screen = this._findScreen();

        this._type = pc.ELEMENTTYPE_GROUP;

        // element types
        this.image = null;
        this.text = null;
        this.group = null;

        this._patch();

        if (!_warning) {
            console.warn("Message from PlayCanvas: The element component is currently in Beta. APIs may change without notice.");
            _warning = true;
        }
    };
    ElementComponent = pc.inherits(ElementComponent, pc.Component);

    pc.extend(ElementComponent.prototype, {
        // internal used to get the element world transform
        // and ensure it is synced before returning
        _getWorldTransform: function () {
            var syncList = [];

            return function () {
                var current = this.entity;
                syncList.length = 0;

                while (current !== null) {
                    syncList.push(current);
                    current = current._parent;
                }

                for (var i = syncList.length - 1; i >= 0; i--) {
                    syncList[i].sync();
                }

                return this._worldTransform;
            };
        }(),

        _patch: function () {
            this.entity.sync = this._sync;
            this.entity.setPosition = this._setPosition;
            this.entity.getPosition = this._getPosition;
            this.entity.getRotation = this._getRotation;
        },

        _unpatch: function () {
            this.entity.sync = pc.Entity.prototype.sync;
            this.entity.setPosition = pc.Entity.prototype.setPosition;
            this.entity.getPosition = pc.Entity.prototype.getPosition;
            this.entity.getRotation = pc.Entity.prototype.getRotation;
        },

        _getPosition: function () {
            this.element._getWorldTransform().getTranslation(this.position);
            return this.position;
        },

        _setPosition: function () {
            var position = new pc.Vec3();
            var invParentWtm = new pc.Mat4();

            return function () {
                if (arguments.length === 1) {
                    position.copy(arguments[0]);
                } else {
                    position.set(arguments[0], arguments[1], arguments[2]);
                }

                if (this._parent === null || this._parent && !this._parent.element) {
                    this.localPosition.copy(position);
                } else {
                    invParentWtm.copy(this._parent.element._getWorldTransform()).invert();
                    invParentWtm.transformPoint(position, this.localPosition);
                }
                this.dirtyLocal = true;
            };
        }(),

        _getRotation: function () {
            this.rotation.setFromMat4(this.element._getWorldTransform());
            return this.rotation;
        },

        _sync: function () {
            if (this.dirtyLocal) {
                this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);

                this.dirtyLocal = false;
                this.dirtyWorld = true;
                this._aabbVer++;
            }

            var resx = 0;
            var resy = 0;
            var screen = this.element.screen;

            if (this.element._anchorDirty) {
                var px = 0;
                var py = 1;
                if (this._parent && this._parent.element) {
                    // use parent rect
                    resx = this._parent.element.width;
                    resy = this._parent.element.height;
                    px = this._parent.element.pivot.x;
                    py = this._parent.element.pivot.y;
                } else if (screen) {
                    // use screen rect
                    var resolution = screen.screen.resolution;
                    resx = resolution.x * screen.screen.scale;
                    resy = resolution.y * screen.screen.scale;
                }
                this.element._anchorTransform.setTranslate((resx*(this.element.anchor.x - px)), -(resy * (py-this.element.anchor.y)), 0);
                this.element._anchorDirty = false;
            }

            if (this.dirtyWorld) {
                if (this._parent === null) {
                    this.worldTransform.copy(this.localTransform);
                } else {
                    // transform element hierarchy
                    if (this._parent.element) {
                        this.element._worldTransform.mul2(this._parent.element._worldTransform, this.localTransform);

                        this.element._modelTransform.mul2(this.element._anchorTransform, this.localTransform);
                        this.element._modelTransform.mul2(this._parent.element._modelTransform, this.element._modelTransform);
                        // this.element._worldTransform.mul2(this.element._anchorTransform, this.localTransform);
                        // this.element._worldTransform.mul2(this._parent.element._worldTransform, this.element._worldTransform);
                    } else {
                        this.element._worldTransform.copy(this.localTransform);
                        this.element._modelTransform.mul2(this.element._anchorTransform, this.localTransform);

                        // this.element._worldTransform.mul2(this.element._anchorTransform, this.localTransform);
                    }

                    if (screen) {
                        this.worldTransform.mul2(screen.screen._screenMatrix, this.element._modelTransform);
                        // this.element._modelTransform.mul2(screen.screen._screenMatrix, this.element._worldTransform);

                        if (!screen.screen.screenSpace) {
                            this.worldTransform.mul2(screen.worldTransform, this.worldTransform);
                            // this.worldTransform.mul2(screen.worldTransform, this.element._modelTransform);
                        } else {
                            // this.worldTransform.copy(this.element._modelTransform);
                        }
                    } else {
                        this.worldTransform.copy(this.element._modelTransform);
                    }
                }

                this.dirtyWorld = false;
                var child;

                for (var i = 0, len = this._children.length; i < len; i++) {
                    child = this._children[i];
                    child.dirtyWorld = true;
                    child._aabbVer++;

                }
            }
        },

        _onInsert: function (parent) {
            // when the entity is reparented find a possible new screen
            var screen = this._findScreen();

            if (screen) {
                this._updateScreen(screen);
            }
        },

        _updateScreen: function (screen) {
            if (this.screen && this.screen !== screen) {
                this.screen.screen.off('set:resolution', this._onScreenResize, this);
                this.screen.screen.off('set:screenspace', this._onScreenSpaceChange, this);
            }
            this.screen = screen;
            if (this.screen) {
                this.screen.screen.on('set:resolution', this._onScreenResize, this);
                this.screen.screen.on('set:screenspace', this._onScreenSpaceChange, this);

                this._setAnchors();
                this.fire('set:screen', this.screen);
            }

            this._anchorDirty = true;
            this.entity.dirtyWorld = true;

            // update all child screens
            var children = this.entity.getChildren();
            for (var i = 0, l = children.length; i < l; i++) {
                if (children[i].element) children[i].element._updateScreen(screen);
            }

            // calculate draw order
            this.screen.screen.syncDrawOrder();
        },

        _findScreen: function () {
            var screen = this.entity._parent;
            while(screen && !screen.screen) {
                screen = screen._parent;
            }
            return screen;
        },

        _onScreenResize: function (res) {
            this._anchorDirty = true;
            this.entity.dirtyWorld = true;

            var minx = this._worldAnchor.x;
            var miny = this._worldAnchor.y;
            var maxx = this._worldAnchor.z;
            var maxy = this._worldAnchor.w;
            var oldWidth = this.width;
            var oldHeight = this.height;
            var px = this.pivot.x;
            var py = this.pivot.y;

            this._setAnchors();

            var p = this.entity.getLocalPosition();

            // calculate new width
            var l = minx + p.x - this.pivot.x * this.width;
            var r = minx + p.x + (1 - this.pivot.x) * this.width;
            var ldist = l - minx;
            var rdist = r - maxx;
            var newl = this._worldAnchor.x + ldist;
            var newr = this._worldAnchor.z + rdist;
            var newleft = newl - this._worldAnchor.x;
            var newright = newr - this._worldAnchor.x;
            this.width = newright - newleft;

            // calculate new height
            var b = miny + p.y - (1 - this.pivot.y) * this.height;
            var t = miny + p.y + this.pivot.y*this.height;
            var bdist = b - miny;
            var tdist = t - maxy;
            var newb = this._worldAnchor.y + bdist;
            var newt = this._worldAnchor.w + tdist;
            var newbottom = newb - this._worldAnchor.y;
            var newtop = newt - this._worldAnchor.y;
            this.height = newtop - newbottom;

            // shift position based on pivot
            p.x = p.x - px*oldWidth + px*this.width;
            p.y = p.y - py*oldHeight + py*this.height;
            this.entity.setLocalPosition(p);

            this.fire('screen:set:resolution', res);
        },

        _onScreenSpaceChange: function () {
            this.entity.dirtyWorld = true;
            this.fire('screen:set:screenspace', this.screen.screen.screenSpace);
        },

        // override regular entity.sync method with this one

        // store pixel positions of anchor relative to current parent resolution
        _setAnchors: function () {
            var resx = 0;
            var resy = 0;
            if (this._parent && this._parent.element) {
                resx = this._parent.element.width;
                resy = this._parent.element.height;
            } else if (this.screen) {
                var res = this.screen.screen.resolution
                resx = res.x;
                resy = res.y;
            }

            this._worldAnchor.set(
                this._anchor.x*resx,
                this._anchor.y*resy,
                this._anchor.z*resx,
                this._anchor.w*resy
            );
        },

        onEnable: function () {
            ElementComponent._super.onEnable.call(this);
            if (this.image) this.image.onEnable();
            if (this.text) this.text.onEnable();
            if (this.group) this.group.onEnable();
        },

        onDisable: function () {
            ElementComponent._super.onDisable.call(this);
            if (this.image) this.image.onDisable();
            if (this.text) this.text.onDisable();
            if (this.group) this.group.onDisable();
        }
    });

    Object.defineProperty(ElementComponent.prototype, "type", {
        get: function () {
            return this._type;
        },

        set: function (value) {
            if (value !== this._type) {
                if (this.image) {
                    this.image.destroy();
                }
                if (this.text) {
                    this.text.destroy();
                }

                if (value === pc.ELEMENTTYPE_IMAGE) {
                    this.image = new pc.ImageElement(this);
                } else if (value === pc.ELEMENTTYPE_TEXT) {
                    this.text = new pc.TextElement(this);
                }

            }
            this._type = value;
        }
    });

    Object.defineProperty(ElementComponent.prototype, "drawOrder", {
        get: function () {
            return this._drawOrder;
        },

        set: function (value) {
            this._drawOrder = value;
            this.fire('set:draworder', this._drawOrder);
        }
    });

    // Object.defineProperty(ElementComponent.prototype, "left", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();
    //         return p.x - this.pivot.x * this.width;
    //     },

    //     set: function (value) {
    //         var change = value - this.left;
    //         this.width = this.right - value;
    //         this.entity.translateLocal(change/2,0,0);

    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    // Object.defineProperty(ElementComponent.prototype, "right", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();

    //         return p.x + (1 - this.pivot.x) * this.width;
    //     },

    //     set: function (value) {
    //         var change = value - this.right;
    //         this.width = value - this.left;
    //         this.entity.translateLocal(change/2, 0, 0);
    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    // Object.defineProperty(ElementComponent.prototype, "top", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();
    //         return p.y + this.pivot.y*this.height;
    //     },

    //     set: function (value) {
    //         var change = value - this.top;
    //         this.height = value - this.bottom;
    //         this.entity.translateLocal(0, change/2, 0);
    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    // Object.defineProperty(ElementComponent.prototype, "bottom", {
    //     get: function () {
    //         var p = this.entity.getLocalPosition();
    //         return p.y - (1 - this.pivot.y) * this.height;
    //     },

    //     set: function (value) {
    //         var change = value - this.bottom;
    //         this.height = this.top - value;
    //         this.entity.translateLocal(0, change/2, 0);
    //         // this.fire('set:left', this._width);
    //         // this.fire('resize', this._width, this._height);
    //     }
    // });

    Object.defineProperty(ElementComponent.prototype, "width", {
        get: function () {
            return this._width;
        },

        set: function (value) {
            this._width = value;
            this.fire('set:width', this._width);
            this.fire('resize', this._width, this._height);
        }
    });

    Object.defineProperty(ElementComponent.prototype, "height", {
        get: function () {
            return this._height;
        },

        set: function (value) {
            this._height = value;
            this.fire('set:height', this._height);
            this.fire('resize', this._width, this._height);
        }
    });

    Object.defineProperty(ElementComponent.prototype, "pivot", {
        get: function () {
            return this._pivot;
        },

        set: function (value) {
            if (value instanceof pc.Vec2) {
                this._pivot.set(value.x, value.y);
            } else {
                this._pivot.set(value[0], value[1]);
            }
            this.fire('set:pivot', this._pivot);
        }
    });

    Object.defineProperty(ElementComponent.prototype, "anchor", {
        get: function () {
            return this._anchor;
        },

        set: function (value) {
            if (value instanceof pc.Vec4) {
                this._anchor.set(value.x, value.y, value.z, value.w);
            } else {
                this._anchor.set(value[0], value[1], value[2], value[3]);
            }

            this._setAnchors();

            this._anchorDirty = true;
            this.fire('set:anchor', this._anchor);
        }
    });

    return {
        ElementComponent: ElementComponent
    };
}());

<|endoftext|>


<commit_before>/**
* Utility methods for OpenPhoto - this will include the entire framework
* And ability to swap frameworks (YUI or jQuery) in and out.
*/
(function() {

    // just make sure that OP, the global namespace for OpenPhoto
    // is defined
    if ( typeof(OP) === "undefined") {
        OP = {};
    }

    //constants
    var PLUGIN_FILE_PREFIX = 'openphoto-lib-';

    /**
    * Class that contains all utility functions for OpenPhoto
    * We can use a Constructor function in this case since we will
    * not have multiple instances of Util.  Also, it makes it easier to
    * extend via prototype.
    * @class Util
    */
    function Util() {

        /**
        * default configuration options
        * user can optionally specify an onComplete attribute in the css/js
        * object which will execute when the assets are loaded.
        * @type {object}
        * @property config
        */
        this.config = {
            baseUrl: '',
            jsLocation: '/assets/javascripts/',
            css: {
                assets: []
            },
            js: {
                assets:[]
            }
        };

        /**
        * the event map for click events, maps the HTML
        * classNames to the custom event names
        * @type {object}
        * @property eventMap
        */
        this.eventMap = {};

        /**
        * A hash of custom events
        * @type {object}
        * @property _customEvents
        */
        this._customEvents = {};

        /**
        * Count of the number of scripts loaded
        * @type {Number}
        * @property _scriptLoadCount
        */
        this._scriptLoadCount = 0;

        /**
        * Count of the number of css loaded
        * @type {Number}
        * @property _cssLoadCount
        */
        this._cssLoadCount = 0;

        /**
        * initialization method
        * @param {object} lib - the library to use
        * @param {object} config - the configuration object
        * @method init
        */
        this.init = function(lib, config) {

            OP.Log.info('[Util] init:');

            //merge the config with the user specified config
            this.config = this.merge(this.config, config);

            // we specify what library type in the .ini file
            // either jQuery or YUI - and then the user can load
            // additional css/js assets by specifying the files in the
            // js config - as specified by the plugin file (that will be user generated).

            //the library is a requirement, by default jQuery will be loaded
            this.lib = lib;
            this.libType = this.detectLibrary();

            // get the library plugin file that maps library functions to a normalized
            // naming so that we can use whatever library that is specified
            this.getLibraryPlugin();
        };

        this.addEventMap = function(eventMap) {
          this.eventMap = eventMap;
          for (var eType in this.eventMap) {
            if (this.eventMap.hasOwnProperty(eType)) {
              this.eventMap[eType] = this.merge(this.eventMap[eType], this.eventMap[eType]);
            }
          }
        };

        /**
        * Now that the library plugin has been loaded, we add all event handlers
        * @return {void}
        * @method _init
        */
        this._init = function() {

            OP.Log.info('[Util] _init:')

            var js = this.config.js.assets,
                css = this.config.css.assets,
                i,
                length;

            //attach events
            this.attachEvent( 'body', 'click', this.onviewevent, this);
            this.attachEvent( 'body', 'focus', this.onviewevent, this);
            this.attachEvent( 'body', 'change', this.onviewevent, this);
            this.attachEvent( 'body', 'mouseover', this.onmouseevent, this);
            this.attachEvent( 'body', 'mouseout', this.onmouseevent, this);
            this.attachEvent( 'html', 'keydown', this.onkeydownevent, this);
            this.attachEvent( 'html', 'keyup', this.onkeydownevent, this);
            this.attachEvent( 'form', 'submit', this.onviewevent, this);

            //load additional js in order specified
            for(i=0, j=js.length; i<j; i++) {
                this.loadScript( js[i], this._handleScriptLoad, this );
            }

            //load additional css in order specified
            for(i=0, j=css.length; i<j; i++) {
                this.loadCss( css[i], this._handleCssLoad, this );
            }

        };

        /**
        * handles events - delegates based on className
        * @param {Event} e
        * @return {void}
        * @method onviewevent
        */
        this.onviewevent = function(e) {
            OP.Log.info('[Util] ' + e.type + ': ' + e.target);

            var targ = e.target || e.srcElement,
                classes = targ.className.split(" "),
                length = classes.length,
                map = this.eventMap[e.type],
                cls;

            while (length--) {
                cls = classes[length];
                if (map !== undefined && map[cls]) {
                    //do not prevent the default action, let the callback
                    //function do it if it wants
                    map[cls](e, targ);
                }
            }
        };

        /**
        * handles keydown events
        * @param {Event} e
        * @return {void}
        * @method onkeydownevent
        */
        this.onkeydownevent = function(e) {
            OP.Log.info('[Util] ' + e.type + ': ' + e.target);

            var targ = e.target || e.srcElement,
                classes = targ.className.split(" "),
                length = classes.length,
                map = this.eventMap[e.type],
                nodeName = targ.nodeName.toLowerCase(),
                keyCode = e.keyCode,
                cls;

            if (nodeName === "textarea" || nodeName === "input") {

                //i don't think there is a case where the user needs to know
                //if the user is inputing text, but just in case, lets fire
                //a custom event on user input
                this.fire( 'keydown:user-input', e);

            } else {

                //the event map for key press can be two dimensional, it can be
                //keycode, or className then keyCode, if keyCode, fire the custom event
                if (typeof(map) === 'object' && map[keyCode]) {
                    this.fire( map[keyCode], e);
                }

                //both className and keycode
                while (length--) {
                    cls = classes[length];
                    if (map !== undefined && map[cls] && map[cls][keyCode]) {
                        //do not prevent the default action, let the callback
                        //function do it if it wants
                        this.fire( map[cls][keyCode], e);
                    }
                }

            }

        };

        /**
        * handles mouseover/mouseout events
        * @param {Event} e
        * @return {void}
        * @method onkeydownevent
        */
        this.onmouseevent = function(e) {

            //OP.Log.info('[Util] ' + e.type + ': ' + e.target);

            var targ = e.target || e.srcElement,
                classes = targ.className.split(" "),
                length = classes.length,
                map = this.eventMap[e.type],
                cls;

            while (length--) {
                cls = classes[length];
                if (map !== undefined && map[cls]) {
                    //do not prevent the default action, let the callback
                    //function do it if it wants
                    this.fire( map[cls], e);
                }
            }
        };

        /* -------------------------------------------------
        *         Utilities
        * ------------------------------------------------- */

        /**
        * Get the library plugin which will create a normalized interface
        * for the libraries so that they can be used properly. Onload of the
        * library plugin, we will attach our event listeners
        * @return {void}
        * @method getLibraryPlugin
        */
        this.getLibraryPlugin = function() {

            OP.Log.info('[Util] getLibraryPlugin');

            var url = this.config.baseUrl + this.config.jsLocation + PLUGIN_FILE_PREFIX + this.libType + ".js";

            //load the script and attach the event handlers onload
            this.loadScript(url, this._init, this);

        };

        /**
        * Shallow merge of all objects passed into it in order of the objects passed in
        * this is just needed to merge the config, but will probably be overwritten by
        * the library plugin
        * @return {object} merged object
        * @method merge
        */
        this.merge = function() {

            OP.Log.info('[Util] merge');

            var merged = {},
                i,
                j,
                obj,
                key;

            for (i=0, j=arguments.length; i<j; i++) {
                obj = arguments[i];
                for (key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        merged[key] = obj[key];
                    }
                }
            }

            return merged;

        };

        /**
        * Utility function to dynamically load a script
        * @param {string} url of the source of the script
        * @param {Function} fn the callback function to execute onload
        * @param {object} scope - the scope of the callback function
        * @return {void}
        * @method loadScript
        */
        this.loadScript = function(url, fn, scope) {

            OP.Log.info('[Util] loadScript');

            var head = document.getElementsByTagName('head')[0],
                script = document.createElement('script'),
                onload, onerror;

            onerror = function() {
              OP.Log.info('[Util] loadScript failed for ' + url);
            };

            script.type = "text/javascript";
            script.src = url;
            script.onerror = onerror;

            //callback function was specified - add the onload handlers
            if (typeof(fn) !== 'undefined') {

                scope = scope || window,
                onload = function() {
                    return fn.apply(scope);
                };
                script.onload = onload;

                // This is for IE
                script.onreadystatechange = function() {
                    if (this.readyState === 'complete' || this.readyState === 'loaded') {
                        onload();
                        script.onreadystatechange = null;
                    }
                }

            }

            head.appendChild(script);

        };

        /**
        * Utility function to dynamically load css
        * @param {string} url of the source of the sstylesheet
        * @param {Function} fn the callback function to execute onload
        * @param {object} scope - the scope of the callback function
        * @return {void}
        * @method loadScript
        */
        this.loadCss = function(url, fn, scope) {

            OP.Log.info('[Util] loadCss');

            var head = document.getElementsByTagName('head')[0],
                link = document.createElement('link'),
                callback;

            link.type = 'text/css';
            link.rel = 'stylesheet';
            link.href = url;

            //callback function was specified - add the onload handlers
            if (typeof(fn) !== 'undefined') {

                scope = scope || window,
                callback = function() {
                    return fn.apply(scope);
                };

                link.onload = callback;
                link.onreadystatechange = function() {
                    if (this.readyState === 'complete') {
                        callback();
                    }
                }

            }

            head.appendChild(link);

        };

        /**
        * The user can specify a callback to execute when all of the javascript assets
        * have loaded.  This helper method keeps track of the number of javascript assets
        * loaded and executes the onComplete callback when everything is loaded.
        * @return {void}
        * @method _handleScriptLoad
        */
        this._handleScriptLoad = function() {

            this._scriptLoadCount++;

            if ( (this._scriptLoadCount === this.config.js.assets.length) && (typeof(this.config.js.onComplete) !== 'undefined')) {
                this.config.js.onComplete();
            }

        }

        /**
        * The user can specify a callback to execute when all of the css assets
        * have loaded.  This helper method keeps track of the number of css assets
        * loaded and executes the onComplete callback when everything is loaded.
        * @return {void}
        * @method _handleCssLoad
        */
        this._handleCssLoad = function() {

            this._cssLoadCount++;

            if ( (this._cssLoadCount === this.config.css.assets.length) && (typeof(this.config.css.onComplete) !== 'undefined')) {
                this.config.css.onComplete();
            }

        }

        /**
        * Determines the library type - really simplistic rules for now
        * @return {string} library the library type
        * @method detectLibrary
        */
        this.detectLibrary = function() {

            //very simple for now, but we can extend it later
            var lib = '';

            //jQuery
            if ( typeof(jQuery) !== 'undefined' ) {
                lib = 'jquery';
            }  else {

                //YUI2
                if ( typeof(YAHOO) !== 'undefined' ) {
                    lib = 'yui2';
                } else {
                    lib = 'yui3';
                }

            }

            return lib;

        };

        /* -------------------------------------------------
        *         Custom Events
        * ------------------------------------------------- */

        /**
        * Subscribe to a custom event - the callback will be executed when the custom event is fired
        * @param {string} eventName - the name of the custom event to subscribe to
        * @param {Function} callback - the callback function that will be executed when the event is fired
        * @param {Object} scope - the scope of the callback function (what this will refer to)
        * @return {void}
        * @method on
        */
        this.on = function(eventName, callback, scope) {

            OP.Log.info('[Util] on: ' + eventName)

            var events = this._customEvents,
                cEvent = events[eventName],
                scope = scope || window,
                length;

            if (! cEvent ) {
                events[eventName] = [];
                cEvent = events[eventName];
            }

            //make sure the event doesn't exist already - if it does, return without
            //adding the event again
            length = cEvent.length;
            while(length--) {
                if (cEvent[length].fn === callback) {
                    return;
                }
            }

            cEvent[ cEvent.length ] = {
                fn: callback,
                scope: scope
            };

        };

        /**
        * A little less terse name, but removes an event listener if it exists
        * @param {string} eventName - the name of the custom event to unsubscribe from
        * @param {Function} callback - the callback function that would have been executed on fire of the custom event
        * @return {void}
        * @method unsubscribe
        */
        this.unsubscribe = function(eventName, callback) {

            OP.Log.info('[Util] unsubscribe: ' + callback);

            var events = this._customEvents,
                cEvent = events[eventName],
                length;

            if (!!cEvent) {
                length = cEvent.length;
                while (length--) {
                    if (cEvent[length].fn === callback) {
                        cEvent.splice(length, 1);
                        break;
                    }
                }
            }

        };

        /**
        * Fire a custom event - invoke all listeners passing whatever optional arguments
        * @param {string} eventName - name of the event to fire
        * @return {void}
        * @method fire
        */
        this.fire = function(eventName, arg) {

            OP.Log.info('[Util] fire: ' + eventName);

            var callbacks = this._customEvents[eventName],
                arg = arg || {},
                i, j;

            if (!!callbacks) {
                for ( i=0, j=callbacks.length; i<j; i++ ) {
                    callbacks[i].fn.call(callbacks[i].scope, arg);
                }
            }

        };
    }

    //store the util instance
    OP.Util = new Util();

}());
<commit_msg>Add a check to the 'onload' handler when loading scripts so it is not executed twice in IE<commit_after>/**
* Utility methods for OpenPhoto - this will include the entire framework
* And ability to swap frameworks (YUI or jQuery) in and out.
*/
(function() {

    // just make sure that OP, the global namespace for OpenPhoto
    // is defined
    if ( typeof(OP) === "undefined") {
        OP = {};
    }

    //constants
    var PLUGIN_FILE_PREFIX = 'openphoto-lib-';

    /**
    * Class that contains all utility functions for OpenPhoto
    * We can use a Constructor function in this case since we will
    * not have multiple instances of Util.  Also, it makes it easier to
    * extend via prototype.
    * @class Util
    */
    function Util() {

        /**
        * default configuration options
        * user can optionally specify an onComplete attribute in the css/js
        * object which will execute when the assets are loaded.
        * @type {object}
        * @property config
        */
        this.config = {
            baseUrl: '',
            jsLocation: '/assets/javascripts/',
            css: {
                assets: []
            },
            js: {
                assets:[]
            }
        };

        /**
        * the event map for click events, maps the HTML
        * classNames to the custom event names
        * @type {object}
        * @property eventMap
        */
        this.eventMap = {};

        /**
        * A hash of custom events
        * @type {object}
        * @property _customEvents
        */
        this._customEvents = {};

        /**
        * Count of the number of scripts loaded
        * @type {Number}
        * @property _scriptLoadCount
        */
        this._scriptLoadCount = 0;

        /**
        * Count of the number of css loaded
        * @type {Number}
        * @property _cssLoadCount
        */
        this._cssLoadCount = 0;

        /**
        * initialization method
        * @param {object} lib - the library to use
        * @param {object} config - the configuration object
        * @method init
        */
        this.init = function(lib, config) {

            OP.Log.info('[Util] init:');

            //merge the config with the user specified config
            this.config = this.merge(this.config, config);

            // we specify what library type in the .ini file
            // either jQuery or YUI - and then the user can load
            // additional css/js assets by specifying the files in the
            // js config - as specified by the plugin file (that will be user generated).

            //the library is a requirement, by default jQuery will be loaded
            this.lib = lib;
            this.libType = this.detectLibrary();

            // get the library plugin file that maps library functions to a normalized
            // naming so that we can use whatever library that is specified
            this.getLibraryPlugin();
        };

        this.addEventMap = function(eventMap) {
          this.eventMap = eventMap;
          for (var eType in this.eventMap) {
            if (this.eventMap.hasOwnProperty(eType)) {
              this.eventMap[eType] = this.merge(this.eventMap[eType], this.eventMap[eType]);
            }
          }
        };

        /**
        * Now that the library plugin has been loaded, we add all event handlers
        * @return {void}
        * @method _init
        */
        this._init = function() {

            OP.Log.info('[Util] _init:')

            var js = this.config.js.assets,
                css = this.config.css.assets,
                i,
                length;

            //attach events
            this.attachEvent( 'body', 'click', this.onviewevent, this);
            this.attachEvent( 'body', 'focus', this.onviewevent, this);
            this.attachEvent( 'body', 'change', this.onviewevent, this);
            this.attachEvent( 'body', 'mouseover', this.onmouseevent, this);
            this.attachEvent( 'body', 'mouseout', this.onmouseevent, this);
            this.attachEvent( 'html', 'keydown', this.onkeydownevent, this);
            this.attachEvent( 'html', 'keyup', this.onkeydownevent, this);
            this.attachEvent( 'form', 'submit', this.onviewevent, this);

            //load additional js in order specified
            for(i=0, j=js.length; i<j; i++) {
                this.loadScript( js[i], this._handleScriptLoad, this );
            }

            //load additional css in order specified
            for(i=0, j=css.length; i<j; i++) {
                this.loadCss( css[i], this._handleCssLoad, this );
            }

        };

        /**
        * handles events - delegates based on className
        * @param {Event} e
        * @return {void}
        * @method onviewevent
        */
        this.onviewevent = function(e) {
            OP.Log.info('[Util] ' + e.type + ': ' + e.target);

            var targ = e.target || e.srcElement,
                classes = targ.className.split(" "),
                length = classes.length,
                map = this.eventMap[e.type],
                cls;

            while (length--) {
                cls = classes[length];
                if (map !== undefined && map[cls]) {
                    //do not prevent the default action, let the callback
                    //function do it if it wants
                    map[cls](e, targ);
                }
            }
        };

        /**
        * handles keydown events
        * @param {Event} e
        * @return {void}
        * @method onkeydownevent
        */
        this.onkeydownevent = function(e) {
            OP.Log.info('[Util] ' + e.type + ': ' + e.target);

            var targ = e.target || e.srcElement,
                classes = targ.className.split(" "),
                length = classes.length,
                map = this.eventMap[e.type],
                nodeName = targ.nodeName.toLowerCase(),
                keyCode = e.keyCode,
                cls;

            if (nodeName === "textarea" || nodeName === "input") {

                //i don't think there is a case where the user needs to know
                //if the user is inputing text, but just in case, lets fire
                //a custom event on user input
                this.fire( 'keydown:user-input', e);

            } else {

                //the event map for key press can be two dimensional, it can be
                //keycode, or className then keyCode, if keyCode, fire the custom event
                if (typeof(map) === 'object' && map[keyCode]) {
                    this.fire( map[keyCode], e);
                }

                //both className and keycode
                while (length--) {
                    cls = classes[length];
                    if (map !== undefined && map[cls] && map[cls][keyCode]) {
                        //do not prevent the default action, let the callback
                        //function do it if it wants
                        this.fire( map[cls][keyCode], e);
                    }
                }

            }

        };

        /**
        * handles mouseover/mouseout events
        * @param {Event} e
        * @return {void}
        * @method onkeydownevent
        */
        this.onmouseevent = function(e) {

            //OP.Log.info('[Util] ' + e.type + ': ' + e.target);

            var targ = e.target || e.srcElement,
                classes = targ.className.split(" "),
                length = classes.length,
                map = this.eventMap[e.type],
                cls;

            while (length--) {
                cls = classes[length];
                if (map !== undefined && map[cls]) {
                    //do not prevent the default action, let the callback
                    //function do it if it wants
                    this.fire( map[cls], e);
                }
            }
        };

        /* -------------------------------------------------
        *         Utilities
        * ------------------------------------------------- */

        /**
        * Get the library plugin which will create a normalized interface
        * for the libraries so that they can be used properly. Onload of the
        * library plugin, we will attach our event listeners
        * @return {void}
        * @method getLibraryPlugin
        */
        this.getLibraryPlugin = function() {

            OP.Log.info('[Util] getLibraryPlugin');

            var url = this.config.baseUrl + this.config.jsLocation + PLUGIN_FILE_PREFIX + this.libType + ".js";

            //load the script and attach the event handlers onload
            this.loadScript(url, this._init, this);

        };

        /**
        * Shallow merge of all objects passed into it in order of the objects passed in
        * this is just needed to merge the config, but will probably be overwritten by
        * the library plugin
        * @return {object} merged object
        * @method merge
        */
        this.merge = function() {

            OP.Log.info('[Util] merge');

            var merged = {},
                i,
                j,
                obj,
                key;

            for (i=0, j=arguments.length; i<j; i++) {
                obj = arguments[i];
                for (key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        merged[key] = obj[key];
                    }
                }
            }

            return merged;

        };

        /**
        * Utility function to dynamically load a script
        * @param {string} url of the source of the script
        * @param {Function} fn the callback function to execute onload
        * @param {object} scope - the scope of the callback function
        * @return {void}
        * @method loadScript
        */
        this.loadScript = function(url, fn, scope) {

            OP.Log.info('[Util] loadScript');

            var head = document.getElementsByTagName('head')[0],
                script = document.createElement('script'),
                onload, onerror, loaded = false;

            onerror = function() {
              OP.Log.info('[Util] loadScript failed for ' + url);
            };

            script.type = "text/javascript";
            script.src = url;
            script.onerror = onerror;

            //callback function was specified - add the onload handlers
            if (typeof(fn) !== 'undefined') {

                scope = scope || window,
                onload = function() {
                    if( !loaded ) return fn.apply(scope);
                    return;
                };
                script.onload = onload;

                // This is for IE
                script.onreadystatechange = function() {
                    if (this.readyState === 'complete' || this.readyState === 'loaded') {
                        script.onreadystatechange = null;
                    }
                }

            }

            head.appendChild(script);

        };

        /**
        * Utility function to dynamically load css
        * @param {string} url of the source of the sstylesheet
        * @param {Function} fn the callback function to execute onload
        * @param {object} scope - the scope of the callback function
        * @return {void}
        * @method loadScript
        */
        this.loadCss = function(url, fn, scope) {

            OP.Log.info('[Util] loadCss');

            var head = document.getElementsByTagName('head')[0],
                link = document.createElement('link'),
                callback;

            link.type = 'text/css';
            link.rel = 'stylesheet';
            link.href = url;

            //callback function was specified - add the onload handlers
            if (typeof(fn) !== 'undefined') {

                scope = scope || window,
                callback = function() {
                    return fn.apply(scope);
                };

                link.onload = callback;
                link.onreadystatechange = function() {
                    if (this.readyState === 'complete') {
                        callback();
                    }
                }

            }

            head.appendChild(link);

        };

        /**
        * The user can specify a callback to execute when all of the javascript assets
        * have loaded.  This helper method keeps track of the number of javascript assets
        * loaded and executes the onComplete callback when everything is loaded.
        * @return {void}
        * @method _handleScriptLoad
        */
        this._handleScriptLoad = function() {

            this._scriptLoadCount++;

            if ( (this._scriptLoadCount === this.config.js.assets.length) && (typeof(this.config.js.onComplete) !== 'undefined')) {
                this.config.js.onComplete();
            }

        }

        /**
        * The user can specify a callback to execute when all of the css assets
        * have loaded.  This helper method keeps track of the number of css assets
        * loaded and executes the onComplete callback when everything is loaded.
        * @return {void}
        * @method _handleCssLoad
        */
        this._handleCssLoad = function() {

            this._cssLoadCount++;

            if ( (this._cssLoadCount === this.config.css.assets.length) && (typeof(this.config.css.onComplete) !== 'undefined')) {
                this.config.css.onComplete();
            }

        }

        /**
        * Determines the library type - really simplistic rules for now
        * @return {string} library the library type
        * @method detectLibrary
        */
        this.detectLibrary = function() {

            //very simple for now, but we can extend it later
            var lib = '';

            //jQuery
            if ( typeof(jQuery) !== 'undefined' ) {
                lib = 'jquery';
            }  else {

                //YUI2
                if ( typeof(YAHOO) !== 'undefined' ) {
                    lib = 'yui2';
                } else {
                    lib = 'yui3';
                }

            }

            return lib;

        };

        /* -------------------------------------------------
        *         Custom Events
        * ------------------------------------------------- */

        /**
        * Subscribe to a custom event - the callback will be executed when the custom event is fired
        * @param {string} eventName - the name of the custom event to subscribe to
        * @param {Function} callback - the callback function that will be executed when the event is fired
        * @param {Object} scope - the scope of the callback function (what this will refer to)
        * @return {void}
        * @method on
        */
        this.on = function(eventName, callback, scope) {

            OP.Log.info('[Util] on: ' + eventName)

            var events = this._customEvents,
                cEvent = events[eventName],
                scope = scope || window,
                length;

            if (! cEvent ) {
                events[eventName] = [];
                cEvent = events[eventName];
            }

            //make sure the event doesn't exist already - if it does, return without
            //adding the event again
            length = cEvent.length;
            while(length--) {
                if (cEvent[length].fn === callback) {
                    return;
                }
            }

            cEvent[ cEvent.length ] = {
                fn: callback,
                scope: scope
            };

        };

        /**
        * A little less terse name, but removes an event listener if it exists
        * @param {string} eventName - the name of the custom event to unsubscribe from
        * @param {Function} callback - the callback function that would have been executed on fire of the custom event
        * @return {void}
        * @method unsubscribe
        */
        this.unsubscribe = function(eventName, callback) {

            OP.Log.info('[Util] unsubscribe: ' + callback);

            var events = this._customEvents,
                cEvent = events[eventName],
                length;

            if (!!cEvent) {
                length = cEvent.length;
                while (length--) {
                    if (cEvent[length].fn === callback) {
                        cEvent.splice(length, 1);
                        break;
                    }
                }
            }

        };

        /**
        * Fire a custom event - invoke all listeners passing whatever optional arguments
        * @param {string} eventName - name of the event to fire
        * @return {void}
        * @method fire
        */
        this.fire = function(eventName, arg) {

            OP.Log.info('[Util] fire: ' + eventName);

            var callbacks = this._customEvents[eventName],
                arg = arg || {},
                i, j;

            if (!!callbacks) {
                for ( i=0, j=callbacks.length; i<j; i++ ) {
                    callbacks[i].fn.call(callbacks[i].scope, arg);
                }
            }

        };
    }

    //store the util instance
    OP.Util = new Util();

}());
<|endoftext|>


<commit_before>import CheddarError from '../../consts/err';

import CheddarNumber from '../Number';
import HelperInit from '../../../../helpers/init';

// == STRING ==
export default new Map([
    // Replace " with \"
    //  and replace \
    //  with \\s
    ['repr', (_, LHS) => {
        return HelperInit(LHS.constructor, '"' + LHS.value.replace(
            /"|\\/g, "\\$&"
        ) + '"');
    }],

    // String concatenation
    //  using +, attempt to
    //  implicitly cast
    ['+', (LHS, RHS) => {

        //NOTE: IMPLICIT
        //if (RHS.Cast.has(CheddarString))
        //    RHS = RHS.Cast.get(CheddarString)();

        if (RHS instanceof LHS.constructor)
            return HelperInit(LHS.constructor, LHS.value + RHS.value);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // Logical NOT
    //  if the string is
    //  empty
    ['!', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (LHS === null)
            return HelperInit(CheddarBool, RHS.value.length === 0);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // Comparisons
    //  compares the char
    //  codes of the strings
    ['<', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (RHS instanceof LHS.constructor)
            return HelperInit(CheddarBool, LHS.value < RHS.value);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    ['>', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (RHS instanceof LHS.constructor)
            return HelperInit(CheddarBool, LHS.value > RHS.value);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // has operator
    //  checks if substring
    //  is empty
    ['has', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (RHS instanceof LHS.constructor)
            return HelperInit(CheddarBool, LHS.value.includes(RHS.value));
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // String repetition
    //  when either Operator<*>.Behavior
    //  is (String, Number)
    //  or (Number, String)
    //  repeat <String> <Number> times
    ['*', (LHS, RHS) => {

        if (RHS.constructor.Name === "Number")
            return HelperInit(LHS.constructor, LHS.value.repeat(RHS.value));
        else
            return CheddarError.NO_OP_BEHAVIOR;

    }],

    ['@"', (LHS, RHS) => {
        let CheddarArray = require("../Array");
        if(LHS === null)
            return HelperInit(CheddarArray, ...[...RHS.value].map(x => HelperInit(CheddarNumber, 10, 0, x.charCodeAt())));
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }]
]);<commit_msg>fix [+]: Unary + on string would error<commit_after>import CheddarError from '../../consts/err';

import CheddarNumber from '../Number';
import HelperInit from '../../../../helpers/init';

// == STRING ==
export default new Map([
    // Replace " with \"
    //  and replace \
    //  with \\s
    ['repr', (_, LHS) => {
        return HelperInit(LHS.constructor, '"' + LHS.value.replace(
            /"|\\/g, "\\$&"
        ) + '"');
    }],

    // String concatenation
    //  using +, attempt to
    //  implicitly cast
    ['+', (LHS, RHS) => {

        //NOTE: IMPLICIT
        //if (RHS.Cast.has(CheddarString))
        //    RHS = RHS.Cast.get(CheddarString)();

        if (LHS && RHS instanceof LHS.constructor)
            return HelperInit(LHS.constructor, LHS.value + RHS.value);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // Logical NOT
    //  if the string is
    //  empty
    ['!', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (LHS === null)
            return HelperInit(CheddarBool, RHS.value.length === 0);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // Comparisons
    //  compares the char
    //  codes of the strings
    ['<', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (RHS instanceof LHS.constructor)
            return HelperInit(CheddarBool, LHS.value < RHS.value);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    ['>', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (RHS instanceof LHS.constructor)
            return HelperInit(CheddarBool, LHS.value > RHS.value);
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // has operator
    //  checks if substring
    //  is empty
    ['has', (LHS, RHS) => {
        let CheddarBool = require('../Bool');
        if (RHS instanceof LHS.constructor)
            return HelperInit(CheddarBool, LHS.value.includes(RHS.value));
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }],

    // String repetition
    //  when either Operator<*>.Behavior
    //  is (String, Number)
    //  or (Number, String)
    //  repeat <String> <Number> times
    ['*', (LHS, RHS) => {

        if (RHS.constructor.Name === "Number")
            return HelperInit(LHS.constructor, LHS.value.repeat(RHS.value));
        else
            return CheddarError.NO_OP_BEHAVIOR;

    }],

    ['@"', (LHS, RHS) => {
        let CheddarArray = require("../Array");
        if(LHS === null)
            return HelperInit(CheddarArray, ...[...RHS.value].map(x => HelperInit(CheddarNumber, 10, 0, x.charCodeAt())));
        else
            return CheddarError.NO_OP_BEHAVIOR;
    }]
]);<|endoftext|>