/** * KineticJS JavaScript Library core * http://www.kineticjs.com/ * Copyright 2012, Eric Rowell * Licensed under the MIT or GPL Version 2 licenses. * Date: Mar 19 2012 * * Copyright (C) 2011 - 2012 by Eric Rowell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /////////////////////////////////////////////////////////////////////// // Global Object /////////////////////////////////////////////////////////////////////// /** * Kinetic Namespace * @namespace */ var Kinetic = {}; /** * Kinetic Global Object * @property {Object} GlobalObjet */ Kinetic.GlobalObject = { stages: [], idCounter: 0, frame: { time: 0, timeDiff: 0, lastTime: 0 }, drag: { moving: false, node: undefined, offset: { x: 0, y: 0 } }, extend: function(obj1, obj2) { for(var key in obj2.prototype) { if(obj2.prototype.hasOwnProperty(key) && obj1.prototype[key] === undefined) { obj1.prototype[key] = obj2.prototype[key]; } } }, _isaCanvasAnimating: function() { for(var n = 0; n < this.stages.length; n++) { var stage = this.stages[n]; if(stage.isAnimating) { return true; } for(var i = 0; i < stage.children.length; i++) { var layer = stage.children[i]; if(layer.isTransitioning) { return true; } } } this.frame.lastTime = 0; return false; }, _endTransition: function() { var config = this.config; for(var key in config) { if(config.hasOwnProperty(key)) { if(config[key].x !== undefined || config[key].y !== undefined) { var propArray = ['x', 'y']; for(var n = 0; n < propArray.length; n++) { var prop = propArray[n]; if(config[key][prop] !== undefined) { this.node[key][prop] = config[key][prop]; } } } else { this.node[key] = config[key]; } } } }, _transitionPow: function(transition, key, prop, powFunc) { var pow = powFunc(); var config = transition.config; var timeDiff = this.frame.timeDiff; if(prop !== undefined) { var start = transition.starts[key][prop]; var change = config[key][prop] - start; var b = change / (Math.pow(config.duration * 1000, pow)); transition.node[key][prop] = b * Math.pow(transition.time, pow) + start; } else { var start = transition.starts[key]; var change = config[key] - start; var b = change / (Math.pow(config.duration * 1000, pow)); transition.node[key] = b * Math.pow(transition.time, pow) + start; } }, _chooseTransition: function(transition, key, prop) { var config = transition.config; var that = this; switch(config.easing) { case 'ease-in': this._transitionPow(transition, key, prop, function() { return 2.5; }); break; case 'ease-out': this._transitionPow(transition, key, prop, function() { return 0.4; }); break; case 'ease-in-out': this._transitionPow(transition, key, prop, function() { var change = -2.1; var b = change / (config.duration * 1000); return 2.5 + b * transition.time; }); break; // linear is default default: this._transitionPow(transition, key, prop, function() { return 1; }); break; } }, _runTransition: function(transition) { var config = transition.config; for(var key in config) { if(config.hasOwnProperty(key) && key !== 'duration' && key !== 'easing' && key !== 'callback') { if(config[key].x !== undefined || config[key].y !== undefined) { var propArray = ['x', 'y']; for(var n = 0; n < propArray.length; n++) { var prop = propArray[n]; if(config[key][prop] !== undefined) { this._chooseTransition(transition, key, prop); } } } else { this._chooseTransition(transition, key); } } } }, _removeTransition: function(transition) { var layer = transition.node.getLayer(); var id = transition.id; for(var n = 0; n < layer.transitions.length; n++) { if(layer.transitions[n].id === id) { layer.transitions.splice(0, 1); // exit loop n = layer.transitions.length; } } if(layer.transitions.length === 0) { layer.isTransitioning = false; } }, _runFrames: function() { for(var n = 0; n < this.stages.length; n++) { var stage = this.stages[n]; // run animation if available if(stage.isAnimating && stage.onFrameFunc !== undefined) { stage.onFrameFunc(this.frame); } // loop through layers var layers = stage.getChildren(); for(var k = 0; k < layers.length; k++) { var layer = layers[k]; var didTransition = false; // loop through transitions for(var i = 0; i < layer.transitions.length; i++) { didTransition = true; var transition = layer.transitions[i]; transition.time += this.frame.timeDiff; if(transition.time >= transition.config.duration * 1000) { this._endTransition.apply(transition); this._removeTransition(transition); if(transition.config.callback !== undefined) { transition.config.callback(); } } else { this._runTransition(transition); } } if(didTransition) { layer.draw(); } } } }, _updateFrameObject: function() { var date = new Date(); var time = date.getTime(); if(this.frame.lastTime === 0) { this.frame.lastTime = time; } else { this.frame.timeDiff = time - this.frame.lastTime; this.frame.lastTime = time; this.frame.time += this.frame.timeDiff; } }, _animationLoop: function() { if(this._isaCanvasAnimating()) { this._updateFrameObject(); this._runFrames(); var that = this; requestAnimFrame(function() { that._animationLoop(); }); } }, _handleAnimation: function() { var that = this; if(this._isaCanvasAnimating()) { that._animationLoop(); } } }; window.requestAnimFrame = (function(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); /////////////////////////////////////////////////////////////////////// // Node /////////////////////////////////////////////////////////////////////// /** * Node constructor.  Nodes are entities that can move around * and have events bound to them. They are the building blocks of a KineticJS * application * @constructor * @param {Object} config */ Kinetic.Node = function(config) { this.visible = true; this.isListening = true; this.name = undefined; this.alpha = 1; this.x = 0; this.y = 0; this.scale = { x: 1, y: 1 }; this.rotation = 0; this.centerOffset = { x: 0, y: 0 }; this.eventListeners = {}; this.dragConstraint = 'none'; this.dragBounds = {}; this._draggable = false; // set properties from config if(config) { for(var key in config) { // handle special keys switch (key) { case 'draggable': this.draggable(config[key]); break; case 'listen': this.listen(config[key]); break; case 'rotationDeg': this.rotation = config[key] * Math.PI / 180; break; default: this[key] = config[key]; break; } } } // overrides if(this.centerOffset.x === undefined) { this.centerOffset.x = 0; } if(this.centerOffset.y === undefined) { this.centerOffset.y = 0; } }; /* * Node methods */ Kinetic.Node.prototype = { /** * bind events to the node. KineticJS supports mouseover, mousemove, * mouseout, mousedown, mouseup, click, dblclick, touchstart, touchmove, * touchend, dbltap, dragstart, dragmove, and dragend. Pass in a string * of event types delimmited by a space to bind multiple events at once * such as 'mousedown mouseup mousemove'. include a namespace to bind an * event by name such as 'click.foobar'. * @param {String} typesStr * @param {function} handler */ on: function(typesStr, handler) { var types = typesStr.split(' '); /* * loop through types and attach event listeners to * each one. eg. 'click mouseover.namespace mouseout' * will create three event bindings */ for(var n = 0; n < types.length; n++) { var type = types[n]; var event = (type.indexOf('touch') === -1) ? 'on' + type : type; var parts = event.split('.'); var baseEvent = parts[0]; var name = parts.length > 1 ? parts[1] : ''; if(!this.eventListeners[baseEvent]) { this.eventListeners[baseEvent] = []; } this.eventListeners[baseEvent].push({ name: name, handler: handler }); } }, /** * remove event bindings from the node. Pass in a string of * event types delimmited by a space to remove multiple event * bindings at once such as 'mousedown mouseup mousemove'. * include a namespace to remove an event binding by name * such as 'click.foobar'. * @param {String} typesStr */ off: function(typesStr) { var types = typesStr.split(' '); for(var n = 0; n < types.length; n++) { var type = types[n]; var event = (type.indexOf('touch') === -1) ? 'on' + type : type; var parts = event.split('.'); var baseEvent = parts[0]; if(this.eventListeners[baseEvent] && parts.length > 1) { var name = parts[1]; for(var i = 0; i < this.eventListeners[baseEvent].length; i++) { if(this.eventListeners[baseEvent][i].name === name) { this.eventListeners[baseEvent].splice(i, 1); if(this.eventListeners[baseEvent].length === 0) { this.eventListeners[baseEvent] = undefined; } break; } } } else { this.eventListeners[baseEvent] = undefined; } } }, /** * show node */ show: function() { this.visible = true; }, /** * hide node */ hide: function() { this.visible = false; }, /** * get zIndex */ getZIndex: function() { return this.index; }, /** * set node scale. If only one parameter is passed in, * then both scaleX and scaleY are set with that parameter * @param {Number} scaleX * @param {Number} scaleY */ setScale: function(scaleX, scaleY) { if(scaleY) { this.scale.x = scaleX; this.scale.y = scaleY; } else { this.scale.x = scaleX; this.scale.y = scaleX; } }, /** * get scale */ getScale: function() { return this.scale; }, /** * set node position * @param {Number} x * @param {Number} y */ setPosition: function(x, y) { this.x = x; this.y = y; }, /** * get node position relative to container */ getPosition: function() { return { x: this.x, y: this.y }; }, /** * get absolute position relative to stage */ getAbsolutePosition: function() { var x = this.x; var y = this.y; var parent = this.getParent(); while(parent.className !== 'Stage') { x += parent.x; y += parent.y; parent = parent.parent; } return { x: x, y: y }; }, /** * move node by an amount * @param {Number} x * @param {Number} y */ move: function(x, y) { this.x += x; this.y += y; }, /** * set node rotation in radians * @param {Number} theta */ setRotation: function(theta) { this.rotation = theta; }, /** * set node rotation in degrees * @param {Number} deg */ setRotationDeg: function(deg) { this.rotation = (deg * Math.PI / 180); }, /** * get rotation in radians */ getRotation: function() { return this.rotation; }, /** * get rotation in degrees */ getRotationDeg: function() { return this.rotation * 180 / Math.PI; }, /** * rotate node by an amount in radians * @param {Number} theta */ rotate: function(theta) { this.rotation += theta; }, /** * rotate node by an amount in degrees * @param {Number} deg */ rotateDeg: function(deg) { this.rotation += (deg * Math.PI / 180); }, /** * listen or don't listen to events * @param {Boolean} isListening */ listen: function(isListening) { this.isListening = isListening; }, /** * move node to top */ moveToTop: function() { var index = this.index; this.parent.children.splice(index, 1); this.parent.children.push(this); this.parent._setChildrenIndices(); }, /** * move node up */ moveUp: function() { var index = this.index; this.parent.children.splice(index, 1); this.parent.children.splice(index + 1, 0, this); this.parent._setChildrenIndices(); }, /** * move node down */ moveDown: function() { var index = this.index; if(index > 0) { this.parent.children.splice(index, 1); this.parent.children.splice(index - 1, 0, this); this.parent._setChildrenIndices(); } }, /** * move node to bottom */ moveToBottom: function() { var index = this.index; this.parent.children.splice(index, 1); this.parent.children.unshift(this); this.parent._setChildrenIndices(); }, /** * set zIndex * @param {int} zIndex */ setZIndex: function(zIndex) { var index = this.index; this.parent.children.splice(index, 1); this.parent.children.splice(zIndex, 0, this); this.parent._setChildrenIndices(); }, /** * set alpha. Alpha values range from 0 to 1. * A node with an alpha of 0 is fully transparent, and a node * with an alpha of 1 is fully opaque * @param {Object} alpha */ setAlpha: function(alpha) { this.alpha = alpha; }, /** * get alpha. Alpha values range from 0 to 1. * A node with an alpha of 0 is fully transparent, and a node * with an alpha of 1 is fully opaque */ getAlpha: function() { return this.alpha; }, /** * get absolute alpha */ getAbsoluteAlpha: function() { var absAlpha = 1; var node = this; // traverse upwards while(node.className !== 'Stage') { absAlpha *= node.alpha; node = node.parent; } return absAlpha; }, /** * enable or disable drag and drop * @param {Boolean} isDraggable */ draggable: function(isDraggable) { if(this.draggable !== isDraggable) { if(isDraggable) { this._initDrag(); } else { this._dragCleanup(); } this._draggable = isDraggable; } }, /** * determine if node is currently in drag and drop mode */ isDragging: function() { var go = Kinetic.GlobalObject; return go.drag.node !== undefined && go.drag.node.id === this.id && go.drag.moving; }, /** * move node to another container * @param {Container} newContainer */ moveTo: function(newContainer) { var parent = this.parent; // remove from parent's children parent.children.splice(this.index, 1); parent._setChildrenIndices(); // add to new parent newContainer.children.push(this); this.index = newContainer.children.length - 1; this.parent = newContainer; newContainer._setChildrenIndices(); // update children hashes if(this.name) { parent.childrenNames[this.name] = undefined; newContainer.childrenNames[this.name] = this; } }, /** * get parent container */ getParent: function() { return this.parent; }, /** * get layer associated to node */ getLayer: function() { if(this.className === 'Layer') { return this; } else { return this.getParent().getLayer(); } }, /** * get stage associated to node */ getStage: function() { if(this.className === 'Stage') { return this; } else { return this.getParent().getStage(); } }, /** * get name */ getName: function() { return this.name; }, /** * set center offset * @param {Number} x * @param {Number} y */ setCenterOffset: function(x, y) { this.centerOffset.x = x; this.centerOffset.y = y; }, /** * get center offset */ getCenterOffset: function() { return this.centerOffset; }, /** * transition node to another state. Any property that can accept a real * number such as x, y, rotation, alpha, strokeWidth, radius, scale.x, scale.y, * centerOffset.x and centerOffset.y can be transitioned * @param {Object} config */ transitionTo: function(config) { var layer = this.getLayer(); var that = this; var duration = config.duration * 1000; var starts = {}; /* * clear transition if one is currenlty running. * This make it easy to start new transitions without * having to explicitly cancel old ones */ layer._clearTransition(this); for(var key in config) { if(config.hasOwnProperty(key) && key !== 'duration' && key !== 'easing' && key !== 'callback') { if(config[key].x !== undefined || config[key].y !== undefined) { starts[key] = {}; var propArray = ['x', 'y']; for(var n = 0; n < propArray.length; n++) { var prop = propArray[n]; if(config[key][prop] !== undefined) { starts[key][prop] = this[key][prop]; } } } else { starts[key] = this[key]; } } } layer.transitions.push({ id: layer.transitionIdCounter++, time: 0, config: config, node: this, starts: starts }); layer.isTransitioning = true; Kinetic.GlobalObject._handleAnimation(); }, /** * set drag constraint * @param {String} constraint */ setDragConstraint: function(constraint) { this.dragConstraint = constraint; }, /** * get drag constraint */ getDragConstraint: function() { return this.dragConstraint; }, /** * set drag bounds * @param {Object} bounds * @config {Number} [left] left bounds position * @config {Number} [top] top bounds position * @config {Number} [right] right bounds position * @config {Number} [bottom] bottom bounds position */ setDragBounds: function(bounds) { this.dragBounds = bounds; }, /** * get drag bounds */ getDragBounds: function() { return this.dragBounds; }, /** * initialize drag and drop */ _initDrag: function() { var go = Kinetic.GlobalObject; var that = this; this.on('mousedown.initdrag touchstart.initdrag', function(evt) { var stage = that.getStage(); var pos = stage.getUserPosition(); if(pos) { go.drag.node = that; go.drag.offset.x = pos.x - that.x; go.drag.offset.y = pos.y - that.y; } }); }, /** * remove drag and drop event listener */ _dragCleanup: function() { this.off('mousedown.initdrag'); this.off('touchstart.initdrag'); }, /** * handle node events * @param {String} eventType * @param {Event} evt */ _handleEvents: function(eventType, evt) { var stage = this.getStage(); this._handleEvent(this, stage.mouseoverShape, stage.mouseoutShape, eventType, evt); }, /** * handle node event */ _handleEvent: function(node, mouseoverNode, mouseoutNode, eventType, evt) { var el = node.eventListeners; var okayToRun = true; /* * determine if event handler should be skipped by comparing * parent nodes */ if(eventType === 'onmouseover' && mouseoutNode && mouseoutNode.id === node.id) { okayToRun = false; } else if(eventType === 'onmouseout' && mouseoverNode && mouseoverNode.id === node.id) { okayToRun = false; } if(el[eventType] && okayToRun) { var events = el[eventType]; for(var i = 0; i < events.length; i++) { events[i].handler.apply(node, [evt]); } } var mouseoverParent = mouseoverNode ? mouseoverNode.parent : undefined; var mouseoutParent = mouseoutNode ? mouseoutNode.parent : undefined; // simulate event bubbling if(!evt.cancelBubble && node.parent.className !== 'Stage') { this._handleEvent(node.parent, mouseoverParent, mouseoutParent, eventType, evt); } } }; /////////////////////////////////////////////////////////////////////// // Container /////////////////////////////////////////////////////////////////////// /** * Container constructor.  Containers are used to contain nodes or other containers * @constructor */ Kinetic.Container = function() { this.children = []; this.childrenNames = {}; }; /* * Container methods */ Kinetic.Container.prototype = { /** * get children */ getChildren: function() { return this.children; }, /** * get child node by name * @param {String} name */ getChild: function(name) { return this.childrenNames[name]; }, /** * remove all children */ removeChildren: function() { while(this.children.length > 0) { this.remove(this.children[0]); } }, /** * remove child from container * @param {Node} child */ _remove: function(child) { if(child.name !== undefined) { this.childrenNames[child.name] = undefined; } this.children.splice(child.index, 1); this._setChildrenIndices(); child = undefined; }, /** * draw children */ _drawChildren: function() { var children = this.children; for(var n = 0; n < children.length; n++) { var child = children[n]; if(child.className === 'Shape') { child._draw(child.getLayer()); } else { child._draw(); } } }, /** * add node to container * @param {Node} child */ _add: function(child) { if(child.name) { this.childrenNames[child.name] = child; } child.id = Kinetic.GlobalObject.idCounter++; child.index = this.children.length; child.parent = this; this.children.push(child); }, /** * set children indices */ _setChildrenIndices: function() { /* * if reordering Layers, remove all canvas elements * from the container except the buffer and backstage canvases * and then readd all the layers */ if(this.className === 'Stage') { var canvases = this.content.children; var bufferCanvas = canvases[0]; var backstageCanvas = canvases[1]; this.content.innerHTML = ''; this.content.appendChild(bufferCanvas); this.content.appendChild(backstageCanvas); } for(var n = 0; n < this.children.length; n++) { this.children[n].index = n; if(this.className === 'Stage') { this.content.appendChild(this.children[n].canvas); } } } }; /////////////////////////////////////////////////////////////////////// // Stage /////////////////////////////////////////////////////////////////////// /** * Stage constructor. A stage is used to contain multiple layers and handle * animations * @constructor * @augments Kinetic.Container * @param {String|DomElement} cont Container id or DOM element * @param {int} width * @param {int} height */ Kinetic.Stage = function(cont, width, height) { this.className = 'Stage'; this.container = typeof cont === 'string' ? document.getElementById(cont) : cont; this.content = document.createElement('div'); this.width = width; this.height = height; this.scale = { x: 1, y: 1 }; this.dblClickWindow = 400; this.clickStart = false; this.targetShape = undefined; this.targetFound = false; this.mouseoverShape = undefined; this.mouseoutShape = undefined; // desktop flags this.mousePos = undefined; this.mouseDown = false; this.mouseUp = false; // mobile flags this.touchPos = undefined; this.touchStart = false; this.touchEnd = false; // set stage id this.id = Kinetic.GlobalObject.idCounter++; // animation support this.isAnimating = false; this.onFrameFunc = undefined; this._buildDOM(); this._listen(); this._prepareDrag(); // add stage to global object Kinetic.GlobalObject.stages.push(this); // call super constructor Kinetic.Container.apply(this, []); }; /* * Stage methods */ Kinetic.Stage.prototype = { /** * sets onFrameFunc for animation * @param {function} func */ onFrame: function(func) { this.onFrameFunc = func; }, /** * start animation */ start: function() { this.isAnimating = true; Kinetic.GlobalObject._handleAnimation(); }, /** * stop animation */ stop: function() { this.isAnimating = false; Kinetic.GlobalObject._handleAnimation(); }, /** * draw children */ draw: function() { this._drawChildren(); }, /** * set stage size * @param {int} width * @param {int} height */ setSize: function(width, height) { var layers = this.children; for(var n = 0; n < layers.length; n++) { var layer = layers[n]; layer.getCanvas().width = width; layer.getCanvas().height = height; layer.draw(); } // set stage dimensions this.width = width; this.height = height; // set buffer layer and backstage layer sizes this.bufferLayer.getCanvas().width = width; this.bufferLayer.getCanvas().height = height; this.backstageLayer.getCanvas().width = width; this.backstageLayer.getCanvas().height = height; }, /** * set stage scale. If only one parameter is passed in, then * both scaleX and scaleY are set to the parameter * @param {int} scaleX * @param {int} scaleY */ setScale: function(scaleX, scaleY) { var oldScaleX = this.scale.x; var oldScaleY = this.scale.y; if(scaleY) { this.scale.x = scaleX; this.scale.y = scaleY; } else { this.scale.x = scaleX; this.scale.y = scaleX; } /* * scale all shape positions */ var layers = this.children; var that = this; function scaleChildren(children) { for(var i = 0; i < children.length; i++) { var child = children[i]; child.x *= that.scale.x / oldScaleX; child.y *= that.scale.y / oldScaleY; if(child.children) { scaleChildren(child.children); } } } scaleChildren(layers); }, /** * get scale */ getScale: function() { return this.scale; }, /** * clear all layers */ clear: function() { var layers = this.children; for(var n = 0; n < layers.length; n++) { layers[n].clear(); } }, /** * creates a composite data URL and passes it to a callback * @param {function} callback */ toDataURL: function(callback) { var bufferLayer = this.bufferLayer; var bufferContext = bufferLayer.getContext(); var layers = this.children; function addLayer(n) { var dataURL = layers[n].getCanvas().toDataURL(); var imageObj = new Image(); imageObj.onload = function() { bufferContext.drawImage(this, 0, 0); n++; if(n < layers.length) { addLayer(n); } else { callback(bufferLayer.getCanvas().toDataURL()); } }; imageObj.src = dataURL; } bufferLayer.clear(); addLayer(0); }, /** * remove layer from stage * @param {Layer} layer */ remove: function(layer) { // remove layer canvas from dom this.content.removeChild(layer.canvas); this._remove(layer); }, /** * bind event listener to stage (which is essentially * the container DOM) * @param {String} typesStr * @param {function} handler */ on: function(typesStr, handler) { var types = typesStr.split(' '); for(var n = 0; n < types.length; n++) { var baseEvent = types[n]; this.container.addEventListener(baseEvent, handler, false); } }, /** * add layer to stage * @param {Layer} layer */ add: function(layer) { if(layer.name) { this.childrenNames[layer.name] = layer; } layer.canvas.width = this.width; layer.canvas.height = this.height; this._add(layer); // draw layer and append canvas to container layer.draw(); this.content.appendChild(layer.canvas); }, /** * get mouse position for desktop apps * @param {Event} evt */ getMousePosition: function(evt) { return this.mousePos; }, /** * get touch position for mobile apps * @param {Event} evt */ getTouchPosition: function(evt) { return this.touchPos; }, /** * get user position (mouse position or touch position) * @param {Event} evt */ getUserPosition: function(evt) { return this.getTouchPosition() || this.getMousePosition(); }, /** * get container DOM element */ getContainer: function() { return this.container; }, /** * get stage */ getStage: function() { return this; }, /** * detect event * @param {Shape} shape */ _detectEvent: function(shape, evt) { var isDragging = Kinetic.GlobalObject.drag.moving; var backstageLayer = this.backstageLayer; var backstageLayerContext = backstageLayer.getContext(); var go = Kinetic.GlobalObject; var pos = this.getUserPosition(); var el = shape.eventListeners; shape._draw(backstageLayer); if(this.targetShape && shape.id === this.targetShape.id) { this.targetFound = true; } if(shape.visible && pos !== undefined && backstageLayerContext.isPointInPath(pos.x, pos.y)) { // handle onmousedown if(!isDragging && this.mouseDown) { this.mouseDown = false; this.clickStart = true; shape._handleEvents('onmousedown', evt); return true; } // handle onmouseup & onclick else if(this.mouseUp) { this.mouseUp = false; shape._handleEvents('onmouseup', evt); // detect if click or double click occurred if(this.clickStart) { /* * if dragging and dropping, don't fire click or dbl click * event */ if((!go.drag.moving) || !go.drag.node) { shape._handleEvents('onclick', evt); if(shape.inDoubleClickWindow) { shape._handleEvents('ondblclick', evt); } shape.inDoubleClickWindow = true; setTimeout(function() { shape.inDoubleClickWindow = false; }, this.dblClickWindow); } } return true; } // handle touchstart else if(this.touchStart) { this.touchStart = false; shape._handleEvents('touchstart', evt); if(el.ondbltap && shape.inDoubleClickWindow) { var events = el.ondbltap; for(var i = 0; i < events.length; i++) { events[i].handler.apply(shape, [evt]); } } shape.inDoubleClickWindow = true; setTimeout(function() { shape.inDoubleClickWindow = false; }, this.dblClickWindow); return true; } // handle touchend else if(this.touchEnd) { this.touchEnd = false; shape._handleEvents('touchend', evt); return true; } /* * NOTE: these event handlers require target shape * handling */ // handle onmouseover else if(!isDragging && this._isNewTarget(shape, evt)) { /* * check to see if there are stored mouseout events first. * if there are, run those before running the onmouseover * events */ if(this.mouseoutShape) { this.mouseoverShape = shape; this.mouseoutShape._handleEvents('onmouseout', evt); this.mouseoverShape = undefined; } shape._handleEvents('onmouseover', evt); this._setTarget(shape); return true; } // handle mousemove and touchmove else if(!isDragging) { shape._handleEvents('onmousemove', evt); shape._handleEvents('touchmove', evt); return true; } } // handle mouseout condition else if(!isDragging && this.targetShape && this.targetShape.id === shape.id) { this._setTarget(undefined); this.mouseoutShape = shape; //shape._handleEvents('onmouseout', evt); return true; } return false; }, /** * set new target */ _setTarget: function(shape) { this.targetShape = shape; this.targetFound = true; }, /** * check if shape should be a new target */ _isNewTarget: function(shape, evt) { if(!this.targetShape || (!this.targetFound && shape.id !== this.targetShape.id)) { /* * check if old target has an onmouseout event listener */ if(this.targetShape) { var oldEl = this.targetShape.eventListeners; if(oldEl) { this.mouseoutShape = this.targetShape; //this.targetShape._handleEvents('onmouseout', evt); } } return true; } else { return false; } }, /** * traverse container children * @param {Container} obj */ _traverseChildren: function(obj, evt) { var children = obj.children; // propapgate backwards through children for(var i = children.length - 1; i >= 0; i--) { var child = children[i]; if(child.className === 'Shape') { var exit = this._detectEvent(child, evt); if(exit) { return true; } } else { var exit = this._traverseChildren(child, evt); if(exit) { return true; } } } return false; }, /** * handle incoming event * @param {Event} evt */ _handleEvent: function(evt) { var go = Kinetic.GlobalObject; if(!evt) { evt = window.event; } this._setMousePosition(evt); this._setTouchPosition(evt); var backstageLayer = this.backstageLayer; backstageLayer.clear(); /* * loop through layers. If at any point an event * is triggered, n is set to -1 which will break out of the * three nested loops */ this.targetFound = false; var shapeDetected = false; for(var n = this.children.length - 1; n >= 0; n--) { var layer = this.children[n]; if(layer.visible && n >= 0 && layer.isListening) { if(this._traverseChildren(layer, evt)) { n = -1; shapeDetected = true; } } } /* * if no shape was detected and a mouseout shape has been stored, * then run the onmouseout event handlers */ if(!shapeDetected && this.mouseoutShape) { this.mouseoutShape._handleEvents('onmouseout', evt); this.mouseoutShape = undefined; } }, /** * begin listening for events by adding event handlers * to the container */ _listen: function() { var that = this; // desktop events this.container.addEventListener('mousedown', function(evt) { that.mouseDown = true; that._handleEvent(evt); }, false); this.container.addEventListener('mousemove', function(evt) { that.mouseUp = false; that.mouseDown = false; that._handleEvent(evt); }, false); this.container.addEventListener('mouseup', function(evt) { that.mouseUp = true; that.mouseDown = false; that._handleEvent(evt); that.clickStart = false; }, false); this.container.addEventListener('mouseover', function(evt) { that._handleEvent(evt); }, false); this.container.addEventListener('mouseout', function(evt) { that.mousePos = undefined; }, false); // mobile events this.container.addEventListener('touchstart', function(evt) { evt.preventDefault(); that.touchStart = true; that._handleEvent(evt); }, false); this.container.addEventListener('touchmove', function(evt) { evt.preventDefault(); that._handleEvent(evt); }, false); this.container.addEventListener('touchend', function(evt) { evt.preventDefault(); that.touchEnd = true; that._handleEvent(evt); }, false); }, /** * set mouse positon for desktop apps * @param {Event} evt */ _setMousePosition: function(evt) { var mouseX = evt.clientX - this._getContainerPosition().left + window.pageXOffset; var mouseY = evt.clientY - this._getContainerPosition().top + window.pageYOffset; 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) {// Only deal with // one finger var touch = evt.touches[0]; // Get the information for finger #1 var touchX = touch.clientX - this._getContainerPosition().left + window.pageXOffset; var touchY = touch.clientY - this._getContainerPosition().top + window.pageYOffset; this.touchPos = { x: touchX, y: touchY }; } }, /** * get container position */ _getContainerPosition: function() { var obj = this.container; var top = 0; var left = 0; while(obj && obj.tagName !== 'BODY') { top += obj.offsetTop - obj.scrollTop; left += obj.offsetLeft - obj.scrollLeft; obj = obj.offsetParent; } return { top: top, left: left }; }, /** * disable layer rendering * @param {Layer} layer */ _stripLayer: function(layer) { layer.context.stroke = function() { }; layer.context.fill = function() { }; layer.context.fillRect = function(x, y, width, height) { layer.context.rect(x, y, width, height); }; layer.context.strokeRect = function(x, y, width, height) { layer.context.rect(x, y, width, height); }; layer.context.drawImage = function() { }; layer.context.fillText = function() { }; layer.context.strokeText = function() { }; }, /** * end drag and drop */ _endDrag: function(evt) { var go = Kinetic.GlobalObject; if(go.drag.node) { if(go.drag.moving) { go.drag.moving = false; go.drag.node._handleEvents('ondragend', evt); } } go.drag.node = undefined; }, /** * prepare drag and drop */ _prepareDrag: function() { var that = this; this.on('mousemove touchmove', function(evt) { var go = Kinetic.GlobalObject; var node = go.drag.node; if(node) { var pos = that.getUserPosition(); var ds = node.dragConstraint; var db = node.dragBounds; if(ds === 'none' || ds === 'horizontal') { var newX = pos.x - go.drag.offset.x; if((db.left === undefined || db.left < newX) && (db.right === undefined || db.right > newX)) { node.x = newX; } } if(ds === 'none' || ds === 'vertical') { var newY = pos.y - go.drag.offset.y; if((db.top === undefined || db.top < newY) && (db.bottom === undefined || db.bottom > newY)) { node.y = newY; } } go.drag.node.getLayer().draw(); if(!go.drag.moving) { go.drag.moving = true; // execute dragstart events if defined go.drag.node._handleEvents('ondragstart', evt); } // execute user defined ondragmove if defined go.drag.node._handleEvents('ondragmove', evt); } }, false); this.on('mouseup touchend mouseout', function(evt) { that._endDrag(evt); }); }, /** * build dom */ _buildDOM: function() { // content this.content.style.width = this.width + 'px'; this.content.style.height = this.height + 'px'; this.content.style.position = 'relative'; this.content.style.display = 'inline-block'; this.content.className = 'kineticjs-content'; this.container.appendChild(this.content); // default layers this.bufferLayer = new Kinetic.Layer(); this.backstageLayer = new Kinetic.Layer(); // set parents this.bufferLayer.parent = this; this.backstageLayer.parent = this; // customize back stage context this._stripLayer(this.backstageLayer); this.bufferLayer.getCanvas().style.display = 'none'; this.backstageLayer.getCanvas().style.display = 'none'; // add buffer layer this.bufferLayer.canvas.width = this.width; this.bufferLayer.canvas.height = this.height; this.content.appendChild(this.bufferLayer.canvas); // add backstage layer this.backstageLayer.canvas.width = this.width; this.backstageLayer.canvas.height = this.height; this.content.appendChild(this.backstageLayer.canvas); } }; // extend Container Kinetic.GlobalObject.extend(Kinetic.Stage, Kinetic.Container); /////////////////////////////////////////////////////////////////////// // Layer /////////////////////////////////////////////////////////////////////// /** * Layer constructor. Layers are tied to their own canvas element and are used * to contain groups or shapes * @constructor * @augments Kinetic.Container * @augments Kinetic.Node * @param {Object} config */ Kinetic.Layer = function(config) { this.className = 'Layer'; this.canvas = document.createElement('canvas'); this.context = this.canvas.getContext('2d'); this.canvas.style.position = 'absolute'; this.transitions = []; this.transitionIdCounter = 0; this.isTransitioning = false; // call super constructors Kinetic.Container.apply(this, []); Kinetic.Node.apply(this, [config]); }; /* * Layer methods */ Kinetic.Layer.prototype = { /** * public draw children */ draw: function() { this._draw(); }, /** * clear layer */ clear: function() { var context = this.getContext(); var canvas = this.getCanvas(); context.clearRect(0, 0, canvas.width, canvas.height); }, /** * get layer canvas */ getCanvas: function() { return this.canvas; }, /** * get layer context */ getContext: function() { return this.context; }, /** * add node to layer * @param {Node} node */ add: function(child) { this._add(child); }, /** * remove a child from the layer * @param {Node} child */ remove: function(child) { this._remove(child); }, /** * private draw children */ _draw: function() { this.clear(); if(this.visible) { this._drawChildren(); } }, /** * clear transition if one is running */ _clearTransition: function(shape) { for(var n = 0; n < this.transitions.length; n++) { var transition = this.transitions[n]; if(transition.node.id === shape.id) { Kinetic.GlobalObject._removeTransition(transition); } } } }; // Extend Container and Node Kinetic.GlobalObject.extend(Kinetic.Layer, Kinetic.Container); Kinetic.GlobalObject.extend(Kinetic.Layer, Kinetic.Node); /////////////////////////////////////////////////////////////////////// // Group /////////////////////////////////////////////////////////////////////// /** * Group constructor. Groups are used to contain shapes or other groups. * @constructor * @augments Kinetic.Container * @augments Kinetic.Node * @param {Object} config */ Kinetic.Group = function(config) { this.className = 'Group'; // call super constructors Kinetic.Container.apply(this, []); Kinetic.Node.apply(this, [config]); }; /* * Group methods */ Kinetic.Group.prototype = { /** * add node to group * @param {Node} child */ add: function(child) { this._add(child); }, /** * remove a child node from the group * @param {Node} child */ remove: function(child) { this._remove(child); }, /** * draw children */ _draw: function() { if(this.visible) { this._drawChildren(); } } }; // Extend Container and Node Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Container); Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Node); /////////////////////////////////////////////////////////////////////// // Shape /////////////////////////////////////////////////////////////////////// /** * Shape constructor. Shapes are used to objectify drawing bits of a KineticJS * application * @constructor * @augments Kinetic.Node * @param {Object} config */ Kinetic.Shape = function(config) { this.className = 'Shape'; // defaults if(config.stroke !== undefined || config.strokeWidth !== undefined) { if(config.stroke === undefined) { config.stroke = 'black'; } else if(config.strokeWidth === undefined) { config.strokeWidth = 2; } } // required this.drawFunc = config.drawFunc; // call super constructor Kinetic.Node.apply(this, [config]); }; /* * Shape methods */ Kinetic.Shape.prototype = { /** * get layer context where the shape is being drawn. When * the shape is being rendered, .getContext() returns the context of the * user created layer that contains the shape. When the event detection * engine is determining whether or not an event has occured on that shape, * .getContext() returns the context of the invisible backstage layer. */ getContext: function() { return this.tempLayer.getContext(); }, /** * get shape temp layer canvas */ getCanvas: function() { return this.tempLayer.getCanvas(); }, /** * helper method to fill and stroke a shape based on its * fill, stroke, and strokeWidth properties */ fillStroke: function() { var context = this.getContext(); if(this.fill !== undefined) { context.fillStyle = this.fill; context.fill(); } if(this.stroke !== undefined) { context.lineWidth = this.strokeWidth === undefined ? 1 : this.strokeWidth; context.strokeStyle = this.stroke; context.stroke(); } }, /** * set fill which can be a color, gradient object, * or pattern object * @param {String|CanvasGradient|CanvasPattern} fill */ setFill: function(fill) { this.fill = fill; }, /** * get fill */ getFill: function() { return this.fill; }, /** * set stroke color * @param {String} stroke */ setStroke: function(stroke) { this.stroke = stroke; }, /** * get stroke color */ getStroke: function() { return this.stroke; }, /** * set stroke width * @param {Number} strokeWidth */ setStrokeWidth: function(strokeWidth) { this.strokeWidth = strokeWidth; }, /** * get stroke width */ getStrokeWidth: function() { return this.strokeWidth; }, /** * draw shape * @param {Layer} layer Layer that the shape will be drawn on */ _draw: function(layer) { if(this.visible) { var stage = layer.getStage(); var context = layer.getContext(); var family = []; family.unshift(this); var parent = this.parent; while(parent.className !== 'Stage') { family.unshift(parent); parent = parent.parent; } // children transforms for(var n = 0; n < family.length; n++) { var obj = family[n]; context.save(); if(obj.x !== 0 || obj.y !== 0) { context.translate(obj.x, obj.y); } if(obj.centerOffset.x !== 0 || obj.centerOffset.y !== 0) { context.translate(obj.centerOffset.x, obj.centerOffset.y); } if(obj.rotation !== 0) { context.rotate(obj.rotation); } if(obj.scale.x !== 1 || obj.scale.y !== 1) { context.scale(obj.scale.x, obj.scale.y); } if(obj.centerOffset.x !== 0 || obj.centerOffset.y !== 0) { context.translate(-1 * obj.centerOffset.x, -1 * obj.centerOffset.y); } if(obj.getAbsoluteAlpha() !== 1) { context.globalAlpha = obj.getAbsoluteAlpha(); } } // stage transform context.save(); if(stage && (stage.scale.x !== 1 || stage.scale.y !== 1)) { context.scale(stage.scale.x, stage.scale.y); } this.tempLayer = layer; this.drawFunc.call(this); // children restore for(var i = 0; i < family.length; i++) { context.restore(); } // stage restore context.restore(); } } }; // extend Node Kinetic.GlobalObject.extend(Kinetic.Shape, Kinetic.Node); /////////////////////////////////////////////////////////////////////// // Rect /////////////////////////////////////////////////////////////////////// /** * Rect constructor * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.Rect = function(config) { config.drawFunc = function() { var context = this.getContext(); context.beginPath(); context.rect(0, 0, this.width, this.height); context.closePath(); this.fillStroke(); }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * Rect methods */ Kinetic.Rect.prototype = { /** * set width * @param {Number} width */ setWidth: function(width) { this.width = width; }, /** * get width */ getWidth: function() { return this.width; }, /** * set height * @param {Number} height */ setHeight: function(height) { this.height = height; }, /** * get height */ getHeight: function() { return this.height; }, /** * set width and height * @param {Number} width * @param {Number} height */ setSize: function(width, height) { this.width = width; this.height = height; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Rect, Kinetic.Shape); /////////////////////////////////////////////////////////////////////// // Circle /////////////////////////////////////////////////////////////////////// /** * Circle constructor * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.Circle = function(config) { config.drawFunc = function() { var canvas = this.getCanvas(); var context = this.getContext(); context.beginPath(); context.arc(0, 0, this.radius, 0, Math.PI * 2, true); context.closePath(); this.fillStroke(); }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * Circle methods */ Kinetic.Circle.prototype = { /** * set radius * @param {Number} radius */ setRadius: function(radius) { this.radius = radius; }, /** * get radius */ getRadius: function() { return this.radius; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Circle, Kinetic.Shape); /////////////////////////////////////////////////////////////////////// // Image /////////////////////////////////////////////////////////////////////// /** * Image constructor * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.Image = function(config) { // defaults if(config.width === undefined) { config.width = config.image.width; } if(config.height === undefined) { config.height = config.image.height; } config.drawFunc = function() { var canvas = this.getCanvas(); var context = this.getContext(); context.beginPath(); context.rect(0, 0, this.width, this.height); context.closePath(); this.fillStroke(); context.drawImage(this.image, 0, 0, this.width, this.height); }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * Image methods */ Kinetic.Image.prototype = { /** * set image * @param {ImageObject} image */ setImage: function(image) { this.image = image; }, /** * get image */ getImage: function() { return this.image; }, /** * set width * @param {Number} width */ setWidth: function(width) { this.width = width; }, /** * get width */ getWidth: function() { return this.width; }, /** * set height * @param {Number} height */ setHeight: function(height) { this.height = height; }, /** * get height */ getHeight: function() { return this.height; }, /** * set width and height * @param {Number} width * @param {Number} height */ setSize: function(width, height) { this.width = width; this.height = height; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Image, Kinetic.Shape); /////////////////////////////////////////////////////////////////////// // Polygon /////////////////////////////////////////////////////////////////////// /** * Polygon constructor.  Polygons are defined by an array of points * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.Polygon = function(config) { config.drawFunc = function() { var context = this.getContext(); context.beginPath(); context.moveTo(this.points[0].x, this.points[0].y); for(var n = 1; n < this.points.length; n++) { context.lineTo(this.points[n].x, this.points[n].y); } context.closePath(); this.fillStroke(); }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * Polygon methods */ Kinetic.Polygon.prototype = { /** * set points array * @param {Array} points */ setPoints: function(points) { this.points = points; }, /** * get points array */ getPoints: function() { return this.points; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Polygon, Kinetic.Shape); /////////////////////////////////////////////////////////////////////// // RegularPolygon /////////////////////////////////////////////////////////////////////// /** * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.RegularPolygon = function(config) { config.drawFunc = function() { var context = this.getContext(); context.beginPath(); context.moveTo(0, 0 - this.radius); for(var n = 1; n < this.sides; n++) { var x = this.radius * Math.sin(n * 2 * Math.PI / this.sides); var y = -1 * this.radius * Math.cos(n * 2 * Math.PI / this.sides); context.lineTo(x, y); } context.closePath(); this.fillStroke(); }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * RegularPolygon methods */ Kinetic.RegularPolygon.prototype = { /** * set number of points * @param {int} points */ setPoints: function(points) { this.points = points; }, /** * get number of points */ getPoints: function() { return this.points; }, /** * set radius * @param {Number} radius */ setRadius: function(radius) { this.radius = radius; }, /** * get radius */ getRadius: function() { return this.radius; }, /** * set number of sides * @param {int} sides */ setSides: function(sides) { this.sides = sides; }, /** * get number of sides */ getSides: function() { return this.sides; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.RegularPolygon, Kinetic.Shape); /////////////////////////////////////////////////////////////////////// // Star /////////////////////////////////////////////////////////////////////// /** * Star constructor * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.Star = function(config) { config.drawFunc = function() { var context = this.getContext(); context.beginPath(); context.moveTo(0, 0 - this.outerRadius); for(var n = 1; n < this.points * 2; n++) { var radius = n % 2 === 0 ? this.outerRadius : this.innerRadius; var x = radius * Math.sin(n * Math.PI / this.points); var y = -1 * radius * Math.cos(n * Math.PI / this.points); context.lineTo(x, y); } context.closePath(); this.fillStroke(); }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * Star methods */ Kinetic.Star.prototype = { /** * set points array * @param {Array} points */ setPoints: function(points) { this.points = points; }, /** * get points array */ getPoints: function() { return this.points; }, /** * set outer radius * @param {Number} radius */ setOuterRadius: function(radius) { this.outerRadius = radius; }, /** * get outer radius */ getOuterRadius: function() { return this.outerRadius; }, /** * set inner radius * @param {Number} radius */ setInnerRadius: function(radius) { this.innerRadius = radius; }, /** * get inner radius */ getInnerRadius: function() { return this.innerRadius; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Star, Kinetic.Shape); /////////////////////////////////////////////////////////////////////// // Text /////////////////////////////////////////////////////////////////////// /** * Text constructor * @constructor * @augments Kinetic.Shape * @param {Object} config */ Kinetic.Text = function(config) { /* * defaults */ if(config.textStroke !== undefined || config.textStrokeWidth !== undefined) { if(config.textStroke === undefined) { config.textStroke = 'black'; } else if(config.textStrokeWidth === undefined) { config.textStrokeWidth = 2; } } if(config.align === undefined) { config.align = 'left'; } if(config.verticalAlign === undefined) { config.verticalAlign = 'top'; } if(config.padding === undefined) { config.padding = 0; } config.drawFunc = function() { var context = this.getContext(); context.font = this.fontSize + 'pt ' + this.fontFamily; context.textBaseline = 'middle'; var metrics = context.measureText(this.text); var textHeight = textHeight = parseInt(this.fontSize, 10); var textWidth = metrics.width; var p = this.padding; var x = 0; var y = 0; switch (this.align) { case 'center': x = textWidth / -2 - p; break; case 'right': x = -1 * textWidth - p; break; } switch (this.verticalAlign) { case 'middle': y = textHeight / -2 - p; break; case 'bottom': y = -1 * textHeight - p; break; } // draw path context.save(); context.beginPath(); context.rect(x, y, textWidth + p * 2, textHeight + p * 2); context.closePath(); this.fillStroke(); context.restore(); var tx = p + x; var ty = textHeight / 2 + p + y; // draw text if(this.textFill !== undefined) { context.fillStyle = this.textFill; context.fillText(this.text, tx, ty); } if(this.textStroke !== undefined || this.textStrokeWidth !== undefined) { // defaults if(this.textStroke === undefined) { this.textStroke = 'black'; } else if(this.textStrokeWidth === undefined) { this.textStrokeWidth = 2; } context.lineWidth = this.textStrokeWidth; context.strokeStyle = this.textStroke; context.strokeText(this.text, tx, ty); } }; // call super constructor Kinetic.Shape.apply(this, [config]); }; /* * Text methods */ Kinetic.Text.prototype = { /** * set font family * @param {String} fontFamily */ setFontFamily: function(fontFamily) { this.fontFamily = fontFamily; }, /** * get font family */ getFontFamily: function() { return this.fontFamily; }, /** * set font size * @param {int} fontSize */ setFontSize: function(fontSize) { this.fontSize = fontSize; }, /** * get font size */ getFontSize: function() { return this.fontSize; }, /** * set text fill color * @param {String} textFill */ setTextFill: function(textFill) { this.textFill = textFill; }, /** * get text fill color */ getTextFill: function() { return this.textFill; }, /** * set text stroke color * @param {String} textStroke */ setTextStroke: function(textStroke) { this.textStroke = textStroke; }, /** * get text stroke color */ getTextStroke: function() { return this.textStroke; }, /** * set text stroke width * @param {int} textStrokeWidth */ setTextStrokeWidth: function(textStrokeWidth) { this.textStrokeWidth = textStrokeWidth; }, /** * get text stroke width */ getTextStrokeWidth: function() { return this.textStrokeWidth; }, /** * set padding * @param {int} padding */ setPadding: function(padding) { this.padding = padding; }, /** * get padding */ getPadding: function() { return this.padding; }, /** * set horizontal align of text * @param {String} align align can be 'left', 'center', or 'right' */ setAlign: function(align) { this.align = align; }, /** * get horizontal align */ getAlign: function() { return this.align; }, /** * set vertical align of text * @param {String} verticalAlign verticalAlign can be "top", "middle", or "bottom" */ setVerticalAlign: function(verticalAlign) { this.verticalAlign = verticalAlign; }, /** * get vertical align */ getVerticalAlign: function() { return this.verticalAlign; }, /** * set text * @param {String} text */ setText: function(text) { this.text = text; }, /** * get text */ getText: function() { return this.text; } }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Shape);