Remove activeGroup functionalities from Group, create ActiveSelection class (#4076)

This commit is contained in:
Andrea Bogazzi 2017-07-22 11:37:17 +02:00 committed by GitHub
parent 91376732ac
commit c9b562de9e
14 changed files with 693 additions and 438 deletions

View file

@ -198,6 +198,7 @@ var filesToInclude = [
'src/shapes/polygon.class.js',
'src/shapes/path.class.js',
'src/shapes/group.class.js',
'src/shapes/active_selection.class.js',
'src/shapes/image.class.js',
ifSpecifiedInclude('object_straightening', 'src/mixins/object_straightening.mixin.js'),

View file

@ -328,25 +328,25 @@
* @return {Array} objects to render immediately and pushes the other in the activeGroup.
*/
_chooseObjectsToRender: function() {
var activeGroup = this.getActiveGroup(),
activeObject = this.getActiveObject(),
object, objsToRender = [], activeGroupObjects = [];
var activeObjects = this.getActiveObjects(),
object, objsToRender, activeGroupObjects;
if ((activeGroup || activeObject) && !this.preserveObjectStacking) {
if (activeObjects.length > 0 && !this.preserveObjectStacking) {
objsToRender = [];
activeGroupObjects = [];
for (var i = 0, length = this._objects.length; i < length; i++) {
object = this._objects[i];
if ((!activeGroup || !activeGroup.contains(object)) && object !== activeObject) {
if (activeObjects.indexOf(object) === -1 ) {
objsToRender.push(object);
}
else {
activeGroupObjects.push(object);
}
}
if (activeGroup) {
activeGroup._set('_objects', activeGroupObjects);
objsToRender.push(activeGroup);
if (activeObjects.length > 1) {
this._activeObject._objects = activeGroupObjects;
}
activeObject && objsToRender.push(activeObject);
objsToRender.push.apply(objsToRender, activeGroupObjects);
}
else {
objsToRender = this._objects;
@ -449,7 +449,7 @@
pointer = point || this.getPointer(e, ignoreZoom),
xy;
if (target.group && target.group === this.getActiveGroup()) {
if (target.group && target.group === this._activeObject && target.group.type === 'activeSelection') {
xy = this._normalizePointer(target.group, pointer);
}
else {
@ -511,16 +511,16 @@
* @param {fabric.Object} target
*/
_shouldClearSelection: function (e, target) {
var activeGroup = this.getActiveGroup(),
activeObject = this.getActiveObject();
var activeObjects = this.getActiveObjects(),
activeObject = this._activeObject;
return (
!target
||
(target &&
activeGroup &&
!activeGroup.contains(target) &&
activeGroup !== target &&
activeObject &&
activeObjects.length > 1 &&
activeObjects.indexOf(target) === -1 &&
activeObject !== target &&
!e[this.selectionKey])
||
(target && !target.evented)
@ -1104,23 +1104,25 @@
var ignoreZoom = true,
pointer = this.getPointer(e, ignoreZoom),
activeGroup = this.getActiveGroup(),
activeObject = this.getActiveObject(),
activeObject = this._activeObject,
aObjects = this.getActiveObjects(),
activeTarget;
// first check current group (if one exists)
// active group does not check sub targets like normal groups.
// if active group just exits.
this.targets = [];
if (activeGroup && !skipGroup && activeGroup === this._searchPossibleTargets([activeGroup], pointer)) {
this._fireOverOutEvents(activeGroup, e);
return activeGroup;
}
// if we hit the corner of an activeObject, let's return that.
if (activeObject && activeObject._findTargetCorner(pointer)) {
if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([activeObject], pointer)) {
this._fireOverOutEvents(activeObject, e);
return activeObject;
}
if (activeObject && activeObject === this._searchPossibleTargets([activeObject], pointer)) {
// if we hit the corner of an activeObject, let's return that.
if (aObjects.length === 1 && activeObject._findTargetCorner(pointer)) {
this._fireOverOutEvents(activeObject, e);
return activeObject;
}
if (aObjects.length === 1 &&
activeObject === this._searchPossibleTargets([activeObject], pointer)) {
if (!this.preserveObjectStacking) {
this._fireOverOutEvents(activeObject, e);
return activeObject;
@ -1129,7 +1131,6 @@
activeTarget = activeObject;
}
}
var target = this._searchPossibleTargets(this._objects, pointer);
if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) {
target = activeTarget;
@ -1374,41 +1375,6 @@
return this.upperCanvasEl;
},
/**
* @private
* @param {Object} object
*/
_setActiveObject: function(object) {
var obj = this._activeObject;
if (obj) {
obj.set('active', false);
if (object !== obj && obj.onDeselect && typeof obj.onDeselect === 'function') {
obj.onDeselect();
}
}
this._activeObject = object;
object.set('active', true);
},
/**
* Sets given object as the only active object on canvas
* @param {fabric.Object} object Object to set as an active one
* @param {Event} [e] Event (passed along when firing "object:selected")
* @return {fabric.Canvas} thisArg
* @chainable
*/
setActiveObject: function (object, e) {
var currentActiveObject = this.getActiveObject();
if (currentActiveObject && currentActiveObject !== object) {
currentActiveObject.fire('deselected', { e: e });
}
this._setActiveObject(object);
this.fire('object:selected', { target: object, e: e });
object.fire('selected', { e: e });
this.requestRenderAll();
return this;
},
/**
* Returns currently active object
* @return {fabric.Object} active object
@ -1417,13 +1383,30 @@
return this._activeObject;
},
/**
* Returns an array with the current selected objects
* @return {fabric.Object} active object
*/
getActiveObjects: function () {
var active = this._activeObject;
if (active) {
if (active.type === 'activeSelection' && active._objects) {
return active._objects;
}
else {
return [active];
}
}
return [];
},
/**
* @private
* @param {fabric.Object} obj Object that was removed
*/
_onObjectRemoved: function(obj) {
// removing active object should fire "selection:cleared" events
if (this.getActiveObject() === obj) {
if (obj === this._activeObject) {
this.fire('before:selection:cleared', { target: obj });
this._discardActiveObject();
this.fire('selection:cleared', { target: obj });
@ -1435,18 +1418,57 @@
this.callSuper('_onObjectRemoved', obj);
},
/**
* Sets given object as the only active object on canvas
* @param {fabric.Object} object Object to set as an active one
* @param {Event} [e] Event (passed along when firing "object:selected")
* @return {fabric.Canvas} thisArg
* @chainable
*/
setActiveObject: function (object, e) {
var currentActiveObject = this._activeObject;
if (object === currentActiveObject) {
return this;
}
if (this._setActiveObject(object)) {
currentActiveObject && currentActiveObject.fire('deselected', { e: e });
this.fire('object:selected', { target: object, e: e });
object.fire('selected', { e: e });
};
return this;
},
/**
* @private
* @param {Object} object
*/
_setActiveObject: function(object) {
var active = this._activeObject;
if (active === object) {
return false;
}
if (this._discardActiveObject()) {
this._activeObject = object;
object.set('active', true);
return true;
}
return false;
},
/**
* @private
*/
_discardActiveObject: function() {
var obj = this._activeObject;
if (obj) {
obj.set('active', false);
if (obj.onDeselect && typeof obj.onDeselect === 'function') {
obj.onDeselect();
if (obj && obj.onDeselect && typeof obj.onDeselect === 'function') {
// onDeselect return TRUE to cancel selection;
if (obj.onDeselect()) {
return false;
}
obj.set('active', false);
this._activeObject = null;
}
this._activeObject = null;
return true;
},
/**
@ -1462,121 +1484,14 @@
var activeObject = this._activeObject;
if (activeObject) {
this.fire('before:selection:cleared', { target: activeObject, e: e });
this._discardActiveObject();
this.fire('selection:cleared', { e: e });
activeObject.fire('deselected', { e: e });
if (this._discardActiveObject()) {
this.fire('selection:cleared', { e: e });
activeObject.fire('deselected', { e: e });
}
}
return this;
},
/**
* @private
* @param {fabric.Group} group
*/
_setActiveGroup: function(group) {
this._activeGroup = group;
if (group) {
group.set('active', true);
}
},
/**
* Sets active group to a specified one. If the function is called by fabric
* as a consequence of a mouse event, the event is passed as a parmater and
* sent to the fire function for the custom events. When used as a method the
* e param does not have any application.
* @param {fabric.Group} group Group to set as a current one
* @param {Event} e Event object
* @return {fabric.Canvas} thisArg
* @chainable
*/
setActiveGroup: function (group, e) {
this._setActiveGroup(group);
if (group) {
this.fire('object:selected', { target: group, e: e });
group.fire('selected', { e: e });
}
return this;
},
/**
* Returns currently active group
* @return {fabric.Group} Current group
*/
getActiveGroup: function () {
return this._activeGroup;
},
/**
* @private
*/
_discardActiveGroup: function() {
var g = this.getActiveGroup();
if (g) {
g.destroy();
}
this.setActiveGroup(null);
},
/**
* Discards currently active group and fire events If the function is called by fabric
* as a consequence of a mouse event, the event is passed as a parmater and
* sent to the fire function for the custom events. When used as a method the
* e param does not have any application.
* @return {fabric.Canvas} thisArg
* @chainable
*/
discardActiveGroup: function (e) {
var g = this.getActiveGroup();
if (g) {
this.fire('before:selection:cleared', { e: e, target: g });
this._discardActiveGroup();
this.fire('selection:cleared', { e: e });
}
return this;
},
/**
* Deactivates all objects on canvas, removing any active group or object
* @return {fabric.Canvas} thisArg
* @chainable
*/
deactivateAll: function () {
var allObjects = this.getObjects(),
i = 0,
len = allObjects.length,
obj;
for ( ; i < len; i++) {
obj = allObjects[i];
obj && obj.set('active', false);
}
this._discardActiveGroup();
this._discardActiveObject();
return this;
},
/**
* Deactivates all objects and dispatches appropriate events If the function is called by fabric
* as a consequence of a mouse event, the event is passed as a parmater and
* sent to the fire function for the custom events. When used as a method the
* e param does not have any application.
* @return {fabric.Canvas} thisArg
* @chainable
*/
deactivateAllWithDispatch: function (e) {
var allObjects = this.getObjects(),
i = 0,
len = allObjects.length,
obj;
for ( ; i < len; i++) {
obj = allObjects[i];
obj && obj.set('active', false);
}
this.discardActiveGroup(e);
this.discardActiveObject(e);
return this;
},
/**
* Clears a canvas element and removes all event listeners
* @return {fabric.Canvas} thisArg
@ -1602,7 +1517,7 @@
* @chainable
*/
clear: function () {
this.discardActiveGroup();
// this.discardActiveGroup();
this.discardActiveObject();
this.clearContext(this.contextTop);
return this.callSuper('clear');
@ -1613,27 +1528,10 @@
* @param {CanvasRenderingContext2D} ctx Context to render controls on
*/
drawControls: function(ctx) {
var activeGroup = this.getActiveGroup();
var activeObject = this._activeObject;
if (activeGroup) {
activeGroup._renderControls(ctx);
}
else {
this._drawObjectsControls(ctx);
}
},
/**
* @private
*/
_drawObjectsControls: function(ctx) {
var object;
for (var i = 0, len = this._objects.length; i < len; ++i) {
object = this._objects[i];
if (!object || !object.active) {
continue;
}
object._renderControls(ctx);
if (activeObject) {
activeObject._renderControls(ctx);
}
},
@ -1659,14 +1557,14 @@
* @returns the original values of instance which were changed
*/
_realizeGroupTransformOnObject: function(instance) {
var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width'];
if (instance.group && instance.group === this.getActiveGroup()) {
if (instance.group && instance.group.type === 'activeSelection' && this._activeObject === instance.group) {
var layoutProps = ['angle', 'flipX', 'flipY', 'left', 'scaleX', 'scaleY', 'skewX', 'skewY', 'top'];
//Copy all the positionally relevant properties across now
var originalValues = {};
layoutProps.forEach(function(prop) {
originalValues[prop] = instance[prop];
});
this.getActiveGroup().realizeTransform(instance);
this._activeObject.realizeTransform(instance);
return originalValues;
}
else {
@ -1690,10 +1588,9 @@
* @private
*/
_setSVGObject: function(markup, instance, reviver) {
var originalProperties;
//If the object is in a selection group, simulate what would happen to that
//object when the group is deselected
originalProperties = this._realizeGroupTransformOnObject(instance);
var originalProperties = this._realizeGroupTransformOnObject(instance);
this.callSuper('_setSVGObject', markup, instance, reviver);
this._unwindGroupTransformOnObject(instance, originalProperties);
},

View file

@ -294,7 +294,7 @@
* @param {Object} pointer
*/
_shouldRender: function(target, pointer) {
var activeObject = this.getActiveGroup() || this.getActiveObject();
var activeObject = this._activeObject;
if (activeObject && activeObject.isEditing && target === activeObject) {
// if we mouse up/down over a editing textbox a cursor change,
@ -533,16 +533,14 @@
// save pointer for check in __onMouseUp event
var pointer = this.getPointer(e, true);
this._previousPointer = pointer;
var shouldRender = this._shouldRender(target, pointer),
shouldGroup = this._shouldGroup(e, target);
if (this._shouldClearSelection(e, target)) {
this.deactivateAllWithDispatch(e);
this.discardActiveObject(e);
}
else if (shouldGroup) {
this._handleGrouping(e, target);
target = this.getActiveGroup();
target = this._activeObject;
}
if (this.selection && (!target || (!target.selectable && !target.isEditing))) {
@ -559,13 +557,11 @@
this._beforeTransform(e, target);
this._setupCurrentTransform(e, target);
}
var activeObject = this.getActiveObject();
if (target !== this.getActiveGroup() && target !== activeObject) {
this.deactivateAll();
if (target.selectable) {
activeObject && activeObject.fire('deselected', { e: e });
this.setActiveObject(target, e);
}
if (target.selectable) {
this.setActiveObject(target, e);
}
else {
this.discardActiveObject();
}
}
this._handleEvent(e, 'down', target ? target : null);
@ -806,10 +802,10 @@
}
var hoverCursor = target.hoverCursor || this.hoverCursor,
activeGroup = this.getActiveGroup(),
activeSelection = this._activeObject && this._activeObject.type === 'activeSelection' ?
this._activeObject : null,
// only show proper corner when group selection is not active
corner = target._findTargetCorner
&& (!activeGroup || !activeGroup.contains(target))
corner = (!activeSelection || !activeSelection.contains(target))
&& target._findTargetCorner(this.getPointer(e, true));
if (!corner) {

View file

@ -12,10 +12,9 @@
* @return {Boolean}
*/
_shouldGroup: function(e, target) {
var activeObject = this.getActiveObject();
return e[this.selectionKey] && target && target.selectable &&
(this.getActiveGroup() || (activeObject && activeObject !== target))
&& this.selection;
var activeObject = this._activeObject;
return e[this.selectionKey] && target && target.selectable && this.selection &&
(activeObject !== target || activeObject.type === 'activeSelection');
},
/**
@ -24,67 +23,52 @@
* @param {fabric.Object} target
*/
_handleGrouping: function (e, target) {
var activeGroup = this.getActiveGroup();
if (target === activeGroup) {
var activeObject = this._activeObject;
if (activeObject.__corner !== 0) {
return;
}
if (target === activeObject) {
// if it's a group, find target again, using activeGroup objects
target = this.findTarget(e, true);
// if even object is not found, bail out
// if even object is not found or we are on activeObjectCorner, bail out
if (!target) {
return;
}
}
if (activeGroup) {
this._updateActiveGroup(target, e);
if (activeObject && activeObject.type === 'activeSelection') {
this._updateActiveSelection(target, e);
}
else {
this._createActiveGroup(target, e);
this._createActiveSelection(target, e);
}
},
/**
* @private
*/
_updateActiveGroup: function(target, e) {
var activeGroup = this.getActiveGroup();
if (activeGroup.contains(target)) {
activeGroup.removeWithUpdate(target);
target.set('active', false);
if (activeGroup.size() === 1) {
// remove group alltogether if after removal it only contains 1 object
this.discardActiveGroup(e);
_updateActiveSelection: function(target, e) {
var activeSelection = this._activeObject;
if (activeSelection.contains(target)) {
activeSelection.removeWithUpdate(target);
if (activeSelection.size() === 1) {
// activate last remaining object
this.setActiveObject(activeGroup.item(0), e);
this.setActiveObject(activeSelection.item(0), e);
return;
}
}
else {
activeGroup.addWithUpdate(target);
activeSelection.addWithUpdate(target);
}
this.fire('selection:created', { target: activeGroup, e: e });
activeGroup.set('active', true);
this.fire('selection:created', { target: activeSelection, e: e });
},
/**
* @private
*/
_createActiveGroup: function(target, e) {
if (this._activeObject && target !== this._activeObject) {
var group = this._createGroup(target);
group.addWithUpdate();
this.setActiveGroup(group, e);
this._activeObject = null;
this.fire('selection:created', { target: group, e: e });
}
target.set('active', true);
_createActiveSelection: function(target, e) {
var group = this._createGroup(target);
this.setActiveObject(group, e);
this.fire('selection:created', { target: group, e: e });
},
/**
@ -92,14 +76,13 @@
* @param {Object} target
*/
_createGroup: function(target) {
var objects = this.getObjects(),
isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target),
groupObjects = isActiveLower
? [this._activeObject, target]
: [target, this._activeObject];
this._activeObject.isEditing && this._activeObject.exitEditing();
return new fabric.Group(groupObjects, {
return new fabric.ActiveSelection(groupObjects, {
canvas: this
});
},
@ -117,11 +100,10 @@
this.setActiveObject(group[0], e);
}
else if (group.length > 1) {
group = new fabric.Group(group.reverse(), {
group = new fabric.ActiveSelection(group.reverse(), {
canvas: this
});
group.addWithUpdate();
this.setActiveGroup(group, e);
this.setActiveObject(group, e);
this.fire('selection:created', { target: group, e: e });
this.requestRenderAll();
}
@ -153,7 +135,6 @@
currentObject.containsPoint(selectionX1Y1) ||
currentObject.containsPoint(selectionX2Y2)
) {
currentObject.set('active', true);
group.push(currentObject);
// only add one object if it's a click
@ -173,14 +154,7 @@
if (this.selection && this._groupSelector) {
this._groupSelectedObjects(e);
}
var activeGroup = this.getActiveGroup();
if (activeGroup) {
activeGroup.setObjectsCoords().setCoords();
activeGroup.isMoving = false;
this.setCursor(this.defaultCursor);
}
this.setCursor(this.defaultCursor);
// clear selection and current transformation
this._groupSelector = null;
this._currentTransform = null;

View file

@ -467,25 +467,40 @@
calcTransformMatrix: function(skipGroup) {
var center = this.getCenterPoint(),
translateMatrix = [1, 0, 0, 1, center.x, center.y],
rotateMatrix = this._calcRotateMatrix(),
rotateMatrix,
dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true),
matrix = this.group && !skipGroup ? this.group.calcTransformMatrix() : fabric.iMatrix.concat();
matrix = multiplyMatrices(matrix, translateMatrix);
matrix = multiplyMatrices(matrix, rotateMatrix);
matrix;
if (this.group && !skipGroup) {
matrix = multiplyMatrices(this.group.calcTransformMatrix(), translateMatrix);
}
else {
matrix = translateMatrix;
}
if (this.angle) {
rotateMatrix = this._calcRotateMatrix();
matrix = multiplyMatrices(matrix, rotateMatrix);
}
matrix = multiplyMatrices(matrix, dimensionMatrix);
return matrix;
},
_calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
var skewMatrixX = [1, 0, Math.tan(degreesToRadians(skewX)), 1],
skewMatrixY = [1, Math.tan(degreesToRadians(skewY)), 0, 1],
var skewMatrix,
scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1),
scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1),
scaleMatrix = [scaleX, 0, 0, scaleY],
m = multiplyMatrices(scaleMatrix, skewMatrixX, true);
return multiplyMatrices(m, skewMatrixY, true);
scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0];
if (skewX) {
skewMatrix = [1, 0, Math.tan(degreesToRadians(skewX)), 1];
scaleMatrix = multiplyMatrices(scaleMatrix, skewMatrix, true);
}
if (skewY) {
skewMatrix = [1, Math.tan(degreesToRadians(skewY)), 0, 1];
scaleMatrix = multiplyMatrices(scaleMatrix, skewMatrix, true);
}
return scaleMatrix;
},
/*
* Calculate object dimensions from its properties
* @private

View file

@ -17,7 +17,9 @@
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
*/
_findTargetCorner: function(pointer) {
if (!this.hasControls || !this.active) {
// objects in group, anykind, are not self modificable,
// must not return an hovered corner.
if (!this.hasControls || !this.active || this.group) {
return false;
}
@ -115,7 +117,7 @@
* @chainable
*/
drawSelectionBackground: function(ctx) {
if (!this.selectionBackgroundColor || this.group || !this.active ||
if (!this.selectionBackgroundColor || !this.active ||
(this.canvas && !this.canvas.interactive)) {
return this;
}

View file

@ -0,0 +1,193 @@
(function(global) {
'use strict';
var fabric = global.fabric || (global.fabric = { });
if (fabric.ActiveSelection) {
return;
}
/**
* Group class
* @class fabric.ActiveSelection
* @extends fabric.Group
* @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups}
* @see {@link fabric.ActiveSelection#initialize} for constructor definition
*/
fabric.ActiveSelection = fabric.util.createClass(fabric.Group, /** @lends fabric.ActiveSelection.prototype */ {
/**
* Type of an object
* @type String
* @default
*/
type: 'activeSelection',
/**
* Constructor
* @param {Object} objects ActiveSelection objects
* @param {Object} [options] Options object
* @return {Object} thisArg
*/
initialize: function(objects, options) {
options = options || { };
this._objects = objects || [];
for (var i = this._objects.length; i--; ) {
this._objects[i].group = this;
}
if (options.originX) {
this.originX = options.originX;
}
if (options.originY) {
this.originY = options.originY;
}
this._calcBounds();
this._updateObjectsCoords();
fabric.Object.prototype.initialize.call(this, options);
this.setCoords();
},
/**
* Change te activeSelection to a normal group,
* High level function that automatically adds it to canvas as
* active object. no events fired.
* @since 2.0.0
* @return {fabric.Group}
*/
toGroup: function() {
var objects = this._objects;
this._objects = [];
var options = this.toObject();
var newGroup = new fabric.Group([]);
newGroup.set(options);
newGroup.type = 'group';
objects.forEach(function(object) {
object.group = newGroup;
object.canvas.remove(object);
});
newGroup._objects = objects;
if (!this.canvas) {
return newGroup;
}
var canvas = this.canvas;
canvas._activeObject = newGroup;
canvas.add(newGroup);
newGroup.setCoords();
return newGroup;
},
/**
* If returns true, deselection is cancelled.
* @since 2.0.0
* @return {Boolean} [cancel]
*/
onDeselect: function() {
this.destroy();
return false;
},
/**
* Returns string represenation of a group
* @return {String}
*/
toString: function() {
return '#<fabric.ActiveSelection: (' + this.complexity() + ')>';
},
/**
* @private
*/
_set: function(key, value) {
var i = this._objects.length;
if (key === 'canvas') {
while (i--) {
this._objects[i].set(key, value);
}
}
if (this.useSetOnGroup) {
while (i--) {
this._objects[i].setOnGroup(key, value);
}
}
fabric.Object.prototype._set.call(this, key, value);
},
/**
* Decide if the object should cache or not. Create its own cache level
* objectCaching is a global flag, wins over everything
* needsItsOwnCache should be used when the object drawing method requires
* a cache step. None of the fabric classes requires it.
* Generally you do not cache objects in groups because the group outside is cached.
* @return {Boolean}
*/
shouldCache: function() {
return false;
},
/**
* Check if this object or a child object will cast a shadow
* @return {Boolean}
*/
willDrawShadow: function() {
if (this.shadow) {
return this.callSuper('willDrawShadow');
}
for (var i = 0, len = this._objects.length; i < len; i++) {
if (this._objects[i].willDrawShadow()) {
return true;
}
}
return false;
},
/**
* Check if this group or its parent group are caching, recursively up
* @return {Boolean}
*/
isOnACache: function() {
return false;
},
/**
* Renders controls and borders for the object
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Object} [styleOverride] properties to override the object style
* @param {Object} [childrenOverride] properties to override the children overrides
*/
_renderControls: function(ctx, styleOverride, childrenOverride) {
ctx.save();
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
this.callSuper('_renderControls', ctx, styleOverride);
childrenOverride = childrenOverride || { };
if (typeof childrenOverride.hasControls === 'undefined') {
childrenOverride.hasControls = false;
}
if (typeof childrenOverride.hasRotatingPoint === 'undefined') {
childrenOverride.hasRotatingPoint = false;
}
childrenOverride.forActiveSelection = true;
for (var i = 0, len = this._objects.length; i < len; i++) {
this._objects[i]._renderControls(ctx, childrenOverride);
}
ctx.restore();
},
});
/**
* Returns {@link fabric.ActiveSelection} instance from an object representation
* @static
* @memberOf fabric.ActiveSelection
* @param {Object} object Object to create a group from
* @param {Function} [callback] Callback to invoke when an ActiveSelection instance is created
*/
fabric.ActiveSelection.fromObject = function(object, callback) {
fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
delete object.objects;
callback && callback(new fabric.ActiveSelection(enlivenedObjects, object, true));
});
};
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -86,17 +86,7 @@
this.originY = options.originY;
}
if (isAlreadyGrouped) {
// do not change coordinate of objects enclosed in a group,
// because objects coordinate system have been group coodinate system already.
var object;
for (var i = this._objects.length; i--; ) {
object = this._objects[i];
object.__origHasControls = object.hasControls;
object.hasControls = false;
}
}
else {
if (!isAlreadyGrouped) {
var center = options && options.centerPoint;
// if coming from svg i do not want to calc bounds.
// i assume width and height are passed along options
@ -126,10 +116,6 @@
* @param {fabric.Point} center, current center of group.
*/
_updateObjectCoords: function(object, center) {
// do not display corners of objects enclosed in a group
object.__origHasControls = object.hasControls;
object.hasControls = false;
var objectLeft = object.left,
objectTop = object.top,
ignoreZoom = true, skipAbsolute = true;
@ -138,6 +124,7 @@
left: objectLeft - center.x,
top: objectTop - center.y
});
object.group = this;
object.setCoords(ignoreZoom, skipAbsolute);
},
@ -163,8 +150,6 @@
object.group = this;
object._set('canvas', this.canvas);
}
// since _restoreObjectsState set objects inactive
this.forEachObject(this._setObjectActive, this);
this._calcBounds();
this._updateObjectsCoords();
this.setCoords();
@ -172,14 +157,6 @@
return this;
},
/**
* @private
*/
_setObjectActive: function(object) {
object.set('active', true);
object.group = this;
},
/**
* Removes an object from a group; Then recalculates group's dimension, position.
* @param {Object} object
@ -189,8 +166,6 @@
removeWithUpdate: function(object) {
this._restoreObjectsState();
fabric.util.resetObjectTransform(this);
// since _restoreObjectsState set objects inactive
this.forEachObject(this._setObjectActive, this);
this.remove(object);
this._calcBounds();
@ -206,7 +181,6 @@
_onObjectAdded: function(object) {
this.dirty = true;
object.group = this;
object._set('canvas', this.canvas);
},
/**
@ -215,7 +189,6 @@
_onObjectRemoved: function(object) {
this.dirty = true;
delete object.group;
object.set('active', false);
},
/**
@ -223,18 +196,11 @@
*/
_set: function(key, value) {
var i = this._objects.length;
if (key === 'canvas') {
while (i--) {
this._objects[i].set(key, value);
}
}
if (this.useSetOnGroup) {
while (i--) {
this._objects[i].setOnGroup(key, value);
}
}
this.callSuper('_set', key, value);
},
@ -342,7 +308,7 @@
*/
drawObject: function(ctx) {
for (var i = 0, len = this._objects.length; i < len; i++) {
this._renderObject(this._objects[i], ctx);
this._objects[i].render(ctx);
}
},
@ -369,34 +335,6 @@
return false;
},
/**
* Renders controls and borders for the object
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Object} [styleOverride] properties to override the object style
* @param {Object} [childrenOverride] properties to override the children overrides
*/
_renderControls: function(ctx, styleOverride, childrenOverride) {
ctx.save();
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
this.callSuper('_renderControls', ctx, styleOverride);
if (this.canvas && this === this.canvas.getActiveGroup()) {
for (var i = 0, len = this._objects.length; i < len; i++) {
this._objects[i]._renderControls(ctx, childrenOverride);
}
}
ctx.restore();
},
/**
* @private
*/
_renderObject: function(object, ctx) {
var originalHasRotatingPoint = object.hasRotatingPoint;
object.hasRotatingPoint = false;
object.render(ctx);
object.hasRotatingPoint = originalHasRotatingPoint;
},
/**
* Retores original state of each of group objects (original state is that which was before group was created).
* @private
@ -440,11 +378,7 @@
_restoreObjectState: function(object) {
this.realizeTransform(object);
object.setCoords();
object.hasControls = object.__origHasControls;
delete object.__origHasControls;
object.set('active', false);
delete object.group;
return this;
},
@ -457,6 +391,43 @@
return this._restoreObjectsState();
},
/**
* make a group an active selection, remove the group from canvas
* the group has to be on canvas for this to work.
* @return {fabric.ActiveSelection} thisArg
* @chainable
*/
toActiveSelection: function() {
if (!this.canvas) {
return;
}
var objects = this._objects, canvas = this.canvas;
this._objects = [];
var options = this.toObject();
var activeSelection = new fabric.ActiveSelection([]);
activeSelection.set(options);
activeSelection.type = 'activeSelection';
canvas.remove(this);
objects.forEach(function(object) {
object.group = activeSelection;
object.dirty = true;
canvas.add(object);
});
activeSelection._objects = objects;
canvas._activeObject = activeSelection;
activeSelection.setCoords();
return activeSelection;
},
/**
* Destroys a group (restoring state of its objects)
* @return {fabric.Group} thisArg
* @chainable
*/
ungroupOnCanvas: function() {
return this._restoreObjectsState();
},
/**
* Sets coordinates of all objects inside group
* @return {fabric.Group} thisArg

View file

@ -784,7 +784,6 @@
};
fabric.util.populateWithProperties(this, object, propertiesToInclude);
if (!this.includeDefaultValues) {
object = this._removeDefaultValues(object);
}
@ -1163,7 +1162,7 @@
if (!this.group) {
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
}
if (this.group && this.group === this.canvas.getActiveGroup()) {
if (styleOverride.forActiveSelection) {
ctx.rotate(degreesToRadians(options.angle));
drawBorders && this.drawBordersInGroup(ctx, options, styleOverride);
}

View file

@ -671,14 +671,14 @@
* @chainable true
*/
setViewportTransform: function (vpt) {
var activeGroup = this._activeGroup, object, ignoreVpt = false, skipAbsolute = true;
var activeObject = this._activeObject, object, ignoreVpt = false, skipAbsolute = true;
this.viewportTransform = vpt;
for (var i = 0, len = this._objects.length; i < len; i++) {
object = this._objects[i];
object.group || object.setCoords(ignoreVpt, skipAbsolute);
}
if (activeGroup) {
activeGroup.setCoords(ignoreVpt, skipAbsolute);
if (activeObject && activeObject.type === 'activeSelection') {
activeObject.setCoords(ignoreVpt, skipAbsolute);
}
this.calcViewportBoundaries();
this.renderOnAddRemove && this.requestRenderAll();
@ -1376,7 +1376,6 @@
},
/**
* push single object svg representation in the markup
* @private
*/
_setSVGObject: function(markup, instance, reviver) {
@ -1440,10 +1439,10 @@
if (!object) {
return this;
}
var activeGroup = this._activeGroup,
var activeSelection = this._activeObject,
i, obj, objs;
if (object === activeGroup) {
objs = activeGroup._objects;
if (object === activeSelection && object.type === 'activeSelection') {
objs = activeSelection._objects;
for (i = objs.length; i--;) {
obj = objs[i];
removeFromArray(this._objects, obj);
@ -1469,10 +1468,10 @@
if (!object) {
return this;
}
var activeGroup = this._activeGroup,
var activeSelection = this._activeObject,
i, obj, objs;
if (object === activeGroup) {
objs = activeGroup._objects;
if (object === activeSelection && object.type === 'activeSelection') {
objs = activeSelection._objects;
for (i = 0; i < objs.length; i++) {
obj = objs[i];
removeFromArray(this._objects, obj);
@ -1498,11 +1497,11 @@
if (!object) {
return this;
}
var activeGroup = this._activeGroup,
var activeSelection = this._activeObject,
i, obj, idx, newIdx, objs;
if (object === activeGroup) {
objs = activeGroup._objects;
if (object === activeSelection && object.type === 'activeSelection') {
objs = activeSelection._objects;
for (i = 0; i < objs.length; i++) {
obj = objs[i];
idx = this._objects.indexOf(obj);
@ -1566,11 +1565,11 @@
if (!object) {
return this;
}
var activeGroup = this._activeGroup,
var activeSelection = this._activeObject,
i, obj, idx, newIdx, objs;
if (object === activeGroup) {
objs = activeGroup._objects;
if (object === activeSelection && object.type === 'activeSelection') {
objs = activeSelection._objects;
for (i = objs.length; i--;) {
obj = objs[i];
idx = this._objects.indexOf(obj);

View file

@ -12,6 +12,7 @@ testrunner.run({
deps: "./test/fixtures/test_script.js",
code: "./dist/fabric.js",
tests: [
'./test/unit/activeselection.js',
'./test/unit/animation.js',
'./test/unit/rect.js',
'./test/unit/ellipse.js',

View file

@ -0,0 +1,235 @@
(function() {
var el = fabric.document.createElement('canvas');
el.width = 600; el.height = 600;
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode(600, 600, {enableRetinaScaling: false}) : new fabric.Canvas(el, {enableRetinaScaling: false});
function makeAsWith2Objects() {
var rect1 = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10, strokeWidth: 0 }),
rect2 = new fabric.Rect({ top: 120, left: 50, width: 10, height: 40, strokeWidth: 0 });
return new fabric.ActiveSelection([rect1, rect2], {strokeWidth: 0});
}
function makeAsWith2ObjectsWithOpacity() {
var rect1 = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10, strokeWidth: 0, opacity: 0.5 }),
rect2 = new fabric.Rect({ top: 120, left: 50, width: 10, height: 40, strokeWidth: 0, opacity: 0.8 });
return new fabric.ActiveSelection([rect1, rect2], {strokeWidth: 0});
}
function makeAsWith4Objects() {
var rect1 = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10 }),
rect2 = new fabric.Rect({ top: 120, left: 50, width: 10, height: 40 }),
rect3 = new fabric.Rect({ top: 40, left: 0, width: 20, height: 40 }),
rect4 = new fabric.Rect({ top: 75, left: 75, width: 40, height: 40 });
return new fabric.ActiveSelection([rect1, rect2, rect3, rect4]);
}
QUnit.module('fabric.ActiveSelection', {
teardown: function() {
canvas.clear();
canvas.backgroundColor = fabric.Canvas.prototype.backgroundColor;
canvas.calcOffset();
}
});
test('constructor', function() {
var group = makeAsWith2Objects();
ok(group);
ok(group instanceof fabric.ActiveSelection, 'should be instance of fabric.ActiveSelection');
});
test('toString', function() {
var group = makeAsWith2Objects();
equal(group.toString(), '#<fabric.ActiveSelection: (2)>', 'should return proper representation');
});
test('toObject', function() {
var group = makeAsWith2Objects();
ok(typeof group.toObject === 'function');
var clone = group.toObject();
var expectedObject = {
'type': 'activeSelection',
'originX': 'left',
'originY': 'top',
'left': 50,
'top': 100,
'width': 80,
'height': 60,
'fill': 'rgb(0,0,0)',
'stroke': null,
'strokeWidth': 0,
'strokeDashArray': null,
'strokeLineCap': 'butt',
'strokeLineJoin': 'miter',
'strokeMiterLimit': 10,
'scaleX': 1,
'scaleY': 1,
'shadow': null,
'visible': true,
'backgroundColor': '',
'clipTo': null,
'angle': 0,
'flipX': false,
'flipY': false,
'opacity': 1,
'fillRule': 'nonzero',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,
'skewY': 0,
'objects': clone.objects
};
deepEqual(clone, expectedObject);
ok(group !== clone, 'should produce different object');
ok(group.getObjects() !== clone.objects, 'should produce different object array');
ok(group.getObjects()[0] !== clone.objects[0], 'should produce different objects in array');
});
test('toObject without default values', function() {
var group = makeAsWith2Objects();
group.includeDefaultValues = false;
var clone = group.toObject();
var objects = [{
type: 'rect',
left: 10,
top: -30,
width: 30,
height: 10,
strokeWidth: 0
}, {
type: 'rect',
left: -40,
top: -10,
width: 10,
height: 40,
strokeWidth: 0
}];
var expectedObject = {
'type': 'activeSelection',
'left': 50,
'top': 100,
'width': 80,
'height': 60,
'objects': objects
};
deepEqual(clone, expectedObject);
});
test('_renderControls', function() {
ok(typeof fabric.ActiveSelection.prototype._renderControls === 'function');
});
asyncTest('fromObject', function() {
var group = makeAsWith2ObjectsWithOpacity();
ok(typeof fabric.ActiveSelection.fromObject === 'function');
var groupObject = group.toObject();
fabric.ActiveSelection.fromObject(groupObject, function(newGroupFromObject) {
var objectFromOldGroup = group.toObject();
var objectFromNewGroup = newGroupFromObject.toObject();
ok(newGroupFromObject instanceof fabric.ActiveSelection);
deepEqual(objectFromOldGroup.objects[0], objectFromNewGroup.objects[0]);
deepEqual(objectFromOldGroup.objects[1], objectFromNewGroup.objects[1]);
// delete `objects` arrays, since `assertHashEqual` fails to compare them for equality
delete objectFromOldGroup.objects;
delete objectFromNewGroup.objects;
deepEqual(objectFromOldGroup, objectFromNewGroup);
start();
});
});
test('get with locked objects', function() {
var group = makeAsWith2Objects();
equal(group.get('lockMovementX'), false);
// TODO acitveGroup
// group.getObjects()[0].lockMovementX = true;
// equal(group.get('lockMovementX'), true);
//
// group.getObjects()[0].lockMovementX = false;
// equal(group.get('lockMovementX'), false);
group.set('lockMovementX', true);
equal(group.get('lockMovementX'), true);
// group.set('lockMovementX', false);
// group.getObjects()[0].lockMovementY = true;
// group.getObjects()[1].lockRotation = true;
//
// equal(group.get('lockMovementY'), true);
// equal(group.get('lockRotation'), true);
});
test('insertAt', function() {
var rect1 = new fabric.Rect(),
rect2 = new fabric.Rect(),
group = new fabric.Group();
group.add(rect1, rect2);
ok(typeof group.insertAt == 'function', 'should respond to `insertAt` method');
group.insertAt(rect1, 1);
equal(group.item(1), rect1);
group.insertAt(rect2, 2);
equal(group.item(2), rect2);
equal(group.insertAt(rect1, 2), group, 'should be chainable');
});
test('group addWithUpdate', function() {
var rect1 = new fabric.Rect({ top: 1, left: 1, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: false}),
rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: false}),
group = new fabric.Group([rect1]);
var coords = group.oCoords;
group.addWithUpdate(rect2);
var newCoords = group.oCoords;
notEqual(coords, newCoords, 'object coords have been recalculated - add');
});
test('group removeWithUpdate', function() {
var rect1 = new fabric.Rect({ top: 1, left: 1, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: false}),
rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: false}),
group = new fabric.Group([rect1, rect2]);
var coords = group.oCoords;
group.removeWithUpdate(rect2);
var newCoords = group.oCoords;
notEqual(coords, newCoords, 'object coords have been recalculated - remove');
});
test('ActiveSelection shouldCache', function() {
var rect1 = new fabric.Rect({ top: 1, left: 1, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: true}),
rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: true}),
group = new fabric.ActiveSelection([rect1, rect2], { objectCaching: true});
equal(group.shouldCache(), false, 'Active selection do not cache');
});
test('canvas property propagation', function() {
var g2 = makeAsWith4Objects();
canvas.add(g2);
equal(g2.canvas, canvas);
equal(g2._objects[3].canvas, canvas);
});
})();

View file

@ -105,7 +105,6 @@
},
teardown: function() {
canvas.clear();
canvas.setActiveGroup(null);
canvas.backgroundColor = fabric.Canvas.prototype.backgroundColor;
canvas.overlayColor = fabric.Canvas.prototype.overlayColor;
canvas.calcOffset();
@ -495,8 +494,8 @@
canvas.add(rect1);
canvas.add(rect2);
canvas.add(rect3);
var group = new fabric.Group([rect1, rect2]);
canvas.setActiveGroup(group);
var group = new fabric.ActiveSelection([rect1, rect2]);
canvas.setActiveObject(group);
target = canvas.findTarget({
clientX: 5, clientY: 5
});
@ -526,8 +525,8 @@
canvas.preserveObjectStacking = true;
canvas.add(rect1);
canvas.add(rect2);
var group = new fabric.Group([rect1, rect2]);
canvas.setActiveGroup(group);
var group = new fabric.ActiveSelection([rect1, rect2]);
canvas.setActiveObject(group);
target = canvas.findTarget({
clientX: 8, clientY: 8
});
@ -541,7 +540,7 @@
canvas.preserveObjectStacking = false;
});
test('activeGroup sendToBack', function() {
test('ActiveSelection sendToBack', function() {
var rect1 = makeRect(),
rect2 = makeRect(),
@ -550,11 +549,11 @@
canvas.add(rect1, rect2, rect3, rect4);
var group = new fabric.Group([rect3, rect4]);
canvas.setActiveGroup(group);
var activeSel = new fabric.ActiveSelection([rect3, rect4]);
canvas.setActiveObject(activeSel);
equal(canvas._objects[0], rect1, 'rect1 should be last');
equal(canvas._objects[1], rect2, 'rect2 should be second');
canvas.sendToBack(group);
canvas.sendToBack(activeSel);
equal(canvas._objects[0], rect3, 'rect3 should be the new last');
equal(canvas._objects[1], rect4, 'rect3 should be the new second');
equal(canvas._objects[2], rect1, 'rect1 should be the third object');
@ -570,11 +569,11 @@
canvas.add(rect1, rect2, rect3, rect4);
var group = new fabric.Group([rect1, rect2]);
canvas.setActiveGroup(group);
var activeSel = new fabric.ActiveSelection([rect1, rect2]);
canvas.setActiveObject(activeSel);
equal(canvas._objects[0], rect1, 'rect1 should be last');
equal(canvas._objects[1], rect2, 'rect2 should be second');
canvas.bringToFront(group);
canvas.bringToFront(activeSel);
equal(canvas._objects[0], rect3, 'rect3 should be the new last');
equal(canvas._objects[1], rect4, 'rect3 should be the new second');
equal(canvas._objects[2], rect1, 'rect1 should be the third object');
@ -590,11 +589,11 @@
canvas.add(rect1, rect2, rect3, rect4);
var group = new fabric.Group([rect1, rect2]);
canvas.setActiveGroup(group);
var activeSel = new fabric.ActiveSelection([rect1, rect2]);
canvas.setActiveObject(activeSel);
equal(canvas._objects[0], rect1, 'rect1 should be last');
equal(canvas._objects[1], rect2, 'rect2 should be second');
canvas.bringForward(group);
canvas.bringForward(activeSel);
equal(canvas._objects[0], rect3, 'rect3 should be the new last');
equal(canvas._objects[1], rect1, 'rect1 should be the new second');
equal(canvas._objects[2], rect2, 'rect2 should be the third object');
@ -609,11 +608,11 @@
canvas.add(rect1, rect2, rect3, rect4);
var group = new fabric.Group([rect3, rect4]);
canvas.setActiveGroup(group);
var activeSel = new fabric.ActiveSelection([rect3, rect4]);
canvas.setActiveObject(activeSel);
equal(canvas._objects[0], rect1, 'rect1 should be last');
equal(canvas._objects[1], rect2, 'rect2 should be second');
canvas.sendBackwards(group);
canvas.sendBackwards(activeSel);
equal(canvas._objects[0], rect1, 'rect1 is still last');
equal(canvas._objects[1], rect3, 'rect3 should be shifted down by 1');
equal(canvas._objects[2], rect4, 'rect4 should be shifted down by 1');
@ -718,7 +717,7 @@
canvas.add(rect, circle);
var json = JSON.stringify(canvas);
canvas.setActiveGroup(new fabric.Group([rect, circle])).renderAll();
canvas.setActiveObject(new fabric.ActiveSelection([rect, circle], { canvas: canvas }));
var jsonWithActiveGroup = JSON.stringify(canvas);
equal(json, jsonWithActiveGroup);
@ -979,7 +978,7 @@
canvas.add(group);
canvas.renderAll();
canvas.deactivateAll();
canvas._discardActiveObject();
var json = JSON.stringify( canvas.toDatalessJSON() );
canvas.clear();
canvas.loadFromDatalessJSON(json, function() {
@ -1226,7 +1225,7 @@
test('getActiveObject', function() {
ok(typeof canvas.getActiveObject == 'function');
equal(canvas.getActiveObject(), null, 'should initially be null');
var rect1 = makeRect(),
rect2 = makeRect();
@ -1239,19 +1238,16 @@
equal(canvas.getActiveObject(), rect2);
});
test('getSetActiveGroup', function() {
ok(typeof canvas.getActiveGroup == 'function');
ok(typeof canvas.setActiveGroup == 'function');
equal(canvas.getActiveGroup(), null, 'should initially be null');
test('getsetActiveObject', function() {
equal(canvas.getActiveObject(), null, 'should initially be null');
var group = new fabric.Group([
makeRect({ left: 10, top: 10 }),
makeRect({ left: 20, top: 20 })
]);
equal(canvas.setActiveGroup(group), canvas, 'should be chainable');
equal(canvas.getActiveGroup(), group);
equal(canvas.setActiveObject(group), canvas, 'should be chainable');
equal(canvas.getActiveObject(), group);
});
test('item', function() {
@ -1270,28 +1266,25 @@
equal(canvas.item(0), rect2);
});
test('discardActiveGroup', function() {
ok(typeof canvas.discardActiveGroup == 'function');
var group = new fabric.Group([makeRect(), makeRect()]);
canvas.setActiveGroup(group);
equal(canvas.discardActiveGroup(), canvas, 'should be chainable');
equal(canvas.getActiveGroup(), null, 'removing active group sets it to null');
test('discardActiveObject on ActiveSelection', function() {
var group = new fabric.ActiveSelection([makeRect(), makeRect()]);
canvas.setActiveObject(group);
equal(canvas.discardActiveObject(), canvas, 'should be chainable');
equal(canvas.getActiveObject(), null, 'removing active group sets it to null');
});
test('deactivateAll', function() {
ok(typeof canvas.deactivateAll == 'function');
test('_discardActiveObject', function() {
canvas.add(makeRect());
canvas.setActiveObject(canvas.item(0));
canvas.deactivateAll();
canvas._discardActiveObject();
ok(!canvas.item(0).active);
equal(canvas.getActiveObject(), null);
equal(canvas.getActiveGroup(), null);
});
test('deactivateAllWithDispatch', function() {
ok(typeof canvas.deactivateAllWithDispatch == 'function');
test('discardActiveObject', function() {
ok(typeof canvas.discardActiveObject == 'function');
canvas.add(makeRect());
canvas.setActiveObject(canvas.item(0));
@ -1301,7 +1294,7 @@
makeRect({ left: 20, top: 20 })
]);
canvas.setActiveGroup(group);
canvas.setActiveObject(group);
var eventsFired = {
selectionCleared: false
@ -1311,10 +1304,10 @@
eventsFired.selectionCleared = true;
});
canvas.deactivateAllWithDispatch();
canvas.discardActiveObject();
ok(!canvas.item(0).active);
equal(canvas.getActiveObject(), null);
equal(canvas.getActiveGroup(), null);
equal(canvas.getActiveObject(), null);
for (var prop in eventsFired) {
ok(eventsFired[prop]);
@ -1347,7 +1340,7 @@
canvas.add(rect, circle);
var svg = canvas.toSVG();
canvas.setActiveGroup(new fabric.Group([rect, circle])).renderAll();
canvas.setActiveObject(new fabric.ActiveSelection([rect, circle]));
var svgWithActiveGroup = canvas.toSVG();
equal(svg, svgWithActiveGroup);
@ -1363,13 +1356,13 @@
equal(canvas._objects[1], rect2);
equal(canvas._objects[2], circle1);
equal(canvas._objects[3], circle2);
var aGroup = new fabric.Group([rect2, circle2, rect1, circle1]);
var aGroup = new fabric.ActiveSelection([rect2, circle2, rect1, circle1]);
// before rendering objects are ordered in insert order
equal(aGroup._objects[0], rect2);
equal(aGroup._objects[1], circle2);
equal(aGroup._objects[2], rect1);
equal(aGroup._objects[3], circle1);
canvas.setActiveGroup(aGroup).renderAll();
canvas.setActiveObject(aGroup).renderAll();
// after rendering objects are ordered in canvas stack order
equal(aGroup._objects[0], rect1);
equal(aGroup._objects[1], rect2);

View file

@ -35,7 +35,6 @@
QUnit.module('fabric.Group', {
teardown: function() {
canvas.clear();
canvas.setActiveGroup(null);
canvas.backgroundColor = fabric.Canvas.prototype.backgroundColor;
canvas.calcOffset();
}
@ -484,26 +483,6 @@
equal(group.insertAt(rect1, 2), group, 'should be chainable');
});
test('canvas property propagation', function() {
var g1 = makeGroupWith4Objects(),
g2 = makeGroupWith4Objects(),
rect1 = new fabric.Rect(),
rect2 = new fabric.Rect(),
group1 = new fabric.Group([g1]);
group1.add(g2);
group1.insertAt(rect1, 0);
g2.insertAt(rect2, 0);
canvas.add(group1);
equal(g2.canvas, canvas);
equal(g2._objects[3].canvas, canvas);
equal(g1.canvas, canvas);
equal(g1._objects[3].canvas, canvas);
equal(rect2.canvas, canvas);
equal(rect1.canvas, canvas);
});
test('dirty flag propagation from children up', function() {
var g1 = makeGroupWith4Objects();
var obj = g1.item(0);