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'); } };