diff --git a/Thorfile b/Thorfile index b2358321..58436f7f 100644 --- a/Thorfile +++ b/Thorfile @@ -4,13 +4,14 @@ require 'uglifier' class Build < Thor # This is the list of files to concatenate. The first file will appear at the top of the final file. All files are relative to the lib directory. FILES = [ - "src/Global.js", "src/util/Type.js", "src/util/Canvas.js", "src/util/Tween.js", "src/util/Transform.js", "src/util/Collection.js", + "src/Global.js", "src/util/Type.js", "src/Canvas.js", "src/util/Tween.js", "src/util/Transform.js", "src/util/Collection.js", "src/filters/Grayscale.js", "src/filters/Brighten.js", "src/filters/Invert.js", "src/Animation.js", "src/Node.js", "src/DragAndDrop.js", "src/Transition.js", "src/Container.js", "src/Stage.js", "src/Layer.js", "src/Group.js", "src/Shape.js", "src/shapes/Rect.js", "src/shapes/Circle.js", "src/shapes/Wedge.js", "src/shapes/Ellipse.js", "src/shapes/Image.js", "src/shapes/Polygon.js", "src/shapes/Text.js", "src/shapes/Line.js", "src/shapes/Sprite.js", "src/shapes/Star.js", "src/shapes/RegularPolygon.js", "src/shapes/Path.js", "src/shapes/TextPath.js" ] UNIT_TESTS = [ + "tests/js/unit/globalTests.js", "tests/js/unit/nodeTests.js", "tests/js/unit/stageTests.js", "tests/js/unit/containerTests.js", @@ -38,11 +39,11 @@ class Build < Thor end # dev build - desc "dev", "Concatenate all the js files into /dist/kinetic-VERSION.js." + desc "dev", "Concatenate all the js files into /dist/kinetic-vVERSION.js." def dev(version) - file_name = "dist/kinetic-#{version}.js" + file_name = "dist/kinetic-v#{version}.js" puts ":: Deleting other development files..." Dir.foreach("dist") do |file| @@ -79,9 +80,9 @@ class Build < Thor end #prod build - desc "prod", "Concatenate all the js files in into /dist/kinetic-VERSION.min.js and minify it." + desc "prod", "Concatenate all the js files in into /dist/kinetic-vVERSION.min.js and minify it." def prod(version) - file_name = "dist/kinetic-#{version}.min.js" + file_name = "dist/kinetic-v#{version}.min.js" puts ":: Deleting other prod files..." Dir.foreach("dist") do |file| @@ -106,7 +107,7 @@ class Build < Thor content = IO.read(File.expand_path(file)) << "\n" mod = File.basename(file) mod[".js"] = "" - module_filename = "dist/kinetic-#{mod}-#{version}.min.js" + module_filename = "dist/kinetic-#{mod}-v#{version}.min.js" File.open(module_filename, "w") do |file2| uglify = Uglifier.compile(content, :copyright => mod == "Global") file2.puts replace_tokens(uglify, version) @@ -140,7 +141,7 @@ class Build < Thor date = Time.now.strftime("%b %d %Y") # Add the version number - content.sub!("@version", version) + content.gsub!("@version", version) # Add the date content.sub!("@date", date) diff --git a/src/Animation.js b/src/Animation.js index 5dcea113..ce21428a 100644 --- a/src/Animation.js +++ b/src/Animation.js @@ -1,120 +1,122 @@ -/** - * Stage constructor. A stage is used to contain multiple layers and handle - * animations - * @constructor - * @augments Kinetic.Container - * @param {Function} func function executed on each animation frame - * @param {Kinetic.Node} [node] node to be redrawn.  Specifying a node will improve - * draw performance.  This can be a shape, a group, a layer, or the stage. - */ -Kinetic.Animation = function(func, node) { - this.func = func; - this.node = node; - this.id = Kinetic.Animation.animIdCounter++; -}; -/* - * Animation methods - */ -Kinetic.Animation.prototype = { +(function() { /** - * start animation - * @name start - * @methodOf Kinetic.Animation.prototype + * Stage constructor. A stage is used to contain multiple layers and handle + * animations + * @constructor + * @augments Kinetic.Container + * @param {Function} func function executed on each animation frame + * @param {Kinetic.Node} [node] node to be redrawn.  Specifying a node will improve + * draw performance.  This can be a shape, a group, a layer, or the stage. */ - start: function() { - this.stop(); - Kinetic.Animation._addAnimation(this); - Kinetic.Animation._handleAnimation(); - }, - /** - * stop animation - * @name stop - * @methodOf Kinetic.Animation.prototype - */ - stop: function() { - Kinetic.Animation._removeAnimation(this); - } -}; -Kinetic.Animation.animations = []; -Kinetic.Animation.animIdCounter = 0; -Kinetic.Animation.animRunning = false; -Kinetic.Animation.frame = { - time: 0, - timeDiff: 0, - lastTime: new Date().getTime(), - frameRate: 0 -}; - -Kinetic.Animation.fixedRequestAnimFrame = function(callback) { - window.setTimeout(callback, 1000 / 60); -}; - -Kinetic.Animation._addAnimation = function(anim) { - this.animations.push(anim); -}; -Kinetic.Animation._removeAnimation = function(anim) { - var id = anim.id; - var animations = this.animations; - for(var n = 0; n < animations.length; n++) { - if(animations[n].id === id) { - this.animations.splice(n, 1); - break; - } - } -}; -Kinetic.Animation._updateFrameObject = function() { - var time = new Date().getTime(); - this.frame.timeDiff = time - this.frame.lastTime; - this.frame.lastTime = time; - this.frame.time += this.frame.timeDiff; - this.frame.frameRate = 1000 / this.frame.timeDiff; -}; -Kinetic.Animation._runFrames = function() { - this._updateFrameObject(); - var nodes = {}; + Kinetic.Animation = function(func, node) { + this.func = func; + this.node = node; + this.id = Kinetic.Animation.animIdCounter++; + }; /* - * loop through all animations and execute animation - * function. if the animation object has specified node, - * we can add the node to the nodes hash to eliminate - * drawing the same node multiple times. The node property - * can be the stage itself or a layer + * Animation methods */ - for(var n = 0; n < this.animations.length; n++) { - var anim = this.animations[n]; - if(anim.node && anim.node._id !== undefined) { - nodes[anim.node._id] = anim.node; + Kinetic.Animation.prototype = { + /** + * start animation + * @name start + * @methodOf Kinetic.Animation.prototype + */ + start: function() { + this.stop(); + Kinetic.Animation._addAnimation(this); + Kinetic.Animation._handleAnimation(); + }, + /** + * stop animation + * @name stop + * @methodOf Kinetic.Animation.prototype + */ + stop: function() { + Kinetic.Animation._removeAnimation(this); } - // if animation object has a function, execute it - if(anim.func) { - anim.func(this.frame); - } - } + }; + Kinetic.Animation.animations = []; + Kinetic.Animation.animIdCounter = 0; + Kinetic.Animation.animRunning = false; + Kinetic.Animation.frame = { + time: 0, + timeDiff: 0, + lastTime: new Date().getTime(), + frameRate: 0 + }; - for(var key in nodes) { - nodes[key].draw(); - } -}; -Kinetic.Animation._animationLoop = function() { - if(this.animations.length > 0) { - this._runFrames(); + Kinetic.Animation.fixedRequestAnimFrame = function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + + Kinetic.Animation._addAnimation = function(anim) { + this.animations.push(anim); + }; + Kinetic.Animation._removeAnimation = function(anim) { + var id = anim.id; + var animations = this.animations; + for(var n = 0; n < animations.length; n++) { + if(animations[n].id === id) { + this.animations.splice(n, 1); + break; + } + } + }; + Kinetic.Animation._updateFrameObject = function() { + var time = new Date().getTime(); + this.frame.timeDiff = time - this.frame.lastTime; + this.frame.lastTime = time; + this.frame.time += this.frame.timeDiff; + this.frame.frameRate = 1000 / this.frame.timeDiff; + }; + Kinetic.Animation._runFrames = function() { + this._updateFrameObject(); + var nodes = {}; + /* + * loop through all animations and execute animation + * function. if the animation object has specified node, + * we can add the node to the nodes hash to eliminate + * drawing the same node multiple times. The node property + * can be the stage itself or a layer + */ + for(var n = 0; n < this.animations.length; n++) { + var anim = this.animations[n]; + if(anim.node && anim.node._id !== undefined) { + nodes[anim.node._id] = anim.node; + } + // if animation object has a function, execute it + if(anim.func) { + anim.func(this.frame); + } + } + + for(var key in nodes) { + nodes[key].draw(); + } + }; + Kinetic.Animation._animationLoop = function() { + if(this.animations.length > 0) { + this._runFrames(); + var that = this; + Kinetic.Animation.requestAnimFrame(function() { + that._animationLoop(); + }); + } + else { + this.animRunning = false; + } + }; + Kinetic.Animation._handleAnimation = function() { var that = this; - Kinetic.Animation.requestAnimFrame(function() { + if(!this.animRunning) { + this.animRunning = true; that._animationLoop(); - }); - } - else { - this.animRunning = false; - } -}; -Kinetic.Animation._handleAnimation = function() { - var that = this; - if(!this.animRunning) { - this.animRunning = true; - that._animationLoop(); - } -}; -Kinetic.Animation.requestAnimFrame = function(callback) { - var raf = Kinetic.DD && Kinetic.DD.moving ? this.fixedRequestAnimFrame : window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || Kinetic.Animation.fixedRequestAnimFrame; - - raf(callback); -}; + } + }; + Kinetic.Animation.requestAnimFrame = function(callback) { + var raf = Kinetic.DD && Kinetic.DD.moving ? this.fixedRequestAnimFrame : window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || Kinetic.Animation.fixedRequestAnimFrame; + + raf(callback); + }; +})(); diff --git a/src/Canvas.js b/src/Canvas.js new file mode 100644 index 00000000..4b8a856b --- /dev/null +++ b/src/Canvas.js @@ -0,0 +1,241 @@ +(function() { + /** + * Canvas wrapper constructor + * @constructor + * @param {Number} width + * @param {Number} height + */ + Kinetic.Canvas = function(width, height, isHit) { + this.element = document.createElement('canvas'); + this.context = this.element.getContext('2d'); + + // set dimensions + this.element.width = width || 0; + this.element.height = height || 0; + + this.context.renderer = isHit ? new Kinetic.HitRenderer(this.context) : new Kinetic.SceneRenderer(this.context); + }; + + Kinetic.Canvas.prototype = { + /** + * clear canvas + * @name clear + * @methodOf Kinetic.Canvas.prototype + */ + clear: function() { + var context = this.getContext(); + var el = this.getElement(); + context.clearRect(0, 0, el.width, el.height); + }, + /** + * get element + * @name getElement + * @methodOf Kinetic.Canvas.prototype + */ + getElement: function() { + return this.element; + }, + /** + * get context + * @name getContext + * @methodOf Kinetic.Canvas.prototype + */ + getContext: function() { + return this.context; + }, + /** + * set width + * @name setWidth + * @methodOf Kinetic.Canvas.prototype + */ + setWidth: function(width) { + this.element.width = width; + }, + /** + * set height + * @name setHeight + * @methodOf Kinetic.Canvas.prototype + */ + setHeight: function(height) { + this.element.height = height; + }, + /** + * get width + * @name getWidth + * @methodOf Kinetic.Canvas.prototype + */ + getWidth: function() { + return this.element.width; + }, + /** + * get height + * @name getHeight + * @methodOf Kinetic.Canvas.prototype + */ + getHeight: function() { + return this.element.height; + }, + /** + * set size + * @name setSize + * @methodOf Kinetic.Canvas.prototype + */ + setSize: function(width, height) { + this.setWidth(width); + this.setHeight(height); + }, + /** + * toDataURL + */ + toDataURL: function(mimeType, quality) { + try { + // If this call fails (due to browser bug, like in Firefox 3.6), + // then revert to previous no-parameter image/png behavior + return this.element.toDataURL(mimeType, quality); + } + catch(e) { + try { + return this.element.toDataURL(); + } + catch(e) { + Kinetic.Global.warn('Unable to get data URL. ' + e.message) + return ''; + } + } + } + }; + + Kinetic.SceneRenderer = function(context) { + this.context = context; + }; + + Kinetic.SceneRenderer.prototype = { + _fill: function(shape, skipShadow) { + var context = this.context, fill = shape.getFill(), fillType = shape._getFillType(fill), shadow = shape.getShadow(); + if(fill) { + context.save(); + + if(!skipShadow && shadow) { + this._applyShadow(shape); + } + var s = fill.start; + var e = fill.end; + + // color fill + switch(fillType) { + case 'COLOR': + context.fillStyle = fill; + context.fill(context); + break; + case 'PATTERN': + var repeat = !fill.repeat ? 'repeat' : fill.repeat; + if(fill.scale) { + context.scale(fill.scale.x, fill.scale.y); + } + if(fill.offset) { + context.translate(fill.offset.x, fill.offset.y); + } file:///C:/Users/Eric/Documents/Eric/workspaces/KineticJS/dist/kinetic-current.js + + context.fillStyle = context.createPattern(fill.image, repeat); + context.fill(context); + break; + case 'LINEAR_GRADIENT': + var grd = context.createLinearGradient(s.x, s.y, e.x, e.y); + var colorStops = fill.colorStops; + + // build color stops + for(var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + context.fillStyle = grd; + context.fill(context); + + break; + case 'RADIAL_GRADIENT': + var grd = context.createRadialGradient(s.x, s.y, s.radius, e.x, e.y, e.radius); + var colorStops = fill.colorStops; + + // build color stops + for(var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + context.fillStyle = grd; + context.fill(context); + break; + default: + context.fillStyle = 'black'; + context.fill(context); + break; + } + + context.restore(); + + if(!skipShadow && shadow && shadow.opacity) { + this._fill(shape, true); + } + } + }, + _stroke: function(shape, skipShadow) { + var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(), shadow = shape.getShadow(); + if(stroke || strokeWidth) { + context.save(); + if(!skipShadow && shadow) { + this._applyShadow(shape); + } + context.lineWidth = strokeWidth || 2; + context.strokeStyle = stroke || 'black'; + context.stroke(context); + context.restore(); + + if(!skipShadow && shadow && shadow.opacity) { + this._stroke(shape, true); + } + } + }, + _applyShadow: function(shape) { + var context = this.context, shadow = shape.getShadow(); + if(shadow) { + var aa = shape.getAbsoluteOpacity(); + // defaults + var color = shadow.color || 'black'; + var blur = shadow.blur || 5; + var offset = shadow.offset || { + x: 0, + y: 0 + }; + + if(shadow.opacity) { + context.globalAlpha = shadow.opacity * aa; + } + context.shadowColor = color; + context.shadowBlur = blur; + context.shadowOffsetX = offset.x; + context.shadowOffsetY = offset.y; + } + } + }; + + Kinetic.HitRenderer = function(context) { + this.context = context; + }; + + Kinetic.HitRenderer.prototype = { + _fill: function(shape) { + var context = this.context; + context.save(); + context.fillStyle = '#' + shape.colorKey; + context.fill(context); + context.restore(); + }, + _stroke: function(shape) { + var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(); + if(stroke || strokeWidth) { + context.save(); + context.lineWidth = strokeWidth || 2; + context.strokeStyle = '#' + shape.colorKey; + context.stroke(context); + context.restore(); + } + } + }; +})(); diff --git a/src/Container.js b/src/Container.js index a07db596..5caf22a4 100644 --- a/src/Container.js +++ b/src/Container.js @@ -1,268 +1,270 @@ -/** - * Container constructor.  Containers are used to contain nodes or other containers - * @constructor - * @augments Kinetic.Node - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.alpha] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {String} [config.dragConstraint] can be vertical, horizontal, or none. The default - * is none - * @param {Object} [config.dragBounds] - * @param {Number} [config.dragBounds.top] - * @param {Number} [config.dragBounds.right] - * @param {Number} [config.dragBounds.bottom] - * @param {Number} [config.dragBounds.left] - */ -Kinetic.Container = function(config) { - this._containerInit(config); -}; - -Kinetic.Container.prototype = { - _containerInit: function(config) { - this.children = []; - Kinetic.Node.call(this, config); - }, +(function() { /** - * get children - * @name getChildren - * @methodOf Kinetic.Container.prototype + * Container constructor.  Containers are used to contain nodes or other containers + * @constructor + * @augments Kinetic.Node + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.alpha] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] + * @param {Number} [config.scale.x] + * @param {Number} [config.scale.y] + * @param {Number} [config.rotation] rotation in radians + * @param {Number} [config.rotationDeg] rotation in degrees + * @param {Object} [config.offset] offsets default position point and rotation point + * @param {Number} [config.offset.x] + * @param {Number} [config.offset.y] + * @param {Boolean} [config.draggable] + * @param {String} [config.dragConstraint] can be vertical, horizontal, or none. The default + * is none + * @param {Object} [config.dragBounds] + * @param {Number} [config.dragBounds.top] + * @param {Number} [config.dragBounds.right] + * @param {Number} [config.dragBounds.bottom] + * @param {Number} [config.dragBounds.left] */ - getChildren: function() { - return this.children; - }, - /** - * remove all children - * @name removeChildren - * @methodOf Kinetic.Container.prototype - */ - removeChildren: function() { - while(this.children.length > 0) { - this.children[0].remove(); - } - }, - /** - * add node to container - * @name add - * @methodOf Kinetic.Container.prototype - * @param {Node} child - */ - add: function(child) { - var go = Kinetic.Global, children = this.children; + Kinetic.Container = function(config) { + this._containerInit(config); + }; - child._id = Kinetic.Global.idCounter++; - child.index = children.length; - child.parent = this; - children.push(child); - var stage = child.getStage(); - - if(!stage) { - go._addTempNode(child); - } - else { - stage._addId(child); - stage._addName(child); - - /* - * pull in other nodes that are now linked - * to a stage - */ - go._pullNodes(stage); - } - - // chainable - return this; - }, - /** - * return an array of nodes that match the selector. Use '#' for id selections - * and '.' for name selections - * ex: - * var node = stage.get('#foo'); // selects node with id foo - * var nodes = layer.get('.bar'); // selects nodes with name bar inside layer - * @name get - * @methodOf Kinetic.Container.prototype - * @param {String} selector - */ - get: function(selector) { - var collection = new Kinetic.Collection(); - // ID selector - if(selector.charAt(0) === '#') { - var node = this._getNodeById(selector.slice(1)); - if(node) { - collection.push(node); + Kinetic.Container.prototype = { + _containerInit: function(config) { + this.children = []; + Kinetic.Node.call(this, config); + }, + /** + * get children + * @name getChildren + * @methodOf Kinetic.Container.prototype + */ + getChildren: function() { + return this.children; + }, + /** + * remove all children + * @name removeChildren + * @methodOf Kinetic.Container.prototype + */ + removeChildren: function() { + while(this.children.length > 0) { + this.children[0].remove(); } - } - // name selector - else if(selector.charAt(0) === '.') { - var nodeList = this._getNodesByName(selector.slice(1)); - Kinetic.Collection.apply(collection, nodeList); - } - // unrecognized selector, pass to children - else { - var retArr = []; + }, + /** + * add node to container + * @name add + * @methodOf Kinetic.Container.prototype + * @param {Node} child + */ + add: function(child) { + var go = Kinetic.Global, children = this.children; + + child._id = Kinetic.Global.idCounter++; + child.index = children.length; + child.parent = this; + children.push(child); + var stage = child.getStage(); + + if(!stage) { + go._addTempNode(child); + } + else { + stage._addId(child); + stage._addName(child); + + /* + * pull in other nodes that are now linked + * to a stage + */ + go._pullNodes(stage); + } + + // chainable + return this; + }, + /** + * return an array of nodes that match the selector. Use '#' for id selections + * and '.' for name selections + * ex: + * var node = stage.get('#foo'); // selects node with id foo + * var nodes = layer.get('.bar'); // selects nodes with name bar inside layer + * @name get + * @methodOf Kinetic.Container.prototype + * @param {String} selector + */ + get: function(selector) { + var collection = new Kinetic.Collection(); + // ID selector + if(selector.charAt(0) === '#') { + var node = this._getNodeById(selector.slice(1)); + if(node) { + collection.push(node); + } + } + // name selector + else if(selector.charAt(0) === '.') { + var nodeList = this._getNodesByName(selector.slice(1)); + Kinetic.Collection.apply(collection, nodeList); + } + // unrecognized selector, pass to children + else { + var retArr = []; + var children = this.getChildren(); + var len = children.length; + for(var n = 0; n < len; n++) { + retArr = retArr.concat(children[n]._get(selector)); + } + Kinetic.Collection.apply(collection, retArr); + } + return collection; + }, + _getNodeById: function(key) { + var stage = this.getStage(); + if(stage.ids[key] !== undefined && this.isAncestorOf(stage.ids[key])) { + return stage.ids[key]; + } + return null; + }, + _getNodesByName: function(key) { + var arr = this.getStage().names[key] || []; + return this._getDescendants(arr); + }, + _get: function(selector) { + var retArr = Kinetic.Node.prototype._get.call(this, selector); var children = this.getChildren(); var len = children.length; for(var n = 0; n < len; n++) { retArr = retArr.concat(children[n]._get(selector)); } - Kinetic.Collection.apply(collection, retArr); - } - return collection; - }, - _getNodeById: function(key) { - var stage = this.getStage(); - if(stage.ids[key] !== undefined && this.isAncestorOf(stage.ids[key])) { - return stage.ids[key]; - } - return null; - }, - _getNodesByName: function(key) { - var arr = this.getStage().names[key] || []; - return this._getDescendants(arr); - }, - _get: function(selector) { - var retArr = Kinetic.Node.prototype._get.call(this, selector); - var children = this.getChildren(); - var len = children.length; - for(var n = 0; n < len; n++) { - retArr = retArr.concat(children[n]._get(selector)); - } - return retArr; - }, - // extenders - toObject: function() { - var obj = Kinetic.Node.prototype.toObject.call(this); + return retArr; + }, + // extenders + toObject: function() { + var obj = Kinetic.Node.prototype.toObject.call(this); - obj.children = []; + obj.children = []; - var children = this.getChildren(); - var len = children.length; - for(var n = 0; n < len; n++) { - var child = children[n]; - obj.children.push(child.toObject()); - } - - return obj; - }, - _getDescendants: function(arr) { - var retArr = []; - var len = arr.length; - for(var n = 0; n < len; n++) { - var node = arr[n]; - if(this.isAncestorOf(node)) { - retArr.push(node); + var children = this.getChildren(); + var len = children.length; + for(var n = 0; n < len; n++) { + var child = children[n]; + obj.children.push(child.toObject()); } - } - return retArr; - }, - /** - * determine if node is an ancestor - * of descendant - * @name isAncestorOf - * @methodOf Kinetic.Container.prototype - * @param {Kinetic.Node} node - */ - isAncestorOf: function(node) { - var parent = node.getParent(); - while(parent) { - if(parent._id === this._id) { - return true; + return obj; + }, + _getDescendants: function(arr) { + var retArr = []; + var len = arr.length; + for(var n = 0; n < len; n++) { + var node = arr[n]; + if(this.isAncestorOf(node)) { + retArr.push(node); + } } - parent = parent.getParent(); - } - return false; - }, - /** - * clone node - * @name clone - * @methodOf Kinetic.Container.prototype - * @param {Object} attrs override attrs - */ - clone: function(obj) { - // call super method - var node = Kinetic.Node.prototype.clone.call(this, obj) - - // perform deep clone on containers - for(var key in this.children) { - node.add(this.children[key].clone()); - } - return node; - }, - /** - * get shapes that intersect a point - * @name getIntersections - * @methodOf Kinetic.Container.prototype - * @param {Object} point - */ - getIntersections: function() { - var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)); - var arr = []; - var shapes = this.get('Shape'); - - var len = shapes.length; - for(var n = 0; n < len; n++) { - var shape = shapes[n]; - if(shape.isVisible() && shape.intersects(pos)) { - arr.push(shape); + return retArr; + }, + /** + * determine if node is an ancestor + * of descendant + * @name isAncestorOf + * @methodOf Kinetic.Container.prototype + * @param {Kinetic.Node} node + */ + isAncestorOf: function(node) { + var parent = node.getParent(); + while(parent) { + if(parent._id === this._id) { + return true; + } + parent = parent.getParent(); } - } - return arr; - }, - /** - * set children indices - */ - _setChildrenIndices: function() { - var children = this.children, len = children.length; - for(var n = 0; n < len; n++) { - children[n].index = n; - } - }, - /* - * draw both scene and hit graphs - */ - draw: function() { - this.drawScene(); - this.drawHit(); - }, - drawScene: function() { - if(this.isVisible()) { + return false; + }, + /** + * clone node + * @name clone + * @methodOf Kinetic.Container.prototype + * @param {Object} attrs override attrs + */ + clone: function(obj) { + // call super method + var node = Kinetic.Node.prototype.clone.call(this, obj) + + // perform deep clone on containers + for(var key in this.children) { + node.add(this.children[key].clone()); + } + return node; + }, + /** + * get shapes that intersect a point + * @name getIntersections + * @methodOf Kinetic.Container.prototype + * @param {Object} point + */ + getIntersections: function() { + var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)); + var arr = []; + var shapes = this.get('Shape'); + + var len = shapes.length; + for(var n = 0; n < len; n++) { + var shape = shapes[n]; + if(shape.isVisible() && shape.intersects(pos)) { + arr.push(shape); + } + } + + return arr; + }, + /** + * set children indices + */ + _setChildrenIndices: function() { var children = this.children, len = children.length; for(var n = 0; n < len; n++) { - children[n].drawScene(); + children[n].index = n; + } + }, + /* + * draw both scene and hit graphs + */ + draw: function() { + this.drawScene(); + this.drawHit(); + }, + drawScene: function() { + if(this.isVisible()) { + var children = this.children, len = children.length; + for(var n = 0; n < len; n++) { + children[n].drawScene(); + } + } + }, + drawHit: function() { + if(this.isVisible() && this.isListening()) { + var children = this.children, len = children.length; + for(var n = 0; n < len; n++) { + children[n].drawHit(); + } + } + }, + drawBuffer: function(canvas) { + if(this.isVisible()) { + var children = this.children, len = children.length; + for(var n = 0; n < len; n++) { + children[n].drawBuffer(canvas); + } } } - }, - drawHit: function() { - if(this.isVisible() && this.isListening()) { - var children = this.children, len = children.length; - for(var n = 0; n < len; n++) { - children[n].drawHit(); - } - } - }, - drawBuffer: function(canvas) { - if(this.isVisible()) { - var children = this.children, len = children.length; - for(var n = 0; n < len; n++) { - children[n].drawBuffer(canvas); - } - } - } -}; -Kinetic.Global.extend(Kinetic.Container, Kinetic.Node); + }; + Kinetic.Global.extend(Kinetic.Container, Kinetic.Node); +})(); diff --git a/src/DragAndDrop.js b/src/DragAndDrop.js index 9c217a2a..e1000b04 100644 --- a/src/DragAndDrop.js +++ b/src/DragAndDrop.js @@ -1,168 +1,170 @@ -Kinetic.DD = { - anim: new Kinetic.Animation(), - moving: false, - offset: { - x: 0, - y: 0 - } -}; - -Kinetic.DD._startDrag = function(evt) { - var dd = Kinetic.DD; - var node = dd.node; - - if(node) { - var pos = node.getStage().getUserPosition(); - var dbf = node.attrs.dragBoundFunc; - - var newNodePos = { - x: pos.x - dd.offset.x, - y: pos.y - dd.offset.y - }; - - if(dbf !== undefined) { - newNodePos = dbf.call(node, newNodePos, evt); +(function() { + Kinetic.DD = { + anim: new Kinetic.Animation(), + moving: false, + offset: { + x: 0, + y: 0 } + }; - node.setAbsolutePosition(newNodePos); - - if(!dd.moving) { - dd.moving = true; - node.setListening(false); - - // execute dragstart events if defined - node._handleEvent('dragstart', evt); - } - - // execute user defined ondragmove if defined - node._handleEvent('dragmove', evt); - } -}; -Kinetic.DD._endDrag = function(evt) { - var dd = Kinetic.DD; - var node = dd.node; - if(node) { - node.setListening(true); - if(node.nodeType === 'Stage') { - node.draw(); - } - else { - node.getLayer().draw(); - } - - // handle dragend - if(dd.moving) { - dd.moving = false; - node._handleEvent('dragend', evt); - } - } - dd.node = null; - dd.anim.stop(); -}; -/** - * set draggable - * @name setDraggable - * @methodOf Kinetic.Node.prototype - * @param {String} draggable - */ -Kinetic.Node.prototype.setDraggable = function(draggable) { - this.setAttr('draggable', draggable); - this._dragChange(); -}; -/** - * get draggable - * @name getDraggable - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.getDraggable = function() { - return this.attrs.draggable; -}; -/** - * determine if node is currently in drag and drop mode - * @name isDragging - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.isDragging = function() { - var dd = Kinetic.DD; - return dd.node && dd.node._id === this._id && dd.moving; -}; - -Kinetic.Node.prototype._listenDrag = function() { - this._dragCleanup(); - var that = this; - this.on('mousedown.kinetic touchstart.kinetic', function(evt) { - that._initDrag(); - }); -}; -Kinetic.Node.prototype._initDrag = function() { - var dd = Kinetic.DD; - var stage = this.getStage(); - var pos = stage.getUserPosition(); - - if(pos) { - var m = this.getTransform().getTranslation(); - var am = this.getAbsoluteTransform().getTranslation(); - var ap = this.getAbsolutePosition(); - dd.node = this; - dd.offset.x = pos.x - ap.x; - dd.offset.y = pos.y - ap.y; - - /* - * if dragging and dropping the stage, - * draw all of the layers - */ - if(this.nodeType === 'Stage') { - dd.anim.node = this; - } - else { - dd.anim.node = this.getLayer(); - } - dd.anim.start(); - } -}; -Kinetic.Node.prototype._dragChange = function() { - if(this.attrs.draggable) { - this._listenDrag(); - } - else { - // remove event listeners - this._dragCleanup(); - - /* - * force drag and drop to end - * if this node is currently in - * drag and drop mode - */ - var stage = this.getStage(); + Kinetic.DD._startDrag = function(evt) { var dd = Kinetic.DD; - if(stage && dd.node && dd.node._id === this._id) { - dd._endDrag(); + var node = dd.node; + + if(node) { + var pos = node.getStage().getUserPosition(); + var dbf = node.attrs.dragBoundFunc; + + var newNodePos = { + x: pos.x - dd.offset.x, + y: pos.y - dd.offset.y + }; + + if(dbf !== undefined) { + newNodePos = dbf.call(node, newNodePos, evt); + } + + node.setAbsolutePosition(newNodePos); + + if(!dd.moving) { + dd.moving = true; + node.setListening(false); + + // execute dragstart events if defined + node._handleEvent('dragstart', evt); + } + + // execute user defined ondragmove if defined + node._handleEvent('dragmove', evt); } - } -}; -Kinetic.Node.prototype._dragCleanup = function() { - this.off('mousedown.kinetic'); - this.off('touchstart.kinetic'); -}; -/** - * get draggable. Alias of getDraggable() - * @name isDraggable - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.isDraggable = Kinetic.Node.prototype.getDraggable; + }; + Kinetic.DD._endDrag = function(evt) { + var dd = Kinetic.DD; + var node = dd.node; + if(node) { + node.setListening(true); + if(node.nodeType === 'Stage') { + node.draw(); + } + else { + node.getLayer().draw(); + } -Kinetic.Node.addGettersSetters(Kinetic.Node, ['dragBoundFunc']); + // handle dragend + if(dd.moving) { + dd.moving = false; + node._handleEvent('dragend', evt); + } + } + dd.node = null; + dd.anim.stop(); + }; + /** + * set draggable + * @name setDraggable + * @methodOf Kinetic.Node.prototype + * @param {String} draggable + */ + Kinetic.Node.prototype.setDraggable = function(draggable) { + this.setAttr('draggable', draggable); + this._dragChange(); + }; + /** + * get draggable + * @name getDraggable + * @methodOf Kinetic.Node.prototype + */ + Kinetic.Node.prototype.getDraggable = function() { + return this.attrs.draggable; + }; + /** + * determine if node is currently in drag and drop mode + * @name isDragging + * @methodOf Kinetic.Node.prototype + */ + Kinetic.Node.prototype.isDragging = function() { + var dd = Kinetic.DD; + return dd.node && dd.node._id === this._id && dd.moving; + }; -/** - * set drag bound function. This is used to override the default - * drag and drop position - * @name setDragBoundFunc - * @methodOf Kinetic.Node.prototype - * @param {Function} dragBoundFunc - */ + Kinetic.Node.prototype._listenDrag = function() { + this._dragCleanup(); + var that = this; + this.on('mousedown.kinetic touchstart.kinetic', function(evt) { + that._initDrag(); + }); + }; + Kinetic.Node.prototype._initDrag = function() { + var dd = Kinetic.DD; + var stage = this.getStage(); + var pos = stage.getUserPosition(); -/** - * get dragBoundFunc - * @name getDragBoundFunc - * @methodOf Kinetic.Node.prototype - */ \ No newline at end of file + if(pos) { + var m = this.getTransform().getTranslation(); + var am = this.getAbsoluteTransform().getTranslation(); + var ap = this.getAbsolutePosition(); + dd.node = this; + dd.offset.x = pos.x - ap.x; + dd.offset.y = pos.y - ap.y; + + /* + * if dragging and dropping the stage, + * draw all of the layers + */ + if(this.nodeType === 'Stage') { + dd.anim.node = this; + } + else { + dd.anim.node = this.getLayer(); + } + dd.anim.start(); + } + }; + Kinetic.Node.prototype._dragChange = function() { + if(this.attrs.draggable) { + this._listenDrag(); + } + else { + // remove event listeners + this._dragCleanup(); + + /* + * force drag and drop to end + * if this node is currently in + * drag and drop mode + */ + var stage = this.getStage(); + var dd = Kinetic.DD; + if(stage && dd.node && dd.node._id === this._id) { + dd._endDrag(); + } + } + }; + Kinetic.Node.prototype._dragCleanup = function() { + this.off('mousedown.kinetic'); + this.off('touchstart.kinetic'); + }; + /** + * get draggable. Alias of getDraggable() + * @name isDraggable + * @methodOf Kinetic.Node.prototype + */ + Kinetic.Node.prototype.isDraggable = Kinetic.Node.prototype.getDraggable; + + Kinetic.Node.addGettersSetters(Kinetic.Node, ['dragBoundFunc']); + + /** + * set drag bound function. This is used to override the default + * drag and drop position + * @name setDragBoundFunc + * @methodOf Kinetic.Node.prototype + * @param {Function} dragBoundFunc + */ + + /** + * get dragBoundFunc + * @name getDragBoundFunc + * @methodOf Kinetic.Node.prototype + */ +})(); diff --git a/src/Global.js b/src/Global.js index 2bf51747..f3cc72c8 100644 --- a/src/Global.js +++ b/src/Global.js @@ -1,5 +1,5 @@ /** - * KineticJS JavaScript Library @version + * KineticJS JavaScript Library v@version * http://www.kineticjs.com/ * Copyright 2012, Eric Rowell * Licensed under the MIT or GPL Version 2 licenses. @@ -26,53 +26,116 @@ * THE SOFTWARE. */ /** - * @namespace + * @namespace */ -var Kinetic = {}; -/** - * @namespace - */ -Kinetic.Filters = {}; -Kinetic.Plugins = {}; -Kinetic.Global = { - BUFFER_WHITELIST: ['fill', 'stroke', 'textFill', 'textStroke'], - BUFFER_BLACKLIST: ['shadow'], - stages: [], - idCounter: 0, - tempNodes: {}, - //shapes hash. rgb keys and shape values - shapes: {}, - warn: function(str) { - /* - * IE9 on Windows7 64bit will throw a JS error - * if we don't use window.console in the conditional - */ - if(window.console && console.warn) { - console.warn('Kinetic warning: ' + str); - } - }, - extend: function(c1, c2) { - for(var key in c2.prototype) { - if(!( key in c1.prototype)) { - c1.prototype[key] = c2.prototype[key]; +var Kinetic = {}; (function() { + Kinetic.version = '@version'; + /** + * @namespace + */ + Kinetic.Filters = {}; + Kinetic.Plugins = {}; + Kinetic.Global = { + stages: [], + idCounter: 0, + tempNodes: {}, + //shapes hash. rgb keys and shape values + shapes: {}, + warn: function(str) { + /* + * IE9 on Windows7 64bit will throw a JS error + * if we don't use window.console in the conditional + */ + if(window.console && console.warn) { + console.warn('Kinetic warning: ' + str); } - } - }, - _pullNodes: function(stage) { - var tempNodes = this.tempNodes; - for(var key in tempNodes) { - var node = tempNodes[key]; - if(node.getStage() !== undefined && node.getStage()._id === stage._id) { - stage._addId(node); - stage._addName(node); - this._removeTempNode(node); + }, + extend: function(c1, c2) { + for(var key in c2.prototype) { + if(!( key in c1.prototype)) { + c1.prototype[key] = c2.prototype[key]; + } } + }, + _pullNodes: function(stage) { + var tempNodes = this.tempNodes; + for(var key in tempNodes) { + var node = tempNodes[key]; + if(node.getStage() !== undefined && node.getStage()._id === stage._id) { + stage._addId(node); + stage._addName(node); + this._removeTempNode(node); + } + } + }, + _addTempNode: function(node) { + this.tempNodes[node._id] = node; + }, + _removeTempNode: function(node) { + delete this.tempNodes[node._id]; } - }, - _addTempNode: function(node) { - this.tempNodes[node._id] = node; - }, - _removeTempNode: function(node) { - delete this.tempNodes[node._id]; + }; +})(); + +// Uses Node, AMD or browser globals to create a module. + +// If you want something that will work in other stricter CommonJS environments, +// or if you need to create a circular dependency, see commonJsStrict.js + +// Defines a module "returnExports" that depends another module called "b". +// Note that the name of the module is implied by the file name. It is best +// if the file name and the exported global have matching names. + +// If the 'b' module also uses this type of boilerplate, then +// in the browser, it will create a global .b that is used below. + +// If you do not want to support the browser global path, then you +// can remove the `root` use and the passing `this` as the first arg to +// the top function. + +( function(root, factory) { + if( typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(require('b')); } -}; + else if( typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['b'], factory); + } + else { + // Browser globals (root is window) + root.returnExports = factory(root.b); + } +}(this, function(b) { + //use b in some fashion. + + // Just return a value to define the module export. + // This example returns an object, but the module + // can return a function as the exported value. + return {}; +})); +// if the module has no dependencies, the above pattern can be simplified to +( function(root, factory) { + if( typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } + else if( typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } + else { + // Browser globals (root is window) + root.returnExports = factory(); + } +}(this, function() { + + // Just return a value to define the module export. + // This example returns an object, but the module + // can return a function as the exported value. + return Kinetic; +})); diff --git a/src/Group.js b/src/Group.js index f3358627..6fbb72a1 100644 --- a/src/Group.js +++ b/src/Group.js @@ -1,36 +1,38 @@ -/** - * Group constructor. Groups are used to contain shapes or other groups. - * @constructor - * @augments Kinetic.Container - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Group = function(config) { - this._initGroup(config); -}; +(function() { + /** + * Group constructor. Groups are used to contain shapes or other groups. + * @constructor + * @augments Kinetic.Container + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] + * @param {Number} [config.scale.x] + * @param {Number} [config.scale.y] + * @param {Number} [config.rotation] rotation in radians + * @param {Number} [config.rotationDeg] rotation in degrees + * @param {Object} [config.offset] offsets default position point and rotation point + * @param {Number} [config.offset.x] + * @param {Number} [config.offset.y] + * @param {Boolean} [config.draggable] + * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position + */ + Kinetic.Group = function(config) { + this._initGroup(config); + }; -Kinetic.Group.prototype = { - _initGroup: function(config) { - this.nodeType = 'Group'; + Kinetic.Group.prototype = { + _initGroup: function(config) { + this.nodeType = 'Group'; - // call super constructor - Kinetic.Container.call(this, config); - } -}; -Kinetic.Global.extend(Kinetic.Group, Kinetic.Container); + // call super constructor + Kinetic.Container.call(this, config); + } + }; + Kinetic.Global.extend(Kinetic.Group, Kinetic.Container); +})(); diff --git a/src/Layer.js b/src/Layer.js index 86947601..06d8afe0 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -1,280 +1,282 @@ -/** - * Layer constructor. Layers are tied to their own canvas element and are used - * to contain groups or shapes - * @constructor - * @augments Kinetic.Container - * @param {Object} config - * @param {Boolean} [config.clearBeforeDraw] set this property to true if you'd like to disable - * canvas clearing before each new layer draw - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Layer = function(config) { - this._initLayer(config); -}; - -Kinetic.Layer.prototype = { - _initLayer: function(config) { - this.setDefaultAttrs({ - clearBeforeDraw: true - }); - - this.nodeType = 'Layer'; - this.beforeDrawFunc = undefined; - this.afterDrawFunc = undefined; - this.canvas = new Kinetic.Canvas(); - this.canvas.getElement().style.position = 'absolute'; - this.hitCanvas = new Kinetic.Canvas(0, 0, true); - - // call super constructor - Kinetic.Container.call(this, config); - }, +(function() { /** - * draw children nodes. this includes any groups - * or shapes - * @name draw - * @methodOf Kinetic.Layer.prototype + * Layer constructor. Layers are tied to their own canvas element and are used + * to contain groups or shapes + * @constructor + * @augments Kinetic.Container + * @param {Object} config + * @param {Boolean} [config.clearBeforeDraw] set this property to true if you'd like to disable + * canvas clearing before each new layer draw + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] + * @param {Number} [config.scale.x] + * @param {Number} [config.scale.y] + * @param {Number} [config.rotation] rotation in radians + * @param {Number} [config.rotationDeg] rotation in degrees + * @param {Object} [config.offset] offsets default position point and rotation point + * @param {Number} [config.offset.x] + * @param {Number} [config.offset.y] + * @param {Boolean} [config.draggable] + * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position */ - draw: function() { - // before draw handler - if(this.beforeDrawFunc !== undefined) { - this.beforeDrawFunc.call(this); - } + Kinetic.Layer = function(config) { + this._initLayer(config); + }; - Kinetic.Container.prototype.draw.call(this); + Kinetic.Layer.prototype = { + _initLayer: function(config) { + this.setDefaultAttrs({ + clearBeforeDraw: true + }); - // after draw handler - if(this.afterDrawFunc !== undefined) { - this.afterDrawFunc.call(this); - } - }, - /** - * draw children nodes on hit. this includes any groups - * or shapes - * @name drawHit - * @methodOf Kinetic.Layer.prototype - */ - drawHit: function() { - this.hitCanvas.clear(); - Kinetic.Container.prototype.drawHit.call(this); - }, - /** - * draw children nodes on scene. this includes any groups - * or shapes - * @name drawScene - * @methodOf Kinetic.Layer.prototype - * @param {Kinetic.Canvas} [canvas] - */ - drawScene: function() { - if(this.attrs.clearBeforeDraw) { + this.nodeType = 'Layer'; + this.beforeDrawFunc = undefined; + this.afterDrawFunc = undefined; + this.canvas = new Kinetic.Canvas(); + this.canvas.getElement().style.position = 'absolute'; + this.hitCanvas = new Kinetic.Canvas(0, 0, true); + + // call super constructor + Kinetic.Container.call(this, config); + }, + /** + * draw children nodes. this includes any groups + * or shapes + * @name draw + * @methodOf Kinetic.Layer.prototype + */ + draw: function() { + // before draw handler + if(this.beforeDrawFunc !== undefined) { + this.beforeDrawFunc.call(this); + } + + Kinetic.Container.prototype.draw.call(this); + + // after draw handler + if(this.afterDrawFunc !== undefined) { + this.afterDrawFunc.call(this); + } + }, + /** + * draw children nodes on hit. this includes any groups + * or shapes + * @name drawHit + * @methodOf Kinetic.Layer.prototype + */ + drawHit: function() { + this.hitCanvas.clear(); + Kinetic.Container.prototype.drawHit.call(this); + }, + /** + * draw children nodes on scene. this includes any groups + * or shapes + * @name drawScene + * @methodOf Kinetic.Layer.prototype + * @param {Kinetic.Canvas} [canvas] + */ + drawScene: function() { + if(this.attrs.clearBeforeDraw) { + this.getCanvas().clear(); + } + Kinetic.Container.prototype.drawScene.call(this); + }, + /** + * set before draw handler + * @name beforeDraw + * @methodOf Kinetic.Layer.prototype + * @param {Function} handler + */ + beforeDraw: function(func) { + this.beforeDrawFunc = func; + }, + /** + * set after draw handler + * @name afterDraw + * @methodOf Kinetic.Layer.prototype + * @param {Function} handler + */ + afterDraw: function(func) { + this.afterDrawFunc = func; + }, + /** + * get layer canvas + * @name getCanvas + * @methodOf Kinetic.Layer.prototype + */ + getCanvas: function() { + return this.canvas; + }, + /** + * get layer canvas context + * @name getContext + * @methodOf Kinetic.Layer.prototype + */ + getContext: function() { + return this.canvas.context; + }, + /** + * clear canvas tied to the layer + * @name clear + * @methodOf Kinetic.Layer.prototype + */ + clear: function() { this.getCanvas().clear(); - } - Kinetic.Container.prototype.drawScene.call(this); - }, - /** - * set before draw handler - * @name beforeDraw - * @methodOf Kinetic.Layer.prototype - * @param {Function} handler - */ - beforeDraw: function(func) { - this.beforeDrawFunc = func; - }, - /** - * set after draw handler - * @name afterDraw - * @methodOf Kinetic.Layer.prototype - * @param {Function} handler - */ - afterDraw: function(func) { - this.afterDrawFunc = func; - }, - /** - * get layer canvas - * @name getCanvas - * @methodOf Kinetic.Layer.prototype - */ - getCanvas: function() { - return this.canvas; - }, - /** - * get layer canvas context - * @name getContext - * @methodOf Kinetic.Layer.prototype - */ - getContext: function() { - return this.canvas.context; - }, - /** - * clear canvas tied to the layer - * @name clear - * @methodOf Kinetic.Layer.prototype - */ - clear: function() { - this.getCanvas().clear(); - }, - // extenders - setVisible: function(visible) { - Kinetic.Node.prototype.setVisible.call(this, visible); - if(visible) { - this.canvas.element.style.display = 'block'; - this.hitCanvas.element.style.display = 'block'; - } - else { - this.canvas.element.style.display = 'none'; - this.hitCanvas.element.style.display = 'none'; - } - }, - setZIndex: function(index) { - Kinetic.Node.prototype.setZIndex.call(this, index); - var stage = this.getStage(); - if(stage) { - stage.content.removeChild(this.canvas.element); - - if(index < stage.getChildren().length - 1) { - stage.content.insertBefore(this.canvas.element, stage.getChildren()[index + 1].canvas.element); + }, + // extenders + setVisible: function(visible) { + Kinetic.Node.prototype.setVisible.call(this, visible); + if(visible) { + this.canvas.element.style.display = 'block'; + this.hitCanvas.element.style.display = 'block'; } else { - stage.content.appendChild(this.canvas.element); + this.canvas.element.style.display = 'none'; + this.hitCanvas.element.style.display = 'none'; } - } - }, - moveToTop: function() { - Kinetic.Node.prototype.moveToTop.call(this); - var stage = this.getStage(); - if(stage) { - stage.content.removeChild(this.canvas.element); - stage.content.appendChild(this.canvas.element); - } - }, - moveUp: function() { - if(Kinetic.Node.prototype.moveUp.call(this)) { + }, + setZIndex: function(index) { + Kinetic.Node.prototype.setZIndex.call(this, index); var stage = this.getStage(); if(stage) { stage.content.removeChild(this.canvas.element); - if(this.index < stage.getChildren().length - 1) { - stage.content.insertBefore(this.canvas.element, stage.getChildren()[this.index + 1].canvas.element); + if(index < stage.getChildren().length - 1) { + stage.content.insertBefore(this.canvas.element, stage.getChildren()[index + 1].canvas.element); } else { stage.content.appendChild(this.canvas.element); } } - } - }, - moveDown: function() { - if(Kinetic.Node.prototype.moveDown.call(this)) { + }, + moveToTop: function() { + Kinetic.Node.prototype.moveToTop.call(this); var stage = this.getStage(); if(stage) { - var children = stage.getChildren(); stage.content.removeChild(this.canvas.element); - stage.content.insertBefore(this.canvas.element, children[this.index + 1].canvas.element); + stage.content.appendChild(this.canvas.element); } - } - }, - moveToBottom: function() { - if(Kinetic.Node.prototype.moveToBottom.call(this)) { + }, + moveUp: function() { + if(Kinetic.Node.prototype.moveUp.call(this)) { + var stage = this.getStage(); + if(stage) { + stage.content.removeChild(this.canvas.element); + + if(this.index < stage.getChildren().length - 1) { + stage.content.insertBefore(this.canvas.element, stage.getChildren()[this.index + 1].canvas.element); + } + else { + stage.content.appendChild(this.canvas.element); + } + } + } + }, + moveDown: function() { + if(Kinetic.Node.prototype.moveDown.call(this)) { + var stage = this.getStage(); + if(stage) { + var children = stage.getChildren(); + stage.content.removeChild(this.canvas.element); + stage.content.insertBefore(this.canvas.element, children[this.index + 1].canvas.element); + } + } + }, + moveToBottom: function() { + if(Kinetic.Node.prototype.moveToBottom.call(this)) { + var stage = this.getStage(); + if(stage) { + var children = stage.getChildren(); + stage.content.removeChild(this.canvas.element); + stage.content.insertBefore(this.canvas.element, children[1].canvas.element); + } + } + }, + getLayer: function() { + return this; + }, + /** + * Creates a composite data URL. If MIME type is not + * specified, then "image/png" will result. For "image/jpeg", specify a quality + * level as quality (range 0.0 - 1.0). Note that this method works + * differently from toDataURL() for other nodes because it generates an absolute dataURL + * based on what's currently drawn on the layer, rather than drawing + * the current state of each child node + * @name toDataURL + * @methodOf Kinetic.Layer.prototype + * @param {Object} config + * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.width] data url image width + * @param {Number} [config.height] data url image height + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + */ + toDataURL: function(config) { + var canvas; + var mimeType = config && config.mimeType ? config.mimeType : null; + var quality = config && config.quality ? config.quality : null; + + /* + * if layer is hidden, return blank canvas + * else if width and height are defined, create blank canvas and draw onto it + * else return canvas as is + */ + if(!this.isVisible()) { + var stage = this.getStage(); + canvas = new Kinetic.Canvas(stage.getWidth(), stage.getHeight()); + } + else if(config && config.width && config.height) { + canvas = new Kinetic.Canvas(config.width, config.height); + this.draw(canvas); + } + else { + canvas = this.getCanvas(); + } + return canvas.toDataURL(mimeType, quality); + }, + /** + * remove layer from stage + */ + remove: function() { var stage = this.getStage(); - if(stage) { - var children = stage.getChildren(); + Kinetic.Node.prototype.remove.call(this); + /* + * remove canvas DOM from the document if + * it exists + */ + try { stage.content.removeChild(this.canvas.element); - stage.content.insertBefore(this.canvas.element, children[1].canvas.element); + } + catch(e) { + Kinetic.Global.warn('unable to remove layer scene canvas element from the document'); } } - }, - getLayer: function() { - return this; - }, + }; + Kinetic.Global.extend(Kinetic.Layer, Kinetic.Container); + + // add getters and setters + Kinetic.Node.addGettersSetters(Kinetic.Layer, ['clearBeforeDraw']); + /** - * Creates a composite data URL. If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0). Note that this method works - * differently from toDataURL() for other nodes because it generates an absolute dataURL - * based on what's currently drawn on the layer, rather than drawing - * the current state of each child node - * @name toDataURL + * set flag which determines if the layer is cleared or not + * before drawing + * @name setClearBeforeDraw * @methodOf Kinetic.Layer.prototype - * @param {Object} config - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality + * @param {Boolean} clearBeforeDraw */ - toDataURL: function(config) { - var canvas; - var mimeType = config && config.mimeType ? config.mimeType : null; - var quality = config && config.quality ? config.quality : null; - /* - * if layer is hidden, return blank canvas - * else if width and height are defined, create blank canvas and draw onto it - * else return canvas as is - */ - if(!this.isVisible()) { - var stage = this.getStage(); - canvas = new Kinetic.Canvas(stage.getWidth(), stage.getHeight()); - } - else if(config && config.width && config.height) { - canvas = new Kinetic.Canvas(config.width, config.height); - this.draw(canvas); - } - else { - canvas = this.getCanvas(); - } - return canvas.toDataURL(mimeType, quality); - }, /** - * remove layer from stage + * get flag which determines if the layer is cleared or not + * before drawing + * @name getClearBeforeDraw + * @methodOf Kinetic.Layer.prototype */ - remove: function() { - var stage = this.getStage(); - Kinetic.Node.prototype.remove.call(this); - /* - * remove canvas DOM from the document if - * it exists - */ - try { - stage.content.removeChild(this.canvas.element); - } - catch(e) { - Kinetic.Global.warn('unable to remove layer scene canvas element from the document'); - } - } -}; -Kinetic.Global.extend(Kinetic.Layer, Kinetic.Container); - -// add getters and setters -Kinetic.Node.addGettersSetters(Kinetic.Layer, ['clearBeforeDraw']); - -/** - * set flag which determines if the layer is cleared or not - * before drawing - * @name setClearBeforeDraw - * @methodOf Kinetic.Layer.prototype - * @param {Boolean} clearBeforeDraw - */ - -/** - * get flag which determines if the layer is cleared or not - * before drawing - * @name getClearBeforeDraw - * @methodOf Kinetic.Layer.prototype - */ \ No newline at end of file +})(); diff --git a/src/Stage.js b/src/Stage.js index 7796e51d..2a0e6de1 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -1,652 +1,654 @@ -/** - * Stage constructor. A stage is used to contain multiple layers - * @constructor - * @augments Kinetic.Container - * @param {Object} config - * @param {String|DomElement} config.container Container id or DOM element - * @param {Number} config.width - * @param {Number} config.height - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Stage = function(config) { - this._initStage(config); -}; - -Kinetic.Stage.prototype = { - _initStage: function(config) { - this.setDefaultAttrs({ - width: 400, - height: 200 - }); - - // call super constructor - Kinetic.Container.call(this, config); - - this._setStageDefaultProperties(); - this._id = Kinetic.Global.idCounter++; - this._buildDOM(); - this._bindContentEvents(); - - var go = Kinetic.Global; - go.stages.push(this); - this._addId(this); - this._addName(this); - - }, - setContainer: function(container) { - /* - * if container is a string, assume it's an id for - * a DOM element - */ - if( typeof container === 'string') { - container = document.getElementById(container); - } - this.setAttr('container', container); - }, +(function() { /** - * draw layer scenes - * @name draw - * @methodOf Kinetic.Stage.prototype - */ - - /** - * draw layer hits - * @name drawHit - * @methodOf Kinetic.Stage.prototype - */ - - /** - * set height - * @name setHeight - * @methodOf Kinetic.Stage.prototype - * @param {Number} height - */ - setHeight: function(height) { - Kinetic.Node.prototype.setHeight.call(this, height); - this._resizeDOM(); - }, - /** - * set width - * @name setWidth - * @methodOf Kinetic.Stage.prototype - * @param {Number} width - */ - setWidth: function(width) { - Kinetic.Node.prototype.setWidth.call(this, width); - this._resizeDOM(); - }, - /** - * clear all layers - * @name clear - * @methodOf Kinetic.Stage.prototype - */ - clear: function() { - var layers = this.children; - for(var n = 0; n < layers.length; n++) { - layers[n].clear(); - } - }, - /** - * reset stage to default state - * @name reset - * @methodOf Kinetic.Stage.prototype - */ - reset: function() { - // remove children - this.removeChildren(); - - // defaults - this._setStageDefaultProperties(); - this.setAttrs(this.defaultNodeAttrs); - }, - /** - * get mouse position for desktop apps - * @name getMousePosition - * @methodOf Kinetic.Stage.prototype - * @param {Event} evt - */ - getMousePosition: function(evt) { - return this.mousePos; - }, - /** - * get touch position for mobile apps - * @name getTouchPosition - * @methodOf Kinetic.Stage.prototype - * @param {Event} evt - */ - getTouchPosition: function(evt) { - return this.touchPos; - }, - /** - * get user position (mouse position or touch position) - * @name getUserPosition - * @methodOf Kinetic.Stage.prototype - * @param {Event} evt - */ - getUserPosition: function(evt) { - return this.getTouchPosition() || this.getMousePosition(); - }, - /** - * get stage - * @name getStage - * @methodOf Kinetic.Stage.prototype - */ - getStage: function() { - return this; - }, - /** - * get stage DOM node, which is a div element - * with the class name "kineticjs-content" - * @name getDOM - * @methodOf Kinetic.Stage.prototype - */ - getDOM: function() { - return this.content; - }, - /** - * Creates a composite data URL and requires a callback because the stage - * toDataURL method is asynchronous. If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0). Note that this method works - * differently from toDataURL() for other nodes because it generates an absolute dataURL - * based on what's draw onto the canvases for each layer, rather than drawing - * the current state of each node - * @name toDataURL - * @methodOf Kinetic.Stage.prototype + * Stage constructor. A stage is used to contain multiple layers + * @constructor + * @augments Kinetic.Container * @param {Object} config - * @param {Function} config.callback since the stage toDataURL() method is asynchronous, - * the data url string will be passed into the callback - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality + * @param {String|DomElement} config.container Container id or DOM element + * @param {Number} config.width + * @param {Number} config.height + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] + * @param {Number} [config.scale.x] + * @param {Number} [config.scale.y] + * @param {Number} [config.rotation] rotation in radians + * @param {Number} [config.rotationDeg] rotation in degrees + * @param {Object} [config.offset] offsets default position point and rotation point + * @param {Number} [config.offset.x] + * @param {Number} [config.offset.y] + * @param {Boolean} [config.draggable] + * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position */ - toDataURL: function(config) { - var mimeType = config && config.mimeType ? config.mimeType : null; - var quality = config && config.quality ? config.quality : null; - /* - * need to create a canvas element rather than using the hit canvas - * because this method is asynchonous which means that other parts of the - * code could modify the hit canvas before it's finished - */ - var width = config && config.width ? config.width : this.attrs.width; - var height = config && config.height ? config.height : this.attrs.height; - var canvas = new Kinetic.Canvas(width, height); - var context = canvas.getContext(); - var layers = this.children; + Kinetic.Stage = function(config) { + this._initStage(config); + }; - function drawLayer(n) { - var layer = layers[n]; - var layerUrl = layer.toDataURL(); - var imageObj = new Image(); - imageObj.onload = function() { - context.drawImage(imageObj, 0, 0); + Kinetic.Stage.prototype = { + _initStage: function(config) { + this.setDefaultAttrs({ + width: 400, + height: 200 + }); - if(n < layers.length - 1) { - drawLayer(n + 1); - } - else { - config.callback(canvas.toDataURL(mimeType, quality)); - } - }; - imageObj.src = layerUrl; - } - drawLayer(0); - }, - /** - * converts stage into an image. Since the stage toImage() method - * is asynchronous, a callback function is required - * @name toImage - * @methodOf Kinetic.Stage.prototype - * @param {Object} config - * @param {Function} callback since the toImage() method is asynchonrous, the - * resulting image object is passed into the callback function - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toImage: function(config) { - this.toDataURL({ - callback: function(dataUrl) { - Kinetic.Type._getImage(dataUrl, function(img) { - config.callback(img); - }); + // call super constructor + Kinetic.Container.call(this, config); + + this._setStageDefaultProperties(); + this._id = Kinetic.Global.idCounter++; + this._buildDOM(); + this._bindContentEvents(); + + var go = Kinetic.Global; + go.stages.push(this); + this._addId(this); + this._addName(this); + + }, + setContainer: function(container) { + /* + * if container is a string, assume it's an id for + * a DOM element + */ + if( typeof container === 'string') { + container = document.getElementById(container); } - }); - }, - /** - * get intersection object that contains shape and pixel data - * @name getIntersection - * @methodOf Kinetic.Stage.prototype - * @param {Object} pos point object - */ - getIntersection: function(pos) { - var shape; - var layers = this.getChildren(); - - /* - * traverse through layers from top to bottom and look - * for hit detection + this.setAttr('container', container); + }, + /** + * draw layer scenes + * @name draw + * @methodOf Kinetic.Stage.prototype */ - for(var n = layers.length - 1; n >= 0; n--) { - var layer = layers[n]; - if(layer.isVisible() && layer.isListening()) { - var p = layer.hitCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data; - // this indicates that a hit pixel may have been found - if(p[3] === 255) { - var colorKey = Kinetic.Type._rgbToHex(p[0], p[1], p[2]); - shape = Kinetic.Global.shapes[colorKey]; - return { - shape: shape, - pixel: p - }; - } - // if no shape mapped to that pixel, return pixel array - else if(p[0] > 0 || p[1] > 0 || p[2] > 0 || p[3] > 0) { - return { - pixel: p - }; - } - } - } - return null; - }, - _getNodeById: function(key) { - return this.ids[key] || null; - }, - _getNodesByName: function(key) { - return this.names[key] || []; - }, - _resizeDOM: function() { - if(this.content) { - var width = this.attrs.width; - var height = this.attrs.height; + /** + * draw layer hits + * @name drawHit + * @methodOf Kinetic.Stage.prototype + */ - // set content dimensions - this.content.style.width = width + 'px'; - this.content.style.height = height + 'px'; - - this.bufferCanvas.setSize(width, height); - this.hitCanvas.setSize(width, height); - // set user defined layer dimensions + /** + * set height + * @name setHeight + * @methodOf Kinetic.Stage.prototype + * @param {Number} height + */ + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this._resizeDOM(); + }, + /** + * set width + * @name setWidth + * @methodOf Kinetic.Stage.prototype + * @param {Number} width + */ + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this._resizeDOM(); + }, + /** + * clear all layers + * @name clear + * @methodOf Kinetic.Stage.prototype + */ + clear: function() { var layers = this.children; for(var n = 0; n < layers.length; n++) { + layers[n].clear(); + } + }, + /** + * reset stage to default state + * @name reset + * @methodOf Kinetic.Stage.prototype + */ + reset: function() { + // remove children + this.removeChildren(); + + // defaults + this._setStageDefaultProperties(); + this.setAttrs(this.defaultNodeAttrs); + }, + /** + * get mouse position for desktop apps + * @name getMousePosition + * @methodOf Kinetic.Stage.prototype + * @param {Event} evt + */ + getMousePosition: function(evt) { + return this.mousePos; + }, + /** + * get touch position for mobile apps + * @name getTouchPosition + * @methodOf Kinetic.Stage.prototype + * @param {Event} evt + */ + getTouchPosition: function(evt) { + return this.touchPos; + }, + /** + * get user position (mouse position or touch position) + * @name getUserPosition + * @methodOf Kinetic.Stage.prototype + * @param {Event} evt + */ + getUserPosition: function(evt) { + return this.getTouchPosition() || this.getMousePosition(); + }, + /** + * get stage + * @name getStage + * @methodOf Kinetic.Stage.prototype + */ + getStage: function() { + return this; + }, + /** + * get stage DOM node, which is a div element + * with the class name "kineticjs-content" + * @name getDOM + * @methodOf Kinetic.Stage.prototype + */ + getDOM: function() { + return this.content; + }, + /** + * Creates a composite data URL and requires a callback because the stage + * toDataURL method is asynchronous. If MIME type is not + * specified, then "image/png" will result. For "image/jpeg", specify a quality + * level as quality (range 0.0 - 1.0). Note that this method works + * differently from toDataURL() for other nodes because it generates an absolute dataURL + * based on what's draw onto the canvases for each layer, rather than drawing + * the current state of each node + * @name toDataURL + * @methodOf Kinetic.Stage.prototype + * @param {Object} config + * @param {Function} config.callback since the stage toDataURL() method is asynchronous, + * the data url string will be passed into the callback + * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.width] data url image width + * @param {Number} [config.height] data url image height + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + */ + toDataURL: function(config) { + var mimeType = config && config.mimeType ? config.mimeType : null; + var quality = config && config.quality ? config.quality : null; + /* + * need to create a canvas element rather than using the hit canvas + * because this method is asynchonous which means that other parts of the + * code could modify the hit canvas before it's finished + */ + var width = config && config.width ? config.width : this.attrs.width; + var height = config && config.height ? config.height : this.attrs.height; + var canvas = new Kinetic.Canvas(width, height); + var context = canvas.getContext(); + var layers = this.children; + + function drawLayer(n) { var layer = layers[n]; - layer.getCanvas().setSize(width, height); - layer.hitCanvas.setSize(width, height); - layer.draw(); - } - } - }, - /** - * add layer to stage - * @param {Layer} layer - */ - add: function(layer) { - Kinetic.Container.prototype.add.call(this, layer); - layer.canvas.setSize(this.attrs.width, this.attrs.height); - layer.hitCanvas.setSize(this.attrs.width, this.attrs.height); + var layerUrl = layer.toDataURL(); + var imageObj = new Image(); + imageObj.onload = function() { + context.drawImage(imageObj, 0, 0); - // draw layer and append canvas to container - layer.draw(); - this.content.appendChild(layer.canvas.element); - - // chainable - return this; - }, - _setUserPosition: function(evt) { - if(!evt) { - evt = window.event; - } - this._setMousePosition(evt); - this._setTouchPosition(evt); - }, - /** - * begin listening for events by adding event handlers - * to the container - */ - _bindContentEvents: function() { - var go = Kinetic.Global; - var that = this; - var events = ['mousedown', 'mousemove', 'mouseup', 'mouseout', 'touchstart', 'touchmove', 'touchend']; - - for(var n = 0; n < events.length; n++) { - var pubEvent = events[n]; - // induce scope - ( function() { - var event = pubEvent; - that.content.addEventListener(event, function(evt) { - that['_' + event](evt); - }, false); - }()); - } - }, - _mouseout: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - // if there's a current target shape, run mouseout handlers - var targetShape = this.targetShape; - if(targetShape && (!dd || !dd.moving)) { - targetShape._handleEvent('mouseout', evt); - targetShape._handleEvent('mouseleave', evt); - this.targetShape = null; - } - this.mousePos = undefined; - - // end drag and drop - if(dd) { - dd._endDrag(evt); - } - }, - _mousemove: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - var obj = this.getIntersection(this.getUserPosition()); - - if(obj) { - var shape = obj.shape; - if(shape) { - if((!dd || !dd.moving) && obj.pixel[3] === 255 && (!this.targetShape || this.targetShape._id !== shape._id)) { - if(this.targetShape) { - this.targetShape._handleEvent('mouseout', evt, shape); - this.targetShape._handleEvent('mouseleave', evt, shape); + if(n < layers.length - 1) { + drawLayer(n + 1); } - shape._handleEvent('mouseover', evt, this.targetShape); - shape._handleEvent('mouseenter', evt, this.targetShape); - this.targetShape = shape; - } - else { - shape._handleEvent('mousemove', evt); - } + else { + config.callback(canvas.toDataURL(mimeType, quality)); + } + }; + imageObj.src = layerUrl; } - } - /* - * if no shape was detected, clear target shape and try - * to run mouseout from previous target shape + drawLayer(0); + }, + /** + * converts stage into an image. Since the stage toImage() method + * is asynchronous, a callback function is required + * @name toImage + * @methodOf Kinetic.Stage.prototype + * @param {Object} config + * @param {Function} callback since the toImage() method is asynchonrous, the + * resulting image object is passed into the callback function + * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.width] data url image width + * @param {Number} [config.height] data url image height + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality */ - else if(this.targetShape && (!dd || !dd.moving)) { - this.targetShape._handleEvent('mouseout', evt); - this.targetShape._handleEvent('mouseleave', evt); - this.targetShape = null; - } - - // start drag and drop - if(dd) { - dd._startDrag(evt); - } - }, - _mousedown: function(evt) { - this._setUserPosition(evt); - var obj = this.getIntersection(this.getUserPosition()); - if(obj && obj.shape) { - var shape = obj.shape; - this.clickStart = true; - shape._handleEvent('mousedown', evt); - } - - //init stage drag and drop - if(Kinetic.DD && this.attrs.draggable) { - this._initDrag(); - } - }, - _mouseup: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - var obj = this.getIntersection(this.getUserPosition()); - var that = this; - if(obj && obj.shape) { - var shape = obj.shape; - shape._handleEvent('mouseup', evt); - - // detect if click or double click occurred - if(this.clickStart) { - /* - * if dragging and dropping, don't fire click or dbl click - * event - */ - if(!dd || !dd.moving || !dd.node) { - shape._handleEvent('click', evt); - - if(this.inDoubleClickWindow) { - shape._handleEvent('dblclick', evt); - } - this.inDoubleClickWindow = true; - setTimeout(function() { - that.inDoubleClickWindow = false; - }, this.dblClickWindow); + toImage: function(config) { + this.toDataURL({ + callback: function(dataUrl) { + Kinetic.Type._getImage(dataUrl, function(img) { + config.callback(img); + }); } - } - } - this.clickStart = false; - - // end drag and drop - if(dd) { - dd._endDrag(evt); - } - }, - _touchstart: function(evt) { - this._setUserPosition(evt); - evt.preventDefault(); - var obj = this.getIntersection(this.getUserPosition()); - - if(obj && obj.shape) { - var shape = obj.shape; - this.tapStart = true; - shape._handleEvent('touchstart', evt); - } - - /* - * init stage drag and drop + }); + }, + /** + * get intersection object that contains shape and pixel data + * @name getIntersection + * @methodOf Kinetic.Stage.prototype + * @param {Object} pos point object */ - if(Kinetic.DD && this.attrs.draggable) { - this._initDrag(); - } - }, - _touchend: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - var obj = this.getIntersection(this.getUserPosition()); - var that = this; - if(obj && obj.shape) { - var shape = obj.shape; - shape._handleEvent('touchend', evt); + getIntersection: function(pos) { + var shape; + var layers = this.getChildren(); - // detect if tap or double tap occurred - if(this.tapStart) { - /* - * if dragging and dropping, don't fire tap or dbltap - * event - */ - if(!dd || !dd.moving || !dd.node) { - shape._handleEvent('tap', evt); - - if(this.inDoubleClickWindow) { - shape._handleEvent('dbltap', evt); + /* + * traverse through layers from top to bottom and look + * for hit detection + */ + for(var n = layers.length - 1; n >= 0; n--) { + var layer = layers[n]; + if(layer.isVisible() && layer.isListening()) { + var p = layer.hitCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data; + // this indicates that a hit pixel may have been found + if(p[3] === 255) { + var colorKey = Kinetic.Type._rgbToHex(p[0], p[1], p[2]); + shape = Kinetic.Global.shapes[colorKey]; + return { + shape: shape, + pixel: p + }; + } + // if no shape mapped to that pixel, return pixel array + else if(p[0] > 0 || p[1] > 0 || p[2] > 0 || p[3] > 0) { + return { + pixel: p + }; } - this.inDoubleClickWindow = true; - setTimeout(function() { - that.inDoubleClickWindow = false; - }, this.dblClickWindow); } } - } - this.tapStart = false; + return null; + }, + _getNodeById: function(key) { + return this.ids[key] || null; + }, + _getNodesByName: function(key) { + return this.names[key] || []; + }, + _resizeDOM: function() { + if(this.content) { + var width = this.attrs.width; + var height = this.attrs.height; - // end drag and drop - if(dd) { - dd._endDrag(evt); - } - }, - _touchmove: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - evt.preventDefault(); - var obj = this.getIntersection(this.getUserPosition()); - if(obj && obj.shape) { - var shape = obj.shape; - shape._handleEvent('touchmove', evt); - } + // set content dimensions + this.content.style.width = width + 'px'; + this.content.style.height = height + 'px'; - // start drag and drop - if(dd) { - dd._startDrag(evt); - } - }, - /** - * set mouse positon for desktop apps - * @param {Event} evt - */ - _setMousePosition: function(evt) { - var mouseX = evt.clientX - this._getContentPosition().left; - var mouseY = evt.clientY - this._getContentPosition().top; - this.mousePos = { - x: mouseX, - y: mouseY - }; - }, - /** - * set touch position for mobile apps - * @param {Event} evt - */ - _setTouchPosition: function(evt) { - if(evt.touches !== undefined && evt.touches.length === 1) { - // one finger - var touch = evt.touches[0]; - // Get the information for finger #1 - var touchX = touch.clientX - this._getContentPosition().left; - var touchY = touch.clientY - this._getContentPosition().top; + this.bufferCanvas.setSize(width, height); + this.hitCanvas.setSize(width, height); + // set user defined layer dimensions + var layers = this.children; + for(var n = 0; n < layers.length; n++) { + var layer = layers[n]; + layer.getCanvas().setSize(width, height); + layer.hitCanvas.setSize(width, height); + layer.draw(); + } + } + }, + /** + * add layer to stage + * @param {Layer} layer + */ + add: function(layer) { + Kinetic.Container.prototype.add.call(this, layer); + layer.canvas.setSize(this.attrs.width, this.attrs.height); + layer.hitCanvas.setSize(this.attrs.width, this.attrs.height); - this.touchPos = { - x: touchX, - y: touchY + // draw layer and append canvas to container + layer.draw(); + this.content.appendChild(layer.canvas.element); + + // chainable + return this; + }, + _setUserPosition: function(evt) { + if(!evt) { + evt = window.event; + } + this._setMousePosition(evt); + this._setTouchPosition(evt); + }, + /** + * begin listening for events by adding event handlers + * to the container + */ + _bindContentEvents: function() { + var go = Kinetic.Global; + var that = this; + var events = ['mousedown', 'mousemove', 'mouseup', 'mouseout', 'touchstart', 'touchmove', 'touchend']; + + for(var n = 0; n < events.length; n++) { + var pubEvent = events[n]; + // induce scope + ( function() { + var event = pubEvent; + that.content.addEventListener(event, function(evt) { + that['_' + event](evt); + }, false); + }()); + } + }, + _mouseout: function(evt) { + this._setUserPosition(evt); + var dd = Kinetic.DD; + // if there's a current target shape, run mouseout handlers + var targetShape = this.targetShape; + if(targetShape && (!dd || !dd.moving)) { + targetShape._handleEvent('mouseout', evt); + targetShape._handleEvent('mouseleave', evt); + this.targetShape = null; + } + this.mousePos = undefined; + + // end drag and drop + if(dd) { + dd._endDrag(evt); + } + }, + _mousemove: function(evt) { + this._setUserPosition(evt); + var dd = Kinetic.DD; + var obj = this.getIntersection(this.getUserPosition()); + + if(obj) { + var shape = obj.shape; + if(shape) { + if((!dd || !dd.moving) && obj.pixel[3] === 255 && (!this.targetShape || this.targetShape._id !== shape._id)) { + if(this.targetShape) { + this.targetShape._handleEvent('mouseout', evt, shape); + this.targetShape._handleEvent('mouseleave', evt, shape); + } + shape._handleEvent('mouseover', evt, this.targetShape); + shape._handleEvent('mouseenter', evt, this.targetShape); + this.targetShape = shape; + } + else { + shape._handleEvent('mousemove', evt); + } + } + } + /* + * if no shape was detected, clear target shape and try + * to run mouseout from previous target shape + */ + else if(this.targetShape && (!dd || !dd.moving)) { + this.targetShape._handleEvent('mouseout', evt); + this.targetShape._handleEvent('mouseleave', evt); + this.targetShape = null; + } + + // start drag and drop + if(dd) { + dd._startDrag(evt); + } + }, + _mousedown: function(evt) { + this._setUserPosition(evt); + var obj = this.getIntersection(this.getUserPosition()); + if(obj && obj.shape) { + var shape = obj.shape; + this.clickStart = true; + shape._handleEvent('mousedown', evt); + } + + //init stage drag and drop + if(Kinetic.DD && this.attrs.draggable) { + this._initDrag(); + } + }, + _mouseup: function(evt) { + this._setUserPosition(evt); + var dd = Kinetic.DD; + var obj = this.getIntersection(this.getUserPosition()); + var that = this; + if(obj && obj.shape) { + var shape = obj.shape; + shape._handleEvent('mouseup', evt); + + // detect if click or double click occurred + if(this.clickStart) { + /* + * if dragging and dropping, don't fire click or dbl click + * event + */ + if(!dd || !dd.moving || !dd.node) { + shape._handleEvent('click', evt); + + if(this.inDoubleClickWindow) { + shape._handleEvent('dblclick', evt); + } + this.inDoubleClickWindow = true; + setTimeout(function() { + that.inDoubleClickWindow = false; + }, this.dblClickWindow); + } + } + } + this.clickStart = false; + + // end drag and drop + if(dd) { + dd._endDrag(evt); + } + }, + _touchstart: function(evt) { + this._setUserPosition(evt); + evt.preventDefault(); + var obj = this.getIntersection(this.getUserPosition()); + + if(obj && obj.shape) { + var shape = obj.shape; + this.tapStart = true; + shape._handleEvent('touchstart', evt); + } + + /* + * init stage drag and drop + */ + if(Kinetic.DD && this.attrs.draggable) { + this._initDrag(); + } + }, + _touchend: function(evt) { + this._setUserPosition(evt); + var dd = Kinetic.DD; + var obj = this.getIntersection(this.getUserPosition()); + var that = this; + if(obj && obj.shape) { + var shape = obj.shape; + shape._handleEvent('touchend', evt); + + // detect if tap or double tap occurred + if(this.tapStart) { + /* + * if dragging and dropping, don't fire tap or dbltap + * event + */ + if(!dd || !dd.moving || !dd.node) { + shape._handleEvent('tap', evt); + + if(this.inDoubleClickWindow) { + shape._handleEvent('dbltap', evt); + } + this.inDoubleClickWindow = true; + setTimeout(function() { + that.inDoubleClickWindow = false; + }, this.dblClickWindow); + } + } + } + + this.tapStart = false; + + // end drag and drop + if(dd) { + dd._endDrag(evt); + } + }, + _touchmove: function(evt) { + this._setUserPosition(evt); + var dd = Kinetic.DD; + evt.preventDefault(); + var obj = this.getIntersection(this.getUserPosition()); + if(obj && obj.shape) { + var shape = obj.shape; + shape._handleEvent('touchmove', evt); + } + + // start drag and drop + if(dd) { + dd._startDrag(evt); + } + }, + /** + * set mouse positon for desktop apps + * @param {Event} evt + */ + _setMousePosition: function(evt) { + var mouseX = evt.clientX - this._getContentPosition().left; + var mouseY = evt.clientY - this._getContentPosition().top; + this.mousePos = { + x: mouseX, + y: mouseY }; - } - }, - /** - * get container position - */ - _getContentPosition: function() { - var rect = this.content.getBoundingClientRect(); - return { - top: rect.top, - left: rect.left - }; - }, - /** - * build dom - */ - _buildDOM: function() { - // content - this.content = document.createElement('div'); - this.content.style.position = 'relative'; - this.content.style.display = 'inline-block'; - this.content.className = 'kineticjs-content'; - this.attrs.container.appendChild(this.content); + }, + /** + * set touch position for mobile apps + * @param {Event} evt + */ + _setTouchPosition: function(evt) { + if(evt.touches !== undefined && evt.touches.length === 1) { + // one finger + var touch = evt.touches[0]; + // Get the information for finger #1 + var touchX = touch.clientX - this._getContentPosition().left; + var touchY = touch.clientY - this._getContentPosition().top; - this.bufferCanvas = new Kinetic.Canvas(); - this.hitCanvas = new Kinetic.Canvas(0, 0, true); - - this._resizeDOM(); - }, - _addId: function(node) { - if(node.attrs.id !== undefined) { - this.ids[node.attrs.id] = node; - } - }, - _removeId: function(id) { - if(id !== undefined) { - delete this.ids[id]; - } - }, - _addName: function(node) { - var name = node.attrs.name; - if(name !== undefined) { - if(this.names[name] === undefined) { - this.names[name] = []; + this.touchPos = { + x: touchX, + y: touchY + }; } - this.names[name].push(node); - } - }, - _removeName: function(name, _id) { - if(name !== undefined) { - var nodes = this.names[name]; - if(nodes !== undefined) { - for(var n = 0; n < nodes.length; n++) { - var no = nodes[n]; - if(no._id === _id) { - nodes.splice(n, 1); + }, + /** + * get container position + */ + _getContentPosition: function() { + var rect = this.content.getBoundingClientRect(); + return { + top: rect.top, + left: rect.left + }; + }, + /** + * build dom + */ + _buildDOM: function() { + // content + this.content = document.createElement('div'); + this.content.style.position = 'relative'; + this.content.style.display = 'inline-block'; + this.content.className = 'kineticjs-content'; + this.attrs.container.appendChild(this.content); + + this.bufferCanvas = new Kinetic.Canvas(); + this.hitCanvas = new Kinetic.Canvas(0, 0, true); + + this._resizeDOM(); + }, + _addId: function(node) { + if(node.attrs.id !== undefined) { + this.ids[node.attrs.id] = node; + } + }, + _removeId: function(id) { + if(id !== undefined) { + delete this.ids[id]; + } + }, + _addName: function(node) { + var name = node.attrs.name; + if(name !== undefined) { + if(this.names[name] === undefined) { + this.names[name] = []; + } + this.names[name].push(node); + } + }, + _removeName: function(name, _id) { + if(name !== undefined) { + var nodes = this.names[name]; + if(nodes !== undefined) { + for(var n = 0; n < nodes.length; n++) { + var no = nodes[n]; + if(no._id === _id) { + nodes.splice(n, 1); + } + } + if(nodes.length === 0) { + delete this.names[name]; } } - if(nodes.length === 0) { - delete this.names[name]; - } } - } - }, - /** - * bind event listener to container DOM element - * @param {String} typesStr - * @param {function} handler - */ - _onContent: function(typesStr, handler) { - var types = typesStr.split(' '); - for(var n = 0; n < types.length; n++) { - var baseEvent = types[n]; - this.content.addEventListener(baseEvent, handler, false); - } - }, - /** - * set defaults - */ - _setStageDefaultProperties: function() { - this.nodeType = 'Stage'; - this.dblClickWindow = 400; - this.targetShape = null; - this.mousePos = undefined; - this.clickStart = false; - this.touchPos = undefined; - this.tapStart = false; - - /* - * ids and names hash needs to be stored at the stage level to prevent - * id and name collisions between multiple stages in the document + }, + /** + * bind event listener to container DOM element + * @param {String} typesStr + * @param {function} handler */ - this.ids = {}; - this.names = {}; - } -}; -Kinetic.Global.extend(Kinetic.Stage, Kinetic.Container); + _onContent: function(typesStr, handler) { + var types = typesStr.split(' '); + for(var n = 0; n < types.length; n++) { + var baseEvent = types[n]; + this.content.addEventListener(baseEvent, handler, false); + } + }, + /** + * set defaults + */ + _setStageDefaultProperties: function() { + this.nodeType = 'Stage'; + this.dblClickWindow = 400; + this.targetShape = null; + this.mousePos = undefined; + this.clickStart = false; + this.touchPos = undefined; + this.tapStart = false; -// add getters and setters -Kinetic.Node.addGetters(Kinetic.Stage, ['container']); + /* + * ids and names hash needs to be stored at the stage level to prevent + * id and name collisions between multiple stages in the document + */ + this.ids = {}; + this.names = {}; + } + }; + Kinetic.Global.extend(Kinetic.Stage, Kinetic.Container); -/** - * get container DOM element - * @name getContainer - * @methodOf Kinetic.Stage.prototype - */ \ No newline at end of file + // add getters and setters + Kinetic.Node.addGetters(Kinetic.Stage, ['container']); + + /** + * get container DOM element + * @name getContainer + * @methodOf Kinetic.Stage.prototype + */ +})(); diff --git a/src/Transition.js b/src/Transition.js index 9d713858..3aed362a 100644 --- a/src/Transition.js +++ b/src/Transition.js @@ -1,147 +1,149 @@ -/** - * Transition constructor. The transitionTo() Node method - * returns a reference to the transition object which you can use - * to stop, resume, or restart the transition - * @constructor - */ -Kinetic.Transition = function(node, config) { - this.node = node; - this.config = config; - this.tweens = []; - var that = this; +(function() { + /** + * Transition constructor. The transitionTo() Node method + * returns a reference to the transition object which you can use + * to stop, resume, or restart the transition + * @constructor + */ + Kinetic.Transition = function(node, config) { + this.node = node; + this.config = config; + this.tweens = []; + var that = this; - // add tween for each property - function addTween(c, attrs, obj, rootObj) { - for(var key in c) { - if(key !== 'duration' && key !== 'easing' && key !== 'callback') { - // if val is an object then traverse - if(Kinetic.Type._isObject(c[key])) { - obj[key] = {}; - addTween(c[key], attrs[key], obj[key], rootObj); - } - else { - that._add(that._getTween(attrs, key, c[key], obj, rootObj)); + // add tween for each property + function addTween(c, attrs, obj, rootObj) { + for(var key in c) { + if(key !== 'duration' && key !== 'easing' && key !== 'callback') { + // if val is an object then traverse + if(Kinetic.Type._isObject(c[key])) { + obj[key] = {}; + addTween(c[key], attrs[key], obj[key], rootObj); + } + else { + that._add(that._getTween(attrs, key, c[key], obj, rootObj)); + } } } } - } - var obj = {}; - addTween(config, node.attrs, obj, obj); + var obj = {}; + addTween(config, node.attrs, obj, obj); - var finishedTweens = 0; - for(var n = 0; n < this.tweens.length; n++) { - var tween = this.tweens[n]; - tween.onFinished = function() { - finishedTweens++; - if(finishedTweens >= that.tweens.length) { - that.onFinished(); + var finishedTweens = 0; + for(var n = 0; n < this.tweens.length; n++) { + var tween = this.tweens[n]; + tween.onFinished = function() { + finishedTweens++; + if(finishedTweens >= that.tweens.length) { + that.onFinished(); + } + }; + } + }; + /* + * Transition methods + */ + Kinetic.Transition.prototype = { + /** + * start transition + * @name start + * @methodOf Kinetic.Transition.prototype + */ + start: function() { + for(var n = 0; n < this.tweens.length; n++) { + this.tweens[n].start(); + } + }, + /** + * stop transition + * @name stop + * @methodOf Kinetic.Transition.prototype + */ + stop: function() { + for(var n = 0; n < this.tweens.length; n++) { + this.tweens[n].stop(); + } + }, + /** + * resume transition + * @name resume + * @methodOf Kinetic.Transition.prototype + */ + resume: function() { + for(var n = 0; n < this.tweens.length; n++) { + this.tweens[n].resume(); + } + }, + _onEnterFrame: function() { + for(var n = 0; n < this.tweens.length; n++) { + this.tweens[n].onEnterFrame(); + } + }, + _add: function(tween) { + this.tweens.push(tween); + }, + _getTween: function(attrs, prop, val, obj, rootObj) { + var config = this.config; + var node = this.node; + var easing = config.easing; + if(easing === undefined) { + easing = 'linear'; + } + + var tween = new Kinetic.Tween(node, function(i) { + obj[prop] = i; + node.setAttrs(rootObj); + }, Kinetic.Tweens[easing], attrs[prop], val, config.duration); + + return tween; + } + }; + + /** + * transition node to another state. Any property that can accept a real + * number can be transitioned, including x, y, rotation, opacity, strokeWidth, + * radius, scale.x, scale.y, offset.x, offset.y, etc. + * @name transitionTo + * @methodOf Kinetic.Node.prototype + * @param {Object} config + * @config {Number} duration duration that the transition runs in seconds + * @config {String} [easing] easing function. can be linear, ease-in, ease-out, ease-in-out, + * back-ease-in, back-ease-out, back-ease-in-out, elastic-ease-in, elastic-ease-out, + * elastic-ease-in-out, bounce-ease-out, bounce-ease-in, bounce-ease-in-out, + * strong-ease-in, strong-ease-out, or strong-ease-in-out + * linear is the default + * @config {Function} [callback] callback function to be executed when + * transition completes + */ + Kinetic.Node.prototype.transitionTo = function(config) { + if(!this.transAnim) { + this.transAnim = new Kinetic.Animation(); + } + /* + * create new transition + */ + var node = this.nodeType === 'Stage' ? this : this.getLayer(); + var that = this; + var trans = new Kinetic.Transition(this, config); + + this.transAnim.func = function() { + trans._onEnterFrame(); + }; + this.transAnim.node = node; + + // subscribe to onFinished for first tween + trans.onFinished = function() { + // remove animation + that.transAnim.stop(); + + // callback + if(config.callback) { + config.callback(); } }; - } -}; -/* - * Transition methods - */ -Kinetic.Transition.prototype = { - /** - * start transition - * @name start - * @methodOf Kinetic.Transition.prototype - */ - start: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].start(); - } - }, - /** - * stop transition - * @name stop - * @methodOf Kinetic.Transition.prototype - */ - stop: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].stop(); - } - }, - /** - * resume transition - * @name resume - * @methodOf Kinetic.Transition.prototype - */ - resume: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].resume(); - } - }, - _onEnterFrame: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].onEnterFrame(); - } - }, - _add: function(tween) { - this.tweens.push(tween); - }, - _getTween: function(attrs, prop, val, obj, rootObj) { - var config = this.config; - var node = this.node; - var easing = config.easing; - if(easing === undefined) { - easing = 'linear'; - } - - var tween = new Kinetic.Tween(node, function(i) { - obj[prop] = i; - node.setAttrs(rootObj); - }, Kinetic.Tweens[easing], attrs[prop], val, config.duration); - - return tween; - } -}; - -/** - * transition node to another state. Any property that can accept a real - * number can be transitioned, including x, y, rotation, opacity, strokeWidth, - * radius, scale.x, scale.y, offset.x, offset.y, etc. - * @name transitionTo - * @methodOf Kinetic.Node.prototype - * @param {Object} config - * @config {Number} duration duration that the transition runs in seconds - * @config {String} [easing] easing function. can be linear, ease-in, ease-out, ease-in-out, - * back-ease-in, back-ease-out, back-ease-in-out, elastic-ease-in, elastic-ease-out, - * elastic-ease-in-out, bounce-ease-out, bounce-ease-in, bounce-ease-in-out, - * strong-ease-in, strong-ease-out, or strong-ease-in-out - * linear is the default - * @config {Function} [callback] callback function to be executed when - * transition completes - */ -Kinetic.Node.prototype.transitionTo = function(config) { - if(!this.transAnim) { - this.transAnim = new Kinetic.Animation(); - } - /* - * create new transition - */ - var node = this.nodeType === 'Stage' ? this : this.getLayer(); - var that = this; - var trans = new Kinetic.Transition(this, config); - - this.transAnim.func = function() { - trans._onEnterFrame(); + // auto start + trans.start(); + this.transAnim.start(); + return trans; }; - this.transAnim.node = node; - - // subscribe to onFinished for first tween - trans.onFinished = function() { - // remove animation - that.transAnim.stop(); - - // callback - if(config.callback) { - config.callback(); - } - }; - // auto start - trans.start(); - this.transAnim.start(); - return trans; -}; +})(); diff --git a/src/filters/Brighten.js b/src/filters/Brighten.js index ad9d7615..167cc627 100644 --- a/src/filters/Brighten.js +++ b/src/filters/Brighten.js @@ -1,20 +1,22 @@ -/** - * Brighten Filter - * @function - * @memberOf Kinetic.Filters - * @param {Object} imageData - * @param {Object} config - * @param {Integer} config.val brightness number from -255 to 255.  Positive values increase the brightness and negative values decrease the brightness, making the image darker - */ -Kinetic.Filters.Brighten = function(imageData, config) { - var brightness = config.val || 0; - var data = imageData.data; - for(var i = 0; i < data.length; i += 4) { - // red - data[i] += brightness; - // green - data[i + 1] += brightness; - // blue - data[i + 2] += brightness; - } -}; +(function() { + /** + * Brighten Filter + * @function + * @memberOf Kinetic.Filters + * @param {Object} imageData + * @param {Object} config + * @param {Integer} config.val brightness number from -255 to 255.  Positive values increase the brightness and negative values decrease the brightness, making the image darker + */ + Kinetic.Filters.Brighten = function(imageData, config) { + var brightness = config.val || 0; + var data = imageData.data; + for(var i = 0; i < data.length; i += 4) { + // red + data[i] += brightness; + // green + data[i + 1] += brightness; + // blue + data[i + 2] += brightness; + } + }; +})(); diff --git a/src/filters/Grayscale.js b/src/filters/Grayscale.js index 32b14d41..2228a35f 100644 --- a/src/filters/Grayscale.js +++ b/src/filters/Grayscale.js @@ -1,19 +1,21 @@ -/** - * Grayscale Filter - * @function - * @memberOf Kinetic.Filters - * @param {Object} imageData - * @param {Object} config - */ -Kinetic.Filters.Grayscale = function(imageData, config) { - var data = imageData.data; - for(var i = 0; i < data.length; i += 4) { - var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; - // red - data[i] = brightness; - // green - data[i + 1] = brightness; - // blue - data[i + 2] = brightness; - } -}; +(function() { + /** + * Grayscale Filter + * @function + * @memberOf Kinetic.Filters + * @param {Object} imageData + * @param {Object} config + */ + Kinetic.Filters.Grayscale = function(imageData, config) { + var data = imageData.data; + for(var i = 0; i < data.length; i += 4) { + var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; + // red + data[i] = brightness; + // green + data[i + 1] = brightness; + // blue + data[i + 2] = brightness; + } + }; +})(); diff --git a/src/filters/Invert.js b/src/filters/Invert.js index ee9c65a7..515b6fdd 100644 --- a/src/filters/Invert.js +++ b/src/filters/Invert.js @@ -1,18 +1,20 @@ -/** - * Invert Filter - * @function - * @memberOf Kinetic.Filters - * @param {Object} imageData - * @param {Object} config - */ -Kinetic.Filters.Invert = function(imageData, config) { - var data = imageData.data; - for(var i = 0; i < data.length; i += 4) { - // red - data[i] = 255 - data[i]; - // green - data[i + 1] = 255 - data[i + 1]; - // blue - data[i + 2] = 255 - data[i + 2]; - } -}; +(function() { + /** + * Invert Filter + * @function + * @memberOf Kinetic.Filters + * @param {Object} imageData + * @param {Object} config + */ + Kinetic.Filters.Invert = function(imageData, config) { + var data = imageData.data; + for(var i = 0; i < data.length; i += 4) { + // red + data[i] = 255 - data[i]; + // green + data[i + 1] = 255 - data[i + 1]; + // blue + data[i + 2] = 255 - data[i + 2]; + } + }; +})(); diff --git a/src/shapes/Circle.js b/src/shapes/Circle.js index 3bc0bfa1..a48a79ff 100644 --- a/src/shapes/Circle.js +++ b/src/shapes/Circle.js @@ -1,60 +1,62 @@ -/** - * Circle constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Circle = function(config) { - this._initCircle(config); -}; +(function() { + /** + * Circle constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.Circle = function(config) { + this._initCircle(config); + }; -Kinetic.Circle.prototype = { - _initCircle: function(config) { - this.setDefaultAttrs({ - radius: 0 - }); + Kinetic.Circle.prototype = { + _initCircle: function(config) { + this.setDefaultAttrs({ + radius: 0 + }); - this.shapeType = 'Circle'; + this.shapeType = 'Circle'; - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - context.beginPath(); - context.arc(0, 0, this.getRadius(), 0, Math.PI * 2, true); - context.closePath(); - this.fillStroke(context); - }, - getWidth: function() { - return this.getRadius() * 2; - }, - getHeight: function() { - return this.getRadius() * 2; - }, - setWidth: function(width) { - Kinetic.Node.prototype.setWidth.call(this, width); - this.setRadius(width / 2); - }, - setHeight: function(height) { - Kinetic.Node.prototype.setHeight.call(this, height); - this.setRadius(height / 2); - } -}; -Kinetic.Global.extend(Kinetic.Circle, Kinetic.Shape); + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + context.beginPath(); + context.arc(0, 0, this.getRadius(), 0, Math.PI * 2, true); + context.closePath(); + this.fillStroke(context); + }, + getWidth: function() { + return this.getRadius() * 2; + }, + getHeight: function() { + return this.getRadius() * 2; + }, + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this.setRadius(width / 2); + }, + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this.setRadius(height / 2); + } + }; + Kinetic.Global.extend(Kinetic.Circle, Kinetic.Shape); -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Circle, ['radius']); + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Circle, ['radius']); -/** - * set radius - * @name setRadius - * @methodOf Kinetic.Circle.prototype - * @param {Number} radius - */ + /** + * set radius + * @name setRadius + * @methodOf Kinetic.Circle.prototype + * @param {Number} radius + */ -/** - * get radius - * @name getRadius - * @methodOf Kinetic.Circle.prototype - */ \ No newline at end of file + /** + * get radius + * @name getRadius + * @methodOf Kinetic.Circle.prototype + */ +})(); diff --git a/src/shapes/Ellipse.js b/src/shapes/Ellipse.js index 0b345dd6..fe223458 100644 --- a/src/shapes/Ellipse.js +++ b/src/shapes/Ellipse.js @@ -1,82 +1,84 @@ -/** - * Ellipse constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Ellipse = function(config) { - this._initEllipse(config); -}; - -Kinetic.Ellipse.prototype = { - _initEllipse: function(config) { - this.setDefaultAttrs({ - radius: { - x: 0, - y: 0 - } - }); - - this.shapeType = "Ellipse"; - - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - var r = this.getRadius(); - context.beginPath(); - context.save(); - if(r.x !== r.y) { - context.scale(1, r.y / r.x); - } - context.arc(0, 0, r.x, 0, Math.PI * 2, true); - context.restore(); - context.closePath(); - this.fillStroke(context); - }, +(function() { /** - * set radius - * @name setRadius - * @methodOf Kinetic.Ellipse.prototype - * @param {Object|Array} radius - * radius can be a number, in which the ellipse becomes a circle, - * it can be an object with an x and y component, or it - * can be an array in which the first element is the x component - * and the second element is the y component. The x component - * defines the horizontal radius and the y component - * defines the vertical radius + * Ellipse constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config */ - setRadius: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - this.setAttr('radius', Kinetic.Type._merge(pos, this.getRadius())); - }, - getWidth: function() { - return this.getRadius().x * 2; - }, - getHeight: function() { - return this.getRadius().y * 2; - }, - setWidth: function(width) { - Kinetic.Node.prototype.setWidth.call(this, width); - this.setRadius({ - x: width / 2 - }); - }, - setHeight: function(height) { - Kinetic.Node.prototype.setHeight.call(this, height); - this.setRadius({ - y: height / 2 - }); - } -}; -Kinetic.Global.extend(Kinetic.Ellipse, Kinetic.Shape); + Kinetic.Ellipse = function(config) { + this._initEllipse(config); + }; -// add getters setters -Kinetic.Node.addGetters(Kinetic.Ellipse, ['radius']); + Kinetic.Ellipse.prototype = { + _initEllipse: function(config) { + this.setDefaultAttrs({ + radius: { + x: 0, + y: 0 + } + }); -/** - * get radius - * @name getRadius - * @methodOf Kinetic.Ellipse.prototype - */ \ No newline at end of file + this.shapeType = "Ellipse"; + + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + var r = this.getRadius(); + context.beginPath(); + context.save(); + if(r.x !== r.y) { + context.scale(1, r.y / r.x); + } + context.arc(0, 0, r.x, 0, Math.PI * 2, true); + context.restore(); + context.closePath(); + this.fillStroke(context); + }, + /** + * set radius + * @name setRadius + * @methodOf Kinetic.Ellipse.prototype + * @param {Object|Array} radius + * radius can be a number, in which the ellipse becomes a circle, + * it can be an object with an x and y component, or it + * can be an array in which the first element is the x component + * and the second element is the y component. The x component + * defines the horizontal radius and the y component + * defines the vertical radius + */ + setRadius: function() { + var pos = Kinetic.Type._getXY([].slice.call(arguments)); + this.setAttr('radius', Kinetic.Type._merge(pos, this.getRadius())); + }, + getWidth: function() { + return this.getRadius().x * 2; + }, + getHeight: function() { + return this.getRadius().y * 2; + }, + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this.setRadius({ + x: width / 2 + }); + }, + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this.setRadius({ + y: height / 2 + }); + } + }; + Kinetic.Global.extend(Kinetic.Ellipse, Kinetic.Shape); + + // add getters setters + Kinetic.Node.addGetters(Kinetic.Ellipse, ['radius']); + + /** + * get radius + * @name getRadius + * @methodOf Kinetic.Ellipse.prototype + */ +})(); diff --git a/src/shapes/Image.js b/src/shapes/Image.js index 4b32015c..3b80cf69 100644 --- a/src/shapes/Image.js +++ b/src/shapes/Image.js @@ -1,207 +1,209 @@ -/** - * Image constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - * @param {ImageObject} config.image - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Object} [config.crop] - */ -Kinetic.Image = function(config) { - this._initImage(config); -}; +(function() { + /** + * Image constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + * @param {ImageObject} config.image + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Object} [config.crop] + */ + Kinetic.Image = function(config) { + this._initImage(config); + }; -Kinetic.Image.prototype = { - _initImage: function(config) { - this.shapeType = "Image"; + Kinetic.Image.prototype = { + _initImage: function(config) { + this.shapeType = "Image"; - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); - var that = this; - this.on('imageChange', function(evt) { - that._syncSize(); - }); + var that = this; + this.on('imageChange', function(evt) { + that._syncSize(); + }); - this._syncSize(); - }, - drawFunc: function(context) { - var width = this.getWidth(), height = this.getHeight(), params, that = this; + this._syncSize(); + }, + drawFunc: function(context) { + var width = this.getWidth(), height = this.getHeight(), params, that = this; - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - this.fillStroke(context); - - if(this.attrs.image) { - // if cropping - if(this.attrs.crop && this.attrs.crop.width && this.attrs.crop.height) { - var cropX = this.attrs.crop.x || 0; - var cropY = this.attrs.crop.y || 0; - var cropWidth = this.attrs.crop.width; - var cropHeight = this.attrs.crop.height; - params = [context, this.attrs.image, cropX, cropY, cropWidth, cropHeight, 0, 0, width, height]; - } - // no cropping - else { - params = [context, this.attrs.image, 0, 0, width, height]; - } - - if(this.getShadow()) { - this.applyShadow(context, function() { - that.drawImage.apply(that, params); - }); - } - else { - this.drawImage.apply(this, params); - } - - } - - }, - drawHitFunc: function(context) { - var width = this.getWidth(), height = this.getHeight(), imageHitRegion = this.imageHitRegion, appliedShadow = false; - - if(imageHitRegion) { - this.drawImage(context, imageHitRegion, 0, 0, width, height); - - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - this.stroke(context); - } - else { context.beginPath(); context.rect(0, 0, width, height); context.closePath(); this.fillStroke(context); - } - }, - /** - * apply filter - * @name applyFilter - * @methodOf Kinetic.Image.prototype - * @param {Object} config - * @param {Function} filter filter function - * @param {Object} [config] optional config object used to configure filter - * @param {Function} [callback] callback function to be called once - * filter has been applied - */ - applyFilter: function(filter, config, callback) { - var canvas = new Kinetic.Canvas(this.attrs.image.width, this.attrs.image.height); - var context = canvas.getContext(); - context.drawImage(this.attrs.image, 0, 0); - try { - var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight()); - filter(imageData, config); - var that = this; - Kinetic.Type._getImage(imageData, function(imageObj) { - that.setImage(imageObj); - if(callback) { - callback(); + if(this.attrs.image) { + // if cropping + if(this.attrs.crop && this.attrs.crop.width && this.attrs.crop.height) { + var cropX = this.attrs.crop.x || 0; + var cropY = this.attrs.crop.y || 0; + var cropWidth = this.attrs.crop.width; + var cropHeight = this.attrs.crop.height; + params = [context, this.attrs.image, cropX, cropY, cropWidth, cropHeight, 0, 0, width, height]; } - }); - } - catch(e) { - Kinetic.Global.warn('Unable to apply filter. ' + e.message); - } - }, - /** - * set crop - * @name setCrop - * @methodOf Kinetic.Image.prototype - * @param {Object|Array} config - * @param {Number} config.x - * @param {Number} config.y - * @param {Number} config.width - * @param {Number} config.height - */ - setCrop: function() { - var config = [].slice.call(arguments); - var pos = Kinetic.Type._getXY(config); - var size = Kinetic.Type._getSize(config); - var both = Kinetic.Type._merge(pos, size); - this.setAttr('crop', Kinetic.Type._merge(both, this.getCrop())); - }, - /** - * create image hit region which enables more accurate hit detection mapping of the image - * by avoiding event detections for transparent pixels - * @name createImageHitRegion - * @methodOf Kinetic.Image.prototype - * @param {Function} [callback] callback function to be called once - * the image hit region has been created - */ - createImageHitRegion: function(callback) { - var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height); - var context = canvas.getContext(); - context.drawImage(this.attrs.image, 0, 0); - try { - var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight()); - var data = imageData.data; - var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey); - // replace non transparent pixels with color key - for(var i = 0, n = data.length; i < n; i += 4) { - data[i] = rgbColorKey.r; - data[i + 1] = rgbColorKey.g; - data[i + 2] = rgbColorKey.b; - // i+3 is alpha (the fourth element) - } - - var that = this; - Kinetic.Type._getImage(imageData, function(imageObj) { - that.imageHitRegion = imageObj; - if(callback) { - callback(); + // no cropping + else { + params = [context, this.attrs.image, 0, 0, width, height]; } - }); + + if(this.getShadow()) { + this.applyShadow(context, function() { + that.drawImage.apply(that, params); + }); + } + else { + this.drawImage.apply(this, params); + } + + } + + }, + drawHitFunc: function(context) { + var width = this.getWidth(), height = this.getHeight(), imageHitRegion = this.imageHitRegion, appliedShadow = false; + + if(imageHitRegion) { + this.drawImage(context, imageHitRegion, 0, 0, width, height); + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + this.stroke(context); + } + else { + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + this.fillStroke(context); + } + }, + /** + * apply filter + * @name applyFilter + * @methodOf Kinetic.Image.prototype + * @param {Object} config + * @param {Function} filter filter function + * @param {Object} [config] optional config object used to configure filter + * @param {Function} [callback] callback function to be called once + * filter has been applied + */ + applyFilter: function(filter, config, callback) { + var canvas = new Kinetic.Canvas(this.attrs.image.width, this.attrs.image.height); + var context = canvas.getContext(); + context.drawImage(this.attrs.image, 0, 0); + try { + var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight()); + filter(imageData, config); + var that = this; + Kinetic.Type._getImage(imageData, function(imageObj) { + that.setImage(imageObj); + + if(callback) { + callback(); + } + }); + } + catch(e) { + Kinetic.Global.warn('Unable to apply filter. ' + e.message); + } + }, + /** + * set crop + * @name setCrop + * @methodOf Kinetic.Image.prototype + * @param {Object|Array} config + * @param {Number} config.x + * @param {Number} config.y + * @param {Number} config.width + * @param {Number} config.height + */ + setCrop: function() { + var config = [].slice.call(arguments); + var pos = Kinetic.Type._getXY(config); + var size = Kinetic.Type._getSize(config); + var both = Kinetic.Type._merge(pos, size); + this.setAttr('crop', Kinetic.Type._merge(both, this.getCrop())); + }, + /** + * create image hit region which enables more accurate hit detection mapping of the image + * by avoiding event detections for transparent pixels + * @name createImageHitRegion + * @methodOf Kinetic.Image.prototype + * @param {Function} [callback] callback function to be called once + * the image hit region has been created + */ + createImageHitRegion: function(callback) { + var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height); + var context = canvas.getContext(); + context.drawImage(this.attrs.image, 0, 0); + try { + var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight()); + var data = imageData.data; + var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey); + // replace non transparent pixels with color key + for(var i = 0, n = data.length; i < n; i += 4) { + data[i] = rgbColorKey.r; + data[i + 1] = rgbColorKey.g; + data[i + 2] = rgbColorKey.b; + // i+3 is alpha (the fourth element) + } + + var that = this; + Kinetic.Type._getImage(imageData, function(imageObj) { + that.imageHitRegion = imageObj; + if(callback) { + callback(); + } + }); + } + catch(e) { + Kinetic.Global.warn('Unable to create image hit region. ' + e.message); + } + }, + /** + * clear image hit region + * @name clearImageHitRegion + * @methodOf Kinetic.Image.prototype + */ + clearImageHitRegion: function() { + delete this.imageHitRegion; + }, + _syncSize: function() { + if(this.attrs.image) { + if(!this.attrs.width) { + this.setWidth(this.attrs.image.width); + } + if(!this.attrs.height) { + this.setHeight(this.attrs.image.height); + } + } } - catch(e) { - Kinetic.Global.warn('Unable to create image hit region. ' + e.message); - } - }, + }; + Kinetic.Global.extend(Kinetic.Image, Kinetic.Shape); + + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Image, ['image']); + Kinetic.Node.addGetters(Kinetic.Image, ['crop']); + /** - * clear image hit region - * @name clearImageHitRegion + * set image + * @name setImage + * @methodOf Kinetic.Image.prototype + * @param {ImageObject} image + */ + + /** + * get crop + * @name getCrop * @methodOf Kinetic.Image.prototype */ - clearImageHitRegion: function() { - delete this.imageHitRegion; - }, - _syncSize: function() { - if(this.attrs.image) { - if(!this.attrs.width) { - this.setWidth(this.attrs.image.width); - } - if(!this.attrs.height) { - this.setHeight(this.attrs.image.height); - } - } - } -}; -Kinetic.Global.extend(Kinetic.Image, Kinetic.Shape); -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Image, ['image']); -Kinetic.Node.addGetters(Kinetic.Image, ['crop']); - -/** - * set image - * @name setImage - * @methodOf Kinetic.Image.prototype - * @param {ImageObject} image - */ - -/** - * get crop - * @name getCrop - * @methodOf Kinetic.Image.prototype - */ - -/** - * get image - * @name getImage - * @methodOf Kinetic.Image.prototype - */ \ No newline at end of file + /** + * get image + * @name getImage + * @methodOf Kinetic.Image.prototype + */ +})(); diff --git a/src/shapes/Line.js b/src/shapes/Line.js index 1a34b99c..1f8620a0 100644 --- a/src/shapes/Line.js +++ b/src/shapes/Line.js @@ -1,135 +1,137 @@ -/** - * Line constructor.  Lines are defined by an array of points - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Line = function(config) { - this._initLine(config); -}; - -Kinetic.Line.prototype = { - _initLine: function(config) { - this.setDefaultAttrs({ - points: [], - lineCap: 'butt', - dashArray: [], - detectionType: 'pixel' - }); - - this.shapeType = "Line"; - - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - var lastPos = {}, points = this.getPoints(), length = points.length, dashArray = this.getDashArray(), dashLength = dashArray.length; - context.beginPath(); - - context.moveTo(points[0].x, points[0].y); - - for(var n = 1; n < length; n++) { - var x = points[n].x; - var y = points[n].y; - if(dashLength > 0) { - // draw dashed line - var lastX = points[n - 1].x; - var lastY = points[n - 1].y; - this._dashedLine(context, lastX, lastY, x, y, dashArray); - } - else { - // draw normal line - context.lineTo(x, y); - } - } - - this.stroke(context); - }, +(function() { /** - * set points array - * @name setPoints - * @methodOf Kinetic.Line.prototype - * @param {Array} can be an array of point objects or an array - * of Numbers. e.g. [{x:1,y:2},{x:3,y:4}] or [1,2,3,4] - */ - setPoints: function(val) { - this.setAttr('points', Kinetic.Type._getPoints(val)); - }, - /** - * draw dashed line. Written by Phrogz + * Line constructor.  Lines are defined by an array of points + * @constructor + * @augments Kinetic.Shape + * @param {Object} config */ - _dashedLine: function(context, x, y, x2, y2, dashArray) { - var dashCount = dashArray.length; + Kinetic.Line = function(config) { + this._initLine(config); + }; - var dx = (x2 - x), dy = (y2 - y); - var xSlope = dx > dy; - var slope = (xSlope) ? dy / dx : dx / dy; + Kinetic.Line.prototype = { + _initLine: function(config) { + this.setDefaultAttrs({ + points: [], + lineCap: 'butt', + dashArray: [], + detectionType: 'pixel' + }); - /* - * gaurd against slopes of infinity + this.shapeType = "Line"; + + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + var lastPos = {}, points = this.getPoints(), length = points.length, dashArray = this.getDashArray(), dashLength = dashArray.length; + context.beginPath(); + + context.moveTo(points[0].x, points[0].y); + + for(var n = 1; n < length; n++) { + var x = points[n].x; + var y = points[n].y; + if(dashLength > 0) { + // draw dashed line + var lastX = points[n - 1].x; + var lastY = points[n - 1].y; + this._dashedLine(context, lastX, lastY, x, y, dashArray); + } + else { + // draw normal line + context.lineTo(x, y); + } + } + + this.stroke(context); + }, + /** + * set points array + * @name setPoints + * @methodOf Kinetic.Line.prototype + * @param {Array} can be an array of point objects or an array + * of Numbers. e.g. [{x:1,y:2},{x:3,y:4}] or [1,2,3,4] */ - if(slope > 9999) { - slope = 9999; - } - else if(slope < -9999) { - slope = -9999; + setPoints: function(val) { + this.setAttr('points', Kinetic.Type._getPoints(val)); + }, + /** + * draw dashed line. Written by Phrogz + */ + _dashedLine: function(context, x, y, x2, y2, dashArray) { + var dashCount = dashArray.length; + + var dx = (x2 - x), dy = (y2 - y); + var xSlope = dx > dy; + var slope = (xSlope) ? dy / dx : dx / dy; + + /* + * gaurd against slopes of infinity + */ + if(slope > 9999) { + slope = 9999; + } + else if(slope < -9999) { + slope = -9999; + } + + var distRemaining = Math.sqrt(dx * dx + dy * dy); + var dashIndex = 0, draw = true; + while(distRemaining >= 0.1 && dashIndex < 10000) { + var dashLength = dashArray[dashIndex++ % dashCount]; + if(dashLength === 0) { + dashLength = 0.001; + } + if(dashLength > distRemaining) { + dashLength = distRemaining; + } + var step = Math.sqrt(dashLength * dashLength / (1 + slope * slope)); + if(xSlope) { + x += dx < 0 && dy < 0 ? step * -1 : step; + y += dx < 0 && dy < 0 ? slope * step * -1 : slope * step; + } + else { + x += dx < 0 && dy < 0 ? slope * step * -1 : slope * step; + y += dx < 0 && dy < 0 ? step * -1 : step; + } + context[draw ? 'lineTo' : 'moveTo'](x, y); + distRemaining -= dashLength; + draw = !draw; + } + + context.moveTo(x2, y2); } + }; + Kinetic.Global.extend(Kinetic.Line, Kinetic.Shape); - var distRemaining = Math.sqrt(dx * dx + dy * dy); - var dashIndex = 0, draw = true; - while(distRemaining >= 0.1 && dashIndex < 10000) { - var dashLength = dashArray[dashIndex++ % dashCount]; - if(dashLength === 0) { - dashLength = 0.001; - } - if(dashLength > distRemaining) { - dashLength = distRemaining; - } - var step = Math.sqrt(dashLength * dashLength / (1 + slope * slope)); - if(xSlope) { - x += dx < 0 && dy < 0 ? step * -1 : step; - y += dx < 0 && dy < 0 ? slope * step * -1 : slope * step; - } - else { - x += dx < 0 && dy < 0 ? slope * step * -1 : slope * step; - y += dx < 0 && dy < 0 ? step * -1 : step; - } - context[draw ? 'lineTo' : 'moveTo'](x, y); - distRemaining -= dashLength; - draw = !draw; - } + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Line, ['dashArray']); + Kinetic.Node.addGetters(Kinetic.Line, ['points']); - context.moveTo(x2, y2); - } -}; -Kinetic.Global.extend(Kinetic.Line, Kinetic.Shape); + /** + * set dash array. + * @name setDashArray + * @methodOf Kinetic.Line.prototype + * @param {Array} dashArray + * examples:
+ * [10, 5] dashes are 10px long and 5 pixels apart + * [10, 20, 0, 20] if using a round lineCap, the line will + * be made up of alternating dashed lines that are 10px long + * and 20px apart, and dots that have a radius of 5 and are 20px + * apart + */ -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Line, ['dashArray']); -Kinetic.Node.addGetters(Kinetic.Line, ['points']); + /** + * get dash array + * @name getDashArray + * @methodOf Kinetic.Line.prototype + */ -/** - * set dash array. - * @name setDashArray - * @methodOf Kinetic.Line.prototype - * @param {Array} dashArray - * examples:
- * [10, 5] dashes are 10px long and 5 pixels apart - * [10, 20, 0, 20] if using a round lineCap, the line will - * be made up of alternating dashed lines that are 10px long - * and 20px apart, and dots that have a radius of 5 and are 20px - * apart - */ - -/** - * get dash array - * @name getDashArray - * @methodOf Kinetic.Line.prototype - */ - -/** - * get points array - * @name getPoints - * @methodOf Kinetic.Line.prototype - */ \ No newline at end of file + /** + * get points array + * @name getPoints + * @methodOf Kinetic.Line.prototype + */ +})(); diff --git a/src/shapes/Path.js b/src/shapes/Path.js index 85ccc79f..5ebd9cac 100644 --- a/src/shapes/Path.js +++ b/src/shapes/Path.js @@ -1,559 +1,560 @@ -/** - * Path constructor. - * @author Jason Follas - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Path = function(config) { - this._initPath(config); -}; - -Kinetic.Path.prototype = { - _initPath: function(config) { - this.shapeType = "Path"; - this.dataArray = []; - var that = this; - - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - - this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); - this.on('dataChange', function() { - that.dataArray = Kinetic.Path.parsePathData(that.attrs.data); - }); - }, - drawFunc: function(context) { - var ca = this.dataArray; - // context position - context.beginPath(); - for(var n = 0; n < ca.length; n++) { - var c = ca[n].command; - var p = ca[n].points; - switch (c) { - case 'L': - context.lineTo(p[0], p[1]); - break; - case 'M': - context.moveTo(p[0], p[1]); - break; - case 'C': - context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); - break; - case 'Q': - context.quadraticCurveTo(p[0], p[1], p[2], p[3]); - break; - case 'A': - var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6], fs = p[7]; - - var r = (rx > ry) ? rx : ry; - var scaleX = (rx > ry) ? 1 : rx / ry; - var scaleY = (rx > ry) ? ry / rx : 1; - - context.translate(cx, cy); - context.rotate(psi); - context.scale(scaleX, scaleY); - context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); - context.scale(1 / scaleX, 1 / scaleY); - context.rotate(-psi); - context.translate(-cx, -cy); - - break; - case 'z': - context.closePath(); - break; - } - } - this.fillStroke(context); - } -}; -Kinetic.Global.extend(Kinetic.Path, Kinetic.Shape); - -/* - * Utility methods written by jfollas to - * handle length and point measurements - */ -Kinetic.Path.getLineLength = function(x1, y1, x2, y2) { - return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); -}; -Kinetic.Path.getPointOnLine = function(dist, P1x, P1y, P2x, P2y, fromX, fromY) { - if(fromX === undefined) { - fromX = P1x; - } - if(fromY === undefined) { - fromY = P1y; - } - - var m = (P2y - P1y) / ((P2x - P1x) + 0.00000001); - var run = Math.sqrt(dist * dist / (1 + m * m)); - if(P2x < P1x) run *= -1; - var rise = m * run; - var pt; - - if((fromY - P1y) / ((fromX - P1x) + 0.00000001) === m) { - pt = { - x: fromX + run, - y: fromY + rise - }; - } - else { - var ix, iy; - - var len = this.getLineLength(P1x, P1y, P2x, P2y); - if(len < 0.00000001) { - return undefined; - } - var u = (((fromX - P1x) * (P2x - P1x)) + ((fromY - P1y) * (P2y - P1y))); - u = u / (len * len); - ix = P1x + u * (P2x - P1x); - iy = P1y + u * (P2y - P1y); - - var pRise = this.getLineLength(fromX, fromY, ix, iy); - var pRun = Math.sqrt(dist * dist - pRise * pRise); - run = Math.sqrt(pRun * pRun / (1 + m * m)); - if(P2x < P1x) run *= -1; - rise = m * run; - pt = { - x: ix + run, - y: iy + rise - }; - } - - return pt; -}; - -Kinetic.Path.getPointOnCubicBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { - function CB1(t) { - return t * t * t; - } - function CB2(t) { - return 3 * t * t * (1 - t); - } - function CB3(t) { - return 3 * t * (1 - t) * (1 - t); - } - function CB4(t) { - return (1 - t) * (1 - t) * (1 - t); - } - var x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); - var y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); - - return { - x: x, - y: y +(function() { + /** + * Path constructor. + * @author Jason Follas + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.Path = function(config) { + this._initPath(config); }; -}; -Kinetic.Path.getPointOnQuadraticBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y) { - function QB1(t) { - return t * t; - } - function QB2(t) { - return 2 * t * (1 - t); - } - function QB3(t) { - return (1 - t) * (1 - t); - } - var x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); - var y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); - return { - x: x, - y: y - }; -}; -Kinetic.Path.getPointOnEllipticalArc = function(cx, cy, rx, ry, theta, psi) { - var cosPsi = Math.cos(psi), sinPsi = Math.sin(psi); - var pt = { - x: rx * Math.cos(theta), - y: ry * Math.sin(theta) - }; - return { - x: cx + (pt.x * cosPsi - pt.y * sinPsi), - y: cy + (pt.x * sinPsi + pt.y * cosPsi) - }; -}; -/** - * get parsed data array from the data - * string. V, v, H, h, and l data are converted to - * L data for the purpose of high performance Path - * rendering - */ -Kinetic.Path.parsePathData = function(data) { - // Path Data Segment must begin with a moveTo - //m (x y)+ Relative moveTo (subsequent points are treated as lineTo) - //M (x y)+ Absolute moveTo (subsequent points are treated as lineTo) - //l (x y)+ Relative lineTo - //L (x y)+ Absolute LineTo - //h (x)+ Relative horizontal lineTo - //H (x)+ Absolute horizontal lineTo - //v (y)+ Relative vertical lineTo - //V (y)+ Absolute vertical lineTo - //z (closepath) - //Z (closepath) - //c (x1 y1 x2 y2 x y)+ Relative Bezier curve - //C (x1 y1 x2 y2 x y)+ Absolute Bezier curve - //q (x1 y1 x y)+ Relative Quadratic Bezier - //Q (x1 y1 x y)+ Absolute Quadratic Bezier - //t (x y)+ Shorthand/Smooth Relative Quadratic Bezier - //T (x y)+ Shorthand/Smooth Absolute Quadratic Bezier - //s (x2 y2 x y)+ Shorthand/Smooth Relative Bezier curve - //S (x2 y2 x y)+ Shorthand/Smooth Absolute Bezier curve - //a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Relative Elliptical Arc - //A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Absolute Elliptical Arc + Kinetic.Path.prototype = { + _initPath: function(config) { + this.shapeType = "Path"; + this.dataArray = []; + var that = this; - // return early if data is not defined - if(!data) { - return []; - } + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); - // command string - var cs = data; - - // command chars - var cc = ['m', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A']; - // convert white spaces to commas - cs = cs.replace(new RegExp(' ', 'g'), ','); - // create pipes so that we can split the data - for(var n = 0; n < cc.length; n++) { - cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); - } - // create array - var arr = cs.split('|'); - var ca = []; - // init context point - var cpx = 0; - var cpy = 0; - for(var n = 1; n < arr.length; n++) { - var str = arr[n]; - var c = str.charAt(0); - str = str.slice(1); - // remove ,- for consistency - str = str.replace(new RegExp(',-', 'g'), '-'); - // add commas so that it's easy to split - str = str.replace(new RegExp('-', 'g'), ',-'); - str = str.replace(new RegExp('e,-', 'g'), 'e-'); - var p = str.split(','); - if(p.length > 0 && p[0] === '') { - p.shift(); - } - // convert strings to floats - for(var i = 0; i < p.length; i++) { - p[i] = parseFloat(p[i]); - } - while(p.length > 0) { - if(isNaN(p[0]))// case for a trailing comma before next command - break; - - var cmd = null; - var points = []; - var startX = cpx, startY = cpy; - - // convert l, H, h, V, and v to L - switch (c) { - - // Note: Keep the lineTo's above the moveTo's in this switch - case 'l': - cpx += p.shift(); - cpy += p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'L': - cpx = p.shift(); - cpy = p.shift(); - points.push(cpx, cpy); - break; - - // Note: lineTo handlers need to be above this point - case 'm': - cpx += p.shift(); - cpy += p.shift(); - cmd = 'M'; - points.push(cpx, cpy); - c = 'l'; - // subsequent points are treated as relative lineTo - break; - case 'M': - cpx = p.shift(); - cpy = p.shift(); - cmd = 'M'; - points.push(cpx, cpy); - c = 'L'; - // subsequent points are treated as absolute lineTo - break; - - case 'h': - cpx += p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'H': - cpx = p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'v': - cpy += p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'V': - cpy = p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'C': - points.push(p.shift(), p.shift(), p.shift(), p.shift()); - cpx = p.shift(); - cpy = p.shift(); - points.push(cpx, cpy); - break; - case 'c': - points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift()); - cpx += p.shift(); - cpy += p.shift(); - cmd = 'C'; - points.push(cpx, cpy); - break; - case 'S': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'C') { - ctlPtx = cpx + (cpx - prevCmd.points[2]); - ctlPty = cpy + (cpy - prevCmd.points[3]); - } - points.push(ctlPtx, ctlPty, p.shift(), p.shift()); - cpx = p.shift(); - cpy = p.shift(); - cmd = 'C'; - points.push(cpx, cpy); - break; - case 's': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'C') { - ctlPtx = cpx + (cpx - prevCmd.points[2]); - ctlPty = cpy + (cpy - prevCmd.points[3]); - } - points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift()); - cpx += p.shift(); - cpy += p.shift(); - cmd = 'C'; - points.push(cpx, cpy); - break; - case 'Q': - points.push(p.shift(), p.shift()); - cpx = p.shift(); - cpy = p.shift(); - points.push(cpx, cpy); - break; - case 'q': - points.push(cpx + p.shift(), cpy + p.shift()); - cpx += p.shift(); - cpy += p.shift(); - cmd = 'Q'; - points.push(cpx, cpy); - break; - case 'T': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'Q') { - ctlPtx = cpx + (cpx - prevCmd.points[0]); - ctlPty = cpy + (cpy - prevCmd.points[1]); - } - cpx = p.shift(); - cpy = p.shift(); - cmd = 'Q'; - points.push(ctlPtx, ctlPty, cpx, cpy); - break; - case 't': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'Q') { - ctlPtx = cpx + (cpx - prevCmd.points[0]); - ctlPty = cpy + (cpy - prevCmd.points[1]); - } - cpx += p.shift(); - cpy += p.shift(); - cmd = 'Q'; - points.push(ctlPtx, ctlPty, cpx, cpy); - break; - case 'A': - var rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); - var x1 = cpx, y1 = cpy; - cpx = p.shift(), cpy = p.shift(); - cmd = 'A'; - points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); - break; - case 'a': - var rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); - var x1 = cpx, y1 = cpy; - cpx += p.shift(), cpy += p.shift(); - cmd = 'A'; - points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); - break; - } - - ca.push({ - command: cmd || c, - points: points, - start: { - x: startX, - y: startY - }, - pathLength: this.calcLength(startX, startY, cmd || c, points) + this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); + this.on('dataChange', function() { + that.dataArray = Kinetic.Path.parsePathData(that.attrs.data); }); + }, + drawFunc: function(context) { + var ca = this.dataArray; + // context position + context.beginPath(); + for(var n = 0; n < ca.length; n++) { + var c = ca[n].command; + var p = ca[n].points; + switch (c) { + case 'L': + context.lineTo(p[0], p[1]); + break; + case 'M': + context.moveTo(p[0], p[1]); + break; + case 'C': + context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); + break; + case 'Q': + context.quadraticCurveTo(p[0], p[1], p[2], p[3]); + break; + case 'A': + var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6], fs = p[7]; + + var r = (rx > ry) ? rx : ry; + var scaleX = (rx > ry) ? 1 : rx / ry; + var scaleY = (rx > ry) ? ry / rx : 1; + + context.translate(cx, cy); + context.rotate(psi); + context.scale(scaleX, scaleY); + context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); + context.scale(1 / scaleX, 1 / scaleY); + context.rotate(-psi); + context.translate(-cx, -cy); + + break; + case 'z': + context.closePath(); + break; + } + } + this.fillStroke(context); + } + }; + Kinetic.Global.extend(Kinetic.Path, Kinetic.Shape); + + /* + * Utility methods written by jfollas to + * handle length and point measurements + */ + Kinetic.Path.getLineLength = function(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + }; + Kinetic.Path.getPointOnLine = function(dist, P1x, P1y, P2x, P2y, fromX, fromY) { + if(fromX === undefined) { + fromX = P1x; + } + if(fromY === undefined) { + fromY = P1y; } - if(c === 'z' || c === 'Z') { - ca.push({ - command: 'z', - points: [], - start: undefined, - pathLength: 0 - }); + var m = (P2y - P1y) / ((P2x - P1x) + 0.00000001); + var run = Math.sqrt(dist * dist / (1 + m * m)); + if(P2x < P1x) + run *= -1; + var rise = m * run; + var pt; + + if((fromY - P1y) / ((fromX - P1x) + 0.00000001) === m) { + pt = { + x: fromX + run, + y: fromY + rise + }; } - } + else { + var ix, iy; - return ca; -}; -Kinetic.Path.calcLength = function(x, y, cmd, points) { - var len, p1, p2; - var path = Kinetic.Path; + var len = this.getLineLength(P1x, P1y, P2x, P2y); + if(len < 0.00000001) { + return undefined; + } + var u = (((fromX - P1x) * (P2x - P1x)) + ((fromY - P1y) * (P2y - P1y))); + u = u / (len * len); + ix = P1x + u * (P2x - P1x); + iy = P1y + u * (P2y - P1y); - switch (cmd) { - case 'L': - return path.getLineLength(x, y, points[0], points[1]); - case 'C': - // Approximates by breaking curve into 100 line segments - len = 0.0; - p1 = path.getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); - for( t = 0.01; t <= 1; t += 0.01) { - p2 = path.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; + var pRise = this.getLineLength(fromX, fromY, ix, iy); + var pRun = Math.sqrt(dist * dist - pRise * pRise); + run = Math.sqrt(pRun * pRun / (1 + m * m)); + if(P2x < P1x) + run *= -1; + rise = m * run; + pt = { + x: ix + run, + y: iy + rise + }; + } + + return pt; + }; + + Kinetic.Path.getPointOnCubicBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { + function CB1(t) { + return t * t * t; + } + function CB2(t) { + return 3 * t * t * (1 - t); + } + function CB3(t) { + return 3 * t * (1 - t) * (1 - t); + } + function CB4(t) { + return (1 - t) * (1 - t) * (1 - t); + } + var x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); + var y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); + + return { + x: x, + y: y + }; + }; + Kinetic.Path.getPointOnQuadraticBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y) { + function QB1(t) { + return t * t; + } + function QB2(t) { + return 2 * t * (1 - t); + } + function QB3(t) { + return (1 - t) * (1 - t); + } + var x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); + var y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); + + return { + x: x, + y: y + }; + }; + Kinetic.Path.getPointOnEllipticalArc = function(cx, cy, rx, ry, theta, psi) { + var cosPsi = Math.cos(psi), sinPsi = Math.sin(psi); + var pt = { + x: rx * Math.cos(theta), + y: ry * Math.sin(theta) + }; + return { + x: cx + (pt.x * cosPsi - pt.y * sinPsi), + y: cy + (pt.x * sinPsi + pt.y * cosPsi) + }; + }; + /** + * get parsed data array from the data + * string. V, v, H, h, and l data are converted to + * L data for the purpose of high performance Path + * rendering + */ + Kinetic.Path.parsePathData = function(data) { + // Path Data Segment must begin with a moveTo + //m (x y)+ Relative moveTo (subsequent points are treated as lineTo) + //M (x y)+ Absolute moveTo (subsequent points are treated as lineTo) + //l (x y)+ Relative lineTo + //L (x y)+ Absolute LineTo + //h (x)+ Relative horizontal lineTo + //H (x)+ Absolute horizontal lineTo + //v (y)+ Relative vertical lineTo + //V (y)+ Absolute vertical lineTo + //z (closepath) + //Z (closepath) + //c (x1 y1 x2 y2 x y)+ Relative Bezier curve + //C (x1 y1 x2 y2 x y)+ Absolute Bezier curve + //q (x1 y1 x y)+ Relative Quadratic Bezier + //Q (x1 y1 x y)+ Absolute Quadratic Bezier + //t (x y)+ Shorthand/Smooth Relative Quadratic Bezier + //T (x y)+ Shorthand/Smooth Absolute Quadratic Bezier + //s (x2 y2 x y)+ Shorthand/Smooth Relative Bezier curve + //S (x2 y2 x y)+ Shorthand/Smooth Absolute Bezier curve + //a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Relative Elliptical Arc + //A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Absolute Elliptical Arc + + // return early if data is not defined + if(!data) { + return []; + } + + // command string + var cs = data; + + // command chars + var cc = ['m', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A']; + // convert white spaces to commas + cs = cs.replace(new RegExp(' ', 'g'), ','); + // create pipes so that we can split the data + for(var n = 0; n < cc.length; n++) { + cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); + } + // create array + var arr = cs.split('|'); + var ca = []; + // init context point + var cpx = 0; + var cpy = 0; + for(var n = 1; n < arr.length; n++) { + var str = arr[n]; + var c = str.charAt(0); + str = str.slice(1); + // remove ,- for consistency + str = str.replace(new RegExp(',-', 'g'), '-'); + // add commas so that it's easy to split + str = str.replace(new RegExp('-', 'g'), ',-'); + str = str.replace(new RegExp('e,-', 'g'), 'e-'); + var p = str.split(','); + if(p.length > 0 && p[0] === '') { + p.shift(); } - return len; - case 'Q': - // Approximates by breaking curve into 100 line segments - len = 0.0; - p1 = path.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); - for( t = 0.01; t <= 1; t += 0.01) { - p2 = path.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; + // convert strings to floats + for(var i = 0; i < p.length; i++) { + p[i] = parseFloat(p[i]); } - return len; - case 'A': - // Approximates by breaking curve into line segments - len = 0.0; - var start = points[4]; - // 4 = theta - var dTheta = points[5]; - // 5 = dTheta - var end = points[4] + dTheta; - var inc = Math.PI / 180.0; - // 1 degree resolution - if(Math.abs(start - end) < inc) { - inc = Math.abs(start - end); + while(p.length > 0) { + if(isNaN(p[0]))// case for a trailing comma before next command + break; + + var cmd = null; + var points = []; + var startX = cpx, startY = cpy; + + // convert l, H, h, V, and v to L + switch (c) { + + // Note: Keep the lineTo's above the moveTo's in this switch + case 'l': + cpx += p.shift(); + cpy += p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'L': + cpx = p.shift(); + cpy = p.shift(); + points.push(cpx, cpy); + break; + + // Note: lineTo handlers need to be above this point + case 'm': + cpx += p.shift(); + cpy += p.shift(); + cmd = 'M'; + points.push(cpx, cpy); + c = 'l'; + // subsequent points are treated as relative lineTo + break; + case 'M': + cpx = p.shift(); + cpy = p.shift(); + cmd = 'M'; + points.push(cpx, cpy); + c = 'L'; + // subsequent points are treated as absolute lineTo + break; + + case 'h': + cpx += p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'H': + cpx = p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'v': + cpy += p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'V': + cpy = p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'C': + points.push(p.shift(), p.shift(), p.shift(), p.shift()); + cpx = p.shift(); + cpy = p.shift(); + points.push(cpx, cpy); + break; + case 'c': + points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift()); + cpx += p.shift(); + cpy += p.shift(); + cmd = 'C'; + points.push(cpx, cpy); + break; + case 'S': + var ctlPtx = cpx, ctlPty = cpy; + var prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'C') { + ctlPtx = cpx + (cpx - prevCmd.points[2]); + ctlPty = cpy + (cpy - prevCmd.points[3]); + } + points.push(ctlPtx, ctlPty, p.shift(), p.shift()); + cpx = p.shift(); + cpy = p.shift(); + cmd = 'C'; + points.push(cpx, cpy); + break; + case 's': + var ctlPtx = cpx, ctlPty = cpy; + var prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'C') { + ctlPtx = cpx + (cpx - prevCmd.points[2]); + ctlPty = cpy + (cpy - prevCmd.points[3]); + } + points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift()); + cpx += p.shift(); + cpy += p.shift(); + cmd = 'C'; + points.push(cpx, cpy); + break; + case 'Q': + points.push(p.shift(), p.shift()); + cpx = p.shift(); + cpy = p.shift(); + points.push(cpx, cpy); + break; + case 'q': + points.push(cpx + p.shift(), cpy + p.shift()); + cpx += p.shift(); + cpy += p.shift(); + cmd = 'Q'; + points.push(cpx, cpy); + break; + case 'T': + var ctlPtx = cpx, ctlPty = cpy; + var prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'Q') { + ctlPtx = cpx + (cpx - prevCmd.points[0]); + ctlPty = cpy + (cpy - prevCmd.points[1]); + } + cpx = p.shift(); + cpy = p.shift(); + cmd = 'Q'; + points.push(ctlPtx, ctlPty, cpx, cpy); + break; + case 't': + var ctlPtx = cpx, ctlPty = cpy; + var prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'Q') { + ctlPtx = cpx + (cpx - prevCmd.points[0]); + ctlPty = cpy + (cpy - prevCmd.points[1]); + } + cpx += p.shift(); + cpy += p.shift(); + cmd = 'Q'; + points.push(ctlPtx, ctlPty, cpx, cpy); + break; + case 'A': + var rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); + var x1 = cpx, y1 = cpy; cpx = p.shift(), cpy = p.shift(); + cmd = 'A'; + points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); + break; + case 'a': + var rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); + var x1 = cpx, y1 = cpy; cpx += p.shift(), cpy += p.shift(); + cmd = 'A'; + points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); + break; + } + + ca.push({ + command: cmd || c, + points: points, + start: { + x: startX, + y: startY + }, + pathLength: this.calcLength(startX, startY, cmd || c, points) + }); } - // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi - p1 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); - if(dTheta < 0) {// clockwise - for( t = start - inc; t > end; t -= inc) { - p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + + if(c === 'z' || c === 'Z') { + ca.push({ + command: 'z', + points: [], + start: undefined, + pathLength: 0 + }); + } + } + + return ca; + }; + Kinetic.Path.calcLength = function(x, y, cmd, points) { + var len, p1, p2; + var path = Kinetic.Path; + + switch (cmd) { + case 'L': + return path.getLineLength(x, y, points[0], points[1]); + case 'C': + // Approximates by breaking curve into 100 line segments + len = 0.0; + p1 = path.getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); + for( t = 0.01; t <= 1; t += 0.01) { + p2 = path.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } - } - else {// counter-clockwise - for( t = start + inc; t < end; t += inc) { - p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + return len; + case 'Q': + // Approximates by breaking curve into 100 line segments + len = 0.0; + p1 = path.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); + for( t = 0.01; t <= 1; t += 0.01) { + p2 = path.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } - } - p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + return len; + case 'A': + // Approximates by breaking curve into line segments + len = 0.0; + var start = points[4]; + // 4 = theta + var dTheta = points[5]; + // 5 = dTheta + var end = points[4] + dTheta; + var inc = Math.PI / 180.0; + // 1 degree resolution + if(Math.abs(start - end) < inc) { + inc = Math.abs(start - end); + } + // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi + p1 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); + if(dTheta < 0) {// clockwise + for( t = start - inc; t > end; t -= inc) { + p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + else {// counter-clockwise + for( t = start + inc; t < end; t += inc) { + p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - return len; - } + return len; + } - return 0; -}; -Kinetic.Path.convertEndpointToCenterParameterization = function(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) { - // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes - var psi = psiDeg * (Math.PI / 180.0); - var xp = Math.cos(psi) * (x1 - x2) / 2.0 + Math.sin(psi) * (y1 - y2) / 2.0; - var yp = -1 * Math.sin(psi) * (x1 - x2) / 2.0 + Math.cos(psi) * (y1 - y2) / 2.0; - - var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); - - if(lambda > 1) { - rx *= Math.sqrt(lambda); - ry *= Math.sqrt(lambda); - } - - var f = Math.sqrt((((rx * rx) * (ry * ry)) - ((rx * rx) * (yp * yp)) - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp) + (ry * ry) * (xp * xp))); - - if(fa == fs) { - f *= -1; - } - if(isNaN(f)) { - f = 0; - } - - var cxp = f * rx * yp / ry; - var cyp = f * -ry * xp / rx; - - var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; - var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; - - var vMag = function(v) { - return Math.sqrt(v[0] * v[0] + v[1] * v[1]); + return 0; }; - var vRatio = function(u, v) { - return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); + Kinetic.Path.convertEndpointToCenterParameterization = function(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) { + // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + var psi = psiDeg * (Math.PI / 180.0); + var xp = Math.cos(psi) * (x1 - x2) / 2.0 + Math.sin(psi) * (y1 - y2) / 2.0; + var yp = -1 * Math.sin(psi) * (x1 - x2) / 2.0 + Math.cos(psi) * (y1 - y2) / 2.0; + + var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); + + if(lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + + var f = Math.sqrt((((rx * rx) * (ry * ry)) - ((rx * rx) * (yp * yp)) - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp) + (ry * ry) * (xp * xp))); + + if(fa == fs) { + f *= -1; + } + if(isNaN(f)) { + f = 0; + } + + var cxp = f * rx * yp / ry; + var cyp = f * -ry * xp / rx; + + var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; + var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; + + var vMag = function(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1]); + }; + var vRatio = function(u, v) { + return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); + }; + var vAngle = function(u, v) { + return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); + }; + var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); + var u = [(xp - cxp) / rx, (yp - cyp) / ry]; + var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; + var dTheta = vAngle(u, v); + + if(vRatio(u, v) <= -1) { + dTheta = Math.PI; + } + if(vRatio(u, v) >= 1) { + dTheta = 0; + } + if(fs === 0 && dTheta > 0) { + dTheta = dTheta - 2 * Math.PI; + } + if(fs == 1 && dTheta < 0) { + dTheta = dTheta + 2 * Math.PI; + } + return [cx, cy, rx, ry, theta, dTheta, psi, fs]; }; - var vAngle = function(u, v) { - return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); - }; - var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); - var u = [(xp - cxp) / rx, (yp - cyp) / ry]; - var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; - var dTheta = vAngle(u, v); + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Path, ['data']); - if(vRatio(u, v) <= -1) { - dTheta = Math.PI; - } - if(vRatio(u, v) >= 1) { - dTheta = 0; - } - if(fs === 0 && dTheta > 0) { - dTheta = dTheta - 2 * Math.PI; - } - if(fs == 1 && dTheta < 0) { - dTheta = dTheta + 2 * Math.PI; - } - return [cx, cy, rx, ry, theta, dTheta, psi, fs]; -}; + /** + * set SVG path data string. This method + * also automatically parses the data string + * into a data array. Currently supported SVG data: + * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z + * @name setData + * @methodOf Kinetic.Path.prototype + * @param {String} SVG path command string + */ -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Path, ['data']); - -/** - * set SVG path data string. This method - * also automatically parses the data string - * into a data array. Currently supported SVG data: - * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z - * @name setData - * @methodOf Kinetic.Path.prototype - * @param {String} SVG path command string - */ - -/** - * get SVG path data string - * @name getData - * @methodOf Kinetic.Path.prototype - */ \ No newline at end of file + /** + * get SVG path data string + * @name getData + * @methodOf Kinetic.Path.prototype + */ +})(); diff --git a/src/shapes/Polygon.js b/src/shapes/Polygon.js index febf3cca..17afcf65 100644 --- a/src/shapes/Polygon.js +++ b/src/shapes/Polygon.js @@ -1,52 +1,54 @@ -/** - * Polygon constructor.  Polygons are defined by an array of points - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Polygon = function(config) { - this._initPolygon(config); -}; - -Kinetic.Polygon.prototype = { - _initPolygon: function(config) { - this.setDefaultAttrs({ - points: [] - }); - - this.shapeType = "Polygon"; - - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - context.beginPath(); - context.moveTo(this.attrs.points[0].x, this.attrs.points[0].y); - for(var n = 1; n < this.attrs.points.length; n++) { - context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y); - } - context.closePath(); - this.fillStroke(context); - }, +(function() { /** - * set points array - * @name setPoints - * @methodOf Kinetic.Polygon.prototype - * @param {Array} can be an array of point objects or an array - * of Numbers. e.g. [{x:1,y:2},{x:3,y:4}] or [1,2,3,4] - */ - setPoints: function(val) { - this.setAttr('points', Kinetic.Type._getPoints(val)); - } -}; -Kinetic.Global.extend(Kinetic.Polygon, Kinetic.Shape); + * Polygon constructor.  Polygons are defined by an array of points + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.Polygon = function(config) { + this._initPolygon(config); + }; -// add getters setters -Kinetic.Node.addGetters(Kinetic.Polygon, ['points']); + Kinetic.Polygon.prototype = { + _initPolygon: function(config) { + this.setDefaultAttrs({ + points: [] + }); -/** - * get points array - * @name getPoints - * @methodOf Kinetic.Polygon.prototype - */ \ No newline at end of file + this.shapeType = "Polygon"; + + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + context.beginPath(); + context.moveTo(this.attrs.points[0].x, this.attrs.points[0].y); + for(var n = 1; n < this.attrs.points.length; n++) { + context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y); + } + context.closePath(); + this.fillStroke(context); + }, + /** + * set points array + * @name setPoints + * @methodOf Kinetic.Polygon.prototype + * @param {Array} can be an array of point objects or an array + * of Numbers. e.g. [{x:1,y:2},{x:3,y:4}] or [1,2,3,4] + */ + setPoints: function(val) { + this.setAttr('points', Kinetic.Type._getPoints(val)); + } + }; + Kinetic.Global.extend(Kinetic.Polygon, Kinetic.Shape); + + // add getters setters + Kinetic.Node.addGetters(Kinetic.Polygon, ['points']); + + /** + * get points array + * @name getPoints + * @methodOf Kinetic.Polygon.prototype + */ +})(); diff --git a/src/shapes/Rect.js b/src/shapes/Rect.js index 5e3ae807..ab46625a 100644 --- a/src/shapes/Rect.js +++ b/src/shapes/Rect.js @@ -1,46 +1,49 @@ -/** - * Rect constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Rect = function(config) { - this._initRect(config); -} -Kinetic.Rect.prototype = { - _initRect: function(config) { - this.setDefaultAttrs({ - width: 0, - height: 0, - cornerRadius: 0 - }); - this.shapeType = "Rect"; - - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - context.beginPath(); - var cornerRadius = this.getCornerRadius(), width = this.getWidth(), height = this.getHeight(); - if(cornerRadius === 0) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, width, height); - } - else { - // arcTo would be nicer, but browser support is patchy (Opera) - context.moveTo(cornerRadius, 0); - context.lineTo(width - cornerRadius, 0); - context.arc(width - cornerRadius, cornerRadius, cornerRadius, Math.PI * 3 / 2, 0, false); - context.lineTo(width, height - cornerRadius); - context.arc(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2, false); - context.lineTo(cornerRadius, height); - context.arc(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, false); - context.lineTo(0, cornerRadius); - context.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 3 / 2, false); - } - context.closePath(); - this.fillStroke(context); +(function() { + /** + * Rect constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.Rect = function(config) { + this._initRect(config); } -}; + Kinetic.Rect.prototype = { + _initRect: function(config) { + this.setDefaultAttrs({ + width: 0, + height: 0, + cornerRadius: 0 + }); + this.shapeType = "Rect"; -Kinetic.Global.extend(Kinetic.Rect, Kinetic.Shape); + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + context.beginPath(); + var cornerRadius = this.getCornerRadius(), width = this.getWidth(), height = this.getHeight(); + if(cornerRadius === 0) { + // simple rect - don't bother doing all that complicated maths stuff. + context.rect(0, 0, width, height); + } + else { + // arcTo would be nicer, but browser support is patchy (Opera) + context.moveTo(cornerRadius, 0); + context.lineTo(width - cornerRadius, 0); + context.arc(width - cornerRadius, cornerRadius, cornerRadius, Math.PI * 3 / 2, 0, false); + context.lineTo(width, height - cornerRadius); + context.arc(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2, false); + context.lineTo(cornerRadius, height); + context.arc(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, false); + context.lineTo(0, cornerRadius); + context.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 3 / 2, false); + } + context.closePath(); + this.fillStroke(context); + } + }; + + Kinetic.Global.extend(Kinetic.Rect, Kinetic.Shape); + +})(); diff --git a/src/shapes/RegularPolygon.js b/src/shapes/RegularPolygon.js index c19780c2..120e7718 100644 --- a/src/shapes/RegularPolygon.js +++ b/src/shapes/RegularPolygon.js @@ -1,65 +1,67 @@ -/** - * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.RegularPolygon = function(config) { - this._initRegularPolygon(config); -}; +(function() { + /** + * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.RegularPolygon = function(config) { + this._initRegularPolygon(config); + }; -Kinetic.RegularPolygon.prototype = { - _initRegularPolygon: function(config) { - this.setDefaultAttrs({ - radius: 0, - sides: 0 - }); + Kinetic.RegularPolygon.prototype = { + _initRegularPolygon: function(config) { + this.setDefaultAttrs({ + radius: 0, + sides: 0 + }); - this.shapeType = "RegularPolygon"; + this.shapeType = "RegularPolygon"; - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - context.beginPath(); - context.moveTo(0, 0 - this.attrs.radius); + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + context.beginPath(); + context.moveTo(0, 0 - this.attrs.radius); - for(var n = 1; n < this.attrs.sides; n++) { - var x = this.attrs.radius * Math.sin(n * 2 * Math.PI / this.attrs.sides); - var y = -1 * this.attrs.radius * Math.cos(n * 2 * Math.PI / this.attrs.sides); - context.lineTo(x, y); + for(var n = 1; n < this.attrs.sides; n++) { + var x = this.attrs.radius * Math.sin(n * 2 * Math.PI / this.attrs.sides); + var y = -1 * this.attrs.radius * Math.cos(n * 2 * Math.PI / this.attrs.sides); + context.lineTo(x, y); + } + context.closePath(); + this.fillStroke(context); } - context.closePath(); - this.fillStroke(context); - } -}; -Kinetic.Global.extend(Kinetic.RegularPolygon, Kinetic.Shape); + }; + Kinetic.Global.extend(Kinetic.RegularPolygon, Kinetic.Shape); -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.RegularPolygon, ['radius', 'sides']); + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.RegularPolygon, ['radius', 'sides']); -/** - * set radius - * @name setRadius - * @methodOf Kinetic.RegularPolygon.prototype - * @param {Number} radius - */ + /** + * set radius + * @name setRadius + * @methodOf Kinetic.RegularPolygon.prototype + * @param {Number} radius + */ -/** - * set number of sides - * @name setSides - * @methodOf Kinetic.RegularPolygon.prototype - * @param {int} sides - */ -/** - * get radius - * @name getRadius - * @methodOf Kinetic.RegularPolygon.prototype - */ + /** + * set number of sides + * @name setSides + * @methodOf Kinetic.RegularPolygon.prototype + * @param {int} sides + */ + /** + * get radius + * @name getRadius + * @methodOf Kinetic.RegularPolygon.prototype + */ -/** - * get number of sides - * @name getSides - * @methodOf Kinetic.RegularPolygon.prototype - */ \ No newline at end of file + /** + * get number of sides + * @name getSides + * @methodOf Kinetic.RegularPolygon.prototype + */ +})(); diff --git a/src/shapes/Sprite.js b/src/shapes/Sprite.js index 75c40b4a..184d5891 100644 --- a/src/shapes/Sprite.js +++ b/src/shapes/Sprite.js @@ -1,156 +1,158 @@ -/** - * Sprite constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Sprite = function(config) { - this._initSprite(config); -}; +(function() { + /** + * Sprite constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.Sprite = function(config) { + this._initSprite(config); + }; -Kinetic.Sprite.prototype = { - _initSprite: function(config) { - this.setDefaultAttrs({ - index: 0, - frameRate: 17 - }); - this.shapeType = "Sprite"; - - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - - this.anim = new Kinetic.Animation(); - var that = this; - this.on('animationChange', function() { - // reset index when animation changes - that.setIndex(0); - }); - }, - drawFunc: function(context) { - var anim = this.attrs.animation; - var index = this.attrs.index; - var f = this.attrs.animations[anim][index]; + Kinetic.Sprite.prototype = { + _initSprite: function(config) { + this.setDefaultAttrs({ + index: 0, + frameRate: 17 + }); + this.shapeType = "Sprite"; - if(this.attrs.image) { + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + + this.anim = new Kinetic.Animation(); + var that = this; + this.on('animationChange', function() { + // reset index when animation changes + that.setIndex(0); + }); + }, + drawFunc: function(context) { + var anim = this.attrs.animation; + var index = this.attrs.index; + var f = this.attrs.animations[anim][index]; + + if(this.attrs.image) { + + context.beginPath(); + context.rect(0, 0, f.width, f.height); + context.closePath(); + + this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height); + } + }, + drawHitFunc: function(context) { + var anim = this.attrs.animation; + var index = this.attrs.index; + var f = this.attrs.animations[anim][index]; context.beginPath(); context.rect(0, 0, f.width, f.height); context.closePath(); - - this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height); - } - }, - drawHitFunc: function(context) { - var anim = this.attrs.animation; - var index = this.attrs.index; - var f = this.attrs.animations[anim][index]; - - context.beginPath(); - context.rect(0, 0, f.width, f.height); - context.closePath(); - this.fillStroke(context); - }, - /** - * start sprite animation - * @name start - * @methodOf Kinetic.Sprite.prototype - */ - start: function() { - var that = this; - var layer = this.getLayer(); - - /* - * animation object has no executable function because - * the updates are done with a fixed FPS with the setInterval - * below. The anim object only needs the layer reference for - * redraw + this.fillStroke(context); + }, + /** + * start sprite animation + * @name start + * @methodOf Kinetic.Sprite.prototype */ - this.anim.node = layer; + start: function() { + var that = this; + var layer = this.getLayer(); - this.interval = setInterval(function() { - var index = that.attrs.index; - that._updateIndex(); - if(that.afterFrameFunc && index === that.afterFrameIndex) { - that.afterFrameFunc(); - delete that.afterFrameFunc; - delete that.afterFrameIndex; + /* + * animation object has no executable function because + * the updates are done with a fixed FPS with the setInterval + * below. The anim object only needs the layer reference for + * redraw + */ + this.anim.node = layer; + + this.interval = setInterval(function() { + var index = that.attrs.index; + that._updateIndex(); + if(that.afterFrameFunc && index === that.afterFrameIndex) { + that.afterFrameFunc(); + delete that.afterFrameFunc; + delete that.afterFrameIndex; + } + }, 1000 / this.attrs.frameRate); + + this.anim.start(); + }, + /** + * stop sprite animation + * @name stop + * @methodOf Kinetic.Sprite.prototype + */ + stop: function() { + this.anim.stop(); + clearInterval(this.interval); + }, + /** + * set after frame event handler + * @name afterFrame + * @methodOf Kinetic.Sprite.prototype + * @param {Integer} index frame index + * @param {Function} func function to be executed after frame has been drawn + */ + afterFrame: function(index, func) { + this.afterFrameIndex = index; + this.afterFrameFunc = func; + }, + _updateIndex: function() { + var i = this.attrs.index; + var a = this.attrs.animation; + if(i < this.attrs.animations[a].length - 1) { + this.attrs.index++; } - }, 1000 / this.attrs.frameRate); + else { + this.attrs.index = 0; + } + } + }; + Kinetic.Global.extend(Kinetic.Sprite, Kinetic.Shape); + + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Sprite, ['animation', 'animations', 'index']); - this.anim.start(); - }, /** - * stop sprite animation - * @name stop + * set animation key + * @name setAnimation * @methodOf Kinetic.Sprite.prototype + * @param {String} anim animation key */ - stop: function() { - this.anim.stop(); - clearInterval(this.interval); - }, + /** - * set after frame event handler - * @name afterFrame + * set animations object + * @name setAnimations + * @methodOf Kinetic.Sprite.prototype + * @param {Object} animations + */ + + /** + * set animation frame index + * @name setIndex * @methodOf Kinetic.Sprite.prototype * @param {Integer} index frame index - * @param {Function} func function to be executed after frame has been drawn */ - afterFrame: function(index, func) { - this.afterFrameIndex = index; - this.afterFrameFunc = func; - }, - _updateIndex: function() { - var i = this.attrs.index; - var a = this.attrs.animation; - if(i < this.attrs.animations[a].length - 1) { - this.attrs.index++; - } - else { - this.attrs.index = 0; - } - } -}; -Kinetic.Global.extend(Kinetic.Sprite, Kinetic.Shape); -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Sprite, ['animation', 'animations', 'index']); + /** + * get animation key + * @name getAnimation + * @methodOf Kinetic.Sprite.prototype + */ -/** - * set animation key - * @name setAnimation - * @methodOf Kinetic.Sprite.prototype - * @param {String} anim animation key - */ + /** + * get animations object + * @name getAnimations + * @methodOf Kinetic.Sprite.prototype + */ -/** - * set animations object - * @name setAnimations - * @methodOf Kinetic.Sprite.prototype - * @param {Object} animations - */ - -/** - * set animation frame index - * @name setIndex - * @methodOf Kinetic.Sprite.prototype - * @param {Integer} index frame index - */ - -/** - * get animation key - * @name getAnimation - * @methodOf Kinetic.Sprite.prototype - */ - -/** - * get animations object - * @name getAnimations - * @methodOf Kinetic.Sprite.prototype - */ - -/** - * get animation frame index - * @name getIndex - * @methodOf Kinetic.Sprite.prototype - */ \ No newline at end of file + /** + * get animation frame index + * @name getIndex + * @methodOf Kinetic.Sprite.prototype + */ +})(); diff --git a/src/shapes/Star.js b/src/shapes/Star.js index 3061a93c..c8efc659 100644 --- a/src/shapes/Star.js +++ b/src/shapes/Star.js @@ -1,82 +1,84 @@ -/** - * Star constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Star = function(config) { - this._initStar(config); -}; +(function() { + /** + * Star constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.Star = function(config) { + this._initStar(config); + }; -Kinetic.Star.prototype = { - _initStar: function(config) { - this.setDefaultAttrs({ - numPoints: 0, - innerRadius: 0, - outerRadius: 0 - }); + Kinetic.Star.prototype = { + _initStar: function(config) { + this.setDefaultAttrs({ + numPoints: 0, + innerRadius: 0, + outerRadius: 0 + }); - this.shapeType = "Star"; + this.shapeType = "Star"; - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - }, - drawFunc: function(context) { - context.beginPath(); - context.moveTo(0, 0 - this.attrs.outerRadius); + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + }, + drawFunc: function(context) { + context.beginPath(); + context.moveTo(0, 0 - this.attrs.outerRadius); - for(var n = 1; n < this.attrs.numPoints * 2; n++) { - var radius = n % 2 === 0 ? this.attrs.outerRadius : this.attrs.innerRadius; - var x = radius * Math.sin(n * Math.PI / this.attrs.numPoints); - var y = -1 * radius * Math.cos(n * Math.PI / this.attrs.numPoints); - context.lineTo(x, y); + for(var n = 1; n < this.attrs.numPoints * 2; n++) { + var radius = n % 2 === 0 ? this.attrs.outerRadius : this.attrs.innerRadius; + var x = radius * Math.sin(n * Math.PI / this.attrs.numPoints); + var y = -1 * radius * Math.cos(n * Math.PI / this.attrs.numPoints); + context.lineTo(x, y); + } + context.closePath(); + + this.fillStroke(context); } - context.closePath(); + }; + Kinetic.Global.extend(Kinetic.Star, Kinetic.Shape); - this.fillStroke(context); - } -}; -Kinetic.Global.extend(Kinetic.Star, Kinetic.Shape); + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Star, ['numPoints', 'innerRadius', 'outerRadius']); -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Star, ['numPoints', 'innerRadius', 'outerRadius']); + /** + * set number of points + * @name setNumPoints + * @methodOf Kinetic.Star.prototype + * @param {Integer} points + */ -/** - * set number of points - * @name setNumPoints - * @methodOf Kinetic.Star.prototype - * @param {Integer} points - */ + /** + * set outer radius + * @name setOuterRadius + * @methodOf Kinetic.Star.prototype + * @param {Number} radius + */ -/** - * set outer radius - * @name setOuterRadius - * @methodOf Kinetic.Star.prototype - * @param {Number} radius - */ + /** + * set inner radius + * @name setInnerRadius + * @methodOf Kinetic.Star.prototype + * @param {Number} radius + */ -/** - * set inner radius - * @name setInnerRadius - * @methodOf Kinetic.Star.prototype - * @param {Number} radius - */ + /** + * get number of points + * @name getNumPoints + * @methodOf Kinetic.Star.prototype + */ -/** - * get number of points - * @name getNumPoints - * @methodOf Kinetic.Star.prototype - */ + /** + * get outer radius + * @name getOuterRadius + * @methodOf Kinetic.Star.prototype + */ -/** - * get outer radius - * @name getOuterRadius - * @methodOf Kinetic.Star.prototype - */ - -/** - * get inner radius - * @name getInnerRadius - * @methodOf Kinetic.Star.prototype - */ \ No newline at end of file + /** + * get inner radius + * @name getInnerRadius + * @methodOf Kinetic.Star.prototype + */ +})(); diff --git a/src/shapes/Text.js b/src/shapes/Text.js index 3ba0d848..74e0fe31 100644 --- a/src/shapes/Text.js +++ b/src/shapes/Text.js @@ -1,404 +1,406 @@ -/** - * Text constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Text = function(config) { - this._initText(config); -}; - -Kinetic.Text.prototype = { - _initText: function(config) { - this.setDefaultAttrs({ - fontFamily: 'Calibri', - text: '', - fontSize: 12, - align: 'left', - verticalAlign: 'top', - fontStyle: 'normal', - padding: 0, - width: 'auto', - height: 'auto', - detectionType: 'path', - cornerRadius: 0, - lineHeight: 1.2 - }); - - this.dummyCanvas = document.createElement('canvas'); - this.shapeType = "Text"; - - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); - - // update text data for certain attr changes - var attrs = ['fontFamily', 'fontSize', 'fontStyle', 'padding', 'align', 'lineHeight', 'text', 'width', 'height']; - var that = this; - for(var n = 0; n < attrs.length; n++) { - var attr = attrs[n]; - this.on(attr + 'Change.kinetic', that._setTextData); - } - that._setTextData(); - }, - drawFunc: function(context) { - // draw rect - Kinetic.Rect.prototype.drawFunc.call(this, context); - - // draw text - var p = this.attrs.padding; - var lineHeightPx = this.attrs.lineHeight * this.getTextHeight(); - var textArr = this.textArr; - - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - context.textBaseline = 'middle'; - context.textAlign = 'left'; - context.save(); - context.translate(p, 0); - context.translate(0, p + this.getTextHeight() / 2); - - // draw text lines - for(var n = 0; n < textArr.length; n++) { - var text = textArr[n]; - - // horizontal alignment - context.save(); - if(this.attrs.align === 'right') { - context.translate(this.getWidth() - this._getTextSize(text).width - p * 2, 0); - } - else if(this.attrs.align === 'center') { - context.translate((this.getWidth() - this._getTextSize(text).width - p * 2) / 2, 0); - } - - this.fillStrokeText(context, text); - context.restore(); - context.translate(0, lineHeightPx); - } - context.restore(); - }, - drawHitFunc: Kinetic.Rect.prototype.drawFunc, +(function() { /** - * 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 - * @methodOf Kinetic.Text.prototype - */ - getWidth: function() { - return this.attrs.width === 'auto' ? this.getTextWidth() + this.attrs.padding * 2 : this.attrs.width; - }, - /** - * get height - * @name getHeight - * @methodOf Kinetic.Text.prototype - */ - getHeight: function() { - return this.attrs.height === 'auto' ? (this.getTextHeight() * this.textArr.length * this.attrs.lineHeight) + this.attrs.padding * 2 : this.attrs.height; - }, - /** - * get text width - * @name getTextWidth - * @methodOf Kinetic.Text.prototype - */ - getTextWidth: function() { - return this.textWidth; - }, - /** - * get text height - * @name getTextHeight - * @methodOf Kinetic.Text.prototype - */ - getTextHeight: function() { - return this.textHeight; - }, - _getTextSize: function(text) { - var dummyCanvas = this.dummyCanvas; - var context = dummyCanvas.getContext('2d'); - - context.save(); - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - var metrics = context.measureText(text); - context.restore(); - return { - width: metrics.width, - height: parseInt(this.attrs.fontSize, 10) - }; - }, - fillText: function(context, text, skipShadow) { - var textFill = this.getTextFill(), textShadow = this.getTextShadow(); - if(textFill) { - context.save(); - if(!skipShadow && textShadow) { - this._applyTextShadow(context); - } - context.fillStyle = textFill; - context.fillText(text, 0, 0); - context.restore(); - - if(!skipShadow && textShadow && textShadow.opacity) { - this.fillText(context, text, true); - } - } - }, - strokeText: function(context, text, skipShadow) { - var textStroke = this.getTextStroke(), textStrokeWidth = this.getTextStrokeWidth(), textShadow = this.getTextShadow(); - if(textStroke || textStrokeWidth) { - context.save(); - if(!skipShadow && textShadow) { - this._applyTextShadow(context); - } - - context.lineWidth = textStrokeWidth || 2; - context.strokeStyle = textStroke || 'black'; - context.strokeText(text, 0, 0); - context.restore(); - - if(!skipShadow && textShadow && textShadow.opacity) { - this.strokeText(context, text, true); - } - } - }, - fillStrokeText: function(context, text) { - this.fillText(context, text); - this.strokeText(context, text, this.getTextShadow() && this.getTextFill()); - }, - /** - * set text shadow object - * @name setTextShadow - * @methodOf Kinetic.Text.prototype + * Text constructor + * @constructor + * @augments Kinetic.Shape * @param {Object} config - * @param {String} config.color - * @param {Number} config.blur - * @param {Array|Object|Number} config.offset - * @param {Number} config.opacity */ - setTextShadow: function(config) { - var type = Kinetic.Type; - if(config.offset !== undefined) { - config.offset = type._getXY(config.offset); - } - this.setAttr('textShadow', type._merge(config, this.getTextShadow())); - }, - /** - * set text data. wrap logic and width and height setting occurs - * here - */ - _setTextData: function() { - var charArr = this.attrs.text.split(''); - var arr = []; - var row = 0; - var addLine = true; - this.textWidth = 0; - this.textHeight = this._getTextSize(this.attrs.text).height; - var lineHeightPx = this.attrs.lineHeight * this.textHeight; - while(charArr.length > 0 && addLine && (this.attrs.height === 'auto' || lineHeightPx * (row + 1) < this.attrs.height - this.attrs.padding * 2)) { - var index = 0; - var line = undefined; - addLine = false; + Kinetic.Text = function(config) { + this._initText(config); + }; - while(index < charArr.length) { - if(charArr.indexOf('\n') === index) { - // remove newline char - charArr.splice(index, 1); - line = charArr.splice(0, index).join(''); - break; - } + Kinetic.Text.prototype = { + _initText: function(config) { + this.setDefaultAttrs({ + fontFamily: 'Calibri', + text: '', + fontSize: 12, + align: 'left', + verticalAlign: 'top', + fontStyle: 'normal', + padding: 0, + width: 'auto', + height: 'auto', + detectionType: 'path', + cornerRadius: 0, + lineHeight: 1.2 + }); - // if line exceeds inner box width - var lineArr = charArr.slice(0, index); - if(this.attrs.width !== 'auto' && this._getTextSize(lineArr.join('')).width > this.attrs.width - this.attrs.padding * 2) { - /* - * if a single character is too large to fit inside - * the text box width, then break out of the loop - * and stop processing - */ - if(index == 0) { - break; - } - var lastSpace = lineArr.lastIndexOf(' '); - var lastDash = lineArr.lastIndexOf('-'); - var wrapIndex = Math.max(lastSpace, lastDash); - if(wrapIndex >= 0) { - line = charArr.splice(0, 1 + wrapIndex).join(''); - break; - } - /* - * if not able to word wrap based on space or dash, - * go ahead and wrap in the middle of a word if needed - */ - line = charArr.splice(0, index).join(''); - break; - } - index++; + this.dummyCanvas = document.createElement('canvas'); + this.shapeType = "Text"; - // if the end is reached - if(index === charArr.length) { - line = charArr.splice(0, index).join(''); - } + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); + + // update text data for certain attr changes + var attrs = ['fontFamily', 'fontSize', 'fontStyle', 'padding', 'align', 'lineHeight', 'text', 'width', 'height']; + var that = this; + for(var n = 0; n < attrs.length; n++) { + var attr = attrs[n]; + this.on(attr + 'Change.kinetic', that._setTextData); } - this.textWidth = Math.max(this.textWidth, this._getTextSize(line).width); - if(line !== undefined) { - arr.push(line); - addLine = true; + that._setTextData(); + }, + drawFunc: function(context) { + // draw rect + Kinetic.Rect.prototype.drawFunc.call(this, context); + + // draw text + var p = this.attrs.padding; + var lineHeightPx = this.attrs.lineHeight * this.getTextHeight(); + var textArr = this.textArr; + + context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; + context.textBaseline = 'middle'; + context.textAlign = 'left'; + context.save(); + context.translate(p, 0); + context.translate(0, p + this.getTextHeight() / 2); + + // draw text lines + for(var n = 0; n < textArr.length; n++) { + var text = textArr[n]; + + // horizontal alignment + context.save(); + if(this.attrs.align === 'right') { + context.translate(this.getWidth() - this._getTextSize(text).width - p * 2, 0); + } + else if(this.attrs.align === 'center') { + context.translate((this.getWidth() - this._getTextSize(text).width - p * 2) / 2, 0); + } + + this.fillStrokeText(context, text); + context.restore(); + context.translate(0, lineHeightPx); } - row++; - } - this.textArr = arr; - }, - _applyTextShadow: function(context) { - var textShadow = this.getTextShadow(); - if(textShadow) { - var aa = this.getAbsoluteOpacity(); - // defaults - var color = textShadow.color || 'black'; - var blur = textShadow.blur || 5; - var offset = textShadow.offset || { - x: 0, - y: 0 + context.restore(); + }, + drawHitFunc: Kinetic.Rect.prototype.drawFunc, + /** + * 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 + * @methodOf Kinetic.Text.prototype + */ + getWidth: function() { + return this.attrs.width === 'auto' ? this.getTextWidth() + this.attrs.padding * 2 : this.attrs.width; + }, + /** + * get height + * @name getHeight + * @methodOf Kinetic.Text.prototype + */ + getHeight: function() { + return this.attrs.height === 'auto' ? (this.getTextHeight() * this.textArr.length * this.attrs.lineHeight) + this.attrs.padding * 2 : this.attrs.height; + }, + /** + * get text width + * @name getTextWidth + * @methodOf Kinetic.Text.prototype + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height + * @name getTextHeight + * @methodOf Kinetic.Text.prototype + */ + getTextHeight: function() { + return this.textHeight; + }, + _getTextSize: function(text) { + var dummyCanvas = this.dummyCanvas; + var context = dummyCanvas.getContext('2d'); + + context.save(); + context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; + var metrics = context.measureText(text); + context.restore(); + return { + width: metrics.width, + height: parseInt(this.attrs.fontSize, 10) }; + }, + fillText: function(context, text, skipShadow) { + var textFill = this.getTextFill(), textShadow = this.getTextShadow(); + if(textFill) { + context.save(); + if(!skipShadow && textShadow) { + this._applyTextShadow(context); + } + context.fillStyle = textFill; + context.fillText(text, 0, 0); + context.restore(); - if(textShadow.opacity) { - context.globalAlpha = textShadow.opacity * aa; + if(!skipShadow && textShadow && textShadow.opacity) { + this.fillText(context, text, true); + } + } + }, + strokeText: function(context, text, skipShadow) { + var textStroke = this.getTextStroke(), textStrokeWidth = this.getTextStrokeWidth(), textShadow = this.getTextShadow(); + if(textStroke || textStrokeWidth) { + context.save(); + if(!skipShadow && textShadow) { + this._applyTextShadow(context); + } + + context.lineWidth = textStrokeWidth || 2; + context.strokeStyle = textStroke || 'black'; + context.strokeText(text, 0, 0); + context.restore(); + + if(!skipShadow && textShadow && textShadow.opacity) { + this.strokeText(context, text, true); + } + } + }, + fillStrokeText: function(context, text) { + this.fillText(context, text); + this.strokeText(context, text, this.getTextShadow() && this.getTextFill()); + }, + /** + * set text shadow object + * @name setTextShadow + * @methodOf Kinetic.Text.prototype + * @param {Object} config + * @param {String} config.color + * @param {Number} config.blur + * @param {Array|Object|Number} config.offset + * @param {Number} config.opacity + */ + setTextShadow: function(config) { + var type = Kinetic.Type; + if(config.offset !== undefined) { + config.offset = type._getXY(config.offset); + } + this.setAttr('textShadow', type._merge(config, this.getTextShadow())); + }, + /** + * set text data. wrap logic and width and height setting occurs + * here + */ + _setTextData: function() { + var charArr = this.attrs.text.split(''); + var arr = []; + var row = 0; + var addLine = true; + this.textWidth = 0; + this.textHeight = this._getTextSize(this.attrs.text).height; + var lineHeightPx = this.attrs.lineHeight * this.textHeight; + while(charArr.length > 0 && addLine && (this.attrs.height === 'auto' || lineHeightPx * (row + 1) < this.attrs.height - this.attrs.padding * 2)) { + var index = 0; + var line = undefined; + addLine = false; + + while(index < charArr.length) { + if(charArr.indexOf('\n') === index) { + // remove newline char + charArr.splice(index, 1); + line = charArr.splice(0, index).join(''); + break; + } + + // if line exceeds inner box width + var lineArr = charArr.slice(0, index); + if(this.attrs.width !== 'auto' && this._getTextSize(lineArr.join('')).width > this.attrs.width - this.attrs.padding * 2) { + /* + * if a single character is too large to fit inside + * the text box width, then break out of the loop + * and stop processing + */ + if(index == 0) { + break; + } + var lastSpace = lineArr.lastIndexOf(' '); + var lastDash = lineArr.lastIndexOf('-'); + var wrapIndex = Math.max(lastSpace, lastDash); + if(wrapIndex >= 0) { + line = charArr.splice(0, 1 + wrapIndex).join(''); + break; + } + /* + * if not able to word wrap based on space or dash, + * go ahead and wrap in the middle of a word if needed + */ + line = charArr.splice(0, index).join(''); + break; + } + index++; + + // if the end is reached + if(index === charArr.length) { + line = charArr.splice(0, index).join(''); + } + } + this.textWidth = Math.max(this.textWidth, this._getTextSize(line).width); + if(line !== undefined) { + arr.push(line); + addLine = true; + } + row++; + } + this.textArr = arr; + }, + _applyTextShadow: function(context) { + var textShadow = this.getTextShadow(); + if(textShadow) { + var aa = this.getAbsoluteOpacity(); + // defaults + var color = textShadow.color || 'black'; + var blur = textShadow.blur || 5; + var offset = textShadow.offset || { + x: 0, + y: 0 + }; + + if(textShadow.opacity) { + context.globalAlpha = textShadow.opacity * aa; + } + context.shadowColor = color; + context.shadowBlur = blur; + context.shadowOffsetX = offset.x; + context.shadowOffsetY = offset.y; } - context.shadowColor = color; - context.shadowBlur = blur; - context.shadowOffsetX = offset.x; - context.shadowOffsetY = offset.y; } - } -}; -Kinetic.Global.extend(Kinetic.Text, Kinetic.Shape); + }; + Kinetic.Global.extend(Kinetic.Text, Kinetic.Shape); -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'lineHeight']); -Kinetic.Node.addGetters(Kinetic.Text, ['text', 'textShadow']); -/** - * set font family - * @name setFontFamily - * @methodOf Kinetic.Text.prototype - * @param {String} fontFamily - */ + // add getters setters + Kinetic.Node.addGettersSetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'lineHeight']); + Kinetic.Node.addGetters(Kinetic.Text, ['text', 'textShadow']); + /** + * set font family + * @name setFontFamily + * @methodOf Kinetic.Text.prototype + * @param {String} fontFamily + */ -/** - * set font size - * @name setFontSize - * @methodOf Kinetic.Text.prototype - * @param {int} fontSize - */ + /** + * set font size + * @name setFontSize + * @methodOf Kinetic.Text.prototype + * @param {int} fontSize + */ -/** - * set font style. Can be "normal", "italic", or "bold". "normal" is the default. - * @name setFontStyle - * @methodOf Kinetic.Text.prototype - * @param {String} fontStyle - */ + /** + * set font style. Can be "normal", "italic", or "bold". "normal" is the default. + * @name setFontStyle + * @methodOf Kinetic.Text.prototype + * @param {String} fontStyle + */ -/** - * set text fill color - * @name setTextFill - * @methodOf Kinetic.Text.prototype - * @param {String} textFill - */ + /** + * set text fill color + * @name setTextFill + * @methodOf Kinetic.Text.prototype + * @param {String} textFill + */ -/** - * set text stroke color - * @name setFontStroke - * @methodOf Kinetic.Text.prototype - * @param {String} textStroke - */ + /** + * set text stroke color + * @name setFontStroke + * @methodOf Kinetic.Text.prototype + * @param {String} textStroke + */ -/** - * set text stroke width - * @name setTextStrokeWidth - * @methodOf Kinetic.Text.prototype - * @param {int} textStrokeWidth - */ + /** + * set text stroke width + * @name setTextStrokeWidth + * @methodOf Kinetic.Text.prototype + * @param {int} textStrokeWidth + */ -/** - * set padding - * @name setPadding - * @methodOf Kinetic.Text.prototype - * @param {int} padding - */ + /** + * set padding + * @name setPadding + * @methodOf Kinetic.Text.prototype + * @param {int} padding + */ -/** - * set horizontal align of text - * @name setAlign - * @methodOf Kinetic.Text.prototype - * @param {String} align align can be 'left', 'center', or 'right' - */ + /** + * set horizontal align of text + * @name setAlign + * @methodOf Kinetic.Text.prototype + * @param {String} align align can be 'left', 'center', or 'right' + */ -/** - * set line height - * @name setLineHeight - * @methodOf Kinetic.Text.prototype - * @param {Number} lineHeight default is 1.2 - */ + /** + * set line height + * @name setLineHeight + * @methodOf Kinetic.Text.prototype + * @param {Number} lineHeight default is 1.2 + */ -/** - * get font family - * @name getFontFamily - * @methodOf Kinetic.Text.prototype - */ + /** + * get font family + * @name getFontFamily + * @methodOf Kinetic.Text.prototype + */ -/** - * get font size - * @name getFontSize - * @methodOf Kinetic.Text.prototype - */ + /** + * get font size + * @name getFontSize + * @methodOf Kinetic.Text.prototype + */ -/** - * get font style - * @name getFontStyle - * @methodOf Kinetic.Text.prototype - */ + /** + * get font style + * @name getFontStyle + * @methodOf Kinetic.Text.prototype + */ -/** - * get text fill color - * @name getTextFill - * @methodOf Kinetic.Text.prototype - */ + /** + * get text fill color + * @name getTextFill + * @methodOf Kinetic.Text.prototype + */ -/** - * get text stroke color - * @name getTextStroke - * @methodOf Kinetic.Text.prototype - */ + /** + * get text stroke color + * @name getTextStroke + * @methodOf Kinetic.Text.prototype + */ -/** - * get text stroke width - * @name getTextStrokeWidth - * @methodOf Kinetic.Text.prototype - */ + /** + * get text stroke width + * @name getTextStrokeWidth + * @methodOf Kinetic.Text.prototype + */ -/** - * get padding - * @name getPadding - * @methodOf Kinetic.Text.prototype - */ + /** + * get padding + * @name getPadding + * @methodOf Kinetic.Text.prototype + */ -/** - * get horizontal align - * @name getAlign - * @methodOf Kinetic.Text.prototype - */ + /** + * get horizontal align + * @name getAlign + * @methodOf Kinetic.Text.prototype + */ -/** - * get line height - * @name getLineHeight - * @methodOf Kinetic.Text.prototype - */ + /** + * get line height + * @name getLineHeight + * @methodOf Kinetic.Text.prototype + */ -/** - * get text - * @name getText - * @methodOf Kinetic.Text.prototype - */ \ No newline at end of file + /** + * get text + * @name getText + * @methodOf Kinetic.Text.prototype + */ +})(); diff --git a/src/shapes/TextPath.js b/src/shapes/TextPath.js index 70e53d1e..0dddae92 100644 --- a/src/shapes/TextPath.js +++ b/src/shapes/TextPath.js @@ -1,393 +1,395 @@ -/** - * Path constructor. - * @author Jason Follas - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.TextPath = function(config) { - this._initTextPath(config); -}; +(function() { + /** + * Path constructor. + * @author Jason Follas + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ + Kinetic.TextPath = function(config) { + this._initTextPath(config); + }; -Kinetic.TextPath.prototype = { - _initTextPath: function(config) { - this.setDefaultAttrs({ - fontFamily: 'Calibri', - fontSize: 12, - fontStyle: 'normal', - detectionType: 'path', - text: '' - }); + Kinetic.TextPath.prototype = { + _initTextPath: function(config) { + this.setDefaultAttrs({ + fontFamily: 'Calibri', + fontSize: 12, + fontStyle: 'normal', + detectionType: 'path', + text: '' + }); - this.dummyCanvas = document.createElement('canvas'); - this.shapeType = "TextPath"; - this.dataArray = []; - var that = this; + this.dummyCanvas = document.createElement('canvas'); + this.shapeType = "TextPath"; + this.dataArray = []; + var that = this; - // call super constructor - Kinetic.Shape.call(this, config); - this._setDrawFuncs(); + // call super constructor + Kinetic.Shape.call(this, config); + this._setDrawFuncs(); - this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); - this.on('dataChange', function() { - that.dataArray = Kinetic.Path.parsePathData(this.attrs.data); - }); - // update text data for certain attr changes - var attrs = ['text', 'textStroke', 'textStrokeWidth']; - for(var n = 0; n < attrs.length; n++) { - var attr = attrs[n]; - this.on(attr + 'Change', that._setTextData); - } - that._setTextData(); - }, - drawFunc: function(context) { - var charArr = this.charArr; + this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); + this.on('dataChange', function() { + that.dataArray = Kinetic.Path.parsePathData(this.attrs.data); + }); + // update text data for certain attr changes + var attrs = ['text', 'textStroke', 'textStrokeWidth']; + for(var n = 0; n < attrs.length; n++) { + var attr = attrs[n]; + this.on(attr + 'Change', that._setTextData); + } + that._setTextData(); + }, + drawFunc: function(context) { + var charArr = this.charArr; - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - context.textBaseline = 'middle'; - context.textAlign = 'left'; - context.save(); + context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; + context.textBaseline = 'middle'; + context.textAlign = 'left'; + context.save(); - var glyphInfo = this.glyphInfo; + var glyphInfo = this.glyphInfo; - var appliedShadow = this.appliedShadow; - for(var i = 0; i < glyphInfo.length; i++) { - /* - * need to reset appliedShadow flag so that shadows - * are appropriately applied to each line of text - */ - this.appliedShadow = appliedShadow; + var appliedShadow = this.appliedShadow; + for(var i = 0; i < glyphInfo.length; i++) { + /* + * need to reset appliedShadow flag so that shadows + * are appropriately applied to each line of text + */ + this.appliedShadow = appliedShadow; + + context.save(); + + var p0 = glyphInfo[i].p0; + var p1 = glyphInfo[i].p1; + var ht = parseFloat(this.attrs.fontSize); + + context.translate(p0.x, p0.y); + context.rotate(glyphInfo[i].rotation); + + this.fillStrokeText(context, glyphInfo[i].text); + + context.restore(); + + //// To assist with debugging visually, uncomment following + // context.beginPath(); + // if (i % 2) + // context.strokeStyle = 'cyan'; + // else + // context.strokeStyle = 'green'; + + // context.moveTo(p0.x, p0.y); + // context.lineTo(p1.x, p1.y); + // context.stroke(); + } + + context.restore(); + }, + /** + * get text width in pixels + * @name getTextWidth + * @methodOf Kinetic.TextPath.prototype + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height in pixels + * @name getTextHeight + * @methodOf Kinetic.TextPath.prototype + */ + getTextHeight: function() { + return this.textHeight; + }, + /** + * set text + * @name setText + * @methodOf Kinetic.TextPath.prototype + * @param {String} text + */ + setText: function(text) { + Kinetic.Text.prototype.setText.call(this, text); + }, + _getTextSize: function(text) { + var dummyCanvas = this.dummyCanvas; + var context = dummyCanvas.getContext('2d'); context.save(); - var p0 = glyphInfo[i].p0; - var p1 = glyphInfo[i].p1; - var ht = parseFloat(this.attrs.fontSize); - - context.translate(p0.x, p0.y); - context.rotate(glyphInfo[i].rotation); - - this.fillStrokeText(context, glyphInfo[i].text); + context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; + var metrics = context.measureText(text); context.restore(); - //// To assist with debugging visually, uncomment following - // context.beginPath(); - // if (i % 2) - // context.strokeStyle = 'cyan'; - // else - // context.strokeStyle = 'green'; + return { + width: metrics.width, + height: parseInt(this.attrs.fontSize, 10) + }; + }, + /** + * set text data. + */ + _setTextData: function() { - // context.moveTo(p0.x, p0.y); - // context.lineTo(p1.x, p1.y); - // context.stroke(); - } + var that = this; + var size = this._getTextSize(this.attrs.text); + this.textWidth = size.width; + this.textHeight = size.height; - context.restore(); - }, - /** - * get text width in pixels - * @name getTextWidth - * @methodOf Kinetic.TextPath.prototype - */ - getTextWidth: function() { - return this.textWidth; - }, - /** - * get text height in pixels - * @name getTextHeight - * @methodOf Kinetic.TextPath.prototype - */ - getTextHeight: function() { - return this.textHeight; - }, - /** - * set text - * @name setText - * @methodOf Kinetic.TextPath.prototype - * @param {String} text - */ - setText: function(text) { - Kinetic.Text.prototype.setText.call(this, text); - }, - _getTextSize: function(text) { - var dummyCanvas = this.dummyCanvas; - var context = dummyCanvas.getContext('2d'); + this.glyphInfo = []; - context.save(); + var charArr = this.attrs.text.split(''); - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - var metrics = context.measureText(text); + var p0, p1, pathCmd; - context.restore(); + var pIndex = -1; + var currentT = 0; - return { - width: metrics.width, - height: parseInt(this.attrs.fontSize, 10) - }; - }, - /** - * set text data. - */ - _setTextData: function() { + var getNextPathSegment = function() { + currentT = 0; + var pathData = that.dataArray; - var that = this; - var size = this._getTextSize(this.attrs.text); - this.textWidth = size.width; - this.textHeight = size.height; + for(var i = pIndex + 1; i < pathData.length; i++) { + if(pathData[i].pathLength > 0) { + pIndex = i; - this.glyphInfo = []; - - var charArr = this.attrs.text.split(''); - - var p0, p1, pathCmd; - - var pIndex = -1; - var currentT = 0; - - var getNextPathSegment = function() { - currentT = 0; - var pathData = that.dataArray; - - for(var i = pIndex + 1; i < pathData.length; i++) { - if(pathData[i].pathLength > 0) { - pIndex = i; - - return pathData[i]; - } - else if(pathData[i].command == 'M') { - p0 = { - x: pathData[i].points[0], - y: pathData[i].points[1] - }; - } - } - - return {}; - }; - var findSegmentToFitCharacter = function(c, before) { - - var glyphWidth = that._getTextSize(c).width; - - var currLen = 0; - var attempts = 0; - var needNextSegment = false; - p1 = undefined; - while(Math.abs(glyphWidth - currLen) / glyphWidth > 0.01 && attempts < 25) { - attempts++; - var cumulativePathLength = currLen; - while(pathCmd === undefined) { - pathCmd = getNextPathSegment(); - - if(pathCmd && cumulativePathLength + pathCmd.pathLength < glyphWidth) { - cumulativePathLength += pathCmd.pathLength; - pathCmd = undefined; + return pathData[i]; + } + else if(pathData[i].command == 'M') { + p0 = { + x: pathData[i].points[0], + y: pathData[i].points[1] + }; } } - if(pathCmd === {} || p0 === undefined) - return undefined; + return {}; + }; + var findSegmentToFitCharacter = function(c, before) { - var needNewSegment = false; + var glyphWidth = that._getTextSize(c).width; - switch (pathCmd.command) { - case 'L': - if(Kinetic.Path.getLineLength(p0.x, p0.y, pathCmd.points[0], pathCmd.points[1]) > glyphWidth) { - p1 = Kinetic.Path.getPointOnLine(glyphWidth, p0.x, p0.y, pathCmd.points[0], pathCmd.points[1], p0.x, p0.y); - } - else + var currLen = 0; + var attempts = 0; + var needNextSegment = false; + p1 = undefined; + while(Math.abs(glyphWidth - currLen) / glyphWidth > 0.01 && attempts < 25) { + attempts++; + var cumulativePathLength = currLen; + while(pathCmd === undefined) { + pathCmd = getNextPathSegment(); + + if(pathCmd && cumulativePathLength + pathCmd.pathLength < glyphWidth) { + cumulativePathLength += pathCmd.pathLength; pathCmd = undefined; - break; - case 'A': - - var start = pathCmd.points[4]; - // 4 = theta - var dTheta = pathCmd.points[5]; - // 5 = dTheta - var end = pathCmd.points[4] + dTheta; - - if(currentT === 0) - currentT = start + 0.00000001; - // Just in case start is 0 - else if(glyphWidth > currLen) - currentT += (Math.PI / 180.0) * dTheta / Math.abs(dTheta); - else - currentT -= Math.PI / 360.0 * dTheta / Math.abs(dTheta); - - if(Math.abs(currentT) > Math.abs(end)) { - currentT = end; - needNewSegment = true; } - p1 = Kinetic.Path.getPointOnEllipticalArc(pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], currentT, pathCmd.points[6]); - break; - case 'C': - if(currentT === 0) { - if(glyphWidth > pathCmd.pathLength) - currentT = 0.00000001; + } + + if(pathCmd === {} || p0 === undefined) + return undefined; + + var needNewSegment = false; + + switch (pathCmd.command) { + case 'L': + if(Kinetic.Path.getLineLength(p0.x, p0.y, pathCmd.points[0], pathCmd.points[1]) > glyphWidth) { + p1 = Kinetic.Path.getPointOnLine(glyphWidth, p0.x, p0.y, pathCmd.points[0], pathCmd.points[1], p0.x, p0.y); + } else + pathCmd = undefined; + break; + case 'A': + + var start = pathCmd.points[4]; + // 4 = theta + var dTheta = pathCmd.points[5]; + // 5 = dTheta + var end = pathCmd.points[4] + dTheta; + + if(currentT === 0) + currentT = start + 0.00000001; + // Just in case start is 0 + else if(glyphWidth > currLen) + currentT += (Math.PI / 180.0) * dTheta / Math.abs(dTheta); + else + currentT -= Math.PI / 360.0 * dTheta / Math.abs(dTheta); + + if(Math.abs(currentT) > Math.abs(end)) { + currentT = end; + needNewSegment = true; + } + p1 = Kinetic.Path.getPointOnEllipticalArc(pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], currentT, pathCmd.points[6]); + break; + case 'C': + if(currentT === 0) { + if(glyphWidth > pathCmd.pathLength) + currentT = 0.00000001; + else + currentT = glyphWidth / pathCmd.pathLength; + } + else if(glyphWidth > currLen) + currentT += (glyphWidth - currLen) / pathCmd.pathLength; + else + currentT -= (currLen - glyphWidth) / pathCmd.pathLength; + + if(currentT > 1.0) { + currentT = 1.0; + needNewSegment = true; + } + p1 = Kinetic.Path.getPointOnCubicBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], pathCmd.points[4], pathCmd.points[5]); + break; + case 'Q': + if(currentT === 0) currentT = glyphWidth / pathCmd.pathLength; - } - else if(glyphWidth > currLen) - currentT += (glyphWidth - currLen) / pathCmd.pathLength; - else - currentT -= (currLen - glyphWidth) / pathCmd.pathLength; + else if(glyphWidth > currLen) + currentT += (glyphWidth - currLen) / pathCmd.pathLength; + else + currentT -= (currLen - glyphWidth) / pathCmd.pathLength; - if(currentT > 1.0) { - currentT = 1.0; - needNewSegment = true; - } - p1 = Kinetic.Path.getPointOnCubicBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], pathCmd.points[4], pathCmd.points[5]); - break; - case 'Q': - if(currentT === 0) - currentT = glyphWidth / pathCmd.pathLength; - else if(glyphWidth > currLen) - currentT += (glyphWidth - currLen) / pathCmd.pathLength; - else - currentT -= (currLen - glyphWidth) / pathCmd.pathLength; + if(currentT > 1.0) { + currentT = 1.0; + needNewSegment = true; + } + p1 = Kinetic.Path.getPointOnQuadraticBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3]); + break; - if(currentT > 1.0) { - currentT = 1.0; - needNewSegment = true; - } - p1 = Kinetic.Path.getPointOnQuadraticBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3]); - break; + } + if(p1 !== undefined) { + currLen = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); + } + + if(needNewSegment) { + needNewSegment = false; + pathCmd = undefined; + } } + }; + for(var i = 0; i < charArr.length; i++) { - if(p1 !== undefined) { - currLen = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); - } + // Find p1 such that line segment between p0 and p1 is approx. width of glyph + findSegmentToFitCharacter(charArr[i]); - if(needNewSegment) { - needNewSegment = false; - pathCmd = undefined; - } + if(p0 === undefined || p1 === undefined) + break; + + var width = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); + + // Note: Since glyphs are rendered one at a time, any kerning pair data built into the font will not be used. + // Can foresee having a rough pair table built in that the developer can override as needed. + + var kern = 0; + // placeholder for future implementation + + var midpoint = Kinetic.Path.getPointOnLine(kern + width / 2.0, p0.x, p0.y, p1.x, p1.y); + + var rotation = Math.atan2((p1.y - p0.y), (p1.x - p0.x)); + this.glyphInfo.push({ + transposeX: midpoint.x, + transposeY: midpoint.y, + text: charArr[i], + rotation: rotation, + p0: p0, + p1: p1 + }); + p0 = p1; } - }; - for(var i = 0; i < charArr.length; i++) { - - // Find p1 such that line segment between p0 and p1 is approx. width of glyph - findSegmentToFitCharacter(charArr[i]); - - if(p0 === undefined || p1 === undefined) - break; - - var width = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); - - // Note: Since glyphs are rendered one at a time, any kerning pair data built into the font will not be used. - // Can foresee having a rough pair table built in that the developer can override as needed. - - var kern = 0; - // placeholder for future implementation - - var midpoint = Kinetic.Path.getPointOnLine(kern + width / 2.0, p0.x, p0.y, p1.x, p1.y); - - var rotation = Math.atan2((p1.y - p0.y), (p1.x - p0.x)); - this.glyphInfo.push({ - transposeX: midpoint.x, - transposeY: midpoint.y, - text: charArr[i], - rotation: rotation, - p0: p0, - p1: p1 - }); - p0 = p1; } - } -}; -Kinetic.Global.extend(Kinetic.TextPath, Kinetic.Shape); + }; + Kinetic.Global.extend(Kinetic.TextPath, Kinetic.Shape); -// add setters and getters -Kinetic.Node.addGettersSetters(Kinetic.TextPath, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth']); -Kinetic.Node.addGetters(Kinetic.TextPath, ['text', 'textShadow']); + // add setters and getters + Kinetic.Node.addGettersSetters(Kinetic.TextPath, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth']); + Kinetic.Node.addGetters(Kinetic.TextPath, ['text', 'textShadow']); -// reference Text methods -Kinetic.TextPath.prototype.setTextShadow = Kinetic.Text.prototype.setTextShadow; -Kinetic.TextPath.prototype.fillText = Kinetic.Text.prototype.fillText; -Kinetic.TextPath.prototype.strokeText = Kinetic.Text.prototype.strokeText; -Kinetic.TextPath.prototype.fillStrokeText = Kinetic.Text.prototype.strokeText; + // reference Text methods + Kinetic.TextPath.prototype.setTextShadow = Kinetic.Text.prototype.setTextShadow; + Kinetic.TextPath.prototype.fillText = Kinetic.Text.prototype.fillText; + Kinetic.TextPath.prototype.strokeText = Kinetic.Text.prototype.strokeText; + Kinetic.TextPath.prototype.fillStrokeText = Kinetic.Text.prototype.strokeText; -/** - * set font family - * @name setFontFamily - * @methodOf Kinetic.TextPath.prototype - * @param {String} fontFamily - */ + /** + * set font family + * @name setFontFamily + * @methodOf Kinetic.TextPath.prototype + * @param {String} fontFamily + */ -/** - * set font size - * @name setFontSize - * @methodOf Kinetic.TextPath.prototype - * @param {int} fontSize - */ + /** + * set font size + * @name setFontSize + * @methodOf Kinetic.TextPath.prototype + * @param {int} fontSize + */ -/** - * set font style. Can be "normal", "italic", or "bold". "normal" is the default. - * @name setFontStyle - * @methodOf Kinetic.TextPath.prototype - * @param {String} fontStyle - */ + /** + * set font style. Can be "normal", "italic", or "bold". "normal" is the default. + * @name setFontStyle + * @methodOf Kinetic.TextPath.prototype + * @param {String} fontStyle + */ -/** - * set text fill color - * @name setTextFill - * @methodOf Kinetic.TextPath.prototype - * @param {String} textFill - */ + /** + * set text fill color + * @name setTextFill + * @methodOf Kinetic.TextPath.prototype + * @param {String} textFill + */ -/** - * set text stroke color - * @name setFontStroke - * @methodOf Kinetic.TextPath.prototype - * @param {String} textStroke - */ + /** + * set text stroke color + * @name setFontStroke + * @methodOf Kinetic.TextPath.prototype + * @param {String} textStroke + */ -/** - * set text stroke width - * @name setTextStrokeWidth - * @methodOf Kinetic.TextPath.prototype - * @param {int} textStrokeWidth - */ + /** + * set text stroke width + * @name setTextStrokeWidth + * @methodOf Kinetic.TextPath.prototype + * @param {int} textStrokeWidth + */ -/** - * get font family - * @name getFontFamily - * @methodOf Kinetic.TextPath.prototype - */ + /** + * get font family + * @name getFontFamily + * @methodOf Kinetic.TextPath.prototype + */ -/** - * get font size - * @name getFontSize - * @methodOf Kinetic.TextPath.prototype - */ + /** + * get font size + * @name getFontSize + * @methodOf Kinetic.TextPath.prototype + */ -/** - * get font style - * @name getFontStyle - * @methodOf Kinetic.TextPath.prototype - */ + /** + * get font style + * @name getFontStyle + * @methodOf Kinetic.TextPath.prototype + */ -/** - * get text fill color - * @name getTextFill - * @methodOf Kinetic.TextPath.prototype - */ + /** + * get text fill color + * @name getTextFill + * @methodOf Kinetic.TextPath.prototype + */ -/** - * get text stroke color - * @name getTextStroke - * @methodOf Kinetic.TextPath.prototype - */ + /** + * get text stroke color + * @name getTextStroke + * @methodOf Kinetic.TextPath.prototype + */ -/** - * get text stroke width - * @name getTextStrokeWidth - * @methodOf Kinetic.TextPath.prototype - */ + /** + * get text stroke width + * @name getTextStrokeWidth + * @methodOf Kinetic.TextPath.prototype + */ -/** - * get text - * @name getText - * @methodOf Kinetic.TextPath.prototype - */ \ No newline at end of file + /** + * get text + * @name getText + * @methodOf Kinetic.TextPath.prototype + */ +})(); diff --git a/src/util/Canvas.js b/src/util/Canvas.js deleted file mode 100644 index 66efc525..00000000 --- a/src/util/Canvas.js +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Canvas wrapper constructor - * @constructor - * @param {Number} width - * @param {Number} height - */ -Kinetic.Canvas = function(width, height, isHit) { - this.element = document.createElement('canvas'); - this.context = this.element.getContext('2d'); - - // set dimensions - this.element.width = width || 0; - this.element.height = height || 0; - - this.context.renderer = isHit ? new Kinetic.HitRenderer(this.context) : new Kinetic.SceneRenderer(this.context); -}; - -Kinetic.Canvas.prototype = { - /** - * clear canvas - * @name clear - * @methodOf Kinetic.Canvas.prototype - */ - clear: function() { - var context = this.getContext(); - var el = this.getElement(); - context.clearRect(0, 0, el.width, el.height); - }, - /** - * get element - * @name getElement - * @methodOf Kinetic.Canvas.prototype - */ - getElement: function() { - return this.element; - }, - /** - * get context - * @name getContext - * @methodOf Kinetic.Canvas.prototype - */ - getContext: function() { - return this.context; - }, - /** - * set width - * @name setWidth - * @methodOf Kinetic.Canvas.prototype - */ - setWidth: function(width) { - this.element.width = width; - }, - /** - * set height - * @name setHeight - * @methodOf Kinetic.Canvas.prototype - */ - setHeight: function(height) { - this.element.height = height; - }, - /** - * get width - * @name getWidth - * @methodOf Kinetic.Canvas.prototype - */ - getWidth: function() { - return this.element.width; - }, - /** - * get height - * @name getHeight - * @methodOf Kinetic.Canvas.prototype - */ - getHeight: function() { - return this.element.height; - }, - /** - * set size - * @name setSize - * @methodOf Kinetic.Canvas.prototype - */ - setSize: function(width, height) { - this.setWidth(width); - this.setHeight(height); - }, - /** - * toDataURL - */ - toDataURL: function(mimeType, quality) { - try { - // If this call fails (due to browser bug, like in Firefox 3.6), - // then revert to previous no-parameter image/png behavior - return this.element.toDataURL(mimeType, quality); - } - catch(e) { - try { - return this.element.toDataURL(); - } - catch(e) { - Kinetic.Global.warn('Unable to get data URL. ' + e.message) - return ''; - } - } - } -}; - -Kinetic.SceneRenderer = function(context) { - this.context = context; -}; - -Kinetic.SceneRenderer.prototype = { - _fill: function(shape, skipShadow) { - var context = this.context, fill = shape.getFill(), fillType = shape._getFillType(fill), shadow = shape.getShadow(); - if(fill) { - context.save(); - - if(!skipShadow && shadow) { - this._applyShadow(shape); - } - var s = fill.start; - var e = fill.end; - - // color fill - switch(fillType) { - case 'COLOR': - context.fillStyle = fill; - context.fill(context); - break; - case 'PATTERN': - var repeat = !fill.repeat ? 'repeat' : fill.repeat; - if(fill.scale) { - context.scale(fill.scale.x, fill.scale.y); - } - if(fill.offset) { - context.translate(fill.offset.x, fill.offset.y); - } file:///C:/Users/Eric/Documents/Eric/workspaces/KineticJS/dist/kinetic-current.js - - context.fillStyle = context.createPattern(fill.image, repeat); - context.fill(context); - break; - case 'LINEAR_GRADIENT': - var grd = context.createLinearGradient(s.x, s.y, e.x, e.y); - var colorStops = fill.colorStops; - - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - context.fillStyle = grd; - context.fill(context); - - break; - case 'RADIAL_GRADIENT': - var grd = context.createRadialGradient(s.x, s.y, s.radius, e.x, e.y, e.radius); - var colorStops = fill.colorStops; - - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - context.fillStyle = grd; - context.fill(context); - break; - default: - context.fillStyle = 'black'; - context.fill(context); - break; - } - - context.restore(); - - if(!skipShadow && shadow && shadow.opacity) { - this._fill(shape, true); - } - } - }, - _stroke: function(shape, skipShadow) { - var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(), shadow = shape.getShadow(); - if(stroke || strokeWidth) { - context.save(); - if(!skipShadow && shadow) { - this._applyShadow(shape); - } - context.lineWidth = strokeWidth || 2; - context.strokeStyle = stroke || 'black'; - context.stroke(context); - context.restore(); - - if(!skipShadow && shadow && shadow.opacity) { - this._stroke(shape, true); - } - } - }, - _applyShadow: function(shape) { - var context = this.context, shadow = shape.getShadow(); - if(shadow) { - var aa = shape.getAbsoluteOpacity(); - // defaults - var color = shadow.color || 'black'; - var blur = shadow.blur || 5; - var offset = shadow.offset || { - x: 0, - y: 0 - }; - - if(shadow.opacity) { - context.globalAlpha = shadow.opacity * aa; - } - context.shadowColor = color; - context.shadowBlur = blur; - context.shadowOffsetX = offset.x; - context.shadowOffsetY = offset.y; - } - } -}; - -Kinetic.HitRenderer = function(context) { - this.context = context; -}; - -Kinetic.HitRenderer.prototype = { - _fill: function(shape) { - var context = this.context; - context.save(); - context.fillStyle = '#' + shape.colorKey; - context.fill(context); - context.restore(); - }, - _stroke: function(shape) { - var context = this.context, stroke = shape.getStroke(), strokeWidth = shape.getStrokeWidth(); - if(stroke || strokeWidth) { - context.save(); - context.lineWidth = strokeWidth || 2; - context.strokeStyle = '#' + shape.colorKey; - context.stroke(context); - context.restore(); - } - } -}; diff --git a/src/util/Collection.js b/src/util/Collection.js index c104dbb0..660516e6 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -1,43 +1,44 @@ -/** - * Collection constructor. Collection extends - * Array. This class is used in conjunction with get() - * @constructor - */ -Kinetic.Collection = function() { - var args = [].slice.call( arguments ), - length = args.length, i = 0; +(function() { + /** + * Collection constructor. Collection extends + * Array. This class is used in conjunction with get() + * @constructor + */ + Kinetic.Collection = function() { + var args = [].slice.call(arguments), length = args.length, i = 0; - this.length = length; - for (; i < length; i++ ) { - this[ i ] = args[ i ]; + this.length = length; + for(; i < length; i++) { + this[i] = args[i]; + } + return this; } - return this; -} -Kinetic.Collection.prototype = new Array(); -/** - * apply a method to all nodes in the array - * @name apply - * @methodOf Kinetic.Collection.prototype - * @param {String} method - * @param val - */ - Kinetic.Collection.prototype.apply = function(method) { - args = [].slice.call(arguments); - args.shift(); - for (var n=0; n this.getDuration()) { - if(this.looping) { - this.rewind(t - this._duration); +(function() { + /* + * The Tween class was ported from an Adobe Flash Tween library + * to JavaScript by Xaric. In the context of KineticJS, a Tween is + * an animation of a single Node property. A Transition is a set of + * multiple tweens + */ + Kinetic.Tween = function(obj, propFunc, func, begin, finish, duration) { + this._listeners = []; + this.addListener(this); + this.obj = obj; + this.propFunc = propFunc; + this.begin = begin; + this._pos = begin; + this.setDuration(duration); + this.isPlaying = false; + this._change = 0; + this.prevTime = 0; + this.prevPos = 0; + this.looping = false; + this._time = 0; + this._position = 0; + this._startTime = 0; + this._finish = 0; + this.name = ''; + this.func = func; + this.setFinish(finish); + }; + /* + * Tween methods + */ + Kinetic.Tween.prototype = { + setTime: function(t) { + this.prevTime = this._time; + if(t > this.getDuration()) { + if(this.looping) { + this.rewind(t - this._duration); + this.update(); + this.broadcastMessage('onLooped', { + target: this, + type: 'onLooped' + }); + } + else { + this._time = this._duration; + this.update(); + this.stop(); + this.broadcastMessage('onFinished', { + target: this, + type: 'onFinished' + }); + } + } + else if(t < 0) { + this.rewind(); this.update(); - this.broadcastMessage('onLooped', { - target: this, - type: 'onLooped' - }); } else { - this._time = this._duration; + this._time = t; this.update(); - this.stop(); - this.broadcastMessage('onFinished', { - target: this, - type: 'onFinished' - }); } - } - else if(t < 0) { + }, + getTime: function() { + return this._time; + }, + setDuration: function(d) { + this._duration = (d === null || d <= 0) ? 100000 : d; + }, + getDuration: function() { + return this._duration; + }, + setPosition: function(p) { + this.prevPos = this._pos; + this.propFunc(p); + this._pos = p; + this.broadcastMessage('onChanged', { + target: this, + type: 'onChanged' + }); + }, + getPosition: function(t) { + if(t === undefined) { + t = this._time; + } + return this.func(t, this.begin, this._change, this._duration); + }, + setFinish: function(f) { + this._change = f - this.begin; + }, + getFinish: function() { + return this.begin + this._change; + }, + start: function() { this.rewind(); + this.startEnterFrame(); + this.broadcastMessage('onStarted', { + target: this, + type: 'onStarted' + }); + }, + rewind: function(t) { + this.stop(); + this._time = (t === undefined) ? 0 : t; + this.fixTime(); this.update(); - } - else { - this._time = t; + }, + fforward: function() { + this._time = this._duration; + this.fixTime(); this.update(); - } - }, - getTime: function() { - return this._time; - }, - setDuration: function(d) { - this._duration = (d === null || d <= 0) ? 100000 : d; - }, - getDuration: function() { - return this._duration; - }, - setPosition: function(p) { - this.prevPos = this._pos; - this.propFunc(p); - this._pos = p; - this.broadcastMessage('onChanged', { - target: this, - type: 'onChanged' - }); - }, - getPosition: function(t) { - if(t === undefined) { - t = this._time; - } - return this.func(t, this.begin, this._change, this._duration); - }, - setFinish: function(f) { - this._change = f - this.begin; - }, - getFinish: function() { - return this.begin + this._change; - }, - start: function() { - this.rewind(); - this.startEnterFrame(); - this.broadcastMessage('onStarted', { - target: this, - type: 'onStarted' - }); - }, - rewind: function(t) { - this.stop(); - this._time = (t === undefined) ? 0 : t; - this.fixTime(); - this.update(); - }, - fforward: function() { - this._time = this._duration; - this.fixTime(); - this.update(); - }, - update: function() { - this.setPosition(this.getPosition(this._time)); - }, - startEnterFrame: function() { - this.stopEnterFrame(); - this.isPlaying = true; - this.onEnterFrame(); - }, - onEnterFrame: function() { - if(this.isPlaying) { - this.nextFrame(); - } - }, - nextFrame: function() { - this.setTime((this.getTimer() - this._startTime) / 1000); - }, - stop: function() { - this.stopEnterFrame(); - this.broadcastMessage('onStopped', { - target: this, - type: 'onStopped' - }); - }, - stopEnterFrame: function() { - this.isPlaying = false; - }, - continueTo: function(finish, duration) { - this.begin = this._pos; - this.setFinish(finish); - if(this._duration !== undefined) { - this.setDuration(duration); - } - this.start(); - }, - resume: function() { - this.fixTime(); - this.startEnterFrame(); - this.broadcastMessage('onResumed', { - target: this, - type: 'onResumed' - }); - }, - yoyo: function() { - this.continueTo(this.begin, this._time); - }, - addListener: function(o) { - this.removeListener(o); - return this._listeners.push(o); - }, - removeListener: function(o) { - var a = this._listeners; - var i = a.length; - while(i--) { - if(a[i] == o) { - a.splice(i, 1); - return true; + }, + update: function() { + this.setPosition(this.getPosition(this._time)); + }, + startEnterFrame: function() { + this.stopEnterFrame(); + this.isPlaying = true; + this.onEnterFrame(); + }, + onEnterFrame: function() { + if(this.isPlaying) { + this.nextFrame(); } - } - return false; - }, - broadcastMessage: function() { - var arr = []; - for(var i = 0; i < arguments.length; i++) { - arr.push(arguments[i]); - } - var e = arr.shift(); - var a = this._listeners; - var l = a.length; - for(var i = 0; i < l; i++) { - if(a[i][e]) { - a[i][e].apply(a[i], arr); + }, + nextFrame: function() { + this.setTime((this.getTimer() - this._startTime) / 1000); + }, + stop: function() { + this.stopEnterFrame(); + this.broadcastMessage('onStopped', { + target: this, + type: 'onStopped' + }); + }, + stopEnterFrame: function() { + this.isPlaying = false; + }, + continueTo: function(finish, duration) { + this.begin = this._pos; + this.setFinish(finish); + if(this._duration !== undefined) { + this.setDuration(duration); } + this.start(); + }, + resume: function() { + this.fixTime(); + this.startEnterFrame(); + this.broadcastMessage('onResumed', { + target: this, + type: 'onResumed' + }); + }, + yoyo: function() { + this.continueTo(this.begin, this._time); + }, + addListener: function(o) { + this.removeListener(o); + return this._listeners.push(o); + }, + removeListener: function(o) { + var a = this._listeners; + var i = a.length; + while(i--) { + if(a[i] == o) { + a.splice(i, 1); + return true; + } + } + return false; + }, + broadcastMessage: function() { + var arr = []; + for(var i = 0; i < arguments.length; i++) { + arr.push(arguments[i]); + } + var e = arr.shift(); + var a = this._listeners; + var l = a.length; + for(var i = 0; i < l; i++) { + if(a[i][e]) { + a[i][e].apply(a[i], arr); + } + } + }, + fixTime: function() { + this._startTime = this.getTimer() - this._time * 1000; + }, + getTimer: function() { + return new Date().getTime() - this._time; } - }, - fixTime: function() { - this._startTime = this.getTimer() - this._time * 1000; - }, - getTimer: function() { - return new Date().getTime() - this._time; - } -}; + }; -Kinetic.Tweens = { - 'back-ease-in': function(t, b, c, d, a, p) { - var s = 1.70158; - return c * (t /= d) * t * ((s + 1) * t - s) + b; - }, - 'back-ease-out': function(t, b, c, d, a, p) { - var s = 1.70158; - return c * (( t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; - }, - 'back-ease-in-out': function(t, b, c, d, a, p) { - var s = 1.70158; - if((t /= d / 2) < 1) { - return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + Kinetic.Tweens = { + 'back-ease-in': function(t, b, c, d, a, p) { + var s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + 'back-ease-out': function(t, b, c, d, a, p) { + var s = 1.70158; + return c * (( t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + 'back-ease-in-out': function(t, b, c, d, a, p) { + var s = 1.70158; + if((t /= d / 2) < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + }, + 'elastic-ease-in': function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if(t === 0) { + return b; + } + if((t /= d) == 1) { + return b + c; + } + if(!p) { + p = d * 0.3; + } + if(!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + }, + 'elastic-ease-out': function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if(t === 0) { + return b; + } + if((t /= d) == 1) { + return b + c; + } + if(!p) { + p = d * 0.3; + } + if(!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b); + }, + 'elastic-ease-in-out': function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if(t === 0) { + return b; + } + if((t /= d / 2) == 2) { + return b + c; + } + if(!p) { + p = d * (0.3 * 1.5); + } + if(!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + if(t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; + }, + 'bounce-ease-out': function(t, b, c, d) { + if((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if(t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } + else if(t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } + else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + } + }, + 'bounce-ease-in': function(t, b, c, d) { + return c - Kinetic.Tweens['bounce-ease-out'](d - t, 0, c, d) + b; + }, + 'bounce-ease-in-out': function(t, b, c, d) { + if(t < d / 2) { + return Kinetic.Tweens['bounce-ease-in'](t * 2, 0, c, d) * 0.5 + b; + } + else { + return Kinetic.Tweens['bounce-ease-out'](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + }, + // duplicate + /* + strongEaseInOut: function(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + */ + 'ease-in': function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + 'ease-out': function(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + 'ease-in-out': function(t, b, c, d) { + if((t /= d / 2) < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + 'strong-ease-in': function(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + 'strong-ease-out': function(t, b, c, d) { + return c * (( t = t / d - 1) * t * t * t * t + 1) + b; + }, + 'strong-ease-in-out': function(t, b, c, d) { + if((t /= d / 2) < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + 'linear': function(t, b, c, d) { + return c * t / d + b; } - return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; - }, - 'elastic-ease-in': function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if(t === 0) { - return b; - } - if((t /= d) == 1) { - return b + c; - } - if(!p) { - p = d * 0.3; - } - if(!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; - }, - 'elastic-ease-out': function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if(t === 0) { - return b; - } - if((t /= d) == 1) { - return b + c; - } - if(!p) { - p = d * 0.3; - } - if(!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b); - }, - 'elastic-ease-in-out': function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if(t === 0) { - return b; - } - if((t /= d / 2) == 2) { - return b + c; - } - if(!p) { - p = d * (0.3 * 1.5); - } - if(!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - if(t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; - }, - 'bounce-ease-out': function(t, b, c, d) { - if((t /= d) < (1 / 2.75)) { - return c * (7.5625 * t * t) + b; - } - else if(t < (2 / 2.75)) { - return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; - } - else if(t < (2.5 / 2.75)) { - return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; - } - else { - return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; - } - }, - 'bounce-ease-in': function(t, b, c, d) { - return c - Kinetic.Tweens['bounce-ease-out'](d - t, 0, c, d) + b; - }, - 'bounce-ease-in-out': function(t, b, c, d) { - if(t < d / 2) { - return Kinetic.Tweens['bounce-ease-in'](t * 2, 0, c, d) * 0.5 + b; - } - else { - return Kinetic.Tweens['bounce-ease-out'](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - }, - // duplicate - /* - strongEaseInOut: function(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - }, - */ - 'ease-in': function(t, b, c, d) { - return c * (t /= d) * t + b; - }, - 'ease-out': function(t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - }, - 'ease-in-out': function(t, b, c, d) { - if((t /= d / 2) < 1) { - return c / 2 * t * t + b; - } - return -c / 2 * ((--t) * (t - 2) - 1) + b; - }, - 'strong-ease-in': function(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - }, - 'strong-ease-out': function(t, b, c, d) { - return c * (( t = t / d - 1) * t * t * t * t + 1) + b; - }, - 'strong-ease-in-out': function(t, b, c, d) { - if((t /= d / 2) < 1) { - return c / 2 * t * t * t * t * t + b; - } - return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; - }, - 'linear': function(t, b, c, d) { - return c * t / d + b; - } -}; + }; +})(); diff --git a/src/util/Type.js b/src/util/Type.js index f3e587ae..6449a3e1 100644 --- a/src/util/Type.js +++ b/src/util/Type.js @@ -1,300 +1,302 @@ -/* - * utilities that handle data type detection, conversion, and manipulation - */ -Kinetic.Type = { +(function() { /* - * cherry-picked utilities from underscore.js + * utilities that handle data type detection, conversion, and manipulation */ - _isElement: function(obj) { - return !!(obj && obj.nodeType == 1); - }, - _isFunction: function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }, - _isObject: function(obj) { - return (!!obj && obj.constructor == Object); - }, - _isArray: function(obj) { - return Object.prototype.toString.call(obj) == '[object Array]'; - }, - _isNumber: function(obj) { - return Object.prototype.toString.call(obj) == '[object Number]'; - }, - _isString: function(obj) { - return Object.prototype.toString.call(obj) == '[object String]'; - }, - /* - * other utils - */ - _hasMethods: function(obj) { - var names = []; - for(var key in obj) { - if(this._isFunction(obj[key])) - names.push(key); - } - return names.length > 0; - }, - /* - * The argument can be: - * - an integer (will be applied to both x and y) - * - an array of one integer (will be applied to both x and y) - * - an array of two integers (contains x and y) - * - an array of four integers (contains x, y, width, and height) - * - an object with x and y properties - * - an array of one element which is an array of integers - * - an array of one element of an object - */ - _getXY: function(arg) { - if(this._isNumber(arg)) { - return { - x: arg, - y: arg - }; - } - else if(this._isArray(arg)) { - // if arg is an array of one element - if(arg.length === 1) { - var val = arg[0]; - // if arg is an array of one element which is a number - if(this._isNumber(val)) { - return { - x: val, - y: val - }; - } - // if arg is an array of one element which is an array - else if(this._isArray(val)) { - return { - x: val[0], - y: val[1] - }; - } - // if arg is an array of one element which is an object - else if(this._isObject(val)) { - return val; - } + Kinetic.Type = { + /* + * cherry-picked utilities from underscore.js + */ + _isElement: function(obj) { + return !!(obj && obj.nodeType == 1); + }, + _isFunction: function(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }, + _isObject: function(obj) { + return (!!obj && obj.constructor == Object); + }, + _isArray: function(obj) { + return Object.prototype.toString.call(obj) == '[object Array]'; + }, + _isNumber: function(obj) { + return Object.prototype.toString.call(obj) == '[object Number]'; + }, + _isString: function(obj) { + return Object.prototype.toString.call(obj) == '[object String]'; + }, + /* + * other utils + */ + _hasMethods: function(obj) { + var names = []; + for(var key in obj) { + if(this._isFunction(obj[key])) + names.push(key); } - // if arg is an array of two or more elements - else if(arg.length >= 2) { + return names.length > 0; + }, + /* + * The argument can be: + * - an integer (will be applied to both x and y) + * - an array of one integer (will be applied to both x and y) + * - an array of two integers (contains x and y) + * - an array of four integers (contains x, y, width, and height) + * - an object with x and y properties + * - an array of one element which is an array of integers + * - an array of one element of an object + */ + _getXY: function(arg) { + if(this._isNumber(arg)) { return { - x: arg[0], - y: arg[1] + x: arg, + y: arg }; } - } - // if arg is an object return the object - else if(this._isObject(arg)) { - return arg; - } - - // default - return { - x: 0, - y: 0 - }; - }, - /* - * The argument can be: - * - an integer (will be applied to both width and height) - * - an array of one integer (will be applied to both width and height) - * - an array of two integers (contains width and height) - * - an array of four integers (contains x, y, width, and height) - * - an object with width and height properties - * - an array of one element which is an array of integers - * - an array of one element of an object - */ - _getSize: function(arg) { - if(this._isNumber(arg)) { - return { - width: arg, - height: arg - }; - } - else if(this._isArray(arg)) { - // if arg is an array of one element - if(arg.length === 1) { - var val = arg[0]; - // if arg is an array of one element which is a number - if(this._isNumber(val)) { - return { - width: val, - height: val - }; - } - // if arg is an array of one element which is an array - else if(this._isArray(val)) { - /* - * if arg is an array of one element which is an - * array of four elements - */ - if(val.length >= 4) { + else if(this._isArray(arg)) { + // if arg is an array of one element + if(arg.length === 1) { + var val = arg[0]; + // if arg is an array of one element which is a number + if(this._isNumber(val)) { return { - width: val[2], - height: val[3] + x: val, + y: val }; } - /* - * if arg is an array of one element which is an - * array of two elements - */ - else if(val.length >= 2) { + // if arg is an array of one element which is an array + else if(this._isArray(val)) { return { - width: val[0], - height: val[1] + x: val[0], + y: val[1] }; } + // if arg is an array of one element which is an object + else if(this._isObject(val)) { + return val; + } } - // if arg is an array of one element which is an object - else if(this._isObject(val)) { - return val; + // if arg is an array of two or more elements + else if(arg.length >= 2) { + return { + x: arg[0], + y: arg[1] + }; } } - // if arg is an array of four elements - else if(arg.length >= 4) { + // if arg is an object return the object + else if(this._isObject(arg)) { + return arg; + } + + // default + return { + x: 0, + y: 0 + }; + }, + /* + * The argument can be: + * - an integer (will be applied to both width and height) + * - an array of one integer (will be applied to both width and height) + * - an array of two integers (contains width and height) + * - an array of four integers (contains x, y, width, and height) + * - an object with width and height properties + * - an array of one element which is an array of integers + * - an array of one element of an object + */ + _getSize: function(arg) { + if(this._isNumber(arg)) { return { - width: arg[2], - height: arg[3] + width: arg, + height: arg }; } - // if arg is an array of two elements - else if(arg.length >= 2) { - return { - width: arg[0], - height: arg[1] - }; + else if(this._isArray(arg)) { + // if arg is an array of one element + if(arg.length === 1) { + var val = arg[0]; + // if arg is an array of one element which is a number + if(this._isNumber(val)) { + return { + width: val, + height: val + }; + } + // if arg is an array of one element which is an array + else if(this._isArray(val)) { + /* + * if arg is an array of one element which is an + * array of four elements + */ + if(val.length >= 4) { + return { + width: val[2], + height: val[3] + }; + } + /* + * if arg is an array of one element which is an + * array of two elements + */ + else if(val.length >= 2) { + return { + width: val[0], + height: val[1] + }; + } + } + // if arg is an array of one element which is an object + else if(this._isObject(val)) { + return val; + } + } + // if arg is an array of four elements + else if(arg.length >= 4) { + return { + width: arg[2], + height: arg[3] + }; + } + // if arg is an array of two elements + else if(arg.length >= 2) { + return { + width: arg[0], + height: arg[1] + }; + } } - } - // if arg is an object return the object - else if(this._isObject(arg)) { - return arg; - } - - // default - return { - width: 0, - height: 0 - }; - }, - /* - * arg will be an array of numbers or - * an array of point objects - */ - _getPoints: function(arg) { - if(arg === undefined) { - return []; - } - - // an array of objects - if(this._isObject(arg[0])) { - return arg; - } - // an array of integers - else { - /* - * convert array of numbers into an array - * of objects containing x, y - */ - var arr = []; - for(var n = 0; n < arg.length; n += 2) { - arr.push({ - x: arg[n], - y: arg[n + 1] - }); + // if arg is an object return the object + else if(this._isObject(arg)) { + return arg; } - return arr; - } - }, - /* - * arg can be an image object or image data - */ - _getImage: function(arg, callback) { - // if arg is null or undefined - if(!arg) { - callback(null); - } - - // if arg is already an image object - else if(this._isElement(arg)) { - callback(arg); - } - - // if arg is a string, then it's a data url - else if(this._isString(arg)) { - var imageObj = new Image(); - /** @ignore */ - imageObj.onload = function() { - callback(imageObj); + // default + return { + width: 0, + height: 0 + }; + }, + /* + * arg will be an array of numbers or + * an array of point objects + */ + _getPoints: function(arg) { + if(arg === undefined) { + return []; } - imageObj.src = arg; - } - //if arg is an object that contains the data property, it's an image object - else if(arg.data) { - var canvas = document.createElement('canvas'); - canvas.width = arg.width; - canvas.height = arg.height; - var context = canvas.getContext('2d'); - context.putImageData(arg, 0, 0); - var dataUrl = canvas.toDataURL(); - var imageObj = new Image(); - /** @ignore */ - imageObj.onload = function() { - callback(imageObj); + // an array of objects + if(this._isObject(arg[0])) { + return arg; } - imageObj.src = dataUrl; - } - else { - callback(null); - } - }, - _rgbToHex: function(r, g, b) { - return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - }, - _hexToRgb: function(hex) { - var bigint = parseInt(hex, 16); - return { - r: (bigint >> 16) & 255, - g: (bigint >> 8) & 255, - b: bigint & 255 - }; - }, - _getRandomColorKey: function() { - var r = Math.round(Math.random() * 255); - var g = Math.round(Math.random() * 255); - var b = Math.round(Math.random() * 255); - return this._rgbToHex(r, g, b); - }, - // o1 takes precedence over o2 - _merge: function(o1, o2) { - var retObj = this._clone(o2); - for(var key in o1) { - if(this._isObject(o1[key])) { - retObj[key] = this._merge(o1[key], retObj[key]); + // an array of integers + else { + /* + * convert array of numbers into an array + * of objects containing x, y + */ + var arr = []; + for(var n = 0; n < arg.length; n += 2) { + arr.push({ + x: arg[n], + y: arg[n + 1] + }); + } + + return arr; + } + }, + /* + * arg can be an image object or image data + */ + _getImage: function(arg, callback) { + // if arg is null or undefined + if(!arg) { + callback(null); + } + + // if arg is already an image object + else if(this._isElement(arg)) { + callback(arg); + } + + // if arg is a string, then it's a data url + else if(this._isString(arg)) { + var imageObj = new Image(); + /** @ignore */ + imageObj.onload = function() { + callback(imageObj); + } + imageObj.src = arg; + } + + //if arg is an object that contains the data property, it's an image object + else if(arg.data) { + var canvas = document.createElement('canvas'); + canvas.width = arg.width; + canvas.height = arg.height; + var context = canvas.getContext('2d'); + context.putImageData(arg, 0, 0); + var dataUrl = canvas.toDataURL(); + var imageObj = new Image(); + /** @ignore */ + imageObj.onload = function() { + callback(imageObj); + } + imageObj.src = dataUrl; } else { - retObj[key] = o1[key]; + callback(null); } + }, + _rgbToHex: function(r, g, b) { + return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + }, + _hexToRgb: function(hex) { + var bigint = parseInt(hex, 16); + return { + r: (bigint >> 16) & 255, + g: (bigint >> 8) & 255, + b: bigint & 255 + }; + }, + _getRandomColorKey: function() { + var r = Math.round(Math.random() * 255); + var g = Math.round(Math.random() * 255); + var b = Math.round(Math.random() * 255); + return this._rgbToHex(r, g, b); + }, + // o1 takes precedence over o2 + _merge: function(o1, o2) { + var retObj = this._clone(o2); + for(var key in o1) { + if(this._isObject(o1[key])) { + retObj[key] = this._merge(o1[key], retObj[key]); + } + else { + retObj[key] = o1[key]; + } + } + return retObj; + }, + // deep clone + _clone: function(obj) { + var retObj = {}; + for(var key in obj) { + if(this._isObject(obj[key])) { + retObj[key] = this._clone(obj[key]); + } + else { + retObj[key] = obj[key]; + } + } + return retObj; + }, + _degToRad: function(deg) { + return deg * Math.PI / 180; + }, + _radToDeg: function(rad) { + return rad * 180 / Math.PI; } - return retObj; - }, - // deep clone - _clone: function(obj) { - var retObj = {}; - for(var key in obj) { - if(this._isObject(obj[key])) { - retObj[key] = this._clone(obj[key]); - } - else { - retObj[key] = obj[key]; - } - } - return retObj; - }, - _degToRad: function(deg) { - return deg * Math.PI / 180; - }, - _radToDeg: function(rad) { - return rad * 180 / Math.PI; - } -}; + }; +})(); diff --git a/tests/html/functionalTests.html b/tests/html/functionalTests.html index 2738ff69..5f3d4e50 100644 --- a/tests/html/functionalTests.html +++ b/tests/html/functionalTests.html @@ -6,7 +6,7 @@ - + diff --git a/tests/html/manualTests.html b/tests/html/manualTests.html index 79a940dc..a4392592 100644 --- a/tests/html/manualTests.html +++ b/tests/html/manualTests.html @@ -2,7 +2,7 @@ - + diff --git a/tests/html/performanceTests.html b/tests/html/performanceTests.html index ab7a3f02..c14a829a 100644 --- a/tests/html/performanceTests.html +++ b/tests/html/performanceTests.html @@ -2,7 +2,7 @@ - + diff --git a/tests/html/special/coreCustomBuild.html b/tests/html/special/coreCustomBuild.html index 9e1de26f..be7e7590 100644 --- a/tests/html/special/coreCustomBuild.html +++ b/tests/html/special/coreCustomBuild.html @@ -7,19 +7,19 @@
- - - - - - - - - - - + + + + + + + + + + + - + - - - - + + + + + - + - + - + - - - - - + + + + + - + + + - - + + + - + - - + + - + - + - + - - - - - + + + + + - + + diff --git a/tests/js/unit/globalTests.js b/tests/js/unit/globalTests.js new file mode 100644 index 00000000..98ce03db --- /dev/null +++ b/tests/js/unit/globalTests.js @@ -0,0 +1,5 @@ +Test.Modules.GLOBAL = { + 'test Kinetic version number': function(containerId) { + test(Kinetic.version === 'current', 'Kinetic.version should equal current'); + } +};