Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deep save state #3263

Merged
merged 7 commits into from
Sep 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions src/canvas.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,23 @@

/**
* Indicates which key enable multiple click selection
* values: altKey, shiftKey, ctrlKey
* values: altKey, shiftKey, ctrlKey, cmdKey
* @since 1.6.2
* @type String
* @default
*/
selectionKey: 'shiftKey',

/**
* Indicates which key enable alternative selection
* in case of target overlapping with active object
* values: altKey, shiftKey, ctrlKey, cmdKey
* @since 1.6.5
* @type null|String
* @default
*/
altSelectionKey: null,

/**
* Color of selection
* @type String
Expand Down Expand Up @@ -398,7 +408,6 @@
vptPointer = this.restorePointerVpt(pointer),
p = fabric.util.transformPoint(vptPointer, invertedM);
return fabric.util.transformPoint(p, vpt);
//return { x: p.x * vpt[0], y: p.y * vpt[3] };
},

/**
Expand Down Expand Up @@ -1006,22 +1015,34 @@
var ignoreZoom = true,
pointer = this.getPointer(e, ignoreZoom),
activeGroup = this.getActiveGroup(),
activeObject = this.getActiveObject();
activeObject = this.getActiveObject(),
activeTarget;

// first check current group (if one exists)
// active group does not check sub targets like normal groups.
// if active group just exits.
if (activeGroup && !skipGroup && this._checkTarget(pointer, activeGroup)) {
return activeGroup;
}

if (activeObject && this._checkTarget(pointer, activeObject)) {
// if we hit the corner of an activeObject, let's return that.
if (activeObject && activeObject._findTargetCorner(pointer)) {
return activeObject;
}
if (activeObject && this._checkTarget(pointer, activeObject)) {
if (!this.preserveObjectStacking) {
return activeObject;
}
else {
activeTarget = activeObject;
}
}

this.targets = [];

var target = this._searchPossibleTargets(this._objects, pointer);
if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) {
target = activeTarget;
}
this._fireOverOutEvents(target, e);
return target;
},
Expand Down
4 changes: 4 additions & 0 deletions src/mixins/object_geometry.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@
return this;
},

/*
* calculate rotation matrix of an object
* @return {Array} rotation matrix for the object
*/
_calcRotateMatrix: function() {
if (this.angle) {
var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta);
Expand Down
112 changes: 71 additions & 41 deletions src/mixins/stateful.mixin.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,75 @@
/*
Depends on `stateProperties`
*/
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {

/**
* Returns true if object state (one of its state properties) was changed
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
*/
hasStateChanged: function() {
return this.stateProperties.some(function(prop) {
return this.get(prop) !== this.originalState[prop];
}, this);
},

/**
* Saves state of an object
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state
* @return {fabric.Object} thisArg
*/
saveState: function(options) {
this.stateProperties.forEach(function(prop) {
this.originalState[prop] = this.get(prop);
}, this);

if (options && options.stateProperties) {
options.stateProperties.forEach(function(prop) {
this.originalState[prop] = this.get(prop);
}, this);
}
(function() {

return this;
},
var extend = fabric.util.object.extend;

/**
* Setups state of an object
* @return {fabric.Object} thisArg
*/
setupState: function() {
this.originalState = { };
this.saveState();
/*
Depends on `stateProperties`
*/
function saveProps(origin, destination, props) {
var tmpObj = { }, deep = true;
props.forEach(function(prop) {
tmpObj[prop] = origin[prop];
});
extend(origin[destination], tmpObj, deep);
}

return this;
function _isEqual(origValue, currentValue) {
if (origValue instanceof Array) {
if (origValue.length !== currentValue.length) {
return false
}
var _currentValue = currentValue.concat().sort(),
_origValue = origValue.concat().sort();
return !_origValue.some(function(v, i) {
return !_isEqual(_currentValue[i], v);
});
}
else if (origValue instanceof Object) {
for (var key in origValue) {
if (!_isEqual(origValue[key], currentValue[key])) {
return false;
}
}
return true;
}
else {
return origValue === currentValue;
}
}
});


fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {

/**
* Returns true if object state (one of its state properties) was changed
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
*/
hasStateChanged: function() {
return !_isEqual(this.originalState, this);
},

/**
* Saves state of an object
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state
* @return {fabric.Object} thisArg
*/
saveState: function(options) {
saveProps(this, 'originalState', this.stateProperties);
if (options && options.stateProperties) {
saveProps(this, 'originalState', options.stateProperties);
}
return this;
},

/**
* Setups state of an object
* @param {Object} [options] Object with additional `stateProperties` array to include when saving state
* @return {fabric.Object} thisArg
*/
setupState: function(options) {
this.originalState = { };
this.saveState(options);
return this;
}
});
})();
15 changes: 15 additions & 0 deletions src/shapes/image.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
return;
}

var stateProperties = fabric.Object.prototype.stateProperties.concat();
stateProperties.push(
'alignX',
'alignY',
'meetOrSlice'
);

/**
* Image class
* @class fabric.Image
Expand Down Expand Up @@ -97,6 +104,14 @@
*/
minimumScaleTrigger: 0.5,

/**
* List of properties to consider when checking if
* state of an object is changed ({@link fabric.Object#hasStateChanged})
* as well as for history (undo/redo) purposes
* @type Array
*/
stateProperties: stateProperties,

/**
* Constructor
* @param {HTMLImageElement | String} element Image element
Expand Down
2 changes: 1 addition & 1 deletion src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
'alignX alignY meetOrSlice skewX skewY'
'skewX skewY'
).split(' '),

/**
Expand Down
30 changes: 25 additions & 5 deletions src/util/lang_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,30 @@
* @param {Object} source Where to copy from
* @return {Object}
*/
function extend(destination, source) {
function extend(destination, source, deep) {
// JScript DontEnum bug is not taken care of
for (var property in source) {
destination[property] = source[property];
// the deep clone is for internal use, is not meant to avoid
// javascript traps or cloning html element or self referenced objects.
if (deep) {
if (source instanceof Array) {
destination = source.map(function(v) {
return clone(v, deep)
})
}
else if (source instanceof Object) {
for (var property in source) {
destination[property] = clone(source[property], deep)
}
}
else {
// this sounds odd for an extend but is ok for recursive use
destination = source;
}
}
else {
for (var property in source) {
destination[property] = source[property];
}
}
return destination;
}
Expand All @@ -21,8 +41,8 @@
* @param {Object} object Object to clone
* @return {Object}
*/
function clone(object) {
return extend({ }, object);
function clone(object, deep) {
return extend({ }, object, deep);
}

/** @namespace fabric.util.object */
Expand Down
1 change: 1 addition & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ testrunner.run({
'./test/unit/collection.js',
'./test/unit/point.js',
'./test/unit/intersection.js',
'./test/unit/stateful.js'
]
}, function(err, report) {
if (err) {
Expand Down
14 changes: 10 additions & 4 deletions test/unit/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,17 +346,23 @@
test('findTarget preserveObjectStacking true', function() {
ok(typeof canvas.findTarget == 'function');
canvas.preserveObjectStacking = true;
var rect = makeRect({ left: 0, top: 0 }),
rectOver = makeRect({ left: 0, top: 0 }),
var rect = makeRect({ left: 0, top: 0, width: 30, height: 30 }),
rectOver = makeRect({ left: 0, top: 0, width: 30, height: 30 }),
target,
pointer = { clientX: 5, clientY: 5 };
pointer = { clientX: 15, clientY: 15, 'shiftKey': true },
pointer2 = { clientX: 4, clientY: 4 };
canvas.add(rect);
canvas.add(rectOver);
target = canvas.findTarget(pointer);
equal(target, rectOver, 'Should return the rectOver, rect is not considered');
canvas.setActiveObject(rect);
target = canvas.findTarget(pointer);
equal(target, rect, 'Should return the rect, because it is active');
equal(target, rectOver, 'Should still return rectOver because is above active object');
target = canvas.findTarget(pointer2);
equal(target, rect, 'Should rect because a corner of the activeObject has been hit');
canvas.altSelectionKey = 'shiftKey';
target = canvas.findTarget(pointer);
equal(target, rect, 'Should rect because active and altSelectionKey is pressed');
canvas.preserveObjectStacking = false;
});

Expand Down
22 changes: 0 additions & 22 deletions test/unit/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,28 +680,6 @@
}
});

test('hasStateChanged', function() {
var cObj = new fabric.Object();
ok(typeof cObj.hasStateChanged == 'function');
cObj.setupState();
ok(!cObj.hasStateChanged());
cObj.saveState();
cObj.set('left', 123).set('top', 456);
ok(cObj.hasStateChanged());
});

test('saveState', function() {
var cObj = new fabric.Object();
ok(typeof cObj.saveState == 'function');
cObj.setupState();
equal(cObj.saveState(), cObj, 'chainable');
cObj.set('left', 123).set('top', 456);
cObj.saveState();
cObj.set('left', 223).set('top', 556);
equal(cObj.originalState.left, 123);
equal(cObj.originalState.top, 456);
});

test('intersectsWithRectangle', function() {
var cObj = new fabric.Object({ left: 50, top: 50, width: 100, height: 100 });
cObj.setCoords();
Expand Down
Loading