diff --git a/Thorfile b/Thorfile
index 7ba0834c..eab292b7 100644
--- a/Thorfile
+++ b/Thorfile
@@ -17,7 +17,6 @@ class Build < Thor
"tests/js/unit/layerTests.js",
"tests/js/unit/shapeTests.js",
"tests/js/unit/ddTests.js",
- "tests/js/unit/customShapeTests.js",
"tests/js/unit/animationTests.js",
"tests/js/unit/transitionTests.js",
"tests/js/unit/shapes/rectTests.js",
diff --git a/src/Shape.js b/src/Shape.js
index 564f9a84..6ef1ec41 100644
--- a/src/Shape.js
+++ b/src/Shape.js
@@ -64,7 +64,6 @@ Kinetic.Shape = (function() {
Shape.prototype = {
_initShape: function(config) {
this.nodeType = 'Shape';
- this.appliedShadow = false;
// set colorKey
var shapes = Kinetic.Global.shapes;
@@ -104,36 +103,35 @@ Kinetic.Shape = (function() {
* @name stroke
* @methodOf Kinetic.Shape.prototype
*/
- stroke: function(context) {
+ stroke: function(context, stroke, strokeWidth, shadow) {
if(context.type === 'scene') {
- this._strokeScene(context);
+ return this._strokeScene(context, stroke, strokeWidth, shadow);
}
else if(context.type === 'hit') {
- this._strokeHit(context);
+ return this._strokeHit(context, strokeWidth);
}
+ return false;
},
- _strokeScene: function(context) {
- var strokeWidth = this.getStrokeWidth(), stroke = this.getStroke();
+ _strokeScene: function(context, stroke, strokeWidth, shadow) {
if(stroke || strokeWidth) {
- var appliedShadow = false;
-
context.save();
- if(this.attrs.shadow && !this.appliedShadow) {
- appliedShadow = this._applyShadow(context);
- }
-
+ var appliedShadow = this._applyShadow(context, shadow);
context.lineWidth = strokeWidth || 2;
context.strokeStyle = stroke || 'black';
context.stroke(context);
context.restore();
if(appliedShadow) {
- this.stroke(context);
+ if(shadow.opacity) {
+ this._strokeScene(context, stroke);
+ }
+ return true;
}
}
+ return false;
},
- _strokeHit: function(context) {
- var strokeWidth = this.getStrokeWidth(), stroke = this.colorKey;
+ _strokeHit: function(context, strokeWidth) {
+ var stroke = this.colorKey;
if(stroke || strokeWidth) {
context.save();
context.lineWidth = strokeWidth || 2;
@@ -141,6 +139,7 @@ Kinetic.Shape = (function() {
context.stroke(context);
context.restore();
}
+ return false;
},
_getFillType: function(fill) {
var type = Kinetic.Type;
@@ -163,27 +162,42 @@ Kinetic.Shape = (function() {
return 'UNKNOWN';
}
},
+ /**
+ * helper method to fill and stroke shape based on fill and stroke properties,
+ * and also automatically handle shadows, line joins, and line caps
+ * @name render
+ * @methodOf Kinetic.Shape.prototype
+ */
+ render: function(context) {
+ this.applyLineJoin(context, this.getLineJoin());
+ this.applyLineCap(context, this.getLineCap());
+ if(context.type === 'scene') {
+ this.applyOpacity(context);
+ }
+
+ var appliedShadow = this.fill(context, this.getFill(), this.getShadow());
+ this.stroke(context, this.getStroke(), this.getStrokeWidth(), appliedShadow ? null : this.getShadow());
+ },
/**
* helper method to fill the shape
* @name fill
* @methodOf Kinetic.Shape.prototype
- * */
- fill: function(context) {
+ */
+ fill: function(context, fill, shadow) {
if(context.type === 'scene') {
- this._fillScene(context);
+ return this._fillScene(context, fill, shadow);
}
else if(context.type === 'hit') {
- this._fillHit(context);
+ return this._fillHit(context);
}
+ return false;
},
- _fillScene: function(context) {
- var appliedShadow = false, fill = this.getFill(), fillType = this._getFillType(fill);
+ _fillScene: function(context, fill, shadow) {
+ var fillType = this._getFillType(fill);
if(fill) {
context.save();
- if(this.attrs.shadow && !this.appliedShadow) {
- appliedShadow = this._applyShadow(context);
- }
+ var appliedShadow = this._applyShadow(context, shadow);
var s = fill.start;
var e = fill.end;
@@ -233,36 +247,36 @@ Kinetic.Shape = (function() {
context.fill(context);
break;
}
- context.restore();
- }
- if(appliedShadow) {
- this.fill(context);
+ context.restore();
+
+ if(appliedShadow) {
+ if(shadow.opacity) {
+ this._fillScene(context, fill);
+ }
+ return true;
+ }
}
+ return false;
},
_fillHit: function(context) {
context.save();
context.fillStyle = this.colorKey;
context.fill(context);
context.restore();
+ return false;
},
/**
- * helper method to draw an image and apply
- * a shadow if needed
+ * helper method to draw an image
* @name drawImage
* @methodOf Kinetic.Shape.prototype
*/
drawImage: function() {
- var appliedShadow = false;
var context = arguments[0];
context.save();
var a = Array.prototype.slice.call(arguments);
if(a.length === 6 || a.length === 10) {
- if(this.attrs.shadow && !this.appliedShadow) {
- appliedShadow = this._applyShadow(context);
- }
-
if(a.length === 6) {
context.drawImage(a[1], a[2], a[3], a[4], a[5]);
}
@@ -272,10 +286,6 @@ Kinetic.Shape = (function() {
}
context.restore();
-
- if(appliedShadow) {
- this.drawImage.apply(this, a);
- }
},
applyOpacity: function(context) {
var absOpacity = this.getAbsoluteOpacity();
@@ -288,9 +298,10 @@ Kinetic.Shape = (function() {
* based on the applyLineJoin property
* @name lineJoin
* @methodOf Kinetic.Shape.prototype
+ * @param {CanvasContext} context
+ * @param {String} lineJoin
*/
- applyLineJoin: function(context) {
- var lineJoin = this.attrs.lineJoin;
+ applyLineJoin: function(context, lineJoin) {
if(lineJoin) {
context.lineJoin = lineJoin;
}
@@ -300,9 +311,10 @@ Kinetic.Shape = (function() {
* based on the lineCap property
* @name applyLineCap
* @methodOf Kinetic.Shape.prototype
+ * @param {CanvasContext} context
+ * @param {String} lineCap
*/
- applyLineCap: function(context) {
- var lineCap = this.attrs.lineCap;
+ applyLineCap: function(context, lineCap) {
if(lineCap) {
context.lineCap = lineCap;
}
@@ -379,30 +391,24 @@ Kinetic.Shape = (function() {
_get: function(selector) {
return this.nodeType === selector || this.shapeType === selector ? [this] : [];
},
- /**
- * apply shadow. return true if shadow was applied
- * and false if it was not
- */
- _applyShadow: function(context) {
- var s = this.attrs.shadow;
- if(s) {
+ _applyShadow: function(context, shadow) {
+ if(shadow) {
var aa = this.getAbsoluteOpacity();
// defaults
- var color = s.color ? s.color : 'black';
- var blur = s.blur ? s.blur : 5;
- var offset = s.offset ? s.offset : {
+ var color = shadow.color || 'black';
+ var blur = shadow.blur || 5;
+ var offset = shadow.offset || {
x: 0,
y: 0
};
- if(s.opacity) {
- context.globalAlpha = s.opacity * aa;
+ if(shadow.opacity) {
+ context.globalAlpha = shadow.opacity * aa;
}
context.shadowColor = color;
context.shadowBlur = blur;
context.shadowOffsetX = offset.x;
context.shadowOffsetY = offset.y;
- this.appliedShadow = true;
return true;
}
@@ -447,11 +453,6 @@ Kinetic.Shape = (function() {
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
- this.applyOpacity(context);
- this.applyLineJoin(context);
- this.applyLineCap(context);
- this.appliedShadow = false;
-
drawFunc.call(this, context);
context.restore();
}
@@ -474,12 +475,7 @@ Kinetic.Shape = (function() {
var node = family[n], t = node.getTransform(), m = t.getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
-
- this.applyOpacity(context);
- this.applyLineJoin(context);
- this.applyLineCap(context);
- this.appliedShadow = false;
-
+
drawFunc.call(this, context);
context.restore();
}
@@ -503,10 +499,6 @@ Kinetic.Shape = (function() {
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
- // don't draw shadows on hit context
- this.applyLineJoin(context);
- this.applyLineCap(context);
-
drawFunc.call(this, context);
context.restore();
}
@@ -523,7 +515,7 @@ Kinetic.Shape = (function() {
Kinetic.Global.extend(Shape, Kinetic.Node);
// add getters and setters
- Kinetic.Node.addGettersSetters(Shape, ['stroke', 'lineJoin', 'strokeWidth', 'drawFunc', 'drawHitFunc', 'cornerRadius']);
+ Kinetic.Node.addGettersSetters(Shape, ['stroke', 'lineJoin', 'lineCap', 'strokeWidth', 'drawFunc', 'drawHitFunc', 'cornerRadius']);
Kinetic.Node.addGetters(Shape, ['shadow', 'fill']);
/**
diff --git a/src/shapes/Circle.js b/src/shapes/Circle.js
index d6005592..006545a3 100644
--- a/src/shapes/Circle.js
+++ b/src/shapes/Circle.js
@@ -27,8 +27,7 @@ Kinetic.Circle.prototype = {
context.beginPath();
context.arc(0, 0, this.getRadius(), 0, Math.PI * 2, true);
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
},
getWidth: function() {
return this.getRadius() * 2;
diff --git a/src/shapes/Ellipse.js b/src/shapes/Ellipse.js
index f3329310..c383d571 100644
--- a/src/shapes/Ellipse.js
+++ b/src/shapes/Ellipse.js
@@ -36,8 +36,7 @@ Kinetic.Ellipse.prototype = {
context.arc(0, 0, r.x, 0, Math.PI * 2, true);
context.restore();
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
},
/**
* set radius
diff --git a/src/shapes/Image.js b/src/shapes/Image.js
index eafe8e0d..1e382dd9 100644
--- a/src/shapes/Image.js
+++ b/src/shapes/Image.js
@@ -36,8 +36,7 @@ Kinetic.Image.prototype = {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
if(this.attrs.image) {
// if cropping
@@ -55,19 +54,16 @@ Kinetic.Image.prototype = {
}
},
drawHitFunc: function(context) {
- var width = this.getWidth(), height = this.getHeight(), imageBuffer = this.imageBuffer;
+ var width = this.getWidth(), height = this.getHeight(), imageBuffer = this.imageBuffer, appliedShadow = false;
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
+ this.render(context);
if(imageBuffer) {
this.drawImage(context, this.imageBuffer, 0, 0, width, height);
}
- else {
- this.fill(context);
- }
- this.stroke(context);
},
/**
* apply filter
diff --git a/src/shapes/Line.js b/src/shapes/Line.js
index a4a83a75..624fb89b 100644
--- a/src/shapes/Line.js
+++ b/src/shapes/Line.js
@@ -47,7 +47,7 @@ Kinetic.Line.prototype = {
}
}
- this.stroke(context);
+ this.stroke(context, this.getStroke(), this.getStrokeWidth(), this.getShadow());
},
/**
* set points array
diff --git a/src/shapes/Path.js b/src/shapes/Path.js
index 7abb89d1..38b8d1d0 100644
--- a/src/shapes/Path.js
+++ b/src/shapes/Path.js
@@ -68,8 +68,7 @@ Kinetic.Path.prototype = {
break;
}
}
- this.fill(context);
- this.stroke(context);
+ this.render(context);
}
};
Kinetic.Global.extend(Kinetic.Path, Kinetic.Shape);
diff --git a/src/shapes/Polygon.js b/src/shapes/Polygon.js
index 4b01b588..ab0befc3 100644
--- a/src/shapes/Polygon.js
+++ b/src/shapes/Polygon.js
@@ -30,8 +30,7 @@ Kinetic.Polygon.prototype = {
context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y);
}
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
},
/**
* set points array
diff --git a/src/shapes/Rect.js b/src/shapes/Rect.js
index 3e9efac7..e08bb2cf 100644
--- a/src/shapes/Rect.js
+++ b/src/shapes/Rect.js
@@ -42,8 +42,7 @@ Kinetic.Rect.prototype = {
}
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
}
};
diff --git a/src/shapes/RegularPolygon.js b/src/shapes/RegularPolygon.js
index 164c2b74..5e7d3c82 100644
--- a/src/shapes/RegularPolygon.js
+++ b/src/shapes/RegularPolygon.js
@@ -34,8 +34,7 @@ Kinetic.RegularPolygon.prototype = {
context.lineTo(x, y);
}
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
}
};
Kinetic.Global.extend(Kinetic.RegularPolygon, Kinetic.Shape);
diff --git a/src/shapes/Sprite.js b/src/shapes/Sprite.js
index abd06508..3b005e40 100644
--- a/src/shapes/Sprite.js
+++ b/src/shapes/Sprite.js
@@ -38,8 +38,7 @@ Kinetic.Sprite.prototype = {
context.beginPath();
context.rect(0, 0, f.width, f.height);
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
if(this.attrs.image) {
@@ -58,8 +57,8 @@ Kinetic.Sprite.prototype = {
context.beginPath();
context.rect(0, 0, f.width, f.height);
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.fill(context, this.getFill(), null);
+ this.stroke(context, this.getStroke(), this.getStrokeWidth(), null);
},
/**
* start sprite animation
diff --git a/src/shapes/Star.js b/src/shapes/Star.js
index d566206b..19b386da 100644
--- a/src/shapes/Star.js
+++ b/src/shapes/Star.js
@@ -37,8 +37,7 @@ Kinetic.Star.prototype = {
}
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
}
};
Kinetic.Global.extend(Kinetic.Star, Kinetic.Shape);
diff --git a/src/shapes/Text.js b/src/shapes/Text.js
index b27c2612..36adfd7f 100644
--- a/src/shapes/Text.js
+++ b/src/shapes/Text.js
@@ -68,8 +68,7 @@ Kinetic.Text.prototype = {
}
context.closePath();
- this.fill(context);
- this.stroke(context);
+ this.render(context);
/*
* draw text
*/
@@ -85,14 +84,8 @@ Kinetic.Text.prototype = {
context.translate(0, p + this.getTextHeight() / 2);
// draw text lines
- var appliedShadow = this.appliedShadow;
for(var n = 0; n < textArr.length; n++) {
var text = textArr[n];
- /*
- * need to reset appliedShadow flag so that shadows
- * are appropriately applied to each line of text
- */
- this.appliedShadow = appliedShadow;
// horizontal alignment
context.save();
@@ -103,24 +96,50 @@ Kinetic.Text.prototype = {
context.translate((this.getWidth() - this._getTextSize(text).width - p * 2) / 2, 0);
}
- this.fillText(context, text);
- this.strokeText(context, text);
+ var appliedShadow = this.fillText(context, text, this.getTextFill(), this.getShadow());
+ this.strokeText(context, text, this.getTextStroke(), this.getTextStrokeWidth(), appliedShadow ? null : this.getShadow());
context.restore();
context.translate(0, lineHeightPx);
}
context.restore();
},
+ drawHitFunc: function(context) {
+ // draw rect
+ context.beginPath();
+ var boxWidth = this.getWidth();
+ var boxHeight = this.getHeight();
+
+ if(this.attrs.cornerRadius === 0) {
+ // simple rect - don't bother doing all that complicated maths stuff.
+ context.rect(0, 0, boxWidth, boxHeight);
+ }
+ else {
+ // arcTo would be nicer, but browser support is patchy (Opera)
+ context.moveTo(this.attrs.cornerRadius, 0);
+ context.lineTo(boxWidth - this.attrs.cornerRadius, 0);
+ context.arc(boxWidth - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false);
+ context.lineTo(boxWidth, boxHeight - this.attrs.cornerRadius);
+ context.arc(boxWidth - this.attrs.cornerRadius, boxHeight - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false);
+ context.lineTo(this.attrs.cornerRadius, boxHeight);
+ context.arc(this.attrs.cornerRadius, boxHeight - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false);
+ context.lineTo(0, this.attrs.cornerRadius);
+ context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false);
+ }
+ context.closePath();
+
+ this.render(context);
+ },
/**
- * set text
- * @name setText
- * @methodOf Kinetic.Text.prototype
- * @param {String} text
- */
- setText: function(text) {
- var str = Kinetic.Type._isString(text) ? text : text.toString();
- this.setAttr('text', str);
- },
+ * set text
+ * @name setText
+ * @methodOf Kinetic.Text.prototype
+ * @param {String} text
+ */
+ setText: function(text) {
+ var str = Kinetic.Type._isString(text) ? text : text.toString();
+ this.setAttr('text', str);
+ },
/**
* get width
* @name getWidth
@@ -172,36 +191,38 @@ Kinetic.Text.prototype = {
* @name fillText
* @methodOf Kinetic.Text.prototype
*/
- fillText: function(context, text) {
+ fillText: function(context, text, textFill, shadow) {
if(context.type === 'scene') {
- this._fillTextScene(context, text);
+ return this._fillTextScene(context, text, textFill, shadow);
}
- else if(context.type === 'buffer') {
- this._fillTextBuffer(context, text);
+ else if(context.type === 'hit') {
+ return this._fillTextHit(context, text);
}
+ return false;
},
- _fillTextScene: function(context, text) {
- var appliedShadow = false;
- if(this.attrs.textFill) {
+ _fillTextScene: function(context, text, textFill, shadow) {
+ if(textFill) {
context.save();
- if(this.attrs.shadow && !this.appliedShadow) {
- appliedShadow = this._applyShadow(context);
+ var appliedShadow = this._applyShadow(context, shadow);
+ context.fillStyle = textFill;
+ context.fillText(text, 0, 0);
+ context.restore();
+
+ if(appliedShadow) {
+ if(shadow.opacity) {
+ this._fillTextScene(context, text, textFill);
+ return true;
+ }
}
- context.fillStyle = this.attrs.textFill;
- context.fillText(text, 0, 0);
- context.restore();
- }
- if(appliedShadow) {
- this.fillText(context, text, 0, 0);
}
+ return false;
},
- _fillTextBuffer: function(context, text) {
- if(this.attrs.textFill) {
- context.save();
- context.fillStyle = this.colorKey;
- context.fillText(text, 0, 0);
- context.restore();
- }
+ _fillTextHit: function(context, text) {
+ context.save();
+ context.fillStyle = this.colorKey;
+ context.fillText(text, 0, 0);
+ context.restore();
+ return false;
},
/**
* helper method to stroke text
@@ -210,46 +231,46 @@ Kinetic.Text.prototype = {
* @methodOf Kinetic.Shape.prototype
* @param {String} text
*/
- strokeText: function(context, text) {
+ strokeText: function(context, text, textStroke, textStrokeWidth, shadow) {
if(context.type === 'scene') {
- this._strokeTextScene(context, text);
+ this._strokeTextScene(context, text, textStroke, textStrokeWidth, shadow);
}
- else if(context.type === 'buffer') {
- this._strokeTextBuffer(context, text);
+ else if(context.type === 'hit') {
+ this._strokeTextHit(context, text, textStrokeWidth);
}
+ return false;
},
- _strokeTextScene: function(context, text) {
- var appliedShadow = false;
-
- if(this.attrs.textStroke || this.attrs.textStrokeWidth) {
+ _strokeTextScene: function(context, text, textStroke, textStrokeWidth, shadow) {
+ if(textStroke || textStrokeWidth) {
context.save();
- if(this.attrs.shadow && !this.appliedShadow) {
- appliedShadow = this._applyShadow(context);
+ var appliedShadow = this._applyShadow(context, shadow);
+ // defaults
+ textStroke = textStroke || 'black';
+ textStrokeWidth = textStrokeWidth || 2;
+ context.lineWidth = textStrokeWidth;
+ context.strokeStyle = textStroke;
+ context.strokeText(text, 0, 0);
+ context.restore();
+
+ if(appliedShadow) {
+ if(shadow.opacity) {
+ this._strokeTextScene(context, text, textStroke, textStrokeWidth);
+ return true;
+ }
}
- // defaults
- var textStroke = this.attrs.textStroke ? this.attrs.textStroke : 'black';
- var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2;
- context.lineWidth = textStrokeWidth;
- context.strokeStyle = textStroke;
- context.strokeText(text, 0, 0);
- context.restore();
- }
-
- if(appliedShadow) {
- this.strokeText(context, text, 0, 0);
}
+ return false;
},
- _strokeTextBuffer: function(context, text) {
- if(this.attrs.textStroke || this.attrs.textStrokeWidth) {
- context.save();
- // defaults
- var textStroke = this.colorKey ? this.colorKey : 'black';
- var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2;
- context.lineWidth = textStrokeWidth;
- context.strokeStyle = textStroke;
- context.strokeText(text, 0, 0);
- context.restore();
- }
+ _strokeTextHit: function(context, text, textStrokeWidth) {
+ context.save();
+ // defaults
+ var textStroke = this.colorKey ? this.colorKey : 'black';
+ var textStrokeWidth = textStrokeWidth || 2;
+ context.lineWidth = textStrokeWidth;
+ context.strokeStyle = textStroke;
+ context.strokeText(text, 0, 0);
+ context.restore();
+ return false;
},
/**
* set text data. wrap logic and width and height setting occurs
diff --git a/src/shapes/TextPath.js b/src/shapes/TextPath.js
index 3ded1781..e99b177f 100644
--- a/src/shapes/TextPath.js
+++ b/src/shapes/TextPath.js
@@ -68,11 +68,9 @@ Kinetic.TextPath.prototype = {
var ht = parseFloat(this.attrs.fontSize);
context.translate(p0.x, p0.y);
-
context.rotate(glyphInfo[i].rotation);
- this.fillText(context, glyphInfo[i].text);
- this.strokeText(context, glyphInfo[i].text);
+ this.render(context);
context.restore();
@@ -310,10 +308,10 @@ Kinetic.Node.addGetters(Kinetic.TextPath, ['text']);
// reference Text methods
Kinetic.TextPath.prototype.fillText = Kinetic.Text.prototype.fillText;
Kinetic.TextPath.prototype._fillTextScene = Kinetic.Text.prototype._fillTextScene;
-Kinetic.TextPath.prototype._fillTextBuffer = Kinetic.Text.prototype._fillTextBuffer;
+Kinetic.TextPath.prototype._fillTextHit = Kinetic.Text.prototype._fillTextHit;
Kinetic.TextPath.prototype.strokeText = Kinetic.Text.prototype.strokeText;
Kinetic.TextPath.prototype._strokeTextScene = Kinetic.Text.prototype._strokeTextScene;
-Kinetic.TextPath.prototype._strokeTextBuffer = Kinetic.Text.prototype._strokeTextBuffer;
+Kinetic.TextPath.prototype._strokeTextHit = Kinetic.Text.prototype._strokeTextHit;
/**
* set font family
* @name setFontFamily
diff --git a/tests/dataUrls/cloneGroup.js b/tests/dataUrls/cloneGroup.js
new file mode 100644
index 00000000..a367ba22
--- /dev/null
+++ b/tests/dataUrls/cloneGroup.js
@@ -0,0 +1 @@
+var cloneGroup = 'data:image/png;base64,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';
\ No newline at end of file
diff --git a/tests/dataUrls/customShapeTwoFills.js b/tests/dataUrls/customShapeTwoFills.js
new file mode 100644
index 00000000..319ff2c6
--- /dev/null
+++ b/tests/dataUrls/customShapeTwoFills.js
@@ -0,0 +1 @@
+var customShapeTwoFills = 'data:image/png;base64,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';
diff --git a/tests/html/unitTests.html b/tests/html/unitTests.html
index a6af0ee7..bf54f400 100644
--- a/tests/html/unitTests.html
+++ b/tests/html/unitTests.html
@@ -10,6 +10,8 @@
+
+
diff --git a/tests/js/unit/customShapeTests.js b/tests/js/unit/customShapeTests.js
deleted file mode 100644
index 504e3c44..00000000
--- a/tests/js/unit/customShapeTests.js
+++ /dev/null
@@ -1,91 +0,0 @@
-Test.Modules['CUSTOM SHAPE'] = {
- 'custom shape with fill, stroke, and strokeWidth': function(containerId) {
- var stage = new Kinetic.Stage({
- container: containerId,
- width: 578,
- height: 200
- });
- var layer = new Kinetic.Layer();
- var shape = new Kinetic.Shape({
- drawFunc: function(context) {
- context.beginPath();
- context.moveTo(0, 0);
- context.lineTo(100, 0);
- context.lineTo(100, 100);
- context.closePath();
- this.fill(context);
- this.stroke(context);
- },
- x: 200,
- y: 100,
- fill: 'green',
- stroke: 'blue',
- strokeWidth: 5
- });
-
- layer.add(shape);
- stage.add(layer);
- },
- 'change custom shape draw func': function(containerId) {
- var stage = new Kinetic.Stage({
- container: containerId,
- width: 578,
- height: 200
- });
- var layer = new Kinetic.Layer();
- var shape = new Kinetic.Shape({
- drawFunc: function(context) {
- context.beginPath();
- context.moveTo(0, 0);
- context.lineTo(100, 0);
- context.lineTo(100, 100);
- context.closePath();
- this.fill(context);
- this.stroke(context);
- },
- x: 200,
- y: 100,
- fill: 'green',
- stroke: 'blue',
- strokeWidth: 5
- });
-
- shape.setDrawFunc(function(context) {
- context.beginPath();
- context.moveTo(0, 0);
- context.lineTo(200, 0);
- context.lineTo(200, 100);
- context.closePath();
- this.fill(context);
- this.stroke(context);
- });
- var rect = new Kinetic.Rect({
- x: 10,
- y: 10,
- width: 100,
- height: 50,
- fill: 'green',
- stroke: 'black',
- strokeWidth: 4,
- draggable: true
- });
-
- rect.setDrawFunc(function(context) {
- context.beginPath();
- context.moveTo(0, 0);
- context.lineTo(200, 0);
- context.lineTo(200, 100);
- context.closePath();
- this.fill(context);
- this.stroke(context);
- });
-
- layer.add(shape);
- layer.add(rect);
- stage.add(layer);
-
- var dataUrl = layer.toDataURL();
-
- test(dataUrls['SHAPE - change custom shape draw func'] === dataUrl, 'problem with setDrawFunc');
- }
-};
diff --git a/tests/js/unit/nodeTests.js b/tests/js/unit/nodeTests.js
index 457184df..8d4d58e1 100644
--- a/tests/js/unit/nodeTests.js
+++ b/tests/js/unit/nodeTests.js
@@ -388,6 +388,8 @@ Test.Modules.NODE = {
stage.draw();
+ warn(layer.toDataURL() === cloneGroup, 'problem cloning group');
+
},
'test on attr change': function(containerId) {
diff --git a/tests/js/unit/shapeTests.js b/tests/js/unit/shapeTests.js
index 84ae7855..ad2e30b5 100644
--- a/tests/js/unit/shapeTests.js
+++ b/tests/js/unit/shapeTests.js
@@ -1,5 +1,5 @@
Test.Modules.SHAPE = {
- 'SHAPE - test intersects()': function(containerId) {
+ 'test intersects()': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
@@ -44,5 +44,146 @@ Test.Modules.SHAPE = {
y: 153
}) === false, '(303, 153) should not intersect the shape');
+ },
+ 'custom shape with two fills and two strokes': function(containerId) {
+ var stage = new Kinetic.Stage({
+ container: containerId,
+ width: 578,
+ height: 200
+ });
+ var layer = new Kinetic.Layer();
+
+ var drawTriangle = function(context) {
+ context.beginPath();
+ context.moveTo(200, 50);
+ context.lineTo(420, 80);
+ context.quadraticCurveTo(300, 100, 260, 170);
+ context.closePath();
+ this.fill(context, 'red');
+ this.stroke(context, 'black', this.getStrokeWidth(), {
+ color: 'black',
+ offset: {
+ x: 20,
+ y: 20
+ },
+ opacity: 0.5
+ });
+
+ context.beginPath();
+ context.moveTo(300, 150);
+ context.lineTo(520, 180);
+ context.quadraticCurveTo(400, 200, 360, 270);
+ context.closePath();
+
+ this.fill(context, 'green', {
+ color: 'black',
+ offset: {
+ x: 20,
+ y: 20
+ },
+ opacity: 0.5
+ });
+
+ this.stroke(context, 'yellow', this.getStrokeWidth());
+ };
+ var triangle = new Kinetic.Shape({
+ drawFunc: drawTriangle,
+ fill: "#00D2FF",
+ stroke: "black",
+ strokeWidth: 4,
+ id: 'myTriangle',
+ draggable: true
+ });
+
+ stage.add(layer.add(triangle));
+
+ warn(layer.toDataURL() === customShapeTwoFills, 'problem with custom shape with two fills');
+
+ },
+ 'custom shape with fill, stroke, and strokeWidth': function(containerId) {
+ var stage = new Kinetic.Stage({
+ container: containerId,
+ width: 578,
+ height: 200
+ });
+ var layer = new Kinetic.Layer();
+ var shape = new Kinetic.Shape({
+ drawFunc: function(context) {
+ context.beginPath();
+ context.moveTo(0, 0);
+ context.lineTo(100, 0);
+ context.lineTo(100, 100);
+ context.closePath();
+ this.fill(context);
+ this.stroke(context);
+ },
+ x: 200,
+ y: 100,
+ fill: 'green',
+ stroke: 'blue',
+ strokeWidth: 5
+ });
+
+ layer.add(shape);
+ stage.add(layer);
+ },
+ 'change custom shape draw func': function(containerId) {
+ var stage = new Kinetic.Stage({
+ container: containerId,
+ width: 578,
+ height: 200
+ });
+ var layer = new Kinetic.Layer();
+ var shape = new Kinetic.Shape({
+ drawFunc: function(context) {
+ context.beginPath();
+ context.moveTo(0, 0);
+ context.lineTo(100, 0);
+ context.lineTo(100, 100);
+ context.closePath();
+ this.render(context);
+ },
+ x: 200,
+ y: 100,
+ fill: 'green',
+ stroke: 'blue',
+ strokeWidth: 5
+ });
+
+ shape.setDrawFunc(function(context) {
+ context.beginPath();
+ context.moveTo(0, 0);
+ context.lineTo(200, 0);
+ context.lineTo(200, 100);
+ context.closePath();
+ this.render(context);
+ });
+ var rect = new Kinetic.Rect({
+ x: 10,
+ y: 10,
+ width: 100,
+ height: 50,
+ fill: 'green',
+ stroke: 'black',
+ strokeWidth: 4,
+ draggable: true
+ });
+
+ rect.setDrawFunc(function(context) {
+ context.beginPath();
+ context.moveTo(0, 0);
+ context.lineTo(200, 0);
+ context.lineTo(200, 100);
+ context.closePath();
+ this.render(context);
+ });
+
+ layer.add(shape);
+ layer.add(rect);
+ stage.add(layer);
+
+ var dataUrl = layer.toDataURL();
+
+ test(dataUrls['SHAPE - change custom shape draw func'] === dataUrl, 'problem with setDrawFunc');
}
};