UNPKG

@sky-foundry/two.js

Version:

A renderer agnostic two-dimensional drawing api for the web.

309 lines (251 loc) 9.37 kB
(function(Two) { // Localized variables var commands = Two.Commands; var _ = Two.Utils; /** * @class * @name Two.Anchor * @param {Number} [x=0] - The x position of the root anchor point. * @param {Number} [y=0] - The y position of the root anchor point. * @param {Number} [lx=0] - The x position of the left handle point. * @param {Number} [ly=0] - The y position of the left handle point. * @param {Number} [rx=0] - The x position of the right handle point. * @param {Number} [ry=0] - The y position of the right handle point. * @param {String} [command=Two.Commands.move] - The command to describe how to render. Applicable commands are {@link Two.Commands} * @extends Two.Vector * @description An object that holds 3 `Two.Vector`s, the anchor point and its corresponding handles: `left` and `right`. In order to properly describe the bezier curve about the point there is also a command property to describe what type of drawing should occur when Two.js renders the anchors. */ var Anchor = Two.Anchor = function(x, y, lx, ly, rx, ry, command) { Two.Vector.call(this, x, y); this._broadcast = _.bind(function() { this.trigger(Two.Events.change); }, this); this._command = command || commands.move; this._relative = true; var ilx = _.isNumber(lx); var ily = _.isNumber(ly); var irx = _.isNumber(rx); var iry = _.isNumber(ry); // Append the `controls` object only if control points are specified, // keeping the Two.Anchor inline with a Two.Vector until it needs to // evolve beyond those functions — e.g: a simple 2 component vector. if (ilx || ily || irx || iry) { Two.Anchor.AppendCurveProperties(this); } if (ilx) { this.controls.left.x = lx; } if (ily) { this.controls.left.y = ly; } if (irx) { this.controls.right.x = rx; } if (iry) { this.controls.right.y = ry; } }; _.extend(Two.Anchor, { /** * @name Two.Anchor.AppendCurveProperties * @function * @param {Two.Anchor} anchor - The instance to append the `control`object to. * @description Adds the `controls` property as an object with `left` and `right` properties to access the bezier control handles that define how the curve is drawn. It also sets the `relative` property to `true` making vectors in the `controls` object relative to their corresponding root anchor point. */ AppendCurveProperties: function(anchor) { anchor.relative = true; /** * @name Two.Anchor#controls * @property {Object} controls * @description An plain object that holds the controls handles for a {@link Two.Anchor}. */ anchor.controls = {}; /** * @name Two.Anchor#controls#left * @property {Two.Vector} left * @description The "left" control point to define handles on a bezier curve. */ anchor.controls.left = new Two.Vector(0, 0); /** * @name Two.Anchor#controls#right * @property {Two.Vector} right * @description The "left" control point to define handles on a bezier curve. */ anchor.controls.right = new Two.Vector(0, 0); }, /** * @name Two.Anchor.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a `Two.Anchor` to any object. Handy if you'd like to extend the `Two.Anchor` class on a custom class. */ MakeObservable: function(object) { /** * @name Two.Anchor#command * @property {Two.Commands} * @description A draw command associated with the anchor point. */ Object.defineProperty(object, 'command', { enumerable: true, get: function() { return this._command; }, set: function(c) { this._command = c; if (this._command === commands.curve && !_.isObject(this.controls)) { Anchor.AppendCurveProperties(this); } return this.trigger(Two.Events.change); } }); /** * @name Two.Anchor#relative * @property {Boolean} * @description A boolean to render control points relative to the root anchor point or in global coordinate-space to the rest of the scene. */ Object.defineProperty(object, 'relative', { enumerable: true, get: function() { return this._relative; }, set: function(b) { if (this._relative == b) { return this; } this._relative = !!b; return this.trigger(Two.Events.change); } }); _.extend(object, Two.Vector.prototype, AnchorProto); // Make it possible to bind and still have the Anchor specific // inheritance from Two.Vector. In this case relying on `Two.Vector` // to do much of the heavy event-listener binding / unbinding. object.bind = object.on = function() { var bound = this._bound; Two.Vector.prototype.bind.apply(this, arguments); if (!bound) { _.extend(this, AnchorProto); } }; } }); var AnchorProto = { constructor: Two.Anchor, /** * @name Two.Anchor#listen * @function * @description Convenience method used mainly by {@link Two.Path#vertices} to listen and propagate changes from control points up to their respective anchors and further if necessary. */ listen: function() { if (!_.isObject(this.controls)) { Two.Anchor.AppendCurveProperties(this); } this.controls.left.bind(Two.Events.change, this._broadcast); this.controls.right.bind(Two.Events.change, this._broadcast); return this; }, /** * @name Two.Anchor#ignore * @function * @description Convenience method used mainly by {@link Two.Path#vertices} to ignore changes from a specific anchor's control points. */ ignore: function() { this.controls.left.unbind(Two.Events.change, this._broadcast); this.controls.right.unbind(Two.Events.change, this._broadcast); return this; }, /** * @name Two.Anchor#copy * @function * @param {Two.Anchor} v - The anchor to apply values to. * @description Copy the properties of one {@link Two.Anchor} onto another. */ copy: function(v) { this.x = v.x; this.y = v.y; if (_.isString(v.command)) { this.command = v.command; } if (_.isObject(v.controls)) { if (!_.isObject(this.controls)) { Two.Anchor.AppendCurveProperties(this); } // TODO: Do we need to listen here? this.controls.left.copy(v.controls.left); this.controls.right.copy(v.controls.right); } if (_.isBoolean(v.relative)) { this.relative = v.relative; } // TODO: Hack for `Two.Commands.arc` if (this.command === Two.Commands.arc) { this.rx = v.rx; this.ry = v.ry; this.xAxisRotation = v.xAxisRotation; this.largeArcFlag = v.largeArcFlag; this.sweepFlag = v.sweepFlag; } return this; }, /** * @name Two.Anchor#clone * @function * @returns {Two.Anchor} * @description Create a new {@link Two.Anchor}, set all its values to the current instance and return it for use. */ clone: function() { var controls = this.controls; var clone = new Two.Anchor( this.x, this.y, controls && controls.left.x, controls && controls.left.y, controls && controls.right.x, controls && controls.right.y, this.command ); clone.relative = this._relative; return clone; }, /** * @name Two.Anchor#toObject * @function * @returns {Object} - An object with properties filled out to mirror {@link Two.Anchor}. * @description Create a JSON compatible plain object of the current instance. Intended for use with storing values in a database. */ toObject: function() { var o = { x: this.x, y: this.y }; if (this._command) { o.command = this._command; } if (this._relative) { o.relative = this._relative; } if (this.controls) { o.controls = { left: this.controls.left.toObject(), right: this.controls.right.toObject() }; } return o; }, /** * @name Two.Anchor#toString * @function * @returns {String} - A String with comma-separated values reflecting the various values on the current instance. * @description Create a string form of the current instance. Intended for use with storing values in a database. This is lighter to store than the JSON compatible {@link Two.Anchor#toObject}. */ toString: function() { if (!this.controls) { return [this._x, this._y].join(', '); } return [this._x, this._y, this.controls.left.x, this.controls.left.y, this.controls.right.x, this.controls.right.y, this._command, this._relative ? 1 : 0].join(', '); } }; Two.Anchor.MakeObservable(Two.Anchor.prototype); })((typeof global !== 'undefined' ? global : (this || window)).Two);