mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-22 15:04:41 +00:00
395 lines
13 KiB
JavaScript
395 lines
13 KiB
JavaScript
(function() {
|
|
|
|
var degreesToRadians = fabric.util.degreesToRadians;
|
|
|
|
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
|
|
|
|
/**
|
|
* The object interactivity controls.
|
|
* @private
|
|
*/
|
|
_controlsVisibility: null,
|
|
|
|
/**
|
|
* Determines which corner has been clicked
|
|
* @private
|
|
* @param {Object} pointer The pointer indicating the mouse position
|
|
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
|
|
*/
|
|
_findTargetCorner: function(pointer) {
|
|
if (!this.hasControls || !this.active) {
|
|
return false;
|
|
}
|
|
|
|
var ex = pointer.x,
|
|
ey = pointer.y,
|
|
xPoints,
|
|
lines;
|
|
this.__corner = 0;
|
|
for (var i in this.oCoords) {
|
|
|
|
if (!this.isControlVisible(i)) {
|
|
continue;
|
|
}
|
|
|
|
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,
|
|
newTheta = degreesToRadians(45 - this.angle),
|
|
/* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */
|
|
/* 0.707106 stands for sqrt(2)/2 */
|
|
cornerHypotenuse = this.cornerSize * 0.707106,
|
|
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
|
|
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
|
|
x, y;
|
|
|
|
for (var point in coords) {
|
|
x = coords[point].x;
|
|
y = coords[point].y;
|
|
coords[point].corner = {
|
|
tl: {
|
|
x: x - sinHalfOffset,
|
|
y: y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: x + cosHalfOffset,
|
|
y: y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: x - cosHalfOffset,
|
|
y: y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: x + sinHalfOffset,
|
|
y: y + cosHalfOffset
|
|
}
|
|
};
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Draws a colored layer behind the object, inside its selection borders.
|
|
* Requires public options: padding, selectionBackgroundColor
|
|
* this function is called when the context is transformed
|
|
* has checks to be skipped when the object is on a staticCanvas
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawSelectionBackground: function(ctx) {
|
|
if (!this.selectionBackgroundColor || this.group || !this.active ||
|
|
(this.canvas && !this.canvas.interactive)) {
|
|
return this;
|
|
}
|
|
ctx.save();
|
|
var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(),
|
|
vpt = this.canvas.viewportTransform;
|
|
ctx.translate(center.x, center.y);
|
|
ctx.scale(1 / vpt[0], 1 / vpt[3]);
|
|
ctx.rotate(degreesToRadians(this.angle));
|
|
ctx.fillStyle = this.selectionBackgroundColor;
|
|
ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y);
|
|
ctx.restore();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
* @param {Object} styleOverride object to override the object style
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawBorders: function(ctx, styleOverride) {
|
|
styleOverride = styleOverride || {};
|
|
var wh = this._calculateCurrentDimensions(),
|
|
strokeWidth = 1 / this.borderScaleFactor,
|
|
width = wh.x + strokeWidth,
|
|
height = wh.y + strokeWidth,
|
|
drawRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined' ?
|
|
styleOverride.hasRotatingPoint : this.hasRotatingPoint,
|
|
hasControls = typeof styleOverride.hasControls !== 'undefined' ?
|
|
styleOverride.hasControls : this.hasControls,
|
|
rotatingPointOffset = typeof styleOverride.rotatingPointOffset !== 'undefined' ?
|
|
styleOverride.rotatingPointOffset : this.rotatingPointOffset;
|
|
|
|
ctx.save();
|
|
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
|
|
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
|
|
|
|
ctx.strokeRect(
|
|
-width / 2,
|
|
-height / 2,
|
|
width,
|
|
height
|
|
);
|
|
|
|
if (drawRotatingPoint && this.isControlVisible('mtr') && hasControls) {
|
|
|
|
var rotateHeight = -height / 2;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, rotateHeight);
|
|
ctx.lineTo(0, rotateHeight - rotatingPointOffset);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Draws borders of an object's bounding box when it is inside a group.
|
|
* Requires public properties: width, height
|
|
* Requires public options: padding, borderColor
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
|
* @param {object} options object representing current object parameters
|
|
* @param {Object} styleOverride object to override the object style
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawBordersInGroup: function(ctx, options, styleOverride) {
|
|
styleOverride = styleOverride || {};
|
|
var p = this._getNonTransformedDimensions(),
|
|
matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX),
|
|
wh = fabric.util.transformPoint(p, matrix),
|
|
strokeWidth = 1 / this.borderScaleFactor,
|
|
width = wh.x + strokeWidth,
|
|
height = wh.y + strokeWidth;
|
|
|
|
ctx.save();
|
|
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
|
|
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
|
|
|
|
ctx.strokeRect(
|
|
-width / 2,
|
|
-height / 2,
|
|
width,
|
|
height
|
|
);
|
|
|
|
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
|
|
* @param {Object} styleOverride object to override the object style
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawControls: function(ctx, styleOverride) {
|
|
styleOverride = styleOverride || {};
|
|
var wh = this._calculateCurrentDimensions(),
|
|
width = wh.x,
|
|
height = wh.y,
|
|
scaleOffset = styleOverride.cornerSize || this.cornerSize,
|
|
left = -(width + scaleOffset) / 2,
|
|
top = -(height + scaleOffset) / 2,
|
|
transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ?
|
|
styleOverride.transparentCorners : this.transparentCorners,
|
|
hasRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined' ?
|
|
styleOverride.hasRotatingPoint : this.hasRotatingPoint,
|
|
methodName = transparentCorners ? 'stroke' : 'fill';
|
|
|
|
ctx.save();
|
|
ctx.strokeStyle = ctx.fillStyle = styleOverride.cornerColor || this.cornerColor;
|
|
if (!this.transparentCorners) {
|
|
ctx.strokeStyle = styleOverride.cornerStrokeColor || this.cornerStrokeColor;
|
|
}
|
|
this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray, null);
|
|
|
|
// top-left
|
|
this._drawControl('tl', ctx, methodName,
|
|
left,
|
|
top, styleOverride);
|
|
|
|
// top-right
|
|
this._drawControl('tr', ctx, methodName,
|
|
left + width,
|
|
top, styleOverride);
|
|
|
|
// bottom-left
|
|
this._drawControl('bl', ctx, methodName,
|
|
left,
|
|
top + height, styleOverride);
|
|
|
|
// bottom-right
|
|
this._drawControl('br', ctx, methodName,
|
|
left + width,
|
|
top + height, styleOverride);
|
|
|
|
if (!this.get('lockUniScaling')) {
|
|
|
|
// middle-top
|
|
this._drawControl('mt', ctx, methodName,
|
|
left + width / 2,
|
|
top, styleOverride);
|
|
|
|
// middle-bottom
|
|
this._drawControl('mb', ctx, methodName,
|
|
left + width / 2,
|
|
top + height, styleOverride);
|
|
|
|
// middle-right
|
|
this._drawControl('mr', ctx, methodName,
|
|
left + width,
|
|
top + height / 2, styleOverride);
|
|
|
|
// middle-left
|
|
this._drawControl('ml', ctx, methodName,
|
|
left,
|
|
top + height / 2, styleOverride);
|
|
}
|
|
|
|
// middle-top-rotate
|
|
if (hasRotatingPoint) {
|
|
this._drawControl('mtr', ctx, methodName,
|
|
left + width / 2,
|
|
top - this.rotatingPointOffset, styleOverride);
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawControl: function(control, ctx, methodName, left, top, styleOverride) {
|
|
styleOverride = styleOverride || {};
|
|
if (!this.isControlVisible(control)) {
|
|
return;
|
|
}
|
|
var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor;
|
|
switch (styleOverride.cornerStyle || this.cornerStyle) {
|
|
case 'circle':
|
|
ctx.beginPath();
|
|
ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
|
|
ctx[methodName]();
|
|
if (stroke) {
|
|
ctx.stroke();
|
|
}
|
|
break;
|
|
default:
|
|
this.transparentCorners || ctx.clearRect(left, top, size, size);
|
|
ctx[methodName + 'Rect'](left, top, size, size);
|
|
if (stroke) {
|
|
ctx.strokeRect(left, top, size, size);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if the specified control is visible, false otherwise.
|
|
* @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
|
|
* @returns {Boolean} true if the specified control is visible, false otherwise
|
|
*/
|
|
isControlVisible: function(controlName) {
|
|
return this._getControlsVisibility()[controlName];
|
|
},
|
|
|
|
/**
|
|
* Sets the visibility of the specified control.
|
|
* @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
|
|
* @param {Boolean} visible true to set the specified control visible, false otherwise
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
setControlVisible: function(controlName, visible) {
|
|
this._getControlsVisibility()[controlName] = visible;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets the visibility state of object controls.
|
|
* @param {Object} [options] Options object
|
|
* @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
|
|
* @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it
|
|
* @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it
|
|
* @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it
|
|
* @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it
|
|
* @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it
|
|
* @param {Boolean} [options.tl] true to enable the top-left control, false to disable it
|
|
* @param {Boolean} [options.tr] true to enable the top-right control, false to disable it
|
|
* @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
setControlsVisibility: function(options) {
|
|
options || (options = { });
|
|
|
|
for (var p in options) {
|
|
this.setControlVisible(p, options[p]);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns the instance of the control visibility set for this object.
|
|
* @private
|
|
* @returns {Object}
|
|
*/
|
|
_getControlsVisibility: function() {
|
|
if (!this._controlsVisibility) {
|
|
this._controlsVisibility = {
|
|
tl: true,
|
|
tr: true,
|
|
br: true,
|
|
bl: true,
|
|
ml: true,
|
|
mt: true,
|
|
mr: true,
|
|
mb: true,
|
|
mtr: true
|
|
};
|
|
}
|
|
return this._controlsVisibility;
|
|
}
|
|
});
|
|
})();
|