diff --git a/build.js b/build.js index 38f3b45ab05..feb32032535 100644 --- a/build.js +++ b/build.js @@ -195,6 +195,7 @@ var filesToInclude = [ 'src/shapes/polygon.class.js', 'src/shapes/path.class.js', 'src/shapes/group.class.js', + 'src/shapes/layer.class.js', ifSpecifiedInclude('interaction', 'src/shapes/active_selection.class.js'), 'src/shapes/image.class.js', diff --git a/src/canvas.class.js b/src/canvas.class.js index 03f0c450941..cf32fb08a25 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -386,15 +386,21 @@ * @return {Array} objects to render immediately and pushes the other in the activeGroup. */ _chooseObjectsToRender: function() { - var activeObjects = this.getActiveObjects(), + var activeObjects = this.getActiveObjects(), objects = this._objects, object, objsToRender, activeGroupObjects; if (activeObjects.length > 0 && !this.preserveObjectStacking) { objsToRender = []; activeGroupObjects = []; + var ancestors = activeObjects.map(function (obj) { + while (obj && objects.indexOf(obj) === -1) { + obj = obj.parent || obj.group; + } + return obj; + }); for (var i = 0, length = this._objects.length; i < length; i++) { object = this._objects[i]; - if (activeObjects.indexOf(object) === -1 ) { + if (activeObjects.indexOf(object) === -1 && ancestors.indexOf(object) === -1) { objsToRender.push(object); } else { @@ -404,6 +410,9 @@ if (activeObjects.length > 1) { this._activeObject._objects = activeGroupObjects; } + else if (activeObjects[0].parent) { + activeGroupObjects.push(activeObjects[0]); + } objsToRender.push.apply(objsToRender, activeGroupObjects); } else { @@ -823,7 +832,7 @@ this._normalizePointer(objToCheck.group, pointer) : pointer; if (this._checkTarget(pointerToUse, objToCheck, pointer)) { target = objects[i]; - if (target.subTargetCheck && target instanceof fabric.Group) { + if (target.subTargetCheck && Array.isArray(target._objects)) { subTarget = this._searchPossibleTargets(target._objects, pointer); subTarget && this.targets.push(subTarget); } @@ -1138,18 +1147,34 @@ * @param {Event} [e] Event (passed along when firing "object:selected") * @return {Boolean} true if the selection happened */ - _setActiveObject: function(object, e) { - if (this._activeObject === object) { + _setActiveObject: function (object, e) { + var result, isCollection = Array.isArray(object._objects), activeObject = this._activeObject; + if (this._activeObject === object && !isCollection) { return false; } + // return if active object doesn't allow to be deselected if (!this._discardActiveObject(e, object)) { return false; } - if (object.onSelect({ e: e })) { + result = object.onSelect({ + e: e, + object: activeObject, + subTargets: isCollection ? this.targets.concat() : undefined + }); + if (result === true) { return false; } - this._activeObject = object; - return true; + else if (result && result instanceof fabric.Object) { + // prepare `subTargets` and re-run + this._searchPossibleTargets([result], this.getPointer(e, true)); + this._setActiveObject(result, e); + return true; + } + else { + var current = this._activeObject; + this._activeObject = object; + return current !== this._activeObject; + } }, /** diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index 6d27be65041..1be49cfe878 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -450,9 +450,12 @@ ); } } + var actualTarget = target; if (target) { if (target.selectable && target !== this._activeObject && target.activeOn === 'up') { this.setActiveObject(target, e); + // reassign in case a different object was selected + actualTarget = this._activeObject; shouldRender = true; } else { @@ -469,7 +472,7 @@ } target.isMoving = false; } - this._setCursorFromEvent(e, target); + this._setCursorFromEvent(e, actualTarget); this._handleEvent(e, 'up', LEFT_CLICK, isClick); this._groupSelector = null; this._currentTransform = null; @@ -720,10 +723,15 @@ } if (target) { - var alreadySelected = target === this._activeObject; if (target.selectable && target.activeOn === 'down') { this.setActiveObject(target, e); + // reassign in case a different object was selected + if (target !== this._activeObject) { + shouldRender = true; + target = this._target = this._activeObject; + } } + var alreadySelected = target === this._activeObject; var corner = target._findTargetCorner( this.getPointer(e, true), fabric.util.isTouchEvent(e) diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index 4aea3954d05..5540a643119 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -295,6 +295,7 @@ * try to to deselect this object. If the function returns true, the process is cancelled * @param {Object} [options] options sent from the upper functions * @param {Event} [options.e] event if the process is generated by an event + * @param {fabric.Object} [options.object] the object that is about to be selected if the process finishes */ onDeselect: function() { // implemented by sub-classes, as needed. @@ -306,9 +307,12 @@ * try to to select this object. If the function returns true, the process is cancelled * @param {Object} [options] options sent from the upper functions * @param {Event} [options.e] event if the process is generated by an event + * @param {fabric.Object} [options.object] the object that was deselected */ - onSelect: function() { + onSelect: function (opt) { // implemented by sub-classes, as needed. + if (opt.object && opt.object.parent && opt.object.parent !== this.parent + && opt.object.parent !== this && opt.object !== this) {return opt.object.parent;} } }); })(); diff --git a/src/mixins/object_stacking.mixin.js b/src/mixins/object_stacking.mixin.js index 8c8e87d5efd..d21bc6531c6 100644 --- a/src/mixins/object_stacking.mixin.js +++ b/src/mixins/object_stacking.mixin.js @@ -5,13 +5,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {fabric.Object} thisArg * @chainable */ - sendToBack: function() { - if (this.group) { - fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); - } - else if (this.canvas) { - this.canvas.sendToBack(this); - } + sendToBack: function () { + var stack = this.parent || this.group || this.canvas; + stack && fabric.StaticCanvas.prototype.sendToBack.call(stack, this); return this; }, @@ -21,12 +17,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @chainable */ bringToFront: function() { - if (this.group) { - fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); - } - else if (this.canvas) { - this.canvas.bringToFront(this); - } + var stack = this.parent || this.group || this.canvas; + stack && fabric.StaticCanvas.prototype.bringToFront.call(stack, this); return this; }, @@ -37,12 +29,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @chainable */ sendBackwards: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); - } - else if (this.canvas) { - this.canvas.sendBackwards(this, intersecting); - } + var stack = this.parent || this.group || this.canvas; + stack && fabric.StaticCanvas.prototype.sendBackwards.call(stack, this, intersecting); return this; }, @@ -52,13 +40,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {fabric.Object} thisArg * @chainable */ - bringForward: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); - } - else if (this.canvas) { - this.canvas.bringForward(this, intersecting); - } + bringForward: function (intersecting) { + var stack = this.parent || this.group || this.canvas; + stack && fabric.StaticCanvas.prototype.bringForward.call(stack, this, intersecting); return this; }, @@ -69,12 +53,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @chainable */ moveTo: function(index) { - if (this.group && this.group.type !== 'activeSelection') { - fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); - } - else if (this.canvas) { - this.canvas.moveTo(this, index); - } + var stack = this.parent || this.group || this.canvas; + stack && fabric.StaticCanvas.prototype.moveTo.call(stack, this, index); return this; } }); diff --git a/src/shapes/active_selection.class.js b/src/shapes/active_selection.class.js index 93ee250c1a5..dda0ad5ae23 100644 --- a/src/shapes/active_selection.class.js +++ b/src/shapes/active_selection.class.js @@ -15,7 +15,7 @@ * @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 */ { + fabric.ActiveSelection = fabric.util.createClass(fabric.Layer, /** @lends fabric.ActiveSelection.prototype */ { /** * Type of an object @@ -24,70 +24,11 @@ */ 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); + initialize: function (objects, options) { + this.callSuper('initialize', objects, 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.concat(); - this._objects = []; - var options = fabric.Object.prototype.toObject.call(this); - var newGroup = new fabric.Group([]); - delete options.type; - newGroup.set(options); - objects.forEach(function(object) { - object.canvas.remove(object); - object.group = newGroup; - }); - newGroup._objects = objects; - if (!this.canvas) { - return newGroup; - } - var canvas = this.canvas; - canvas.add(newGroup); - canvas._activeObject = 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 representation of a group * @return {String} @@ -108,14 +49,6 @@ 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 @@ -148,7 +81,7 @@ fabric.ActiveSelection.fromObject = function(object, callback) { fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { delete object.objects; - callback && callback(new fabric.ActiveSelection(enlivenedObjects, object, true)); + callback && callback(new fabric.ActiveSelection(enlivenedObjects, object)); }); }; diff --git a/src/shapes/layer.class.js b/src/shapes/layer.class.js new file mode 100644 index 00000000000..baccd03176a --- /dev/null +++ b/src/shapes/layer.class.js @@ -0,0 +1,450 @@ +(function (global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = {}), + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, + invertTransform = fabric.util.invertTransform, + applyTransformToObject = fabric.util.applyTransformToObject, + clone = fabric.util.object.clone, + extend = fabric.util.object.extend; + + if (fabric.Layer) { + fabric.warn('fabric.Layer is already defined'); + return; + } + + /** + * Layer class + * @class fabric.Layer + * @extends fabric.Object + * @mixes fabric.Collection + * @see {@link fabric.Layer#initialize} for constructor definition + */ + fabric.Layer = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Layer.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'layer', + + layout: 'auto', + + fill: '', + + subTargetCheck: true, + + /** + * Constructor + * We set `disableTransformPropagation=true` in order to guard objects' transformations from excessive mutations during initializion. + * + * @param {fabric.Object[]} [objects] layer objects + * @param {Object} [options] Options object + * @return {fabric.Layer} thisArg + */ + initialize: function (objects, options) { + this.disableTransformPropagation = true; + this._objects = objects || []; + this.__objectMonitor = this.__objectMonitor.bind(this); + this.callSuper('initialize', options); + this._applyLayoutStrategy({ type: 'initializion' }); + if (!this.subTargetCheck) { + this.ownMatrixCache.initialValue = this.calcOwnMatrix(); + } + this.forEachObject(function (object) { + this.subTargetCheck && object.setCoords(); + this.objectCaching && object._set('objectCaching', false); + object.set('parent', this); + }, this); + }, + + /** + * @private + * @param {string} key + * @param {*} value + */ + _set: function (key, value) { + if (key === 'subTargetCheck' && this.ownMatrixCache) { + // we want to avoid setting `initialValue` during initializion + var initialValue = this.ownMatrixCache.initialValue; + if (value && initialValue) { + this._applyMatrixDiff(initialValue, this.callSuper('calcOwnMatrix')); + delete this.ownMatrixCache.initialValue; + } + else if (!value && !initialValue) { + // we want to prevent this logic from writing over the exisitng value before it has been applied to objects + this.ownMatrixCache.initialValue = this.calcOwnMatrix(); + } + } + this.callSuper('_set', key, value); + if (key === 'canvas') { + this.forEachObject(function (object) { + object._set(key, value); + }); + } + if (key === 'layout') { + this._applyLayoutStrategy({ type: 'layout_change' }); + } + if (key === 'subTargetCheck') { + this.forEachObject(this._watchObject.bind(this, value)); + } + if (key === 'objectCaching' && value) { + this.forEachObject(function(object) { + object._set('objectCaching', false); + }); + } + return this; + }, + + /** + * Applies the matrix diff on all objects. + * @param {number[]} from The matrix objects are curretly relating to + * @param {number[]} to The matrix objects should relate to + */ + _applyMatrixDiff: function (from, to) { + this.forEachObject(function (object) { + var objectTransform = multiplyTransformMatrices(invertTransform(from), object.calcTransformMatrix()); + applyTransformToObject(object, multiplyTransformMatrices(to, objectTransform)); + object.setCoords(); + }); + }, + + /** + * Compares changes made to the transform matrix and applies them to instance's objects. + * Call this method before adding objects to prevent the existing transform diff from being applied to them unnecessarily. + * In other words, call this method to make the current transform the starting point of a transform diff for objects. + * Use `disableTransformPropagation` to disable propagation of current transform diff to objects. + * @returns Transform matrix + */ + calcOwnMatrix: function () { + var key = this.transformMatrixKey(true), cache = this.ownMatrixCache || (this.ownMatrixCache = {}), + dirty = cache.key !== key, transform = cache.value || fabric.iMatrix; + var matrix = this.callSuper('calcOwnMatrix'); + if (dirty && !this.disableTransformPropagation && this.subTargetCheck) { + this._applyMatrixDiff(transform, matrix); + } + return matrix; + }, + + add: function () { + this._onBeforeObjectsChange(); + fabric.Collection.add.apply(this, arguments); + }, + + insertAt: function (object, index, nonSplicing) { + this._onBeforeObjectsChange(); + this.callSuper('insertAt', object, index, nonSplicing); + }, + + remove: function () { + this._onBeforeObjectsChange(); + fabric.Collection.remove.apply(this, arguments); + }, + + /** + * @private + */ + _onBeforeObjectsChange: function () { + this.calcOwnMatrix(); + this.disableTransformPropagation = true; + }, + + __objectMonitor: function (opt) { + this._applyLayoutStrategy(extend(opt, { + type: 'object_modified' + })); + }, + + /** + * @private + * @param {boolean} watch + * @param {fabric.Object} object + */ + _watchObject: function (watch, object) { + object[watch ? 'on' : 'off']('modified', this.__objectMonitor); + }, + + /** + * @private + * @param {fabric.Object} object + */ + _onObjectAdded: function (object) { + object._set('canvas', this.canvas); + object._set('parent', this); + this.objectCaching && object._set('objectCaching', false); + this._watchObject(true, object); + this._applyLayoutStrategy({ + type: 'object_added', + target: object + }); + }, + + /** + * @private + * @param {fabric.Object} object + */ + _onObjectRemoved: function (object) { + delete object.canvas; + delete object.parent; + this._watchObject(false, object); + this._applyLayoutStrategy({ + type: 'object_removed', + target: object + }); + }, + + /** + * + * @param {object} opt + * @param {fabric.Object[]} opt.subTargets + * @returns true to abort selection, a `subTarget` to select that or false to defer to default behavior and allow selection to take place + */ + onSelect: function (opt) { + return opt.subTargets && opt.subTargets.length > 0 ? + opt.subTargets[0] : + this.callSuper('onSelect', opt); + }, + + isCacheDirty: function (skipCanvas) { + return this.callSuper('isCacheDirty', skipCanvas) + || this._objects.some(function (object) { + return object.isCacheDirty(skipCanvas); + }); + }, + + /** + * Performance optimization, `subTargetCheck === false`: + * In case we don't need instance to be interactive (selectable objects etc.) we don't apply the transform diff to the objects in order to minimize the number of iterations. + * We transform the entire ctx with the diff instead. + * We store the initial value of the transform matrix to do so, leaving objects as they were when the initial value was stored, rather than updating them continueously. + * This means that objects will render correctly on screen, **BUT** that's it. All geometry methods will **NOT WORK**. + * This optimization is crucial for an instance that contains a very large amount of objects. + * In case you need to select objects toggle `subTargetCheck` accordingly. + * + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function (ctx) { + fabric.Rect.prototype._render.call(this, ctx); + ctx.save(); + // if `subTargetCheck === true` then we transform ctx back to canvas plane, objects are up to date with the latest diff + // else we apply the matrix diif on ctx by transforming it back by the initial matrix, while objects relate (but not relative) to the initial matrix + var t = this.subTargetCheck ? this.calcTransformMatrix() : this.ownMatrixCache.initialValue; + ctx.transform.apply(ctx, invertTransform(t)); + this.forEachObject(function (object) { + object.render(ctx); + }); + ctx.restore(); + }, + + /** + * @public + * @param {string} layoutDirective + * @param {fabric.Object[]} objects + * @param {object} context object with data regarding what triggered the call + * @param {'initializion'|'object_modified'|'object_added'|'object_removed'|'layout_change'} context.type + * @returns options object + */ + getLayoutStrategyResult: function (layoutDirective, objects, context) { // eslint-disable-line no-unused-vars + if (layoutDirective === 'auto') { + return this.getObjectsBoundingBox(objects); + } + }, + + /** + * @private + * @param {object} context see `getLayoutStrategyResult` + */ + _applyLayoutStrategy: function (context) { + this.disableTransformPropagation = true; + this.set(this.getLayoutStrategyResult(this.layout, this._objects, context)); + this.calcOwnMatrix(); + context.type !== 'initialization' && this.setCoords(); + this.disableTransformPropagation = false; + }, + + /** + * + * @param {fabric.Object[]} objects + * @returns + */ + getObjectsBoundingBox: function (objects) { + var minX = 0, minY = 0, maxX = 0, maxY = 0; + for (var i = 0, o; i < objects.length; ++i) { + o = objects[i]; + var box = o.getBoundingRect(true, true); + if (i === 0) { + minX = Math.min(box.left, box.left + box.width); + maxX = Math.max(box.left, box.left + box.width); + minY = Math.min(box.top, box.top + box.height); + maxY = Math.max(box.top, box.top + box.height); + } + else { + minX = Math.min(minX, box.left, box.left + box.width); + maxX = Math.max(maxX, box.left, box.left + box.width); + minY = Math.min(minY, box.top, box.top + box.height); + maxY = Math.max(maxY, box.top, box.top + box.height); + } + } + return { + left: minX, + top: minY, + width: (maxX - minX) / (this.scaleX || 1), + height: (maxY - minY) / (this.scaleY || 1), + originX: 'left', + originY: 'top' + }; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function (propertiesToInclude) { + var _includeDefaultValues = this.includeDefaultValues; + var objsToObject = this._objects + .filter(function (obj) { + return !obj.excludeFromExport; + }) + .map(function (obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); + var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude); + obj.objects = objsToObject; + return obj; + }, + + /** + * Returns object representation of an instance, in dataless mode. + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function (propertiesToInclude) { + var objsToObject, sourcePath = this.sourcePath; + if (sourcePath) { + objsToObject = sourcePath; + } + else { + var _includeDefaultValues = this.includeDefaultValues; + objsToObject = this._objects.map(function (obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toDatalessObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); + } + var obj = fabric.Object.prototype.toDatalessObject.call(this, propertiesToInclude); + obj.objects = objsToObject; + return obj; + }, + + toString: function () { + return '#'; + }, + + dispose: function () { + this.forEachObject(function (object) { + object.dispose && object.dispose(); + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + _toSVG: function (reviver) { + var svgString = ['\n']; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t\t', this._objects[i].toSVG(reviver)); + } + svgString.push('\n'); + return svgString; + }, + + /** + * Returns styles-string for svg-export, specific version for layer + * @return {String} + */ + getSvgStyles: function () { + var opacity = typeof this.opacity !== 'undefined' && this.opacity !== 1 ? + 'opacity: ' + this.opacity + ';' : '', + visibility = this.visible ? '' : ' visibility: hidden;'; + return [ + opacity, + this.getSvgFilter(), + visibility + ].join(''); + }, + + /** + * @override instance's transformations are excessive + * @param {boolean} full + * @param {string} additionalTransform + * @returns + */ + getSvgTransform: function (full, additionalTransform) { + var svgTransform = 'transform="' + fabric.util.matrixToSVG(fabric.iMatrix); + return svgTransform + + (additionalTransform || '') + '" '; + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function (reviver) { + var svgString = []; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t', this._objects[i].toClipPathSVG(reviver)); + } + + return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver }); + }, + /* _TO_SVG_END_ */ + }); + + /** + * Returns fabric.Layer instance from an object representation + * @static + * @memberOf fabric.Layer + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + */ + fabric.Layer.fromObject = function (object, callback) { + var objects = object.objects, + options = clone(object, true); + delete options.objects; + if (typeof objects === 'string') { + // it has to be a url or something went wrong. + fabric.loadSVGFromURL(objects, function (elements) { + var group = fabric.util.groupSVGElements(elements, object, objects); + group.set(options); + group._restoreObjectsState(); + callback && callback(new fabric.Layer(group._objects, options)); + }); + return; + } + fabric.util.enlivenObjects(objects, function (enlivenedObjects) { + fabric.util.enlivenObjects([object.clipPath], function (enlivedClipPath) { + var options = clone(object, true); + options.clipPath = enlivedClipPath[0]; + delete options.objects; + callback && callback(new fabric.Layer(enlivenedObjects, options)); + }); + }); + }; + +})(typeof exports !== 'undefined' ? exports : this); diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 7db7c565077..115c237b2d1 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1003,8 +1003,9 @@ else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { value = new fabric.Shadow(value); } - else if (key === 'dirty' && this.group) { - this.group.set('dirty', value); + else if (key === 'dirty') { + this.group && this.group.set('dirty', value); + this.parent && this.parent.set('dirty', value); } this[key] = value; @@ -1014,9 +1015,11 @@ if (this.cacheProperties.indexOf(key) > -1) { this.dirty = true; groupNeedsUpdate && this.group.set('dirty', true); + this.parent && this.parent.set('dirty', true); } - else if (groupNeedsUpdate && this.stateProperties.indexOf(key) > -1) { - this.group.set('dirty', true); + else if (this.stateProperties.indexOf(key) > -1) { + groupNeedsUpdate && this.group.set('dirty', true); + this.parent && this.parent.set('dirty', true); } } return this;