Target in group (#2997)

Added gorup.subTargetCheck, fixed transparent target selected on mouse up, fix text grouping while editing
This commit is contained in:
Andrea Bogazzi 2016-05-31 11:41:02 +02:00
parent 5a6b11ef50
commit 0c6a665bcb
6 changed files with 141 additions and 87 deletions

View file

@ -295,12 +295,19 @@
* Checks if point is contained within an area of given object
* @param {Event} e Event object
* @param {fabric.Object} target Object to test against
* @param {Object} [point] x,y object of point coordinates we want to check.
* @return {Boolean} true if point is contained within an area of given object
*/
containsPoint: function (e, target) {
var pointer = this.getPointer(e, true),
containsPoint: function (e, target, point) {
var pointer = point || this.getPointer(e, true),
xy = this._normalizePointer(target, pointer);
if (target.group && target.group === this.getActiveGroup()) {
xy = this._normalizePointer(target.group, pointer);
}
else {
xy = { x: pointer.x, y: pointer.y };
}
// http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
// http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
return (target.containsPoint(xy) || target._findTargetCorner(pointer));
@ -310,24 +317,17 @@
* @private
*/
_normalizePointer: function (object, pointer) {
var activeGroup = this.getActiveGroup(),
isObjectInGroup = (
activeGroup &&
object.type !== 'group' &&
activeGroup.contains(object)),
lt, m;
var lt, m;
if (isObjectInGroup) {
m = fabric.util.multiplyTransformMatrices(
this.viewportTransform,
activeGroup.calcTransformMatrix());
m = fabric.util.multiplyTransformMatrices(
this.viewportTransform,
object.calcTransformMatrix());
m = fabric.util.invertTransform(m);
pointer = fabric.util.transformPoint(pointer, m , false);
lt = fabric.util.transformPoint(activeGroup.getCenterPoint(), m , false);
pointer.x -= lt.x;
pointer.y -= lt.y;
}
m = fabric.util.invertTransform(m);
pointer = fabric.util.transformPoint(pointer, m , false);
lt = fabric.util.transformPoint(object.getCenterPoint(), m , false);
pointer.x -= lt.x;
pointer.y -= lt.y;
return { x: pointer.x, y: pointer.y };
},
@ -927,38 +927,43 @@
/**
* @private
*/
_isLastRenderedObject: function(e) {
_isLastRenderedObject: function(pointer) {
var lastRendered = this.lastRenderedWithControls;
return (
this.controlsAboveOverlay &&
this.lastRenderedObjectWithControlsAboveOverlay &&
this.lastRenderedObjectWithControlsAboveOverlay.visible &&
this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) &&
this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true)));
lastRendered &&
lastRendered.visible &&
(this.containsPoint(null, lastRendered, pointer) ||
lastRendered._findTargetCorner(pointer)));
},
/**
* Method that determines what object we are clicking on
* @param {Event} e mouse event
* @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
* @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through
*/
findTarget: function (e, skipGroup) {
if (this.skipTargetFind) {
return;
}
if (this._isLastRenderedObject(e)) {
return this.lastRenderedObjectWithControlsAboveOverlay;
}
// first check current group (if one exists)
// avtive group does not check sub targets like normal groups.
// if active group just exits.
var activeGroup = this.getActiveGroup();
if (!skipGroup && this._checkTarget(e, activeGroup, this.getPointer(e, true))) {
if (activeGroup && !skipGroup && this._checkTarget(pointer, activeGroup)) {
return activeGroup;
}
var target = this._searchPossibleTargets(e, skipGroup);
this._fireOverOutEvents(target, e);
var pointer = this.getPointer(e, true),
objects = this._objects;
this.targets = [ ];
if (this._isLastRenderedObject(pointer)) {
objects = [this.lastRenderedWithControls];
}
var target = this._searchPossibleTargets(objects, pointer);
this._fireOverOutEvents(target, e);
return target;
},
@ -987,11 +992,11 @@
/**
* @private
*/
_checkTarget: function(e, obj, pointer) {
_checkTarget: function(pointer, obj) {
if (obj &&
obj.visible &&
obj.evented &&
this.containsPoint(e, obj)){
this.containsPoint(null, obj, pointer)){
if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
if (!isTransparent) {
@ -1007,22 +1012,23 @@
/**
* @private
*/
_searchPossibleTargets: function(e, skipGroup) {
_searchPossibleTargets: function(objects, pointer) {
// Cache all targets where their bounding box contains point.
var target,
pointer = this.getPointer(e, true),
i = this._objects.length;
var target, i = objects.length, normalizedPointer, subTarget;
// Do not check for currently grouped objects, since we check the parent group itself.
// untill we call this function specifically to search inside the activeGroup
while (i--) {
if ((!this._objects[i].group || skipGroup) && this._checkTarget(e, this._objects[i], pointer)){
this.relatedTarget = this._objects[i];
target = this._objects[i];
if (this._checkTarget(pointer, objects[i])) {
target = objects[i];
if (target.type === 'group' && target.subTargetCheck) {
normalizedPointer = this._normalizePointer(target, pointer);
subTarget = this._searchPossibleTargets(target._objects, normalizedPointer);
subTarget && this.targets.push(subTarget);
}
break;
}
}
return target;
},
@ -1353,7 +1359,7 @@
continue;
}
this._objects[i]._renderControls(ctx);
this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
this.lastRenderedWithControls = this._objects[i];
}
}
});

View file

@ -263,7 +263,9 @@
* @param {Event} e Event object fired on mouseup
*/
__onMouseUp: function (e) {
var target, searchTarget = true, transform = this._currentTransform;
var target, searchTarget = true, transform = this._currentTransform,
groupSelector = this._groupSelector,
isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0));
if (this.isDrawingMode && this._isCurrentlyDrawing) {
this._onMouseUpInDrawingMode(e);
@ -279,29 +281,51 @@
var shouldRender = this._shouldRender(target, this.getPointer(e));
this._maybeGroupObjects(e);
if (target || !isClick) {
this._maybeGroupObjects(e);
}
else {
// those are done by default on mouse up
// by _maybeGroupObjects, we are skipping it in case of no target find
this._groupSelector = null;
this._currentTransform = null;
}
if (target) {
target.isMoving = false;
}
this._handleCursorAndEvent(e, target, 'up');
shouldRender && this.renderAll();
this._handleCursorAndEvent(e, target);
},
_handleCursorAndEvent: function(e, target) {
/**
* set cursor for mouse up and handle mouseUp event
* @param {Event} e event from mouse
* @param {fabric.Object} target receiving event
* @param {String} eventType event to fire (up, down or move)
*/
_handleCursorAndEvent: function(e, target, eventType) {
this._setCursorFromEvent(e, target);
this._handleEvent(e, eventType, target ? target : null);
},
// Can't find any reason, disabling for now
// TODO: why are we doing this?
/* var _this = this;
setTimeout(function () {
_this._setCursorFromEvent(e, target);
}, 50); */
/**
* Handle event firing for target and subtargets
* @param {Event} e event from mouse
* @param {String} eventType event to fire (up, down or move)
* @param {fabric.Object} targetObj receiving event
*/
_handleEvent: function(e, eventType, targetObj) {
var target = typeof targetObj === undefined ? this.findTarget(e) : targetObj,
targets = this.targets,
options = { e: e, target: target, subTargets: targets };
this.fire('mouse:up', { target: target, e: e });
target && target.fire('mouseup', { e: e });
this.fire('mouse:' + eventType, options);
target && target.fire('mouse' + eventType, options);
for (var i = 0; i < targets.length; i++) {
targets[i].fire('mouse' + eventType, options);
}
},
/**
@ -361,12 +385,7 @@
var ivt = fabric.util.invertTransform(this.viewportTransform),
pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
this.freeDrawingBrush.onMouseDown(pointer);
this.fire('mouse:down', { e: e });
var target = this.findTarget(e);
if (typeof target !== 'undefined') {
target.fire('mousedown', { e: e, target: target });
}
this._handleEvent(e, 'down');
},
/**
@ -380,12 +399,7 @@
this.freeDrawingBrush.onMouseMove(pointer);
}
this.setCursor(this.freeDrawingCursor);
this.fire('mouse:move', { e: e });
var target = this.findTarget(e);
if (typeof target !== 'undefined') {
target.fire('mousemove', { e: e, target: target });
}
this._handleEvent(e, 'move');
},
/**
@ -398,12 +412,7 @@
this.contextTop.restore();
}
this.freeDrawingBrush.onMouseUp();
this.fire('mouse:up', { e: e });
var target = this.findTarget(e);
if (typeof target !== 'undefined') {
target.fire('mouseup', { e: e, target: target });
}
this._handleEvent(e, 'up');
},
/**
@ -460,11 +469,9 @@
target.selectable && this.setActiveObject(target, e);
}
}
this._handleEvent(e, 'down', target ? target : null);
// we must renderAll so that active image is placed on the top canvas
shouldRender && this.renderAll();
this.fire('mouse:down', { target: target, e: e });
target && target.fire('mousedown', { e: e });
},
/**
@ -578,9 +585,7 @@
else {
this._transformObject(e);
}
this.fire('mouse:move', { target: target, e: e });
target && target.fire('mousemove', { e: e });
this._handleEvent(e, 'move', target ? target : null);
},
/**

View file

@ -24,18 +24,17 @@
* @param {fabric.Object} target
*/
_handleGrouping: function (e, target) {
var activeGroup = this.getActiveGroup();
if (target === this.getActiveGroup()) {
// if it's a group, find target again, this time skipping group
if (target === activeGroup) {
// 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 (!target || target.isType('group')) {
if (!target) {
return;
}
}
if (this.getActiveGroup()) {
if (activeGroup) {
this._updateActiveGroup(target, e);
}
else {
@ -103,7 +102,7 @@
groupObjects = isActiveLower
? [ this._activeObject, target ]
: [ target, this._activeObject ];
this._activeObject.isEditing && this._activeObject.exitEditing();
return new fabric.Group(groupObjects, {
canvas: this
});

View file

@ -87,6 +87,9 @@
* @return {Boolean} true if point is inside the object
*/
containsPoint: function(point) {
if (!this.oCoords) {
this.setCoords();
}
var lines = this._getImageLines(this.oCoords),
xPoints = this._findCrossPoints(point, lines);

View file

@ -48,6 +48,13 @@
*/
strokeWidth: 0,
/**
* Indicates if click events should also check for subtargets
* @type Boolean
* @default
*/
subTargetCheck: false,
/**
* Constructor
* @param {Object} objects Group objects

View file

@ -234,6 +234,40 @@
canvas.remove(rect);
});
test('findTarget with subTargetCheck', function() {
var rect = makeRect({ left: 0, top: 0 }),
rect2 = makeRect({ left: 30, top: 30}), target,
group = new fabric.Group([rect, rect2]);
canvas.add(group);
target = canvas.findTarget({
clientX: 5, clientY: 5
}, true);
equal(target, group, 'Should return the group');
equal(canvas.targets[0], undefined, 'no subtarget should return');
target = canvas.findTarget({
clientX: 30, clientY: 30
}, true);
equal(target, group, 'Should return the group');
group.subTargetCheck = true;
target = canvas.findTarget({
clientX: 5, clientY: 5
}, true);
equal(target, group, 'Should return the group');
equal(canvas.targets[0], rect, 'should return the rect');
target = canvas.findTarget({
clientX: 15, clientY: 15
}, true);
equal(target, group, 'Should return the group');
equal(canvas.targets[0], undefined, 'no subtarget should return');
target = canvas.findTarget({
clientX: 32, clientY: 32
}, true);
equal(target, group, 'Should return the group');
equal(canvas.targets[0], rect2, 'should return the rect2');
canvas.remove(group);
});
test('findTarget with perPixelTargetFind', function() {
ok(typeof canvas.findTarget == 'function');
var triangle = makeTriangle({ left: 0, top: 0 }), target;