mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-25 16:04:42 +00:00
Target in group (#2997)
Added gorup.subTargetCheck, fixed transparent target selected on mouse up, fix text grouping while editing
This commit is contained in:
parent
5a6b11ef50
commit
0c6a665bcb
6 changed files with 141 additions and 87 deletions
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue