@sky-foundry/two.js
Version:
A renderer agnostic two-dimensional drawing api for the web.
277 lines (219 loc) • 7.44 kB
JavaScript
(function(Two) {
var _ = Two.Utils;
/**
* @name Two.Shape
* @class
* @extends Two.Utils.Events
* @description The foundational transformation object for the Two.js scenegraph.
*/
var Shape = Two.Shape = function() {
/**
* @name Two.Shape#_renderer
* @property {Object}
* @private
* @description A private object to store relevant renderer specific variables.
* @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape._renderer.elem`.
*/
this._renderer = {};
this._renderer.flagMatrix = _.bind(Shape.FlagMatrix, this);
this.isShape = true;
/**
* @name Two.Shape#id
* @property {String} - Session specific unique identifier.
* @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too.
*/
this.id = Two.Identifier + Two.uniqueId();
/**
* @name Two.Shape#classList
* @property {Array}
* @description A list of class strings stored if imported / interpreted from an SVG element.
*/
this.classList = [];
/**
* @name Two.Shape#_matrix
* @property
* @description The transformation matrix of the shape.
* @nota-bene {@link Two.Shape#translation}, {@link Two.Shape#rotation}, and {@link Two.Shape#scale} apply their values to the matrix when changed. The matrix is what is sent to the renderer to be drawn.
*/
this._matrix = new Two.Matrix();
/**
* @name Two.Shape#translation
* @property {Two.Vector} - The x and y value for where the shape is placed relative to its parent.
*/
this.translation = new Two.Vector();
/**
* @name Two.Shape#rotation
* @property {Radians} - The value in radians for how much the shape is rotated relative to its parent.
*/
this.rotation = 0;
/**
* @name Two.Shape#scale
* @property {Number} - The value for how much the shape is scaled relative to its parent.
* @nota-bene This value can be replaced with a {@link Two.Vector} to do non-uniform scaling. e.g: `shape.scale = new Two.Vector(2, 1);`
*/
this.scale = 1;
};
_.extend(Shape, {
/**
* @name Two.Shape.FlagMatrix
* @function
* @description Utility function used in conjunction with event handlers to update the flagMatrix of a shape.
*/
FlagMatrix: function() {
this._flagMatrix = true;
},
/**
* @name Two.Shape.MakeObservable
* @function
* @param {Object} object - The object to make observable.
* @description Convenience function to apply observable qualities of a `Two.Shape` to any object. Handy if you'd like to extend the `Two.Shape` class on a custom class.
*/
MakeObservable: function(object) {
Object.defineProperty(object, 'translation', {
enumerable: true,
get: function() {
return this._translation;
},
set: function(v) {
if (this._translation) {
this._translation.unbind(Two.Events.change, this._renderer.flagMatrix);
}
this._translation = v;
this._translation.bind(Two.Events.change, this._renderer.flagMatrix);
Shape.FlagMatrix.call(this);
}
});
Object.defineProperty(object, 'rotation', {
enumerable: true,
get: function() {
return this._rotation;
},
set: function(v) {
this._rotation = v;
this._flagMatrix = true;
}
});
Object.defineProperty(object, 'scale', {
enumerable: true,
get: function() {
return this._scale;
},
set: function(v) {
if (this._scale instanceof Two.Vector) {
this._scale.unbind(Two.Events.change, this._renderer.flagMatrix);
}
this._scale = v;
if (this._scale instanceof Two.Vector) {
this._scale.bind(Two.Events.change, this._renderer.flagMatrix);
}
this._flagMatrix = true;
this._flagScale = true;
}
});
}
});
_.extend(Shape.prototype, Two.Utils.Events, {
// Flags
/**
* @name Two.Shape#_flagMatrix
* @private
* @property {Boolean} - Determines whether the matrix needs updating.
*/
_flagMatrix: true,
/**
* @name Two.Shape#_flagScale
* @private
* @property {Boolean} - Determines whether the scale needs updating.
*/
_flagScale: false,
// _flagMask: false,
// _flagClip: false,
// Underlying Properties
/**
* @name Two.Shape#_translation
* @private
* @property {Two.Vector} - The translation values as a {@link Two.Vector}.
*/
_translation: null,
/**
* @name Two.Shape#_rotation
* @private
* @property {Radians} - The rotation value in radians.
*/
_rotation: 0,
/**
* @name Two.Shape#_translation
* @private
* @property {Two.Vector} - The translation values as a {@link Two.Vector}.
*/
_scale: 1,
// _mask: null,
// _clip: false,
constructor: Shape,
/**
* @name Two.Shape#addTo
* @function
* @param {Two.Group} group - The parent the shape adds itself to.
* @description Convenience method to add itself to the scenegraph.
*/
addTo: function(group) {
group.add(this);
return this;
},
/**
* @name Two.Shape#clone
* @function
* @param {Two.Group} [parent] - Optional argument to automatically add the shape to a scenegraph.
* @returns {Two.Shape}
* @description Create a new `Two.Shape` with the same values as the current shape.
*/
clone: function(parent) {
var clone = new Shape();
clone.translation.copy(this.translation);
clone.rotation = this.rotation;
clone.scale = this.scale;
if (parent) {
parent.add(clone);
}
return clone._update();
},
/**
* @name Two.Shape#_update
* @function
* @private
* @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
* @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
* @nota-bene Try not to call this method more than once a frame.
*/
_update: function(bubbles) {
if (!this._matrix.manual && this._flagMatrix) {
this._matrix
.identity()
.translate(this.translation.x, this.translation.y);
if (this._scale instanceof Two.Vector) {
this._matrix.scale(this._scale.x, this._scale.y);
} else {
this._matrix.scale(this._scale);
}
this._matrix.rotate(this.rotation);
}
if (bubbles) {
if (this.parent && this.parent._update) {
this.parent._update();
}
}
return this;
},
/**
* @name Two.Shape#flagReset
* @function
* @private
* @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
*/
flagReset: function() {
this._flagMatrix = this._flagScale = false;
return this;
}
});
Shape.MakeObservable(Shape.prototype);
})((typeof global !== 'undefined' ? global : (this || window)).Two);