fabric.js/src/mixins/object_interactivity.mixin.js
2013-11-19 16:21:29 +00:00

418 lines
13 KiB
JavaScript

(function(){
var getPointer = fabric.util.getPointer,
degreesToRadians = fabric.util.degreesToRadians;
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
/**
* Determines which one of the four corners has been clicked
* @private
* @param {Event} e Event object
* @param {Object} offset Canvas offset
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
*/
_findTargetCorner: function(e, offset) {
if (!this.hasControls || !this.active) return false;
var pointer = this.canvas.getPointer(e, true),
ex = pointer.x,
ey = pointer.y,
xPoints,
lines;
for (var i in this.oCoords) {
if (i === 'mtr' && !this.hasRotatingPoint) {
continue;
}
if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
continue;
}
lines = this._getImageLines(this.oCoords[i].corner);
// debugging
// canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
// canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
// canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
// canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
// canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
// canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
// canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
// canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
xPoints = this._findCrossPoints({x: ex, y: ey}, lines);
if (xPoints !== 0 && xPoints % 2 === 1) {
this.__corner = i;
return i;
}
}
return false;
},
/**
* Sets the coordinates of the draggable boxes in the corners of
* the image used to scale/rotate it.
* @private
*/
_setCornerCoords: function() {
var coords = this.oCoords,
theta = degreesToRadians(this.angle),
newTheta = degreesToRadians(45 - this.angle),
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
sinTh = Math.sin(theta),
cosTh = Math.cos(theta);
coords.tl.corner = {
tl: {
x: coords.tl.x - sinHalfOffset,
y: coords.tl.y - cosHalfOffset
},
tr: {
x: coords.tl.x + cosHalfOffset,
y: coords.tl.y - sinHalfOffset
},
bl: {
x: coords.tl.x - cosHalfOffset,
y: coords.tl.y + sinHalfOffset
},
br: {
x: coords.tl.x + sinHalfOffset,
y: coords.tl.y + cosHalfOffset
}
};
coords.tr.corner = {
tl: {
x: coords.tr.x - sinHalfOffset,
y: coords.tr.y - cosHalfOffset
},
tr: {
x: coords.tr.x + cosHalfOffset,
y: coords.tr.y - sinHalfOffset
},
br: {
x: coords.tr.x + sinHalfOffset,
y: coords.tr.y + cosHalfOffset
},
bl: {
x: coords.tr.x - cosHalfOffset,
y: coords.tr.y + sinHalfOffset
}
};
coords.bl.corner = {
tl: {
x: coords.bl.x - sinHalfOffset,
y: coords.bl.y - cosHalfOffset
},
bl: {
x: coords.bl.x - cosHalfOffset,
y: coords.bl.y + sinHalfOffset
},
br: {
x: coords.bl.x + sinHalfOffset,
y: coords.bl.y + cosHalfOffset
},
tr: {
x: coords.bl.x + cosHalfOffset,
y: coords.bl.y - sinHalfOffset
}
};
coords.br.corner = {
tr: {
x: coords.br.x + cosHalfOffset,
y: coords.br.y - sinHalfOffset
},
bl: {
x: coords.br.x - cosHalfOffset,
y: coords.br.y + sinHalfOffset
},
br: {
x: coords.br.x + sinHalfOffset,
y: coords.br.y + cosHalfOffset
},
tl: {
x: coords.br.x - sinHalfOffset,
y: coords.br.y - cosHalfOffset
}
};
coords.ml.corner = {
tl: {
x: coords.ml.x - sinHalfOffset,
y: coords.ml.y - cosHalfOffset
},
tr: {
x: coords.ml.x + cosHalfOffset,
y: coords.ml.y - sinHalfOffset
},
bl: {
x: coords.ml.x - cosHalfOffset,
y: coords.ml.y + sinHalfOffset
},
br: {
x: coords.ml.x + sinHalfOffset,
y: coords.ml.y + cosHalfOffset
}
};
coords.mt.corner = {
tl: {
x: coords.mt.x - sinHalfOffset,
y: coords.mt.y - cosHalfOffset
},
tr: {
x: coords.mt.x + cosHalfOffset,
y: coords.mt.y - sinHalfOffset
},
bl: {
x: coords.mt.x - cosHalfOffset,
y: coords.mt.y + sinHalfOffset
},
br: {
x: coords.mt.x + sinHalfOffset,
y: coords.mt.y + cosHalfOffset
}
};
coords.mr.corner = {
tl: {
x: coords.mr.x - sinHalfOffset,
y: coords.mr.y - cosHalfOffset
},
tr: {
x: coords.mr.x + cosHalfOffset,
y: coords.mr.y - sinHalfOffset
},
bl: {
x: coords.mr.x - cosHalfOffset,
y: coords.mr.y + sinHalfOffset
},
br: {
x: coords.mr.x + sinHalfOffset,
y: coords.mr.y + cosHalfOffset
}
};
coords.mb.corner = {
tl: {
x: coords.mb.x - sinHalfOffset,
y: coords.mb.y - cosHalfOffset
},
tr: {
x: coords.mb.x + cosHalfOffset,
y: coords.mb.y - sinHalfOffset
},
bl: {
x: coords.mb.x - cosHalfOffset,
y: coords.mb.y + sinHalfOffset
},
br: {
x: coords.mb.x + sinHalfOffset,
y: coords.mb.y + cosHalfOffset
}
};
coords.mtr.corner = {
tl: {
x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset),
y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset)
},
tr: {
x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset),
y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset)
},
bl: {
x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset),
y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset)
},
br: {
x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset),
y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset)
}
};
},
/**
* Draws borders of an object's bounding box.
* Requires public properties: width, height
* Requires public options: padding, borderColor
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @return {fabric.Object} thisArg
* @chainable
*/
drawBorders: function(ctx) {
if (!this.hasBorders) return this;
var padding = this.padding,
padding2 = padding * 2,
strokeWidth = ~~(this.strokeWidth / 2) * 2; // Round down to even number
ctx.save();
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
ctx.strokeStyle = this.borderColor;
var scaleX = 1 / this._constrainScale(this.scaleX),
scaleY = 1 / this._constrainScale(this.scaleY);
ctx.lineWidth = 1 / this.borderScaleFactor;
var vpt = this.canvas.viewportTransform,
wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true),
sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true),
w = wh.x,
h = wh.y,
sx= sxy.x,
sy= sxy.y;
if (this.group) {
w = w * this.group.scaleX;
h = h * this.group.scaleY;
}
ctx.strokeRect(
~~(-(w / 2) - padding - strokeWidth / 2 * sx) - 0.5, // offset needed to make lines look sharper
~~(-(h / 2) - padding - strokeWidth / 2 * sy) - 0.5,
~~(w + padding2 + strokeWidth * sx) + 1, // double offset needed to make lines look sharper
~~(h + padding2 + strokeWidth * sy) + 1
);
if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) {
var rotateHeight = (
this.flipY
? h + (strokeWidth * sx) + (padding * 2)
: -h - (strokeWidth * sy) - (padding * 2)
) / 2;
ctx.beginPath();
ctx.moveTo(0, rotateHeight);
ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset));
ctx.closePath();
ctx.stroke();
}
ctx.restore();
return this;
},
/**
* Draws corners of an object's bounding box.
* Requires public properties: width, height
* Requires public options: cornerSize, padding
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @return {fabric.Object} thisArg
* @chainable
*/
drawControls: function(ctx) {
if (!this.hasControls) return this;
var size = this.cornerSize,
size2 = size / 2,
strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down
wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true),
width = wh.x,
height = wh.y,
left = -(width / 2),
top = -(height / 2),
_left,
_top,
padding = this.padding,
scaleOffset = size2,
scaleOffsetSize = size2 - size,
methodName = this.transparentCorners ? 'strokeRect' : 'fillRect',
transparent = this.transparentCorners,
isVML = typeof G_vmlCanvasManager !== 'undefined';
ctx.save();
ctx.lineWidth = 1;
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
// top-left
_left = left - scaleOffset - strokeWidth2 - padding;
_top = top - scaleOffset - strokeWidth2 - padding;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
// top-right
_left = left + width - scaleOffset + strokeWidth2 + padding;
_top = top - scaleOffset - strokeWidth2 - padding;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
// bottom-left
_left = left - scaleOffset - strokeWidth2 - padding;
_top = top + height + scaleOffsetSize + strokeWidth2 + padding;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
// bottom-right
_left = left + width + scaleOffsetSize + strokeWidth2 + padding;
_top = top + height + scaleOffsetSize + strokeWidth2 + padding;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
if (!this.get('lockUniScaling')) {
// middle-top
_left = left + width/2 - scaleOffset;
_top = top - scaleOffset - strokeWidth2 - padding;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
// middle-bottom
_left = left + width/2 - scaleOffset;
_top = top + height + scaleOffsetSize + strokeWidth2 + padding;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
// middle-right
_left = left + width + scaleOffsetSize + strokeWidth2 + padding;
_top = top + height/2 - scaleOffset;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
// middle-left
_left = left - scaleOffset - strokeWidth2 - padding;
_top = top + height/2 - scaleOffset;
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
}
// middle-top-rotate
if (this.hasRotatingPoint) {
_left = left + width/2 - scaleOffset;
_top = this.flipY ?
(top + height + (this.rotatingPointOffset) - size2 + strokeWidth2 + padding)
: (top - (this.rotatingPointOffset) - size2 - strokeWidth2 - padding);
isVML || transparent || ctx.clearRect(_left, _top, size, size);
ctx[methodName](_left, _top, size, size);
}
ctx.restore();
return this;
}
});
})();