fabric.js/src/canvas.class.js

1096 lines
No EOL
32 KiB
JavaScript

(function() {
var extend = fabric.util.object.extend,
getPointer = fabric.util.getPointer,
addListener = fabric.util.addListener,
removeListener = fabric.util.removeListener,
cursorMap = {
'tr': 'ne-resize',
'br': 'se-resize',
'bl': 'sw-resize',
'tl': 'nw-resize',
'ml': 'w-resize',
'mt': 'n-resize',
'mr': 'e-resize',
'mb': 's-resize'
},
utilMin = fabric.util.array.min,
utilMax = fabric.util.array.max,
sqrt = Math.sqrt,
pow = Math.pow,
atan2 = Math.atan2,
abs = Math.abs,
min = Math.min,
max = Math.max,
STROKE_OFFSET = 0.5;
/**
* @class fabric.Canvas
* @constructor
* @extends fabric.StaticCanvas
* @param {HTMLElement | String} el <canvas> element to initialize instance on
* @param {Object} [options] Options object
*/
fabric.Canvas = function(el, options) {
options || (options = { });
this._initStatic(el, options);
this._initInteractive();
fabric.Canvas.activeInstance = this;
};
function ProtoProxy(){ }
ProtoProxy.prototype = fabric.StaticCanvas.prototype;
fabric.Canvas.prototype = new ProtoProxy;
var InteractiveMethods = /** @scope fabric.Canvas.prototype */ {
/**
* Indicates that canvas is interactive. This property should not be changed.
* @property
* @type Boolean
*/
interactive: true,
/**
* Indicates whether group selection should be enabled
* @property
* @type Boolean
*/
selection: true,
/**
* Color of selection
* @property
* @type String
*/
selectionColor: 'rgba(100, 100, 255, 0.3)', // blue
/**
* Color of the border of selection (usually slightly darker than color of selection itself)
* @property
* @type String
*/
selectionBorderColor: 'rgba(255, 255, 255, 0.3)',
/**
* Width of a line used in object/group selection
* @property
* @type Number
*/
selectionLineWidth: 1,
/**
* Color of the line used in free drawing mode
* @property
* @type String
*/
freeDrawingColor: 'rgb(0, 0, 0)',
/**
* Width of a line used in free drawing mode
* @property
* @type Number
*/
freeDrawingLineWidth: 1,
/**
* Default cursor value used when hovering over an object on canvas
* @constant
* @type String
*/
HOVER_CURSOR: 'move',
/**
* Default cursor value used for the entire canvas
* @constant
* @type String
*/
CURSOR: 'default',
/**
* Default element class that's given to wrapper (div) element of canvas
* @constant
* @type String
*/
CONTAINER_CLASS: 'canvas-container',
_initInteractive: function() {
this._currentTransform = null;
this._groupSelector = null;
this._freeDrawingXPoints = [ ];
this._freeDrawingYPoints = [ ];
this._initWrapperElement();
this._createUpperCanvas();
this._initEvents();
this.calcOffset();
},
/**
* Adds mouse listeners to canvas
* @method _initEvents
* @private
* See configuration documentation for more details.
*/
_initEvents: function () {
var _this = this;
this._onMouseDown = function (e) {
_this.__onMouseDown(e);
addListener(fabric.document, 'mouseup', _this._onMouseUp);
fabric.isTouchSupported && addListener(fabric.document, 'touchend', _this._onMouseUp);
addListener(fabric.document, 'mousemove', _this._onMouseMove);
fabric.isTouchSupported && addListener(fabric.document, 'touchmove', _this._onMouseMove);
removeListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove);
fabric.isTouchSupported && removeListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove);
};
this._onMouseUp = function (e) {
_this.__onMouseUp(e);
removeListener(fabric.document, 'mouseup', _this._onMouseUp);
fabric.isTouchSupported && removeListener(fabric.document, 'touchend', _this._onMouseUp);
removeListener(fabric.document, 'mousemove', _this._onMouseMove);
fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', _this._onMouseMove);
addListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove);
fabric.isTouchSupported && addListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove);
};
this._onMouseMove = function (e) {
e.preventDefault && e.preventDefault();
_this.__onMouseMove(e);
};
this._onResize = function (e) {
_this.calcOffset();
};
addListener(fabric.window, 'resize', this._onResize);
if (fabric.isTouchSupported) {
addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
}
else {
addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
}
},
/**
* Method that defines the actions when mouse is released on canvas.
* The method resets the currentTransform parameters, store the image corner
* position in the image object and render the canvas on top.
* @method __onMouseUp
* @param {Event} e Event object fired on mouseup
*
*/
__onMouseUp: function (e) {
if (this.isDrawingMode && this._isCurrentlyDrawing) {
this._finalizeDrawingPath();
return;
}
if (this._currentTransform) {
var transform = this._currentTransform,
target = transform.target;
if (target._scaling) {
target._scaling = false;
}
// determine the new coords everytime the image changes its position
var i = this._objects.length;
while (i--) {
this._objects[i].setCoords();
}
// only fire :modified event if target coordinates were changed during mousedown-mouseup
if (this.stateful && target.hasStateChanged()) {
target.isMoving = false;
this.fire('object:modified', { target: target });
}
}
this._currentTransform = null;
if (this._groupSelector) {
// group selection was completed, determine its bounds
this._findSelectedObjects(e);
}
var activeGroup = this.getActiveGroup();
if (activeGroup) {
activeGroup.setObjectsCoords();
activeGroup.set('isMoving', false);
this._setCursor(this.CURSOR);
}
// clear selection
this._groupSelector = null;
this.renderAll();
this._setCursorFromEvent(e, target);
// fix for FF
this._setCursor('');
var _this = this;
setTimeout(function () {
_this._setCursorFromEvent(e, target);
}, 50);
this.fire('mouse:up', { target: target, e: e });
},
/**
* Method that defines the actions when mouse is clic ked on canvas.
* The method inits the currentTransform parameters and renders all the
* canvas so the current image can be placed on the top canvas and the rest
* in on the container one.
* @method __onMouseDown
* @param e {Event} Event object fired on mousedown
*
*/
__onMouseDown: function (e) {
// accept only left clicks
var isLeftClick = 'which' in e ? e.which == 1 : e.button == 1;
if (!isLeftClick && !fabric.isTouchSupported) return;
if (this.isDrawingMode) {
this._prepareForDrawing(e);
// capture coordinates immediately; this allows to draw dots (when movement never occurs)
this._captureDrawingPath(e);
return;
}
// ignore if some object is being transformed at this moment
if (this._currentTransform) return;
var target = this.findTarget(e),
pointer = this.getPointer(e),
activeGroup = this.getActiveGroup(),
corner;
if (this._shouldClearSelection(e)) {
this._groupSelector = {
ex: pointer.x,
ey: pointer.y,
top: 0,
left: 0
};
this.deactivateAllWithDispatch();
}
else {
// determine if it's a drag or rotate case
// rotate and scale will happen at the same time
this.stateful && target.saveState();
if (corner = target._findTargetCorner(e, this._offset)) {
this.onBeforeScaleRotate(target);
}
this._setupCurrentTransform(e, target);
var shouldHandleGroupLogic = e.shiftKey && (activeGroup || this.getActiveObject());
if (shouldHandleGroupLogic) {
this._handleGroupLogic(e, target);
}
else {
if (target !== this.getActiveGroup()) {
this.deactivateAll();
}
this.setActiveObject(target, e);
}
}
// we must renderAll so that active image is placed on the top canvas
this.renderAll();
this.fire('mouse:down', { target: target, e: e });
},
/**
* Method that defines the actions when mouse is hovering the canvas.
* The currentTransform parameter will definde whether the user is rotating/scaling/translating
* an image or neither of them (only hovering). A group selection is also possible and would cancel
* all any other type of action.
* In case of an image transformation only the top canvas will be rendered.
* @method __onMouseMove
* @param e {Event} Event object fired on mousemove
*
*/
__onMouseMove: function (e) {
if (this.isDrawingMode) {
if (this._isCurrentlyDrawing) {
this._captureDrawingPath(e);
}
return;
}
var groupSelector = this._groupSelector;
// We initially clicked in an empty area, so we draw a box for multiple selection.
if (groupSelector !== null) {
var pointer = getPointer(e);
groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
this.renderTop();
}
else if (!this._currentTransform) {
// alias style to elimintate unnecessary lookup
var style = this.upperCanvasEl.style;
// Here we are hovering the canvas then we will determine
// what part of the pictures we are hovering to change the caret symbol.
// We won't do that while dragging or rotating in order to improve the
// performance.
var target = this.findTarget(e);
if (!target) {
// image/text was hovered-out from, we remove its borders
for (var i = this._objects.length; i--; ) {
if (this._objects[i] && !this._objects[i].active) {
this._objects[i].setActive(false);
}
}
style.cursor = this.CURSOR;
}
else {
// set proper cursor
this._setCursorFromEvent(e, target);
if (target.isActive()) {
// display corners when hovering over an image
target.setCornersVisibility && target.setCornersVisibility(true);
}
}
}
else {
// object is being transformed (scaled/rotated/moved/etc.)
var pointer = getPointer(e),
x = pointer.x,
y = pointer.y;
this._currentTransform.target.isMoving = true;
if (this._currentTransform.action === 'rotate') {
// rotate object only if shift key is not pressed
// and if it is not a group we are transforming
if (!e.shiftKey) {
this._rotateObject(x, y);
this.fire('object:rotating', {
target: this._currentTransform.target
});
}
this._scaleObject(x, y);
this.fire('object:scaling', {
target: this._currentTransform.target
});
}
else if (this._currentTransform.action === 'scaleX') {
this._scaleObject(x, y, 'x');
this.fire('object:scaling', {
target: this._currentTransform.target
});
}
else if (this._currentTransform.action === 'scaleY') {
this._scaleObject(x, y, 'y');
this.fire('object:scaling', {
target: this._currentTransform.target
});
}
else {
this._translateObject(x, y);
this.fire('object:moving', {
target: this._currentTransform.target
});
}
// only commit here. when we are actually moving the pictures
this.renderAll();
}
this.fire('mouse:move', { target: target, e: e });
},
/**
* Applies one implementation of 'point inside polygon' algorithm
* @method containsPoint
* @param e { Event } event object
* @param target { fabric.Object } object to test against
* @return {Boolean} true if point contains within area of given object
*/
containsPoint: function (e, target) {
var pointer = this.getPointer(e),
xy = this._normalizePointer(target, pointer),
x = xy.x,
y = xy.y;
// http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
// http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
// we iterate through each object. If target found, return it.
var iLines = target._getImageLines(target.oCoords),
xpoints = target._findCrossPoints(x, y, iLines);
// if xcount is odd then we clicked inside the object
// For the specific case of square images xcount === 1 in all true cases
if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) {
return true;
}
return false;
},
/**
* @private
* @method _normalizePointer
*/
_normalizePointer: function (object, pointer) {
var activeGroup = this.getActiveGroup(),
x = pointer.x,
y = pointer.y;
var isObjectInGroup = (
activeGroup &&
object.type !== 'group' &&
activeGroup.contains(object)
);
if (isObjectInGroup) {
x -= activeGroup.left;
y -= activeGroup.top;
}
return { x: x, y: y };
},
/**
* @private
* @method _shouldClearSelection
*/
_shouldClearSelection: function (e) {
var target = this.findTarget(e),
activeGroup = this.getActiveGroup();
return (
!target || (
target &&
activeGroup &&
!activeGroup.contains(target) &&
activeGroup !== target &&
!e.shiftKey
)
);
},
/**
* @private
* @method _setupCurrentTransform
*/
_setupCurrentTransform: function (e, target) {
var action = 'drag',
corner,
pointer = getPointer(e);
if (corner = target._findTargetCorner(e, this._offset)) {
action = (corner === 'ml' || corner === 'mr')
? 'scaleX'
: (corner === 'mt' || corner === 'mb')
? 'scaleY'
: 'rotate';
}
this._currentTransform = {
target: target,
action: action,
scaleX: target.scaleX,
scaleY: target.scaleY,
offsetX: pointer.x - target.left,
offsetY: pointer.y - target.top,
ex: pointer.x,
ey: pointer.y,
left: target.left,
top: target.top,
theta: target.theta,
width: target.width * target.scaleX
};
this._currentTransform.original = {
left: target.left,
top: target.top
};
},
_handleGroupLogic: function (e, target) {
if (target.isType('group')) {
// if it's a group, find target again, this time skipping group
target = this.findTarget(e, true);
// if even object is not found, bail out
if (!target || target.isType('group')) {
return;
}
}
var activeGroup = this.getActiveGroup();
if (activeGroup) {
if (activeGroup.contains(target)) {
activeGroup.remove(target);
target.setActive(false);
if (activeGroup.size() === 1) {
// remove group alltogether if after removal it only contains 1 object
this.discardActiveGroup();
}
}
else {
activeGroup.add(target);
}
this.fire('selection:created', { target: activeGroup, e: e });
activeGroup.setActive(true);
}
else {
// group does not exist
if (this._activeObject) {
// only if there's an active object
if (target !== this._activeObject) {
// and that object is not the actual target
var group = new fabric.Group([ this._activeObject,target ]);
this.setActiveGroup(group);
activeGroup = this.getActiveGroup();
}
}
// activate target object in any case
target.setActive(true);
}
if (activeGroup) {
activeGroup.saveCoords();
}
},
/**
* @private
* @method _prepareForDrawing
*/
_prepareForDrawing: function(e) {
this._isCurrentlyDrawing = true;
this.discardActiveObject().renderAll();
var pointer = this.getPointer(e);
this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
this._freeDrawingXPoints.push(pointer.x);
this._freeDrawingYPoints.push(pointer.y);
this.contextTop.beginPath();
this.contextTop.moveTo(pointer.x, pointer.y);
this.contextTop.strokeStyle = this.freeDrawingColor;
this.contextTop.lineWidth = this.freeDrawingLineWidth;
this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
},
/**
* @private
* @method _captureDrawingPath
*/
_captureDrawingPath: function(e) {
var pointer = this.getPointer(e);
this._freeDrawingXPoints.push(pointer.x);
this._freeDrawingYPoints.push(pointer.y);
this.contextTop.lineTo(pointer.x, pointer.y);
this.contextTop.stroke();
},
/**
* @private
* @method _finalizeDrawingPath
*/
_finalizeDrawingPath: function() {
this.contextTop.closePath();
this._isCurrentlyDrawing = false;
var minX = utilMin(this._freeDrawingXPoints),
minY = utilMin(this._freeDrawingYPoints),
maxX = utilMax(this._freeDrawingXPoints),
maxY = utilMax(this._freeDrawingYPoints),
ctx = this.contextTop,
path = [ ],
xPoint,
yPoint,
xPoints = this._freeDrawingXPoints,
yPoints = this._freeDrawingYPoints;
path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
for (var i = 1; xPoint = xPoints[i], yPoint = yPoints[i]; i++) {
path.push('L ', xPoint - minX, ' ', yPoint - minY, ' ');
}
// TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path,
// and instead fire something like "drawing:completed" event with path string
path = path.join('');
if (path === "M 0 0 L 0 0 ") {
// do not create 0 width/height paths, as they are rendered inconsistently across browsers
// Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
return;
}
var p = new fabric.Path(path);
p.fill = null;
p.stroke = this.freeDrawingColor;
p.strokeWidth = this.freeDrawingLineWidth;
this.add(p);
p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
this.renderAll();
this.fire('path:created', { path: p });
},
/**
* Translates object by "setting" its left/top
* @method _translateObject
* @param x {Number} pointer's x coordinate
* @param y {Number} pointer's y coordinate
*/
_translateObject: function (x, y) {
var target = this._currentTransform.target;
target.lockMovementX || target.set('left', x - this._currentTransform.offsetX);
target.lockMovementY || target.set('top', y - this._currentTransform.offsetY);
},
/**
* Scales object by invoking its scaleX/scaleY methods
* @method _scaleObject
* @param x {Number} pointer's x coordinate
* @param y {Number} pointer's y coordinate
* @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
* When not provided, an object is scaled by both dimensions equally
*/
_scaleObject: function (x, y, by) {
var t = this._currentTransform,
offset = this._offset,
target = t.target;
if (target.lockScalingX && target.lockScalingY) return;
var lastLen = sqrt(pow(t.ey - t.top - offset.top, 2) + pow(t.ex - t.left - offset.left, 2)),
curLen = sqrt(pow(y - t.top - offset.top, 2) + pow(x - t.left - offset.left, 2));
target._scaling = true;
if (!by) {
target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen);
target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen);
}
else if (by === 'x' && !target.lockUniScaling) {
target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen);
}
else if (by === 'y' && !target.lockUniScaling) {
target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen);
}
},
/**
* Rotates object by invoking its rotate method
* @method _rotateObject
* @param x {Number} pointer's x coordinate
* @param y {Number} pointer's y coordinate
*/
_rotateObject: function (x, y) {
var t = this._currentTransform,
o = this._offset;
if (t.target.lockRotation) return;
var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left),
curAngle = atan2(y - t.top - o.top, x - t.left - o.left);
t.target.set('theta', (curAngle - lastAngle) + t.theta);
},
/**
* @method _setCursor
*/
_setCursor: function (value) {
this.upperCanvasEl.style.cursor = value;
},
/**
* Sets the cursor depending on where the canvas is being hovered.
* Note: very buggy in Opera
* @method _setCursorFromEvent
* @param e {Event} Event object
* @param target {Object} Object that the mouse is hovering, if so.
*/
_setCursorFromEvent: function (e, target) {
var s = this.upperCanvasEl.style;
if (!target) {
s.cursor = this.CURSOR;
return false;
}
else {
var activeGroup = this.getActiveGroup();
// only show proper corner when group selection is not active
var corner = !!target._findTargetCorner
&& (!activeGroup || !activeGroup.contains(target))
&& target._findTargetCorner(e, this._offset);
if (!corner) {
s.cursor = this.HOVER_CURSOR;
}
else {
if (corner in cursorMap) {
s.cursor = cursorMap[corner];
}
else {
s.cursor = this.CURSOR;
return false;
}
}
}
return true;
},
/**
* @method _drawSelection
* @private
*/
_drawSelection: function () {
var groupSelector = this._groupSelector,
left = groupSelector.left,
top = groupSelector.top,
aleft = abs(left),
atop = abs(top);
this.contextTop.fillStyle = this.selectionColor;
this.contextTop.fillRect(
groupSelector.ex - ((left > 0) ? 0 : -left),
groupSelector.ey - ((top > 0) ? 0 : -top),
aleft,
atop
);
this.contextTop.lineWidth = this.selectionLineWidth;
this.contextTop.strokeStyle = this.selectionBorderColor;
this.contextTop.strokeRect(
groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
aleft,
atop
);
},
_findSelectedObjects: function (e) {
var target,
targetRegion,
group = [ ],
x1 = this._groupSelector.ex,
y1 = this._groupSelector.ey,
x2 = x1 + this._groupSelector.left,
y2 = y1 + this._groupSelector.top,
currentObject,
selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2));
for (var i = 0, len = this._objects.length; i < len; ++i) {
currentObject = this._objects[i];
if (!currentObject) continue;
if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) {
if (this.selection && currentObject.selectable) {
currentObject.setActive(true);
group.push(currentObject);
}
}
}
// do not create group for 1 element only
if (group.length === 1) {
this.setActiveObject(group[0], e);
}
else if (group.length > 1) {
var group = new fabric.Group(group);
this.setActiveGroup(group);
group.saveCoords();
this.fire('selection:created', { target: group });
}
this.renderAll();
},
/**
* Method that determines what object we are clicking on
* @method findTarget
* @param {Event} e mouse event
* @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
*/
findTarget: function (e, skipGroup) {
var target,
pointer = this.getPointer(e);
// first check current group (if one exists)
var activeGroup = this.getActiveGroup();
if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
target = activeGroup;
return target;
}
// then check all of the objects on canvas
for (var i = this._objects.length; i--; ) {
if (this._objects[i] && this.containsPoint(e, this._objects[i])) {
target = this._objects[i];
this.relatedTarget = target;
break;
}
}
if (target && target.selectable) {
return target;
}
},
/**
* Returns pointer coordinates relative to canvas.
* @method getPointer
* @return {Object} object with "x" and "y" number values
*/
getPointer: function (e) {
var pointer = getPointer(e);
return {
x: pointer.x - this._offset.left,
y: pointer.y - this._offset.top
};
},
/**
* @method _createUpperCanvas
* @param {HTMLElement|String} canvasEl Canvas element
* @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
*/
_createUpperCanvas: function () {
this.upperCanvasEl = this._createCanvasElement();
this.upperCanvasEl.className = 'upper-canvas';
this.wrapperEl.appendChild(this.upperCanvasEl);
this._applyCanvasStyle(this.upperCanvasEl);
this.contextTop = this.upperCanvasEl.getContext('2d');
},
/**
* @private
* @method _initWrapperElement
* @param {Number} width
* @param {Number} height
*/
_initWrapperElement: function () {
this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
'class': this.CONTAINER_CLASS
});
fabric.util.setStyle(this.wrapperEl, {
width: this.getWidth() + 'px',
height: this.getHeight() + 'px',
position: 'relative'
});
fabric.util.makeElementUnselectable(this.wrapperEl);
},
/**
* @private
* @method _applyCanvasStyle
* @param {Element} element
*/
_applyCanvasStyle: function (element) {
var width = this.getWidth() || element.width,
height = this.getHeight() || element.height;
fabric.util.setStyle(element, {
position: 'absolute',
width: width + 'px',
height: height + 'px',
left: 0,
top: 0
});
element.width = width;
element.height = height;
fabric.util.makeElementUnselectable(element);
},
/**
* Returns topmost canvas context
* @method getContext
* @return {CanvasRenderingContext2D}
*/
getContext: function () {
return this.contextTop;
},
/**
* Sets given object as active
* @method setActiveObject
* @param object {fabric.Object} Object to set as an active one
* @return {fabric.Canvas} thisArg
* @chainable
*/
setActiveObject: function (object, e) {
if (this._activeObject) {
this._activeObject.setActive(false);
}
this._activeObject = object;
object.setActive(true);
this.renderAll();
this.fire('object:selected', { target: object, e: e });
return this;
},
/**
* Returns currently active object
* @method getActiveObject
* @return {fabric.Object} active object
*/
getActiveObject: function () {
return this._activeObject;
},
/**
* Discards currently active object
* @method discardActiveObject
* @return {fabric.Canvas} thisArg
* @chainable
*/
discardActiveObject: function () {
if (this._activeObject) {
this._activeObject.setActive(false);
}
this._activeObject = null;
return this;
},
/**
* Sets active group to a speicified one
* @method setActiveGroup
* @param {fabric.Group} group Group to set as a current one
* @return {fabric.Canvas} thisArg
* @chainable
*/
setActiveGroup: function (group) {
this._activeGroup = group;
return this;
},
/**
* Returns currently active group
* @method getActiveGroup
* @return {fabric.Group} Current group
*/
getActiveGroup: function () {
return this._activeGroup;
},
/**
* Removes currently active group
* @method discardActiveGroup
* @return {fabric.Canvas} thisArg
*/
discardActiveGroup: function () {
var g = this.getActiveGroup();
if (g) {
g.destroy();
}
return this.setActiveGroup(null);
},
/**
* Deactivates all objects by calling their setActive(false)
* @method deactivateAll
* @return {fabric.Canvas} thisArg
*/
deactivateAll: function () {
var allObjects = this.getObjects(),
i = 0,
len = allObjects.length;
for ( ; i < len; i++) {
allObjects[i].setActive(false);
}
this.discardActiveGroup();
this.discardActiveObject();
return this;
},
/**
* Deactivates all objects and dispatches appropriate events
* @method deactivateAllWithDispatch
* @return {fabric.Canvas} thisArg
*/
deactivateAllWithDispatch: function () {
var activeObject = this.getActiveGroup() || this.getActiveObject();
if (activeObject) {
this.fire('before:selection:cleared', { target: activeObject });
}
this.deactivateAll();
if (activeObject) {
this.fire('selection:cleared');
}
return this;
}
};
fabric.Canvas.prototype.toString = fabric.StaticCanvas.prototype.toString;
extend(fabric.Canvas.prototype, InteractiveMethods);
// iterating manually to workaround Opera's bug
// where "prototype" property is enumerable and overrides existing prototype
for (var prop in fabric.StaticCanvas) {
if (prop !== 'prototype') {
fabric.Canvas[prop] = fabric.StaticCanvas[prop];
}
}
if (fabric.isTouchSupported) {
fabric.Canvas.prototype._setCursorFromEvent = function() { };
}
/**
* @class fabric.Element
* @alias fabric.Canvas
* @deprecated
* @constructor
*/
fabric.Element = fabric.Canvas;
})();