1 /**
  2  * KineticJS JavaScript Library v3.9.4
  3  * http://www.kineticjs.com/
  4  * Copyright 2012, Eric Rowell
  5  * Licensed under the MIT or GPL Version 2 licenses.
  6  * Date: Apr 28 2012
  7  *
  8  * Copyright (C) 2011 - 2012 by Eric Rowell
  9  *
 10  * Permission is hereby granted, free of charge, to any person obtaining a copy
 11  * of this software and associated documentation files (the "Software"), to deal
 12  * in the Software without restriction, including without limitation the rights
 13  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 14  * copies of the Software, and to permit persons to whom the Software is
 15  * furnished to do so, subject to the following conditions:
 16  *
 17  * The above copyright notice and this permission notice shall be included in
 18  * all copies or substantial portions of the Software.
 19  *
 20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 25  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 26  * THE SOFTWARE.
 27  */
 28 
 29 ///////////////////////////////////////////////////////////////////////
 30 //  Global Object
 31 ///////////////////////////////////////////////////////////////////////
 32 /**
 33  * Kinetic Namespace
 34  * @namespace
 35  */
 36 var Kinetic = {};
 37 /**
 38  * Kinetic Global Object
 39  * @property {Object} GlobalObjet
 40  */
 41 Kinetic.GlobalObject = {
 42     stages: [],
 43     idCounter: 0,
 44     tempNodes: [],
 45     animations: [],
 46     animIdCounter: 0,
 47     dragTimeInterval: 0,
 48     maxDragTimeInterval: 20,
 49     frame: {
 50         time: 0,
 51         timeDiff: 0,
 52         lastTime: 0
 53     },
 54     drag: {
 55         moving: false,
 56         node: undefined,
 57         offset: {
 58             x: 0,
 59             y: 0
 60         },
 61         lastDrawTime: 0
 62     },
 63     extend: function(obj1, obj2) {
 64         for(var key in obj2.prototype) {
 65             if(obj2.prototype.hasOwnProperty(key) && obj1.prototype[key] === undefined) {
 66                 obj1.prototype[key] = obj2.prototype[key];
 67             }
 68         }
 69     },
 70     _pullNodes: function(stage) {
 71         var tempNodes = this.tempNodes;
 72         for(var n = 0; n < tempNodes.length; n++) {
 73             var node = tempNodes[n];
 74             if(node.getStage() !== undefined && node.getStage()._id === stage._id) {
 75                 stage._addId(node);
 76                 stage._addName(node);
 77                 this.tempNodes.splice(n, 1);
 78                 n -= 1;
 79             }
 80         }
 81     },
 82     /*
 83      * animation support
 84      */
 85     _addAnimation: function(anim) {
 86         anim.id = this.animIdCounter++;
 87         this.animations.push(anim);
 88     },
 89     _removeAnimation: function(anim) {
 90         var id = anim.id;
 91         var animations = this.animations;
 92         for(var n = 0; n < animations.length; n++) {
 93             if(animations[n].id === id) {
 94                 this.animations.splice(n, 1);
 95                 return false;
 96             }
 97         }
 98     },
 99     _runFrames: function() {
100         var nodes = {};
101         for(var n = 0; n < this.animations.length; n++) {
102             var anim = this.animations[n];
103             if(anim.node && anim.node._id !== undefined) {
104                 nodes[anim.node._id] = anim.node;
105             }
106             anim.func(this.frame);
107         }
108 
109         for(var key in nodes) {
110             nodes[key].draw();
111         }
112     },
113     _updateFrameObject: function() {
114         var date = new Date();
115         var time = date.getTime();
116         if(this.frame.lastTime === 0) {
117             this.frame.lastTime = time;
118         }
119         else {
120             this.frame.timeDiff = time - this.frame.lastTime;
121             this.frame.lastTime = time;
122             this.frame.time += this.frame.timeDiff;
123         }
124     },
125     _animationLoop: function() {
126         if(this.animations.length > 0) {
127             this._updateFrameObject();
128             this._runFrames();
129             var that = this;
130             requestAnimFrame(function() {
131                 that._animationLoop();
132             });
133         }
134         else {
135             this.frame.lastTime = 0;
136         }
137     },
138     _handleAnimation: function() {
139         var that = this;
140         if(this.animations.length > 0) {
141             that._animationLoop();
142         }
143         else {
144             this.frame.lastTime = 0;
145         }
146     },
147     /*
148      * utilities
149      */
150     _isElement: function(obj) {
151         return !!(obj && obj.nodeType == 1);
152     },
153     _isFunction: function(obj) {
154         return !!(obj && obj.constructor && obj.call && obj.apply);
155     },
156     _getPoint: function(arg) {
157 
158         if(arg.length === 1) {
159             return arg[0];
160         }
161         else {
162             return {
163                 x: arg[0],
164                 y: arg[1]
165             }
166         }
167     }
168 };
169 
170 window.requestAnimFrame = (function(callback) {
171     return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
172     function(callback) {
173         window.setTimeout(callback, 1000 / 60);
174     };
175 })();
176 
177 ///////////////////////////////////////////////////////////////////////
178 //  Node
179 ///////////////////////////////////////////////////////////////////////
180 /**
181  * Node constructor.  Nodes are entities that can move around
182  * and have events bound to them.  They are the building blocks of a KineticJS
183  * application
184  * @constructor
185  * @param {Object} config
186  */
187 Kinetic.Node = function(config) {
188     this.setDefaultAttrs({
189         visible: true,
190         listening: true,
191         name: undefined,
192         alpha: 1,
193         x: 0,
194         y: 0,
195         scale: {
196             x: 1,
197             y: 1
198         },
199         rotation: 0,
200         centerOffset: {
201             x: 0,
202             y: 0
203         },
204         dragConstraint: 'none',
205         dragBounds: {},
206         draggable: false
207     });
208 
209     this.eventListeners = {};
210     this.setAttrs(config);
211 };
212 /*
213  * Node methods
214  */
215 Kinetic.Node.prototype = {
216     /**
217      * bind events to the node.  KineticJS supports mouseover, mousemove,
218      * mouseout, mousedown, mouseup, click, dblclick, touchstart, touchmove,
219      * touchend, dbltap, dragstart, dragmove, and dragend.  Pass in a string
220      * of event types delimmited by a space to bind multiple events at once
221      * such as 'mousedown mouseup mousemove'. include a namespace to bind an
222      * event by name such as 'click.foobar'.
223      * @param {String} typesStr
224      * @param {function} handler
225      */
226     on: function(typesStr, handler) {
227         var types = typesStr.split(' ');
228         /*
229          * loop through types and attach event listeners to
230          * each one.  eg. 'click mouseover.namespace mouseout'
231          * will create three event bindings
232          */
233         for(var n = 0; n < types.length; n++) {
234             var type = types[n];
235             var event = (type.indexOf('touch') === -1) ? 'on' + type : type;
236             var parts = event.split('.');
237             var baseEvent = parts[0];
238             var name = parts.length > 1 ? parts[1] : '';
239 
240             if(!this.eventListeners[baseEvent]) {
241                 this.eventListeners[baseEvent] = [];
242             }
243 
244             this.eventListeners[baseEvent].push({
245                 name: name,
246                 handler: handler
247             });
248         }
249     },
250     /**
251      * remove event bindings from the node.  Pass in a string of
252      * event types delimmited by a space to remove multiple event
253      * bindings at once such as 'mousedown mouseup mousemove'.
254      * include a namespace to remove an event binding by name
255      * such as 'click.foobar'.
256      * @param {String} typesStr
257      */
258     off: function(typesStr) {
259         var types = typesStr.split(' ');
260 
261         for(var n = 0; n < types.length; n++) {
262             var type = types[n];
263             var event = (type.indexOf('touch') === -1) ? 'on' + type : type;
264             var parts = event.split('.');
265             var baseEvent = parts[0];
266 
267             if(this.eventListeners[baseEvent] && parts.length > 1) {
268                 var name = parts[1];
269 
270                 for(var i = 0; i < this.eventListeners[baseEvent].length; i++) {
271                     if(this.eventListeners[baseEvent][i].name === name) {
272                         this.eventListeners[baseEvent].splice(i, 1);
273                         if(this.eventListeners[baseEvent].length === 0) {
274                             this.eventListeners[baseEvent] = undefined;
275                         }
276                         break;
277                     }
278                 }
279             }
280             else {
281                 this.eventListeners[baseEvent] = undefined;
282             }
283         }
284     },
285     /**
286      * get attrs
287      */
288     getAttrs: function() {
289         return this.attrs;
290     },
291     /**
292      * set default attrs
293      * @param {Object} confic
294      */
295     setDefaultAttrs: function(config) {
296         // create attrs object if undefined
297         if(this.attrs === undefined) {
298             this.attrs = {};
299         }
300 
301         if(config) {
302             for(var key in config) {
303                 var val = config[key];
304                 this.attrs[key] = config[key];
305             }
306         }
307     },
308     /**
309      * set attrs
310      * @param {Object} config
311      */
312     setAttrs: function(config) {
313         var go = Kinetic.GlobalObject;
314         // set properties from config
315         if(config) {
316             for(var key in config) {
317                 var val = config[key];
318 
319                 /*
320                  * add functions, DOM elements, and images
321                  * directly to the node
322                  */
323                 if(go._isFunction(val) || go._isElement(val)) {
324                     this[key] = val;
325                 }
326                 /*
327                  * add all other object types to attrs object
328                  */
329                 else {
330                     // handle special keys
331                     switch (key) {
332                         /*
333                          * config properties that require a method to
334                          * be set
335                          */
336                         case 'draggable':
337                             this.draggable(config[key]);
338                             break;
339                         case 'listening':
340                             this.listen(config[key]);
341                             break;
342                         case 'rotationDeg':
343                             this.attrs.rotation = config[key] * Math.PI / 180;
344                             break;
345                         /*
346                          * config objects
347                          */
348                         case 'centerOffset':
349                             if(val.x !== undefined) {
350                                 this.attrs[key].x = val.x;
351                             }
352                             if(val.y !== undefined) {
353                                 this.attrs[key].y = val.y;
354                             }
355                             break;
356                         case 'scale':
357                             if(val.x !== undefined) {
358                                 this.attrs[key].x = val.x;
359                             }
360                             if(val.y !== undefined) {
361                                 this.attrs[key].y = val.y;
362                             }
363                             break;
364                         case 'crop':
365                             if(val.x !== undefined) {
366                                 this.attrs[key].x = val.x;
367                             }
368                             if(val.y !== undefined) {
369                                 this.attrs[key].y = val.y;
370                             }
371                             if(val.width !== undefined) {
372                                 this.attrs[key].width = val.width;
373                             }
374                             if(val.height !== undefined) {
375                                 this.attrs[key].height = val.height;
376                             }
377                             break;
378                         default:
379                             this.attrs[key] = config[key];
380                             break;
381                     }
382                 }
383             }
384         }
385     },
386     /**
387      * determine if shape is visible or not
388      */
389     isVisible: function() {
390         return this.attrs.visible;
391     },
392     /**
393      * show node
394      */
395     show: function() {
396         this.attrs.visible = true;
397     },
398     /**
399      * hide node
400      */
401     hide: function() {
402         this.attrs.visible = false;
403     },
404     /**
405      * get zIndex
406      */
407     getZIndex: function() {
408         return this.index;
409     },
410     /**
411      * get absolute z-index by taking into account
412      * all parent and sibling indices
413      */
414     getAbsoluteZIndex: function() {
415         var level = this.getLevel();
416         var stage = this.getStage();
417         var that = this;
418         var index = 0;
419         function addChildren(children) {
420             var nodes = [];
421             for(var n = 0; n < children.length; n++) {
422                 var child = children[n];
423                 index++;
424 
425                 if(child.nodeType !== 'Shape') {
426                     nodes = nodes.concat(child.getChildren());
427                 }
428 
429                 if(child._id === that._id) {
430                     n = children.length;
431                 }
432             }
433 
434             if(nodes.length > 0 && nodes[0].getLevel() <= level) {
435                 addChildren(nodes);
436             }
437         }
438         if(that.nodeType !== 'Stage') {
439             addChildren(that.getStage().getChildren());
440         }
441 
442         return index;
443     },
444     /**
445      * get node level in node tree
446      */
447     getLevel: function() {
448         var level = 0;
449         var parent = this.parent;
450         while(parent) {
451             level++;
452             parent = parent.parent;
453         }
454         return level;
455     },
456     /**
457      * set node scale.  If only one parameter is passed in,
458      * then both scaleX and scaleY are set with that parameter
459      * @param {Number} scaleX
460      * @param {Number} scaleY
461      */
462     setScale: function(scaleX, scaleY) {
463         if(scaleY) {
464             this.attrs.scale.x = scaleX;
465             this.attrs.scale.y = scaleY;
466         }
467         else {
468             this.attrs.scale.x = scaleX;
469             this.attrs.scale.y = scaleX;
470         }
471     },
472     /**
473      * get scale
474      */
475     getScale: function() {
476         return this.attrs.scale;
477     },
478     /**
479      * set node position
480      * @param {Object} point
481      */
482     setPosition: function() {
483         var pos = Kinetic.GlobalObject._getPoint(arguments);
484         this.attrs.x = pos.x;
485         this.attrs.y = pos.y;
486     },
487     /**
488      * set node x position
489      * @param {Number} x
490      */
491     setX: function(x) {
492         this.attrs.x = x;
493     },
494     /**
495      * set node y position
496      * @param {Number} y
497      */
498     setY: function(y) {
499         this.attrs.y = y;
500     },
501     /**
502      * get node x position
503      */
504     getX: function() {
505         return this.attrs.x;
506     },
507     /**
508      * get node y position
509      */
510     getY: function() {
511         return this.attrs.y;
512     },
513     /**
514      * set detection type
515      * @param {String} type can be "path" or "pixel"
516      */
517     setDetectionType: function(type) {
518         this.attrs.detectionType = type;
519     },
520     /**
521      * get detection type
522      */
523     getDetectionType: function() {
524         return this.attrs.detectionType;
525     },
526     /**
527      * get node position relative to container
528      */
529     getPosition: function() {
530         return {
531             x: this.attrs.x,
532             y: this.attrs.y
533         };
534     },
535     /**
536      * get absolute position relative to stage
537      */
538     getAbsolutePosition: function() {
539         return this.getAbsoluteTransform().getTranslation();
540     },
541     /**
542      * set absolute position relative to stage
543      * @param {Object} pos object containing an x and
544      *  y property
545      */
546     setAbsolutePosition: function() {
547         var pos = Kinetic.GlobalObject._getPoint(arguments);
548         /*
549          * save rotation and scale and
550          * then remove them from the transform
551          */
552         var rot = this.attrs.rotation;
553         var scale = {
554             x: this.attrs.scale.x,
555             y: this.attrs.scale.y
556         };
557         var centerOffset = {
558             x: this.attrs.centerOffset.x,
559             y: this.attrs.centerOffset.y
560         };
561 
562         this.attrs.rotation = 0;
563         this.attrs.scale = {
564             x: 1,
565             y: 1
566         };
567 
568         /*
569         this.attrs.centerOffset = {
570         x: 0,
571         y: 0
572         };
573         */
574 
575         //this.move(-1 * this.attrs.centerOffset.x, -1 * this.attrs.centerOffset.y);
576 
577         // unravel transform
578         var it = this.getAbsoluteTransform();
579         it.invert();
580         it.translate(pos.x, pos.y);
581         pos = {
582             x: this.attrs.x + it.getTranslation().x,
583             y: this.attrs.y + it.getTranslation().y
584         };
585 
586         this.setPosition(pos.x, pos.y);
587 
588         //this.move(-1* this.attrs.centerOffset.x, -1* this.attrs.centerOffset.y);
589 
590         // restore rotation and scale
591         this.rotate(rot);
592         this.attrs.scale = {
593             x: scale.x,
594             y: scale.y
595         };
596 
597     },
598     /**
599      * move node by an amount
600      * @param {Number} x
601      * @param {Number} y
602      */
603     move: function(x, y) {
604         this.attrs.x += x;
605         this.attrs.y += y;
606     },
607     /**
608      * set node rotation in radians
609      * @param {Number} theta
610      */
611     setRotation: function(theta) {
612         this.attrs.rotation = theta;
613     },
614     /**
615      * set node rotation in degrees
616      * @param {Number} deg
617      */
618     setRotationDeg: function(deg) {
619         this.attrs.rotation = (deg * Math.PI / 180);
620     },
621     /**
622      * get rotation in radians
623      */
624     getRotation: function() {
625         return this.attrs.rotation;
626     },
627     /**
628      * get rotation in degrees
629      */
630     getRotationDeg: function() {
631         return this.attrs.rotation * 180 / Math.PI;
632     },
633     /**
634      * rotate node by an amount in radians
635      * @param {Number} theta
636      */
637     rotate: function(theta) {
638         this.attrs.rotation += theta;
639     },
640     /**
641      * rotate node by an amount in degrees
642      * @param {Number} deg
643      */
644     rotateDeg: function(deg) {
645         this.attrs.rotation += (deg * Math.PI / 180);
646     },
647     /**
648      * listen or don't listen to events
649      * @param {Boolean} listening
650      */
651     listen: function(listening) {
652         this.attrs.listening = listening;
653     },
654     /**
655      * move node to top
656      */
657     moveToTop: function() {
658         var index = this.index;
659         this.parent.children.splice(index, 1);
660         this.parent.children.push(this);
661         this.parent._setChildrenIndices();
662     },
663     /**
664      * move node up
665      */
666     moveUp: function() {
667         var index = this.index;
668         this.parent.children.splice(index, 1);
669         this.parent.children.splice(index + 1, 0, this);
670         this.parent._setChildrenIndices();
671     },
672     /**
673      * move node down
674      */
675     moveDown: function() {
676         var index = this.index;
677         if(index > 0) {
678             this.parent.children.splice(index, 1);
679             this.parent.children.splice(index - 1, 0, this);
680             this.parent._setChildrenIndices();
681         }
682     },
683     /**
684      * move node to bottom
685      */
686     moveToBottom: function() {
687         var index = this.index;
688         this.parent.children.splice(index, 1);
689         this.parent.children.unshift(this);
690         this.parent._setChildrenIndices();
691     },
692     /**
693      * set zIndex
694      * @param {int} zIndex
695      */
696     setZIndex: function(zIndex) {
697         var index = this.index;
698         this.parent.children.splice(index, 1);
699         this.parent.children.splice(zIndex, 0, this);
700         this.parent._setChildrenIndices();
701     },
702     /**
703      * set alpha.  Alpha values range from 0 to 1.
704      * A node with an alpha of 0 is fully transparent, and a node
705      * with an alpha of 1 is fully opaque
706      * @param {Object} alpha
707      */
708     setAlpha: function(alpha) {
709         this.attrs.alpha = alpha;
710     },
711     /**
712      * get alpha.  Alpha values range from 0 to 1.
713      * A node with an alpha of 0 is fully transparent, and a node
714      * with an alpha of 1 is fully opaque
715      */
716     getAlpha: function() {
717         return this.attrs.alpha;
718     },
719     /**
720      * get absolute alpha
721      */
722     getAbsoluteAlpha: function() {
723         var absAlpha = 1;
724         var node = this;
725         // traverse upwards
726         while(node.nodeType !== 'Stage') {
727             absAlpha *= node.attrs.alpha;
728             node = node.parent;
729         }
730         return absAlpha;
731     },
732     /**
733      * enable or disable drag and drop
734      * @param {Boolean} isDraggable
735      */
736     draggable: function(isDraggable) {
737         if(this.attrs.draggable !== isDraggable) {
738             if(isDraggable) {
739                 this._initDrag();
740             }
741             else {
742                 this._dragCleanup();
743             }
744             this.attrs.draggable = isDraggable;
745         }
746     },
747     /**
748      * determine if node is currently in drag and drop mode
749      */
750     isDragging: function() {
751         var go = Kinetic.GlobalObject;
752         return go.drag.node !== undefined && go.drag.node._id === this._id && go.drag.moving;
753     },
754     /**
755      * move node to another container
756      * @param {Container} newContainer
757      */
758     moveTo: function(newContainer) {
759         var parent = this.parent;
760         // remove from parent's children
761         parent.children.splice(this.index, 1);
762         parent._setChildrenIndices();
763 
764         // add to new parent
765         newContainer.children.push(this);
766         this.index = newContainer.children.length - 1;
767         this.parent = newContainer;
768         newContainer._setChildrenIndices();
769     },
770     /**
771      * get parent container
772      */
773     getParent: function() {
774         return this.parent;
775     },
776     /**
777      * get layer associated to node
778      */
779     getLayer: function() {
780         if(this.nodeType === 'Layer') {
781             return this;
782         }
783         else {
784             return this.getParent().getLayer();
785         }
786     },
787     /**
788      * get stage associated to node
789      */
790     getStage: function() {
791         if(this.nodeType === 'Stage') {
792             return this;
793         }
794         else {
795             if(this.getParent() === undefined) {
796                 return undefined;
797             }
798             else {
799                 return this.getParent().getStage();
800             }
801         }
802     },
803     /**
804      * get name
805      */
806     getName: function() {
807         return this.attrs.name;
808     },
809     /**
810      * set center offset
811      * @param {Number} x
812      * @param {Number} y
813      */
814     setCenterOffset: function(x, y) {
815         this.attrs.centerOffset.x = x;
816         this.attrs.centerOffset.y = y;
817     },
818     /**
819      * get center offset
820      */
821     getCenterOffset: function() {
822         return this.attrs.centerOffset;
823     },
824     /**
825      * transition node to another state.  Any property that can accept a real
826      *  number can be transitioned, including x, y, rotation, alpha, strokeWidth,
827      *  radius, scale.x, scale.y, centerOffset.x, centerOffset.y, etc.
828      * @param {Object} config
829      * @config {Number} [duration] duration that the transition runs in seconds
830      * @config {String} [easing] easing function.  can be linear, ease-in, ease-out, ease-in-out,
831      *  back-ease-in, back-ease-out, back-ease-in-out, elastic-ease-in, elastic-ease-out,
832      *  elastic-ease-in-out, bounce-ease-out, bounce-ease-in, bounce-ease-in-out,
833      *  strong-ease-in, strong-ease-out, or strong-ease-in-out
834      *  linear is the default
835      * @config {Function} [callback] callback function to be executed when
836      *  transition completes
837      */
838     transitionTo: function(config) {
839         var go = Kinetic.GlobalObject;
840 
841         /*
842          * clear transition if one is currently running for this
843          * node
844          */
845         if(this.transAnim !== undefined) {
846             go._removeAnimation(this.transAnim);
847             this.transAnim = undefined;
848         }
849 
850         /*
851          * create new transition
852          */
853         var node = this.nodeType === 'Stage' ? this : this.getLayer();
854         var that = this;
855         var trans = new Kinetic.Transition(this, config);
856         var anim = {
857             func: function() {
858                 trans.onEnterFrame();
859             },
860             node: node
861         };
862 
863         // store reference to transition animation
864         this.transAnim = anim;
865 
866         /*
867          * adding the animation with the addAnimation
868          * method auto generates an id
869          */
870         go._addAnimation(anim);
871 
872         // subscribe to onFinished for first tween
873         trans.onFinished = function() {
874             // remove animation
875             go._removeAnimation(anim);
876             that.transAnim = undefined;
877 
878             // callback
879             if(config.callback !== undefined) {
880                 config.callback();
881             }
882 
883             anim.node.draw();
884         };
885         // auto start
886         trans.start();
887 
888         go._handleAnimation();
889 
890         return trans;
891     },
892     /**
893      * set drag constraint
894      * @param {String} constraint
895      */
896     setDragConstraint: function(constraint) {
897         this.attrs.dragConstraint = constraint;
898     },
899     /**
900      * get drag constraint
901      */
902     getDragConstraint: function() {
903         return this.attrs.dragConstraint;
904     },
905     /**
906      * set drag bounds
907      * @param {Object} bounds
908      * @config {Number} [left] left bounds position
909      * @config {Number} [top] top bounds position
910      * @config {Number} [right] right bounds position
911      * @config {Number} [bottom] bottom bounds position
912      */
913     setDragBounds: function(bounds) {
914         this.attrs.dragBounds = bounds;
915     },
916     /**
917      * get drag bounds
918      */
919     getDragBounds: function() {
920         return this.attrs.dragBounds;
921     },
922     /**
923      * get transform of the node while taking into
924      * account the transforms of its parents
925      */
926     getAbsoluteTransform: function() {
927         // absolute transform
928         var am = new Kinetic.Transform();
929 
930         var family = [];
931         var parent = this.parent;
932 
933         family.unshift(this);
934         while(parent) {
935             family.unshift(parent);
936             parent = parent.parent;
937         }
938 
939         for(var n = 0; n < family.length; n++) {
940             var node = family[n];
941             var m = node.getTransform();
942 
943             am.multiply(m);
944         }
945 
946         return am;
947     },
948     /**
949      * get transform of the node while not taking
950      * into account the transforms of its parents
951      */
952     getTransform: function() {
953         var m = new Kinetic.Transform();
954 
955         if(this.attrs.x !== 0 || this.attrs.y !== 0) {
956             m.translate(this.attrs.x, this.attrs.y);
957         }
958         if(this.attrs.rotation !== 0) {
959             m.rotate(this.attrs.rotation);
960         }
961         if(this.attrs.scale.x !== 1 || this.attrs.scale.y !== 1) {
962             m.scale(this.attrs.scale.x, this.attrs.scale.y);
963         }
964 
965         return m;
966     },
967     /**
968      * initialize drag and drop
969      */
970     _initDrag: function() {
971         this._dragCleanup();
972         var go = Kinetic.GlobalObject;
973         var that = this;
974         this.on('mousedown.initdrag touchstart.initdrag', function(evt) {
975             var stage = that.getStage();
976             var pos = stage.getUserPosition();
977 
978             if(pos) {
979                 var m = that.getTransform().getTranslation();
980                 var am = that.getAbsoluteTransform().getTranslation();
981                 go.drag.node = that;
982                 go.drag.offset.x = pos.x - that.getAbsoluteTransform().getTranslation().x;
983                 go.drag.offset.y = pos.y - that.getAbsoluteTransform().getTranslation().y;
984             }
985         });
986     },
987     /**
988      * remove drag and drop event listener
989      */
990     _dragCleanup: function() {
991         this.off('mousedown.initdrag');
992         this.off('touchstart.initdrag');
993     },
994     /**
995      * handle node events
996      * @param {String} eventType
997      * @param {Event} evt
998      */
999     _handleEvents: function(eventType, evt) {
1000         if(this.nodeType === 'Shape') {
1001             evt.shape = this;
1002         }
1003         var stage = this.getStage();
1004         this._handleEvent(this, stage.mouseoverShape, stage.mouseoutShape, eventType, evt);
1005     },
1006     /**
1007      * handle node event
1008      */
1009     _handleEvent: function(node, mouseoverNode, mouseoutNode, eventType, evt) {
1010         var el = node.eventListeners;
1011         var okayToRun = true;
1012 
1013         /*
1014          * determine if event handler should be skipped by comparing
1015          * parent nodes
1016          */
1017         if(eventType === 'onmouseover' && mouseoutNode && mouseoutNode._id === node._id) {
1018             okayToRun = false;
1019         }
1020         else if(eventType === 'onmouseout' && mouseoverNode && mouseoverNode._id === node._id) {
1021             okayToRun = false;
1022         }
1023 
1024         if(el[eventType] && okayToRun) {
1025             var events = el[eventType];
1026             for(var i = 0; i < events.length; i++) {
1027                 events[i].handler.apply(node, [evt]);
1028             }
1029         }
1030 
1031         var mouseoverParent = mouseoverNode ? mouseoverNode.parent : undefined;
1032         var mouseoutParent = mouseoutNode ? mouseoutNode.parent : undefined;
1033 
1034         // simulate event bubbling
1035         if(!evt.cancelBubble && node.parent.nodeType !== 'Stage') {
1036             this._handleEvent(node.parent, mouseoverParent, mouseoutParent, eventType, evt);
1037         }
1038     }
1039 };
1040 
1041 ///////////////////////////////////////////////////////////////////////
1042 //  Container
1043 ///////////////////////////////////////////////////////////////////////
1044 
1045 /**
1046  * Container constructor.  Containers are used to contain nodes or other containers
1047  * @constructor
1048  */
1049 Kinetic.Container = function() {
1050     this.children = [];
1051 };
1052 /*
1053  * Container methods
1054  */
1055 Kinetic.Container.prototype = {
1056     /**
1057      * get children
1058      */
1059     getChildren: function() {
1060         return this.children;
1061     },
1062     /**
1063      * remove all children
1064      */
1065     removeChildren: function() {
1066         while(this.children.length > 0) {
1067             this.remove(this.children[0]);
1068         }
1069     },
1070     /**
1071      * remove child from container
1072      * @param {Node} child
1073      */ 
1074     _remove: function(child) {
1075         if(child.index !== undefined && this.children[child.index]._id == child._id) {
1076             var stage = this.getStage();
1077             if(stage !== undefined) {
1078                 stage._removeId(child);
1079                 stage._removeName(child);
1080             }
1081 
1082             var go = Kinetic.GlobalObject;
1083             for(var n = 0; n < go.tempNodes.length; n++) {
1084                 var node = go.tempNodes[n];
1085                 if(node._id === child._id) {
1086                     go.tempNodes.splice(n, 1);
1087                     n = go.tempNodes.length;
1088                 }
1089             }
1090 
1091             this.children.splice(child.index, 1);
1092             this._setChildrenIndices();
1093             child = undefined;
1094         }
1095     },
1096     /**
1097      * return an array of nodes that match the selector.  Use '#' for id selections
1098      * and '.' for name selections
1099      * ex:
1100      * var node = stage.get('#foo'); // selects node with id foo
1101      * var nodes = layer.get('.bar'); // selects nodes with name bar inside layer
1102      * @param {String} selector
1103      */
1104     get: function(selector) {
1105         var stage = this.getStage();
1106         var arr;
1107         var key = selector.slice(1);
1108         if(selector.charAt(0) === '#') {
1109             arr = stage.ids[key] !== undefined ? [stage.ids[key]] : [];
1110         }
1111         else if(selector.charAt(0) === '.') {
1112             arr = stage.names[key] !== undefined ? stage.names[key] : [];
1113         }
1114         else if(selector === 'Shape' || selector === 'Group' || selector === 'Layer') {
1115             return this._getNodes(selector);
1116         }
1117         else {
1118             return false;
1119         }
1120 
1121         var retArr = [];
1122         for(var n = 0; n < arr.length; n++) {
1123             var node = arr[n];
1124             if(this.isAncestorOf(node)) {
1125                 retArr.push(node);
1126             }
1127         }
1128 
1129         return retArr;
1130     },
1131     /**
1132      * determine if node is an ancestor
1133      * of descendant
1134      * @param {Kinetic.Node} node
1135      */
1136     isAncestorOf: function(node) {
1137         if(this.nodeType === 'Stage') {
1138             return true;
1139         }
1140 
1141         var parent = node.getParent();
1142         while(parent) {
1143             if(parent._id === this._id) {
1144                 return true;
1145             }
1146             parent = parent.getParent();
1147         }
1148 
1149         return false;
1150     },
1151     /**
1152      * get all shapes inside container
1153      */
1154     _getNodes: function(sel) {
1155         var arr = [];
1156         function traverse(cont) {
1157             var children = cont.getChildren();
1158             for(var n = 0; n < children.length; n++) {
1159                 var child = children[n];
1160                 if(child.nodeType === sel) {
1161                     arr.push(child);
1162                 }
1163                 else if(child.nodeType !== 'Shape') {
1164                     traverse(child);
1165                 }
1166             }
1167         }
1168         traverse(this);
1169 
1170         return arr;
1171     },
1172     /**
1173      * draw children
1174      */
1175     _drawChildren: function() {
1176         var stage = this.getStage();
1177         var children = this.children;
1178         for(var n = 0; n < children.length; n++) {
1179             var child = children[n];
1180             if(child.nodeType === 'Shape' && child.isVisible() && stage.isVisible()) {
1181                 child._draw(child.getLayer());
1182             }
1183             else {
1184                 child._draw();
1185             }
1186         }
1187     },
1188     /**
1189      * add node to container
1190      * @param {Node} child
1191      */
1192     _add: function(child) {
1193         child._id = Kinetic.GlobalObject.idCounter++;
1194         child.index = this.children.length;
1195         child.parent = this;
1196 
1197         this.children.push(child);
1198 
1199         var stage = child.getStage();
1200         if(stage === undefined) {
1201             var go = Kinetic.GlobalObject;
1202             go.tempNodes.push(child);
1203         }
1204         else {
1205             stage._addId(child);
1206             stage._addName(child);
1207 
1208             /*
1209              * pull in other nodes that are now linked
1210              * to a stage
1211              */
1212             var go = Kinetic.GlobalObject;
1213             go._pullNodes(stage);
1214         }
1215     },
1216     /**
1217      * set children indices
1218      */
1219     _setChildrenIndices: function() {
1220         /*
1221          * if reordering Layers, remove all canvas elements
1222          * from the container except the buffer and backstage canvases
1223          * and then readd all the layers
1224          */
1225         if(this.nodeType === 'Stage') {
1226             var canvases = this.content.children;
1227             var bufferCanvas = canvases[0];
1228             var backstageCanvas = canvases[1];
1229 
1230             this.content.innerHTML = '';
1231             this.content.appendChild(bufferCanvas);
1232             this.content.appendChild(backstageCanvas);
1233         }
1234 
1235         for(var n = 0; n < this.children.length; n++) {
1236             this.children[n].index = n;
1237 
1238             if(this.nodeType === 'Stage') {
1239                 this.content.appendChild(this.children[n].canvas);
1240             }
1241         }
1242     }
1243 };
1244 
1245 ///////////////////////////////////////////////////////////////////////
1246 //  Stage
1247 ///////////////////////////////////////////////////////////////////////
1248 /**
1249  * Stage constructor.  A stage is used to contain multiple layers and handle
1250  * animations
1251  * @constructor
1252  * @augments Kinetic.Container
1253  * @augments Kinetic.Node
1254  * @param {String|DomElement} cont Container id or DOM element
1255  * @param {int} width
1256  * @param {int} height
1257  */
1258 Kinetic.Stage = function(config) {
1259     this.setDefaultAttrs({
1260         width: 400,
1261         height: 200
1262     });
1263 
1264     this.nodeType = 'Stage';
1265 
1266     /*
1267      * if container is a string, assume it's an id for
1268      * a DOM element
1269      */
1270     if( typeof config.container === 'string') {
1271         config.container = document.getElementById(config.container);
1272     }
1273 
1274     // call super constructors
1275     Kinetic.Container.apply(this, []);
1276     Kinetic.Node.apply(this, [config]);
1277 
1278     this.container = config.container;
1279     this.content = document.createElement('div');
1280     this.dblClickWindow = 400;
1281 
1282     this._setDefaults();
1283 
1284     // set stage id
1285     this._id = Kinetic.GlobalObject.idCounter++;
1286 
1287     this._buildDOM();
1288     this._listen();
1289     this._prepareDrag();
1290 
1291     var go = Kinetic.GlobalObject;
1292     go.stages.push(this);
1293     this._addId(this);
1294     this._addName(this);
1295 };
1296 /*
1297  * Stage methods
1298  */
1299 Kinetic.Stage.prototype = {
1300     /**
1301      * sets onFrameFunc for animation
1302      * @param {function} func
1303      */
1304     onFrame: function(func) {
1305         var go = Kinetic.GlobalObject;
1306         this.anim = {
1307             func: func
1308         };
1309     },
1310     /**
1311      * start animation
1312      */
1313     start: function() {
1314         if(!this.animRunning) {
1315             var go = Kinetic.GlobalObject;
1316             go._addAnimation(this.anim);
1317             go._handleAnimation();
1318             this.animRunning = true;
1319         }
1320     },
1321     /**
1322      * stop animation
1323      */
1324     stop: function() {
1325         var go = Kinetic.GlobalObject;
1326         go._removeAnimation(this.anim);
1327         this.animRunning = false;
1328     },
1329     /**
1330      * draw children
1331      */
1332     draw: function() {
1333         this._drawChildren();
1334     },
1335     /**
1336      * set stage size
1337      * @param {int} width
1338      * @param {int} height
1339      */
1340     setSize: function(width, height) {
1341         // set stage dimensions
1342         this.attrs.width = width;
1343         this.attrs.height = height;
1344 
1345         // set content dimensions
1346         this.content.style.width = this.attrs.width + 'px';
1347         this.content.style.height = this.attrs.height + 'px';
1348 
1349         // set buffer layer and path layer sizes
1350         this.bufferLayer.getCanvas().width = width;
1351         this.bufferLayer.getCanvas().height = height;
1352         this.pathLayer.getCanvas().width = width;
1353         this.pathLayer.getCanvas().height = height;
1354 
1355         // set user defined layer dimensions
1356         var layers = this.children;
1357         for(var n = 0; n < layers.length; n++) {
1358             var layer = layers[n];
1359             layer.getCanvas().width = width;
1360             layer.getCanvas().height = height;
1361             layer.draw();
1362         }
1363     },
1364     /**
1365      * return stage size
1366      */
1367     getSize: function() {
1368         return {
1369             width: this.attrs.width,
1370             height: this.attrs.height
1371         };
1372     },
1373     /**
1374      * clear all layers
1375      */
1376     clear: function() {
1377         var layers = this.children;
1378         for(var n = 0; n < layers.length; n++) {
1379             layers[n].clear();
1380         }
1381     },
1382     /**
1383      * Creates a composite data URL and passes it to a callback. If MIME type is not
1384      * specified, then "image/png" will result. For "image/jpeg", specify a quality
1385      * level as quality (range 0.0 - 1.0)
1386      * @param {function} callback
1387      * @param {String} mimeType (optional)
1388      * @param {Number} quality (optional)
1389      */
1390     toDataURL: function(callback, mimeType, quality) {
1391         var bufferLayer = this.bufferLayer;
1392         var bufferContext = bufferLayer.getContext();
1393         var layers = this.children;
1394         var that = this;
1395 
1396         function addLayer(n) {
1397             var dataURL = layers[n].getCanvas().toDataURL();
1398             var imageObj = new Image();
1399             imageObj.onload = function() {
1400                 bufferContext.drawImage(this, 0, 0);
1401                 n++;
1402                 if(n < layers.length) {
1403                     addLayer(n);
1404                 }
1405                 else {
1406                     try {
1407                         // If this call fails (due to browser bug, like in Firefox 3.6),
1408                         // then revert to previous no-parameter image/png behavior
1409                         callback(bufferLayer.getCanvas().toDataURL(mimeType, quality));
1410                     }
1411                     catch(exception) {
1412                         callback(bufferLayer.getCanvas().toDataURL());
1413                     }
1414                 }
1415             };
1416             imageObj.src = dataURL;
1417         }
1418 
1419         bufferLayer.clear();
1420         addLayer(0);
1421     },
1422     /**
1423      * serialize stage and children as a JSON object
1424      */
1425     toJSON: function() {
1426         var go = Kinetic.GlobalObject;
1427 
1428         function addNode(node) {
1429             var obj = {};
1430             obj.attrs = node.attrs;
1431 
1432             obj.nodeType = node.nodeType;
1433             obj.shapeType = node.shapeType;
1434 
1435             if(node.nodeType !== 'Shape') {
1436                 obj.children = [];
1437 
1438                 var children = node.getChildren();
1439                 for(var n = 0; n < children.length; n++) {
1440                     var child = children[n];
1441                     obj.children.push(addNode(child));
1442                 }
1443             }
1444 
1445             return obj;
1446         }
1447         return JSON.stringify(addNode(this));
1448     },
1449     /**
1450      * reset stage to default state
1451      */
1452     reset: function() {
1453         // remove children
1454         this.removeChildren();
1455 
1456         // reset stage defaults
1457         this._setDefaults();
1458 
1459         // reset node attrs
1460         this.setDefaultAttrs({
1461             visible: true,
1462             listening: true,
1463             name: undefined,
1464             alpha: 1,
1465             x: 0,
1466             y: 0,
1467             scale: {
1468                 x: 1,
1469                 y: 1
1470             },
1471             rotation: 0,
1472             centerOffset: {
1473                 x: 0,
1474                 y: 0
1475             },
1476             dragConstraint: 'none',
1477             dragBounds: {},
1478             draggable: false
1479         });
1480     },
1481     /**
1482      * load stage with JSON string.  De-serializtion does not generate custom
1483      *  shape drawing functions, images, or event handlers (this would make the
1484      * 	serialized object huge).  If your app uses custom shapes, images, and
1485      *  event handlers (it probably does), then you need to select the appropriate
1486      *  shapes after loading the stage and set these properties via on(), setDrawFunc(),
1487      *  and setImage()
1488      * @param {String} JSON string
1489      */
1490     load: function(json) {
1491         this.reset();
1492 
1493         function loadNode(node, obj) {
1494             var children = obj.children;
1495             if(children !== undefined) {
1496                 for(var n = 0; n < children.length; n++) {
1497                     var child = children[n];
1498                     var type;
1499 
1500                     // determine type
1501                     if(child.nodeType === 'Shape') {
1502                         // add custom shape
1503                         if(child.shapeType === undefined) {
1504                             type = 'Shape';
1505                         }
1506                         // add standard shape
1507                         else {
1508                             type = child.shapeType;
1509                         }
1510                     }
1511                     else {
1512                         type = child.nodeType;
1513                     }
1514 
1515                     var no = new Kinetic[type](child.attrs);
1516                     node.add(no);
1517                     loadNode(no, child);
1518                 }
1519             }
1520         }
1521         var obj = JSON.parse(json);
1522 
1523         // copy over stage properties
1524         this.attrs = obj.attrs;
1525 
1526         loadNode(this, obj);
1527         this.draw();
1528     },
1529     /**
1530      * remove layer from stage
1531      * @param {Layer} layer
1532      */
1533     remove: function(layer) {
1534         /*
1535          * remove canvas DOM from the document if
1536          * it exists
1537          */
1538         try {
1539             this.content.removeChild(layer.canvas);
1540         }
1541         catch(e) {
1542         }
1543         this._remove(layer);
1544     },
1545     /**
1546      * add layer to stage
1547      * @param {Layer} layer
1548      */
1549     add: function(layer) {
1550         layer.canvas.width = this.attrs.width;
1551         layer.canvas.height = this.attrs.height;
1552         this._add(layer);
1553 
1554         // draw layer and append canvas to container
1555         layer.draw();
1556         this.content.appendChild(layer.canvas);
1557     },
1558     /**
1559      * get mouse position for desktop apps
1560      * @param {Event} evt
1561      */
1562     getMousePosition: function(evt) {
1563         return this.mousePos;
1564     },
1565     /**
1566      * get touch position for mobile apps
1567      * @param {Event} evt
1568      */
1569     getTouchPosition: function(evt) {
1570         return this.touchPos;
1571     },
1572     /**
1573      * get user position (mouse position or touch position)
1574      * @param {Event} evt
1575      */
1576     getUserPosition: function(evt) {
1577         return this.getTouchPosition() || this.getMousePosition();
1578     },
1579     /**
1580      * get container DOM element
1581      */
1582     getContainer: function() {
1583         return this.container;
1584     },
1585     /**
1586      * get content DOM element
1587      */
1588     getContent: function() {
1589         return this.content;
1590     },
1591     /**
1592      * get stage
1593      */
1594     getStage: function() {
1595         return this;
1596     },
1597     /**
1598      * get width
1599      */
1600     getWidth: function() {
1601         return this.attrs.width;
1602     },
1603     /**
1604      * get height
1605      */
1606     getHeight: function() {
1607         return this.attrs.height;
1608     },
1609     /**
1610      * get shapes that intersect a point
1611      * @param {Object} point
1612      */
1613     getIntersections: function() {
1614         var pos = Kinetic.GlobalObject._getPoint(arguments);
1615         var arr = [];
1616         var shapes = this.get('Shape');
1617 
1618         for(var n = 0; n < shapes.length; n++) {
1619             var shape = shapes[n];
1620             if(shape.intersects(pos)) {
1621                 arr.push(shape);
1622             }
1623         }
1624 
1625         return arr;
1626     },
1627     /**
1628      * get stage DOM node, which is a div element
1629      * with the class name "kineticjs-content"
1630      */
1631     getDOM: function() {
1632         return this.content;
1633     },
1634     /**
1635      * detect event
1636      * @param {Shape} shape
1637      */
1638     _detectEvent: function(shape, evt) {
1639         var isDragging = Kinetic.GlobalObject.drag.moving;
1640         var go = Kinetic.GlobalObject;
1641         var pos = this.getUserPosition();
1642         var el = shape.eventListeners;
1643 
1644         if(this.targetShape && shape._id === this.targetShape._id) {
1645             this.targetFound = true;
1646         }
1647 
1648         if(shape.attrs.visible && pos !== undefined && shape.intersects(pos)) {
1649             // handle onmousedown
1650             if(!isDragging && this.mouseDown) {
1651                 this.mouseDown = false;
1652                 this.clickStart = true;
1653                 shape._handleEvents('onmousedown', evt);
1654                 return true;
1655             }
1656             // handle onmouseup & onclick
1657             else if(this.mouseUp) {
1658                 this.mouseUp = false;
1659                 shape._handleEvents('onmouseup', evt);
1660 
1661                 // detect if click or double click occurred
1662                 if(this.clickStart) {
1663                     /*
1664                      * if dragging and dropping, don't fire click or dbl click
1665                      * event
1666                      */
1667                     if((!go.drag.moving) || !go.drag.node) {
1668                         shape._handleEvents('onclick', evt);
1669 
1670                         if(shape.inDoubleClickWindow) {
1671                             shape._handleEvents('ondblclick', evt);
1672                         }
1673                         shape.inDoubleClickWindow = true;
1674                         setTimeout(function() {
1675                             shape.inDoubleClickWindow = false;
1676                         }, this.dblClickWindow);
1677                     }
1678                 }
1679                 return true;
1680             }
1681 
1682             // handle touchstart
1683             else if(this.touchStart) {
1684                 this.touchStart = false;
1685                 shape._handleEvents('touchstart', evt);
1686 
1687                 if(el.ondbltap && shape.inDoubleClickWindow) {
1688                     var events = el.ondbltap;
1689                     for(var i = 0; i < events.length; i++) {
1690                         events[i].handler.apply(shape, [evt]);
1691                     }
1692                 }
1693 
1694                 shape.inDoubleClickWindow = true;
1695 
1696                 setTimeout(function() {
1697                     shape.inDoubleClickWindow = false;
1698                 }, this.dblClickWindow);
1699                 return true;
1700             }
1701 
1702             // handle touchend
1703             else if(this.touchEnd) {
1704                 this.touchEnd = false;
1705                 shape._handleEvents('touchend', evt);
1706                 return true;
1707             }
1708 
1709             /*
1710             * NOTE: these event handlers require target shape
1711             * handling
1712             */
1713 
1714             // handle onmouseover
1715             else if(!isDragging && this._isNewTarget(shape, evt)) {
1716                 /*
1717                  * check to see if there are stored mouseout events first.
1718                  * if there are, run those before running the onmouseover
1719                  * events
1720                  */
1721                 if(this.mouseoutShape) {
1722                     this.mouseoverShape = shape;
1723                     this.mouseoutShape._handleEvents('onmouseout', evt);
1724                     this.mouseoverShape = undefined;
1725                 }
1726 
1727                 shape._handleEvents('onmouseover', evt);
1728                 this._setTarget(shape);
1729                 return true;
1730             }
1731 
1732             // handle mousemove and touchmove
1733             else if(!isDragging) {
1734                 shape._handleEvents('onmousemove', evt);
1735                 shape._handleEvents('touchmove', evt);
1736                 return true;
1737             }
1738         }
1739         // handle mouseout condition
1740         else if(!isDragging && this.targetShape && this.targetShape._id === shape._id) {
1741             this._setTarget(undefined);
1742             this.mouseoutShape = shape;
1743             return true;
1744         }
1745 
1746         return false;
1747     },
1748     /**
1749      * set new target
1750      */
1751     _setTarget: function(shape) {
1752         this.targetShape = shape;
1753         this.targetFound = true;
1754     },
1755     /**
1756      * check if shape should be a new target
1757      */
1758     _isNewTarget: function(shape, evt) {
1759         if(!this.targetShape || (!this.targetFound && shape._id !== this.targetShape._id)) {
1760             /*
1761              * check if old target has an onmouseout event listener
1762              */
1763             if(this.targetShape) {
1764                 var oldEl = this.targetShape.eventListeners;
1765                 if(oldEl) {
1766                     this.mouseoutShape = this.targetShape;
1767                 }
1768             }
1769             return true;
1770         }
1771         else {
1772             return false;
1773         }
1774     },
1775     /**
1776      * traverse container children
1777      * @param {Container} obj
1778      */
1779     _traverseChildren: function(obj, evt) {
1780         var children = obj.children;
1781         // propapgate backwards through children
1782         for(var i = children.length - 1; i >= 0; i--) {
1783             var child = children[i];
1784             if(child.attrs.listening) {
1785                 if(child.nodeType === 'Shape') {
1786                     var exit = this._detectEvent(child, evt);
1787                     if(exit) {
1788                         return true;
1789                     }
1790                 }
1791                 else {
1792                     var exit = this._traverseChildren(child, evt);
1793                     if(exit) {
1794                         return true;
1795                     }
1796                 }
1797             }
1798         }
1799 
1800         return false;
1801     },
1802     /**
1803      * handle incoming event
1804      * @param {Event} evt
1805      */
1806     _handleStageEvent: function(evt) {
1807         var go = Kinetic.GlobalObject;
1808         if(!evt) {
1809             evt = window.event;
1810         }
1811 
1812         this._setMousePosition(evt);
1813         this._setTouchPosition(evt);
1814         this.pathLayer.clear();
1815 
1816         /*
1817          * loop through layers.  If at any point an event
1818          * is triggered, n is set to -1 which will break out of the
1819          * three nested loops
1820          */
1821         this.targetFound = false;
1822         var shapeDetected = false;
1823         for(var n = this.children.length - 1; n >= 0; n--) {
1824             var layer = this.children[n];
1825             if(layer.attrs.visible && n >= 0 && layer.attrs.listening) {
1826                 if(this._traverseChildren(layer, evt)) {
1827                     n = -1;
1828                     shapeDetected = true;
1829                 }
1830             }
1831         }
1832 
1833         /*
1834          * if no shape was detected and a mouseout shape has been stored,
1835          * then run the onmouseout event handlers
1836          */
1837         if(!shapeDetected && this.mouseoutShape) {
1838             this.mouseoutShape._handleEvents('onmouseout', evt);
1839             this.mouseoutShape = undefined;
1840         }
1841     },
1842     /**
1843      * begin listening for events by adding event handlers
1844      * to the container
1845      */
1846     _listen: function() {
1847         var that = this;
1848 
1849         // desktop events
1850         this.content.addEventListener('mousedown', function(evt) {
1851             that.mouseDown = true;
1852             that._handleStageEvent(evt);
1853         }, false);
1854 
1855         this.content.addEventListener('mousemove', function(evt) {
1856             that.mouseUp = false;
1857             that.mouseDown = false;
1858             that._handleStageEvent(evt);
1859         }, false);
1860 
1861         this.content.addEventListener('mouseup', function(evt) {
1862             that.mouseUp = true;
1863             that.mouseDown = false;
1864             that._handleStageEvent(evt);
1865 
1866             that.clickStart = false;
1867         }, false);
1868 
1869         this.content.addEventListener('mouseover', function(evt) {
1870             that._handleStageEvent(evt);
1871         }, false);
1872 
1873         this.content.addEventListener('mouseout', function(evt) {
1874             // if there's a current target shape, run mouseout handlers
1875             var targetShape = that.targetShape;
1876             if(targetShape) {
1877                 targetShape._handleEvents('onmouseout', evt);
1878                 that.targetShape = undefined;
1879             }
1880             that.mousePos = undefined;
1881         }, false);
1882         // mobile events
1883         this.content.addEventListener('touchstart', function(evt) {
1884             evt.preventDefault();
1885             that.touchStart = true;
1886             that._handleStageEvent(evt);
1887         }, false);
1888 
1889         this.content.addEventListener('touchmove', function(evt) {
1890             evt.preventDefault();
1891             that._handleStageEvent(evt);
1892         }, false);
1893 
1894         this.content.addEventListener('touchend', function(evt) {
1895             evt.preventDefault();
1896             that.touchEnd = true;
1897             that._handleStageEvent(evt);
1898         }, false);
1899     },
1900     /**
1901      * set mouse positon for desktop apps
1902      * @param {Event} evt
1903      */
1904     _setMousePosition: function(evt) {
1905         var mouseX = evt.offsetX || (evt.clientX - this._getContentPosition().left + window.pageXOffset);
1906         var mouseY = evt.offsetY || (evt.clientY - this._getContentPosition().top + window.pageYOffset);
1907         this.mousePos = {
1908             x: mouseX,
1909             y: mouseY
1910         };
1911     },
1912     /**
1913      * set touch position for mobile apps
1914      * @param {Event} evt
1915      */
1916     _setTouchPosition: function(evt) {
1917         if(evt.touches !== undefined && evt.touches.length === 1) {// Only deal with
1918             // one finger
1919             var touch = evt.touches[0];
1920             // Get the information for finger #1
1921             var touchX = touch.clientX - this._getContentPosition().left + window.pageXOffset;
1922             var touchY = touch.clientY - this._getContentPosition().top + window.pageYOffset;
1923 
1924             this.touchPos = {
1925                 x: touchX,
1926                 y: touchY
1927             };
1928         }
1929     },
1930     /**
1931      * get container position
1932      */
1933     _getContentPosition: function() {
1934         var obj = this.content;
1935         var top = 0;
1936         var left = 0;
1937         while(obj && obj.tagName !== 'BODY') {
1938             top += obj.offsetTop - obj.scrollTop;
1939             left += obj.offsetLeft - obj.scrollLeft;
1940             obj = obj.offsetParent;
1941         }
1942         return {
1943             top: top,
1944             left: left
1945         };
1946     },
1947     /**
1948      * modify path context
1949      * @param {CanvasContext} context
1950      */
1951     _modifyPathContext: function(context) {
1952         context.stroke = function() {
1953         };
1954         context.fill = function() {
1955         };
1956         context.fillRect = function(x, y, width, height) {
1957             context.rect(x, y, width, height);
1958         };
1959         context.strokeRect = function(x, y, width, height) {
1960             context.rect(x, y, width, height);
1961         };
1962         context.drawImage = function() {
1963         };
1964         context.fillText = function() {
1965         };
1966         context.strokeText = function() {
1967         };
1968     },
1969     /**
1970      * end drag and drop
1971      */
1972     _endDrag: function(evt) {
1973         var go = Kinetic.GlobalObject;
1974         if(go.drag.node) {
1975             if(go.drag.moving) {
1976                 go.drag.moving = false;
1977                 go.drag.node._handleEvents('ondragend', evt);
1978             }
1979         }
1980         go.drag.node = undefined;
1981     },
1982     /**
1983      * prepare drag and drop
1984      */
1985     _prepareDrag: function() {
1986         var that = this;
1987 
1988         this._onContent('mousemove touchmove', function(evt) {
1989             var go = Kinetic.GlobalObject;
1990             var node = go.drag.node;
1991             if(node) {
1992                 var date = new Date();
1993                 var time = date.getTime();
1994 
1995                 if(time - go.drag.lastDrawTime > go.dragTimeInterval) {
1996                     go.drag.lastDrawTime = time;
1997 
1998                     var pos = that.getUserPosition();
1999                     var dc = node.attrs.dragConstraint;
2000                     var db = node.attrs.dragBounds;
2001                     var lastNodePos = {
2002                         x: node.attrs.x,
2003                         y: node.attrs.y
2004                     };
2005 
2006                     // default
2007                     var newNodePos = {
2008                         x: pos.x - go.drag.offset.x,
2009                         y: pos.y - go.drag.offset.y
2010                     };
2011 
2012                     // bounds overrides
2013                     if(db.left !== undefined && newNodePos.x < db.left) {
2014                         newNodePos.x = db.left;
2015                     }
2016                     if(db.right !== undefined && newNodePos.x > db.right) {
2017                         newNodePos.x = db.right;
2018                     }
2019                     if(db.top !== undefined && newNodePos.y < db.top) {
2020                         newNodePos.y = db.top;
2021                     }
2022                     if(db.bottom !== undefined && newNodePos.y > db.bottom) {
2023                         newNodePos.y = db.bottom;
2024                     }
2025 
2026                     node.setAbsolutePosition(newNodePos);
2027 
2028                     // constraint overrides
2029                     if(dc === 'horizontal') {
2030                         node.attrs.y = lastNodePos.y;
2031                     }
2032                     else if(dc === 'vertical') {
2033                         node.attrs.x = lastNodePos.x;
2034                     }
2035 
2036                     go.drag.node.getLayer().draw();
2037 
2038                     if(!go.drag.moving) {
2039                         go.drag.moving = true;
2040                         // execute dragstart events if defined
2041                         go.drag.node._handleEvents('ondragstart', evt);
2042                     }
2043 
2044                     // execute user defined ondragmove if defined
2045                     go.drag.node._handleEvents('ondragmove', evt);
2046                 }
2047             }
2048         }, false);
2049 
2050         this._onContent('mouseup touchend mouseout', function(evt) {
2051             that._endDrag(evt);
2052         });
2053     },
2054     /**
2055      * build dom
2056      */
2057     _buildDOM: function() {
2058         // content
2059         this.content.style.position = 'relative';
2060         this.content.style.display = 'inline-block';
2061         this.content.className = 'kineticjs-content';
2062         this.container.appendChild(this.content);
2063 
2064         // default layers
2065         this.bufferLayer = new Kinetic.Layer({
2066             name: 'bufferLayer'
2067         });
2068         this.pathLayer = new Kinetic.Layer({
2069             name: 'pathLayer'
2070         });
2071 
2072         // set parents
2073         this.bufferLayer.parent = this;
2074         this.pathLayer.parent = this;
2075 
2076         // customize back stage context
2077         this._modifyPathContext(this.pathLayer.context);
2078 
2079         // hide canvases
2080         this.bufferLayer.getCanvas().style.display = 'none';
2081         this.pathLayer.getCanvas().style.display = 'none';
2082 
2083         // add buffer layer
2084         this.bufferLayer.canvas.className = 'kineticjs-buffer-layer';
2085         this.content.appendChild(this.bufferLayer.canvas);
2086 
2087         // add path layer
2088         this.pathLayer.canvas.className = 'kineticjs-path-layer';
2089         this.content.appendChild(this.pathLayer.canvas);
2090 
2091         this.setSize(this.attrs.width, this.attrs.height);
2092     },
2093     _addId: function(node) {
2094         if(node.attrs.id !== undefined) {
2095             this.ids[node.attrs.id] = node;
2096         }
2097     },
2098     _removeId: function(node) {
2099         if(node.attrs.id !== undefined) {
2100             this.ids[node.attrs.id] = undefined;
2101         }
2102     },
2103     _addName: function(node) {
2104         var name = node.attrs.name;
2105         if(name !== undefined) {
2106             if(this.names[name] === undefined) {
2107                 this.names[name] = [];
2108             }
2109             this.names[name].push(node);
2110         }
2111     },
2112     _removeName: function(node) {
2113         if(node.attrs.name !== undefined) {
2114             var nodes = this.names[node.attrs.name];
2115             if(nodes !== undefined) {
2116                 for(var n = 0; n < nodes.length; n++) {
2117                     var no = nodes[n];
2118                     if(no._id === node._id) {
2119                         nodes.splice(n, 1);
2120                     }
2121                 }
2122             }
2123         }
2124     },
2125     /**
2126      * bind event listener to container DOM element
2127      * @param {String} typesStr
2128      * @param {function} handler
2129      */
2130     _onContent: function(typesStr, handler) {
2131         var types = typesStr.split(' ');
2132         for(var n = 0; n < types.length; n++) {
2133             var baseEvent = types[n];
2134             this.content.addEventListener(baseEvent, handler, false);
2135         }
2136     },
2137     /**
2138      * set defaults
2139      */
2140     _setDefaults: function() {
2141         this.clickStart = false;
2142         this.targetShape = undefined;
2143         this.targetFound = false;
2144         this.mouseoverShape = undefined;
2145         this.mouseoutShape = undefined;
2146 
2147         // desktop flags
2148         this.mousePos = undefined;
2149         this.mouseDown = false;
2150         this.mouseUp = false;
2151 
2152         // mobile flags
2153         this.touchPos = undefined;
2154         this.touchStart = false;
2155         this.touchEnd = false;
2156 
2157         this.ids = {};
2158         this.names = {};
2159         this.anim = undefined;
2160         this.animRunning = false;
2161     }
2162 };
2163 // Extend Container and Node
2164 Kinetic.GlobalObject.extend(Kinetic.Stage, Kinetic.Container);
2165 Kinetic.GlobalObject.extend(Kinetic.Stage, Kinetic.Node);
2166 
2167 ///////////////////////////////////////////////////////////////////////
2168 //  Layer
2169 ///////////////////////////////////////////////////////////////////////
2170 /**
2171  * Layer constructor.  Layers are tied to their own canvas element and are used
2172  * to contain groups or shapes
2173  * @constructor
2174  * @augments Kinetic.Container
2175  * @augments Kinetic.Node
2176  * @param {Object} config
2177  */
2178 Kinetic.Layer = function(config) {
2179     this.setDefaultAttrs({
2180         throttle: 12
2181     });
2182 
2183     this.nodeType = 'Layer';
2184     this.lastDrawTime = 0;
2185     this.beforeDrawFunc = undefined;
2186     this.afterDrawFunc = undefined;
2187 
2188     this.canvas = document.createElement('canvas');
2189     this.context = this.canvas.getContext('2d');
2190     this.canvas.style.position = 'absolute';
2191 
2192     // call super constructors
2193     Kinetic.Container.apply(this, []);
2194     Kinetic.Node.apply(this, [config]);
2195 };
2196 /*
2197  * Layer methods
2198  */
2199 Kinetic.Layer.prototype = {
2200     /**
2201      * draw children nodes.  this includes any groups
2202      *  or shapes
2203      */
2204     draw: function() {
2205         var throttle = this.attrs.throttle;
2206         var date = new Date();
2207         var time = date.getTime();
2208         var timeDiff = time - this.lastDrawTime;
2209 
2210         if(timeDiff >= throttle) {
2211             this._draw();
2212             this.lastDrawTime = time;
2213             if(this.drawTimeout !== undefined) {
2214                 clearTimeout(this.drawTimeout);
2215                 this.drawTimeout = undefined;
2216             }
2217         }
2218         /*
2219          * if we cannot draw the layer due to throttling,
2220          * try to redraw the layer in the near future
2221          */
2222         else if(this.drawTimeout === undefined) {
2223             var that = this;
2224             /*
2225              * if timeout duration is too short, we will
2226              * get a lot of unecessary layer draws.  Make sure
2227              * that the timeout is slightly more than the throttle
2228              * amount
2229              */
2230             this.drawTimeout = setTimeout(function() {
2231                 that.draw();
2232             }, throttle + 10);
2233         }
2234     },
2235     /**
2236      * set throttle
2237      * @param {Number} throttle in ms
2238      */
2239     setThrottle: function(throttle) {
2240         this.attrs.throttle = throttle;
2241     },
2242     /**
2243      * get throttle
2244      */
2245     getThrottle: function() {
2246         return this.attrs.throttle;
2247     },
2248     /**
2249      * set before draw function handler
2250      */
2251     beforeDraw: function(func) {
2252         this.beforeDrawFunc = func;
2253     },
2254     /**
2255      * set after draw function handler
2256      */
2257     afterDraw: function(func) {
2258         this.afterDrawFunc = func;
2259     },
2260     /**
2261      * clears the canvas context tied to the layer.  Clearing
2262      *  a layer does not remove its children.  The nodes within
2263      *  the layer will be redrawn whenever the .draw() method
2264      *  is used again.
2265      */
2266     clear: function() {
2267         var context = this.getContext();
2268         var canvas = this.getCanvas();
2269         context.clearRect(0, 0, canvas.width, canvas.height);
2270     },
2271     /**
2272      * get layer canvas
2273      */
2274     getCanvas: function() {
2275         return this.canvas;
2276     },
2277     /**
2278      * get layer context
2279      */
2280     getContext: function() {
2281         return this.context;
2282     },
2283     /**
2284      * add a node to the layer.  New nodes are always
2285      * placed at the top.
2286      * @param {Node} node
2287      */
2288     add: function(child) {
2289         this._add(child);
2290     },
2291     /**
2292      * remove a child from the layer
2293      * @param {Node} child
2294      */
2295     remove: function(child) {
2296         this._remove(child);
2297     },
2298     /**
2299      * private draw children
2300      */
2301     _draw: function() {
2302         // before draw  handler
2303         if(this.beforeDrawFunc !== undefined) {
2304             this.beforeDrawFunc();
2305         }
2306 
2307         this.clear();
2308         if(this.attrs.visible) {
2309             this._drawChildren();
2310         }
2311 
2312         // after draw  handler
2313         if(this.afterDrawFunc !== undefined) {
2314             this.afterDrawFunc();
2315         }
2316     }
2317 };
2318 // Extend Container and Node
2319 Kinetic.GlobalObject.extend(Kinetic.Layer, Kinetic.Container);
2320 Kinetic.GlobalObject.extend(Kinetic.Layer, Kinetic.Node);
2321 
2322 ///////////////////////////////////////////////////////////////////////
2323 //  Group
2324 ///////////////////////////////////////////////////////////////////////
2325 
2326 /**
2327  * Group constructor.  Groups are used to contain shapes or other groups.
2328  * @constructor
2329  * @augments Kinetic.Container
2330  * @augments Kinetic.Node
2331  * @param {Object} config
2332  */
2333 Kinetic.Group = function(config) {
2334     this.nodeType = 'Group';;
2335     
2336     // call super constructors
2337     Kinetic.Container.apply(this, []);
2338     Kinetic.Node.apply(this, [config]);
2339 };
2340 /*
2341  * Group methods
2342  */
2343 Kinetic.Group.prototype = {
2344     /**
2345      * add node to group
2346      * @param {Node} child
2347      */
2348     add: function(child) {
2349         this._add(child);
2350     },
2351     /**
2352      * remove a child node from the group
2353      * @param {Node} child
2354      */
2355     remove: function(child) {
2356         this._remove(child);
2357     },
2358     /**
2359      * draw children
2360      */
2361     _draw: function() {
2362         if(this.attrs.visible) {
2363             this._drawChildren();
2364         }
2365     }
2366 };
2367 
2368 // Extend Container and Node
2369 Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Container);
2370 Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Node);
2371 
2372 ///////////////////////////////////////////////////////////////////////
2373 //  Shape
2374 ///////////////////////////////////////////////////////////////////////
2375 /**
2376  * Shape constructor.  Shapes are used to objectify drawing bits of a KineticJS
2377  * application
2378  * @constructor
2379  * @augments Kinetic.Node
2380  * @param {Object} config
2381  * @config {String|CanvasGradient|CanvasPattern} [fill] fill
2382  * @config {String} [stroke] stroke color
2383  * @config {Number} [strokeWidth] stroke width
2384  * @config {String} [lineJoin] line join.  Can be "miter", "round", or "bevel".  The default
2385  *  is "miter"
2386  * @config {String} [detectionType] shape detection type.  Can be "path" or "pixel".
2387  *  The default is "path" because it performs better
2388  */
2389 Kinetic.Shape = function(config) {
2390     this.setDefaultAttrs({
2391         fill: undefined,
2392         stroke: undefined,
2393         strokeWidth: undefined,
2394         lineJoin: undefined,
2395         detectionType: 'path'
2396     });
2397 
2398     this.data = [];
2399     this.nodeType = 'Shape';
2400 
2401     // call super constructor
2402     Kinetic.Node.apply(this, [config]);
2403 };
2404 /*
2405  * Shape methods
2406  */
2407 Kinetic.Shape.prototype = {
2408     /**
2409      * get layer context where the shape is being drawn.  When
2410      * the shape is being rendered, .getContext() returns the context of the
2411      * user created layer that contains the shape.  When the event detection
2412      * engine is determining whether or not an event has occured on that shape,
2413      * .getContext() returns the context of the invisible path layer.
2414      */
2415     getContext: function() {
2416         return this.tempLayer.getContext();
2417     },
2418     /**
2419      * get shape temp layer canvas
2420      */
2421     getCanvas: function() {
2422         return this.tempLayer.getCanvas();
2423     },
2424     /**
2425      * helper method to stroke shape
2426      */
2427     stroke: function() {
2428         var context = this.getContext();
2429 
2430         if(!!this.attrs.stroke || !!this.attrs.strokeWidth) {
2431             var stroke = !!this.attrs.stroke ? this.attrs.stroke : 'black';
2432             var strokeWidth = !!this.attrs.strokeWidth ? this.attrs.strokeWidth : 2;
2433 
2434             context.lineWidth = strokeWidth;
2435             context.strokeStyle = stroke;
2436             context.stroke();
2437         }
2438     },
2439     /**
2440      * helper method to fill and stroke a shape
2441      *  based on its fill, stroke, and strokeWidth, properties
2442      */
2443     fillStroke: function() {
2444         var context = this.getContext();
2445 
2446         /*
2447          * expect that fill, stroke, and strokeWidth could be
2448          * undfined, '', null, or 0.  Use !!
2449          */
2450         if(!!this.attrs.fill) {
2451             context.fillStyle = this.attrs.fill;
2452             context.fill();
2453         }
2454 
2455         this.stroke();
2456     },
2457     /**
2458      * helper method to set the line join of a shape
2459      * based on the lineJoin property
2460      */
2461     applyLineJoin: function() {
2462         var context = this.getContext();
2463         if(this.attrs.lineJoin !== undefined) {
2464             context.lineJoin = this.attrs.lineJoin;
2465         }
2466     },
2467     /**
2468      * set fill which can be a color, gradient object,
2469      *  or pattern object
2470      * @param {String|CanvasGradient|CanvasPattern} fill
2471      */
2472     setFill: function(fill) {
2473         this.attrs.fill = fill;
2474     },
2475     /**
2476      * get fill
2477      */
2478     getFill: function() {
2479         return this.attrs.fill;
2480     },
2481     /**
2482      * set stroke color
2483      * @param {String} stroke
2484      */
2485     setStroke: function(stroke) {
2486         this.attrs.stroke = stroke;
2487     },
2488     /**
2489      * get stroke color
2490      */
2491     getStroke: function() {
2492         return this.attrs.stroke;
2493     },
2494     /**
2495      * set line join
2496      * @param {String} lineJoin.  Can be "miter", "round", or "bevel".  The
2497      *  default is "miter"
2498      */
2499     setLineJoin: function(lineJoin) {
2500         this.attrs.lineJoin = lineJoin;
2501     },
2502     /**
2503      * get line join
2504      */
2505     getLineJoin: function() {
2506         return this.attrs.lineJoin;
2507     },
2508     /**
2509      * set stroke width
2510      * @param {Number} strokeWidth
2511      */
2512     setStrokeWidth: function(strokeWidth) {
2513         this.attrs.strokeWidth = strokeWidth;
2514     },
2515     /**
2516      * get stroke width
2517      */
2518     getStrokeWidth: function() {
2519         return this.attrs.strokeWidth;
2520     },
2521     /**
2522      * set draw function
2523      * @param {Function} func drawing function
2524      */
2525     setDrawFunc: function(func) {
2526         this.drawFunc = func;
2527     },
2528     /**
2529      * save shape data when using pixel detection.
2530      */
2531     saveData: function() {
2532         var stage = this.getStage();
2533         var w = stage.attrs.width;
2534         var h = stage.attrs.height;
2535 
2536         var bufferLayer = stage.bufferLayer;
2537         var bufferLayerContext = bufferLayer.getContext();
2538 
2539         bufferLayer.clear();
2540         this._draw(bufferLayer);
2541 
2542         var imageData = bufferLayerContext.getImageData(0, 0, w, h);
2543         this.data = imageData.data;
2544     },
2545     /**
2546      * clear shape data
2547      */
2548     clearData: function() {
2549         this.data = [];
2550     },
2551     /**
2552      * determines if point is in the shape
2553      */
2554     intersects: function() {
2555         var pos = Kinetic.GlobalObject._getPoint(arguments);
2556         var stage = this.getStage();
2557 
2558         if(this.attrs.detectionType === 'path') {
2559             var pathLayer = stage.pathLayer;
2560             var pathLayerContext = pathLayer.getContext();
2561 
2562             this._draw(pathLayer);
2563 
2564             return pathLayerContext.isPointInPath(pos.x, pos.y);
2565         }
2566         else {
2567             var w = stage.attrs.width;
2568             var alpha = this.data[((w * pos.y) + pos.x) * 4 + 3];
2569             return (alpha !== undefined && alpha !== 0);
2570         }
2571     },
2572     /**
2573      * draw shape
2574      * @param {Layer} layer Layer that the shape will be drawn on
2575      */
2576     _draw: function(layer) {
2577         if(layer !== undefined && this.drawFunc !== undefined) {
2578             var stage = layer.getStage();
2579             var context = layer.getContext();
2580             var family = [];
2581             var parent = this.parent;
2582 
2583             family.unshift(this);
2584             while(parent) {
2585                 family.unshift(parent);
2586                 parent = parent.parent;
2587             }
2588 
2589             context.save();
2590             for(var n = 0; n < family.length; n++) {
2591                 var node = family[n];
2592                 var t = node.getTransform();
2593 
2594                 // center offset
2595                 if(node.attrs.centerOffset.x !== 0 || node.attrs.centerOffset.y !== 0) {
2596                     t.translate(-1 * node.attrs.centerOffset.x, -1 * node.attrs.centerOffset.y);
2597                 }
2598 
2599                 var m = t.getMatrix();
2600                 context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
2601             }
2602 
2603             if(this.getAbsoluteAlpha() !== 1) {
2604                 context.globalAlpha = this.getAbsoluteAlpha();
2605             }
2606 
2607             this.tempLayer = layer;
2608             this.drawFunc.call(this);
2609             context.restore();
2610         }
2611     }
2612 };
2613 // extend Node
2614 Kinetic.GlobalObject.extend(Kinetic.Shape, Kinetic.Node);
2615 
2616 ///////////////////////////////////////////////////////////////////////
2617 //  Rect
2618 ///////////////////////////////////////////////////////////////////////
2619 /**
2620  * Rect constructor
2621  * @constructor
2622  * @augments Kinetic.Shape
2623  * @param {Object} config
2624  */
2625 Kinetic.Rect = function(config) {
2626     this.setDefaultAttrs({
2627         width: 0,
2628         height: 0,
2629         cornerRadius: 0
2630     });
2631 
2632     this.shapeType = "Rect";
2633 
2634     config.drawFunc = function() {
2635         var context = this.getContext();
2636         context.beginPath();
2637         this.applyLineJoin();
2638         if(this.attrs.cornerRadius === 0) {
2639             // simple rect - don't bother doing all that complicated maths stuff.
2640             context.rect(0, 0, this.attrs.width, this.attrs.height);
2641         }
2642         else {
2643             // arcTo would be nicer, but browser support is patchy (Opera)
2644             context.moveTo(this.attrs.cornerRadius, 0);
2645             context.lineTo(this.attrs.width - this.attrs.cornerRadius, 0);
2646             context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false);
2647             context.lineTo(this.attrs.width, this.attrs.height - this.attrs.cornerRadius);
2648             context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false);
2649             context.lineTo(this.attrs.cornerRadius, this.attrs.height);
2650             context.arc(this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false);
2651             context.lineTo(0, this.attrs.cornerRadius);
2652             context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false);
2653         }
2654         context.closePath();
2655         this.fillStroke();
2656     };
2657     // call super constructor
2658     Kinetic.Shape.apply(this, [config]);
2659 };
2660 /*
2661  * Rect methods
2662  */
2663 Kinetic.Rect.prototype = {
2664     /**
2665      * set width
2666      * @param {Number} width
2667      */
2668     setWidth: function(width) {
2669         this.attrs.width = width;
2670     },
2671     /**
2672      * get width
2673      */
2674     getWidth: function() {
2675         return this.attrs.width;
2676     },
2677     /**
2678      * set height
2679      * @param {Number} height
2680      */
2681     setHeight: function(height) {
2682         this.attrs.height = height;
2683     },
2684     /**
2685      * get height
2686      */
2687     getHeight: function() {
2688         return this.attrs.height;
2689     },
2690     /**
2691      * set width and height
2692      * @param {Number} width
2693      * @param {Number} height
2694      */
2695     setSize: function(width, height) {
2696         this.attrs.width = width;
2697         this.attrs.height = height;
2698     },
2699     /**
2700      * return rect size
2701      */
2702     getSize: function() {
2703         return {
2704             width: this.attrs.width,
2705             height: this.attrs.height
2706         };
2707     },
2708     /**
2709      * set corner radius
2710      * @param {Number} radius
2711      */
2712     setCornerRadius: function(radius) {
2713         this.attrs.cornerRadius = radius;
2714     },
2715     /**
2716      * get corner radius
2717      */
2718     getCornerRadius: function() {
2719         return this.attrs.cornerRadius;
2720     },
2721 };
2722 
2723 // extend Shape
2724 Kinetic.GlobalObject.extend(Kinetic.Rect, Kinetic.Shape);
2725 
2726 ///////////////////////////////////////////////////////////////////////
2727 //  Circle
2728 ///////////////////////////////////////////////////////////////////////
2729 /**
2730  * Circle constructor
2731  * @constructor
2732  * @augments Kinetic.Shape
2733  * @param {Object} config
2734  */
2735 Kinetic.Circle = function(config) {
2736     this.setDefaultAttrs({
2737         radius: 0
2738     });
2739 
2740     this.shapeType = "Circle";
2741 
2742     config.drawFunc = function() {
2743         var canvas = this.getCanvas();
2744         var context = this.getContext();
2745         context.beginPath();
2746         this.applyLineJoin();
2747         context.arc(0, 0, this.attrs.radius, 0, Math.PI * 2, true);
2748         context.closePath();
2749         this.fillStroke();
2750     };
2751     // call super constructor
2752     Kinetic.Shape.apply(this, [config]);
2753 };
2754 /*
2755  * Circle methods
2756  */
2757 Kinetic.Circle.prototype = {
2758     /**
2759      * set radius
2760      * @param {Number} radius
2761      */
2762     setRadius: function(radius) {
2763         this.attrs.radius = radius;
2764     },
2765     /**
2766      * get radius
2767      */
2768     getRadius: function() {
2769         return this.attrs.radius;
2770     }
2771 };
2772 
2773 // extend Shape
2774 Kinetic.GlobalObject.extend(Kinetic.Circle, Kinetic.Shape);
2775 
2776 ///////////////////////////////////////////////////////////////////////
2777 //  Image
2778 ///////////////////////////////////////////////////////////////////////
2779 /**
2780  * Image constructor
2781  * @constructor
2782  * @augments Kinetic.Shape
2783  * @param {Object} config
2784  */
2785 Kinetic.Image = function(config) {
2786     this.setDefaultAttrs({
2787         crop: {
2788             x: 0,
2789             y: 0,
2790             width: undefined,
2791             height: undefined
2792         }
2793     });
2794 
2795     this.shapeType = "Image";
2796     config.drawFunc = function() {
2797         if(this.image !== undefined) {
2798             var width = this.attrs.width !== undefined ? this.attrs.width : this.image.width;
2799             var height = this.attrs.height !== undefined ? this.attrs.height : this.image.height;
2800             var cropX = this.attrs.crop.x;
2801             var cropY = this.attrs.crop.y;
2802             var cropWidth = this.attrs.crop.width;
2803             var cropHeight = this.attrs.crop.height;
2804             var canvas = this.getCanvas();
2805             var context = this.getContext();
2806 
2807             context.beginPath();
2808             this.applyLineJoin();
2809             context.rect(0, 0, width, height);
2810             context.closePath();
2811             this.fillStroke();
2812 
2813             // if cropping
2814             if(cropWidth !== undefined && cropHeight !== undefined) {
2815                 context.drawImage(this.image, cropX, cropY, cropWidth, cropHeight, 0, 0, width, height);
2816             }
2817             // no cropping
2818             else {
2819                 context.drawImage(this.image, 0, 0, width, height);
2820             }
2821         }
2822     };
2823     // call super constructor
2824     Kinetic.Shape.apply(this, [config]);
2825 };
2826 /*
2827  * Image methods
2828  */
2829 Kinetic.Image.prototype = {
2830     /**
2831      * set image
2832      * @param {ImageObject} image
2833      */
2834     setImage: function(image) {
2835         this.image = image;
2836     },
2837     /**
2838      * get image
2839      */
2840     getImage: function() {
2841         return this.image;
2842     },
2843     /**
2844      * set width
2845      * @param {Number} width
2846      */
2847     setWidth: function(width) {
2848         this.attrs.width = width;
2849     },
2850     /**
2851      * get width
2852      */
2853     getWidth: function() {
2854         return this.attrs.width;
2855     },
2856     /**
2857      * set height
2858      * @param {Number} height
2859      */
2860     setHeight: function(height) {
2861         this.attrs.height = height;
2862     },
2863     /**
2864      * get height
2865      */
2866     getHeight: function() {
2867         return this.attrs.height;
2868     },
2869     /**
2870      * set width and height
2871      * @param {Number} width
2872      * @param {Number} height
2873      */
2874     setSize: function(width, height) {
2875         this.attrs.width = width;
2876         this.attrs.height = height;
2877     },
2878     /**
2879      * return image size
2880      */
2881     getSize: function() {
2882         return {
2883             width: this.attrs.width,
2884             height: this.attrs.height
2885         };
2886     },
2887     /**
2888      * return cropping
2889      */
2890     getCrop: function() {
2891         return this.attrs.crop;
2892     },
2893     /**
2894      * set cropping
2895      * @param {Object} crop
2896      * @config {Number} [x] crop x
2897      * @config {Number} [y] crop y
2898      * @config {Number} [width] crop width
2899      * @config {Number} [height] crop height
2900      */
2901     setCrop: function(config) {
2902         var c = {};
2903         c.crop = config;
2904         this.setAttrs(c);
2905     }
2906 };
2907 // extend Shape
2908 Kinetic.GlobalObject.extend(Kinetic.Image, Kinetic.Shape);
2909 
2910 ///////////////////////////////////////////////////////////////////////
2911 //  Polygon
2912 ///////////////////////////////////////////////////////////////////////
2913 /**
2914  * Polygon constructor.  Polygons are defined by an array of points
2915  * @constructor
2916  * @augments Kinetic.Shape
2917  * @param {Object} config
2918  */
2919 Kinetic.Polygon = function(config) {
2920     this.setDefaultAttrs({
2921         points: {}
2922     });
2923 
2924     this.shapeType = "Polygon";
2925     config.drawFunc = function() {
2926         var context = this.getContext();
2927         context.beginPath();
2928         this.applyLineJoin();
2929         context.moveTo(this.attrs.points[0].x, this.attrs.points[0].y);
2930         for(var n = 1; n < this.attrs.points.length; n++) {
2931             context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y);
2932         }
2933         context.closePath();
2934         this.fillStroke();
2935     };
2936     // call super constructor
2937     Kinetic.Shape.apply(this, [config]);
2938 };
2939 /*
2940  * Polygon methods
2941  */
2942 Kinetic.Polygon.prototype = {
2943     /**
2944      * set points array
2945      * @param {Array} points
2946      */
2947     setPoints: function(points) {
2948         this.attrs.points = points;
2949     },
2950     /**
2951      * get points array
2952      */
2953     getPoints: function() {
2954         return this.attrs.points;
2955     }
2956 };
2957 
2958 // extend Shape
2959 Kinetic.GlobalObject.extend(Kinetic.Polygon, Kinetic.Shape);
2960 
2961 ///////////////////////////////////////////////////////////////////////
2962 //  RegularPolygon
2963 ///////////////////////////////////////////////////////////////////////
2964 /**
2965  * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc.
2966  * @constructor
2967  * @augments Kinetic.Shape
2968  * @param {Object} config
2969  */
2970 Kinetic.RegularPolygon = function(config) {
2971     this.setDefaultAttrs({
2972         radius: 0,
2973         sides: 0
2974     });
2975 
2976     this.shapeType = "RegularPolygon";
2977     config.drawFunc = function() {
2978         var context = this.getContext();
2979         context.beginPath();
2980         this.applyLineJoin();
2981         context.moveTo(0, 0 - this.attrs.radius);
2982 
2983         for(var n = 1; n < this.attrs.sides; n++) {
2984             var x = this.attrs.radius * Math.sin(n * 2 * Math.PI / this.attrs.sides);
2985             var y = -1 * this.attrs.radius * Math.cos(n * 2 * Math.PI / this.attrs.sides);
2986             context.lineTo(x, y);
2987         }
2988         context.closePath();
2989         this.fillStroke();
2990     };
2991     // call super constructor
2992     Kinetic.Shape.apply(this, [config]);
2993 };
2994 /*
2995  * RegularPolygon methods
2996  */
2997 Kinetic.RegularPolygon.prototype = {
2998     /**
2999      * set radius
3000      * @param {Number} radius
3001      */
3002     setRadius: function(radius) {
3003         this.attrs.radius = radius;
3004     },
3005     /**
3006      * get radius
3007      */
3008     getRadius: function() {
3009         return this.attrs.radius;
3010     },
3011     /**
3012      * set number of sides
3013      * @param {int} sides
3014      */
3015     setSides: function(sides) {
3016         this.attrs.sides = sides;
3017     },
3018     /**
3019      * get number of sides
3020      */
3021     getSides: function() {
3022         return this.attrs.sides;
3023     }
3024 };
3025 
3026 // extend Shape
3027 Kinetic.GlobalObject.extend(Kinetic.RegularPolygon, Kinetic.Shape);
3028 
3029 ///////////////////////////////////////////////////////////////////////
3030 //  Star
3031 ///////////////////////////////////////////////////////////////////////
3032 /**
3033  * Star constructor
3034  * @constructor
3035  * @augments Kinetic.Shape
3036  * @param {Object} config
3037  */
3038 Kinetic.Star = function(config) {
3039     this.setDefaultAttrs({
3040         points: [],
3041         innerRadius: 0,
3042         outerRadius: 0
3043     });
3044 
3045     this.shapeType = "Star";
3046     config.drawFunc = function() {
3047         var context = this.getContext();
3048         context.beginPath();
3049         this.applyLineJoin();
3050         context.moveTo(0, 0 - this.attrs.outerRadius);
3051 
3052         for(var n = 1; n < this.attrs.points * 2; n++) {
3053             var radius = n % 2 === 0 ? this.attrs.outerRadius : this.attrs.innerRadius;
3054             var x = radius * Math.sin(n * Math.PI / this.attrs.points);
3055             var y = -1 * radius * Math.cos(n * Math.PI / this.attrs.points);
3056             context.lineTo(x, y);
3057         }
3058         context.closePath();
3059         this.fillStroke();
3060     };
3061     // call super constructor
3062     Kinetic.Shape.apply(this, [config]);
3063 };
3064 /*
3065  * Star methods
3066  */
3067 Kinetic.Star.prototype = {
3068     /**
3069      * set points array
3070      * @param {Array} points
3071      */
3072     setPoints: function(points) {
3073         this.attrs.points = points;
3074     },
3075     /**
3076      * get points array
3077      */
3078     getPoints: function() {
3079         return this.attrs.points;
3080     },
3081     /**
3082      * set outer radius
3083      * @param {Number} radius
3084      */
3085     setOuterRadius: function(radius) {
3086         this.attrs.outerRadius = radius;
3087     },
3088     /**
3089      * get outer radius
3090      */
3091     getOuterRadius: function() {
3092         return this.attrs.outerRadius;
3093     },
3094     /**
3095      * set inner radius
3096      * @param {Number} radius
3097      */
3098     setInnerRadius: function(radius) {
3099         this.attrs.innerRadius = radius;
3100     },
3101     /**
3102      * get inner radius
3103      */
3104     getInnerRadius: function() {
3105         return this.attrs.innerRadius;
3106     }
3107 };
3108 // extend Shape
3109 Kinetic.GlobalObject.extend(Kinetic.Star, Kinetic.Shape);
3110 
3111 ///////////////////////////////////////////////////////////////////////
3112 //  Text
3113 ///////////////////////////////////////////////////////////////////////
3114 /**
3115  * Text constructor
3116  * @constructor
3117  * @augments Kinetic.Shape
3118  * @param {Object} config
3119  */
3120 Kinetic.Text = function(config) {
3121     this.setDefaultAttrs({
3122         fontFamily: 'Calibri',
3123         text: '',
3124         fontSize: 12,
3125         fill: undefined,
3126         textStroke: undefined,
3127         textStrokeWidth: undefined,
3128         align: 'left',
3129         verticalAlign: 'top',
3130         padding: 0,
3131         fontStyle: 'normal'
3132     });
3133 
3134     this.shapeType = "Text";
3135 
3136     config.drawFunc = function() {
3137         var context = this.getContext();
3138         context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily;
3139         context.textBaseline = 'middle';
3140         var textHeight = this.getTextHeight();
3141         var textWidth = this.getTextWidth();
3142         var p = this.attrs.padding;
3143         var x = 0;
3144         var y = 0;
3145 
3146         switch (this.attrs.align) {
3147             case 'center':
3148                 x = textWidth / -2 - p;
3149                 break;
3150             case 'right':
3151                 x = -1 * textWidth - p;
3152                 break;
3153         }
3154 
3155         switch (this.attrs.verticalAlign) {
3156             case 'middle':
3157                 y = textHeight / -2 - p;
3158                 break;
3159             case 'bottom':
3160                 y = -1 * textHeight - p;
3161                 break;
3162         }
3163 
3164         // draw path
3165         context.save();
3166         context.beginPath();
3167         this.applyLineJoin();
3168         context.rect(x, y, textWidth + p * 2, textHeight + p * 2);
3169         context.closePath();
3170         this.fillStroke();
3171         context.restore();
3172 
3173         var tx = p + x;
3174         var ty = textHeight / 2 + p + y;
3175 
3176         // draw text
3177         if(this.attrs.textFill !== undefined) {
3178             context.fillStyle = this.attrs.textFill;
3179             context.fillText(this.attrs.text, tx, ty);
3180         }
3181         if(this.attrs.textStroke !== undefined || this.attrs.textStrokeWidth !== undefined) {
3182             // defaults
3183             if(this.attrs.textStroke === undefined) {
3184                 this.attrs.textStroke = 'black';
3185             }
3186             else if(this.attrs.textStrokeWidth === undefined) {
3187                 this.attrs.textStrokeWidth = 2;
3188             }
3189             context.lineWidth = this.attrs.textStrokeWidth;
3190             context.strokeStyle = this.attrs.textStroke;
3191             context.strokeText(this.attrs.text, tx, ty);
3192         }
3193     };
3194     // call super constructor
3195     Kinetic.Shape.apply(this, [config]);
3196 };
3197 /*
3198  * Text methods
3199  */
3200 Kinetic.Text.prototype = {
3201     /**
3202      * set font family
3203      * @param {String} fontFamily
3204      */
3205     setFontFamily: function(fontFamily) {
3206         this.attrs.fontFamily = fontFamily;
3207     },
3208     /**
3209      * get font family
3210      */
3211     getFontFamily: function() {
3212         return this.attrs.fontFamily;
3213     },
3214     /**
3215      * set font size
3216      * @param {int} fontSize
3217      */
3218     setFontSize: function(fontSize) {
3219         this.attrs.fontSize = fontSize;
3220     },
3221     /**
3222      * get font size
3223      */
3224     getFontSize: function() {
3225         return this.attrs.fontSize;
3226     },
3227     /**
3228      * set font style.  Can be "normal", "italic", or "bold".  "normal" is the default.
3229      * @param {String} fontStyle
3230      */
3231     setFontStyle: function(fontStyle) {
3232         this.attrs.fontStyle = fontStyle;
3233     },
3234     /**
3235      * get font style
3236      */
3237     getFontStyle: function() {
3238         return this.attrs.fontStyle;
3239     },
3240     /**
3241      * set text fill color
3242      * @param {String} textFill
3243      */
3244     setTextFill: function(textFill) {
3245         this.attrs.textFill = textFill;
3246     },
3247     /**
3248      * get text fill color
3249      */
3250     getTextFill: function() {
3251         return this.attrs.textFill;
3252     },
3253     /**
3254      * set text stroke color
3255      * @param {String} textStroke
3256      */
3257     setTextStroke: function(textStroke) {
3258         this.attrs.textStroke = textStroke;
3259     },
3260     /**
3261      * get text stroke color
3262      */
3263     getTextStroke: function() {
3264         return this.attrs.textStroke;
3265     },
3266     /**
3267      * set text stroke width
3268      * @param {int} textStrokeWidth
3269      */
3270     setTextStrokeWidth: function(textStrokeWidth) {
3271         this.attrs.textStrokeWidth = textStrokeWidth;
3272     },
3273     /**
3274      * get text stroke width
3275      */
3276     getTextStrokeWidth: function() {
3277         return this.attrs.textStrokeWidth;
3278     },
3279     /**
3280      * set padding
3281      * @param {int} padding
3282      */
3283     setPadding: function(padding) {
3284         this.attrs.padding = padding;
3285     },
3286     /**
3287      * get padding
3288      */
3289     getPadding: function() {
3290         return this.attrs.padding;
3291     },
3292     /**
3293      * set horizontal align of text
3294      * @param {String} align align can be 'left', 'center', or 'right'
3295      */
3296     setAlign: function(align) {
3297         this.attrs.align = align;
3298     },
3299     /**
3300      * get horizontal align
3301      */
3302     getAlign: function() {
3303         return this.attrs.align;
3304     },
3305     /**
3306      * set vertical align of text
3307      * @param {String} verticalAlign verticalAlign can be "top", "middle", or "bottom"
3308      */
3309     setVerticalAlign: function(verticalAlign) {
3310         this.attrs.verticalAlign = verticalAlign;
3311     },
3312     /**
3313      * get vertical align
3314      */
3315     getVerticalAlign: function() {
3316         return this.attrs.verticalAlign;
3317     },
3318     /**
3319      * set text
3320      * @param {String} text
3321      */
3322     setText: function(text) {
3323         this.attrs.text = text;
3324     },
3325     /**
3326      * get text
3327      */
3328     getText: function() {
3329         return this.attrs.text;
3330     },
3331     /**
3332      * get text width in pixels
3333      */
3334     getTextWidth: function() {
3335         return this.getTextSize().width;
3336     },
3337     /**
3338      * get text height in pixels
3339      */
3340     getTextHeight: function() {
3341         return this.getTextSize().height;
3342     },
3343     /**
3344      * get text size in pixels
3345      */
3346     getTextSize: function() {
3347         var context = this.getContext();
3348         context.save();
3349         context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily;
3350         var metrics = context.measureText(this.attrs.text);
3351         context.restore();
3352         return {
3353             width: metrics.width,
3354             height: parseInt(this.attrs.fontSize, 10)
3355         };
3356     }
3357 };
3358 // extend Shape
3359 Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Shape);
3360 
3361 ///////////////////////////////////////////////////////////////////////
3362 //  Line
3363 ///////////////////////////////////////////////////////////////////////
3364 /**
3365  * Line constructor.  Lines are defined by an array of points
3366  * @constructor
3367  * @augments Kinetic.Shape
3368  * @param {Object} config
3369  */
3370 Kinetic.Line = function(config) {
3371     this.setDefaultAttrs({
3372         points: {},
3373         lineCap: 'butt'
3374     });
3375 
3376     this.shapeType = "Line";
3377     config.drawFunc = function() {
3378         var context = this.getContext();
3379         context.beginPath();
3380         this.applyLineJoin();
3381         context.moveTo(this.attrs.points[0].x, this.attrs.points[0].y);
3382         for(var n = 1; n < this.attrs.points.length; n++) {
3383             context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y);
3384         }
3385 
3386         if(!!this.attrs.lineCap) {
3387             context.lineCap = this.attrs.lineCap;
3388         }
3389         this.stroke();
3390     };
3391     // call super constructor
3392     Kinetic.Shape.apply(this, [config]);
3393 };
3394 /*
3395  * Line methods
3396  */
3397 Kinetic.Line.prototype = {
3398     /**
3399      * set points array
3400      * @param {Array} points
3401      */
3402     setPoints: function(points) {
3403         this.attrs.points = points;
3404     },
3405     /**
3406      * get points array
3407      */
3408     getPoints: function() {
3409         return this.attrs.points;
3410     },
3411     /**
3412      * set line cap.  Can be butt, round, or square
3413      * @param {String} lineCap
3414      */
3415     setLineCap: function(lineCap) {
3416         this.attrs.lineCap = lineCap;
3417     },
3418     /**
3419      * get line cap
3420      */
3421     getLineCap: function() {
3422         return this.attrs.lineCap;
3423     }
3424 };
3425 
3426 // extend Shape
3427 Kinetic.GlobalObject.extend(Kinetic.Line, Kinetic.Shape);
3428 
3429 /*
3430 * Last updated November 2011
3431 * By Simon Sarris
3432 * www.simonsarris.com
3433 * sarris@acm.org
3434 *
3435 * Free to use and distribute at will
3436 * So long as you are nice to people, etc
3437 */
3438 
3439 /*
3440 * The usage of this class was inspired by some of the work done by a forked
3441 * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform
3442 * class.
3443 */
3444 
3445 /**
3446  * Matrix object
3447  */
3448 Kinetic.Transform = function() {
3449     this.m = [1, 0, 0, 1, 0, 0];
3450 }
3451 
3452 Kinetic.Transform.prototype = {
3453     /**
3454      * Apply translation
3455      * @param {Number} x
3456      * @param {Number} y
3457      */
3458     translate: function(x, y) {
3459         this.m[4] += this.m[0] * x + this.m[2] * y;
3460         this.m[5] += this.m[1] * x + this.m[3] * y;
3461     },
3462     /**
3463      * Apply scale
3464      * @param {Number} sx
3465      * @param {Number} sy
3466      */
3467     scale: function(sx, sy) {
3468         this.m[0] *= sx;
3469         this.m[1] *= sx;
3470         this.m[2] *= sy;
3471         this.m[3] *= sy;
3472     },
3473     /**
3474      * Apply rotation
3475      * @param {Number} rad  Angle in radians
3476      */
3477     rotate: function(rad) {
3478         var c = Math.cos(rad);
3479         var s = Math.sin(rad);
3480         var m11 = this.m[0] * c + this.m[2] * s;
3481         var m12 = this.m[1] * c + this.m[3] * s;
3482         var m21 = this.m[0] * -s + this.m[2] * c;
3483         var m22 = this.m[1] * -s + this.m[3] * c;
3484         this.m[0] = m11;
3485         this.m[1] = m12;
3486         this.m[2] = m21;
3487         this.m[3] = m22;
3488     },
3489     /**
3490      * Returns the translation
3491      * @returns {Object} 2D point(x, y)
3492      */
3493     getTranslation: function() {
3494         return {
3495             x: this.m[4],
3496             y: this.m[5]
3497         };
3498     },
3499     /**
3500      * Transform multiplication
3501      * @param {Kinetic.Transform} matrix
3502      */
3503     multiply: function(matrix) {
3504         var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
3505         var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
3506 
3507         var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
3508         var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
3509 
3510         var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
3511         var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
3512 
3513         this.m[0] = m11;
3514         this.m[1] = m12;
3515         this.m[2] = m21;
3516         this.m[3] = m22;
3517         this.m[4] = dx;
3518         this.m[5] = dy;
3519     },
3520     /**
3521      * Invert the matrix
3522      */
3523     invert: function() {
3524         var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]);
3525         var m0 = this.m[3] * d;
3526         var m1 = -this.m[1] * d;
3527         var m2 = -this.m[2] * d;
3528         var m3 = this.m[0] * d;
3529         var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]);
3530         var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]);
3531         this.m[0] = m0;
3532         this.m[1] = m1;
3533         this.m[2] = m2;
3534         this.m[3] = m3;
3535         this.m[4] = m4;
3536         this.m[5] = m5;
3537     },
3538     /**
3539      * return matrix
3540      */
3541     getMatrix: function() {
3542         return this.m;
3543     }
3544 };
3545 
3546 /*
3547 * The Tween class was ported from an Adobe Flash Tween library
3548 * to JavaScript by Xaric.  In the context of KineticJS, a Tween is
3549 * an animation of a single Node property.  A Transition is a set of
3550 * multiple tweens
3551 */
3552 
3553 /**
3554  * Transition constructor.  KineticJS transitions contain
3555  * multiple Tweens
3556  */
3557 Kinetic.Transition = function(node, config) {
3558     this.node = node;
3559     this.config = config;
3560     this.tweens = [];
3561 
3562     // add tween for each property
3563     for(var key in config) {
3564         if(key !== 'duration' && key !== 'easing' && key !== 'callback') {
3565             if(config[key].x === undefined && config[key].y === undefined) {
3566                 this.add(this._getTween(key, config));
3567             }
3568             if(config[key].x !== undefined) {
3569                 this.add(this._getComponentTween(key, 'x', config));
3570             }
3571             if(config[key].y !== undefined) {
3572                 this.add(this._getComponentTween(key, 'y', config));
3573             }
3574         }
3575     }
3576 
3577     var finishedTweens = 0;
3578     var that = this;
3579     for(var n = 0; n < this.tweens.length; n++) {
3580         var tween = this.tweens[n];
3581         tween.onFinished = function() {
3582             finishedTweens++;
3583             if(finishedTweens >= that.tweens.length) {
3584                 that.onFinished();
3585             }
3586         };
3587     }
3588 };
3589 /*
3590  * Transition methods
3591  */
3592 Kinetic.Transition.prototype = {
3593     /**
3594      * add tween to tweens array
3595      * @param {Kinetic.Tween} tween
3596      */
3597     add: function(tween) {
3598         this.tweens.push(tween);
3599     },
3600     /**
3601      * start transition
3602      */
3603     start: function() {
3604         for(var n = 0; n < this.tweens.length; n++) {
3605             this.tweens[n].start();
3606         }
3607     },
3608     /**
3609      * onEnterFrame
3610      */
3611     onEnterFrame: function() {
3612         for(var n = 0; n < this.tweens.length; n++) {
3613             this.tweens[n].onEnterFrame();
3614         }
3615     },
3616     /**
3617      * stop transition
3618      */
3619     stop: function() {
3620         for(var n = 0; n < this.tweens.length; n++) {
3621             this.tweens[n].stop();
3622         }
3623     },
3624     /**
3625      * resume transition
3626      */
3627     resume: function() {
3628         for(var n = 0; n < this.tweens.length; n++) {
3629             this.tweens[n].resume();
3630         }
3631     },
3632     _getTween: function(key) {
3633         var config = this.config;
3634         var node = this.node;
3635         var easing = config.easing;
3636         if(easing === undefined) {
3637             easing = 'linear';
3638         }
3639 
3640         var tween = new Kinetic.Tween(node, function(i) {
3641             node.attrs[key] = i;
3642         }, Kinetic.Tweens[easing], node.attrs[key], config[key], config.duration);
3643 
3644         return tween;
3645     },
3646     _getComponentTween: function(key, prop) {
3647         var config = this.config;
3648         var node = this.node;
3649         var easing = config.easing;
3650         if(easing === undefined) {
3651             easing = 'linear';
3652         }
3653 
3654         var tween = new Kinetic.Tween(node, function(i) {
3655             node.attrs[key][prop] = i;
3656         }, Kinetic.Tweens[easing], node.attrs[key][prop], config[key][prop], config.duration);
3657 
3658         return tween;
3659     },
3660 };
3661 
3662 /**
3663  * Tween constructor
3664  */
3665 Kinetic.Tween = function(obj, propFunc, func, begin, finish, duration) {
3666     this._listeners = [];
3667     this.addListener(this);
3668     this.obj = obj;
3669     this.propFunc = propFunc;
3670     this.begin = begin;
3671     this._pos = begin;
3672     this.setDuration(duration);
3673     this.isPlaying = false;
3674     this._change = 0;
3675     this.prevTime = 0;
3676     this.prevPos = 0;
3677     this.looping = false;
3678     this._time = 0;
3679     this._position = 0;
3680     this._startTime = 0;
3681     this._finish = 0;
3682     this.name = '';
3683     this.func = func;
3684     this.setFinish(finish);
3685 };
3686 /*
3687  * Tween methods
3688  */
3689 Kinetic.Tween.prototype = {
3690     setTime: function(t) {
3691         this.prevTime = this._time;
3692         if(t > this.getDuration()) {
3693             if(this.looping) {
3694                 this.rewind(t - this._duration);
3695                 this.update();
3696                 this.broadcastMessage('onLooped', {
3697                     target: this,
3698                     type: 'onLooped'
3699                 });
3700             }
3701             else {
3702                 this._time = this._duration;
3703                 this.update();
3704                 this.stop();
3705                 this.broadcastMessage('onFinished', {
3706                     target: this,
3707                     type: 'onFinished'
3708                 });
3709             }
3710         }
3711         else if(t < 0) {
3712             this.rewind();
3713             this.update();
3714         }
3715         else {
3716             this._time = t;
3717             this.update();
3718         }
3719     },
3720     getTime: function() {
3721         return this._time;
3722     },
3723     setDuration: function(d) {
3724         this._duration = (d === null || d <= 0) ? 100000 : d;
3725     },
3726     getDuration: function() {
3727         return this._duration;
3728     },
3729     setPosition: function(p) {
3730         this.prevPos = this._pos;
3731         //var a = this.suffixe != '' ? this.suffixe : '';
3732         this.propFunc(p);
3733         //+ a;
3734         //this.obj(Math.round(p));
3735         this._pos = p;
3736         this.broadcastMessage('onChanged', {
3737             target: this,
3738             type: 'onChanged'
3739         });
3740     },
3741     getPosition: function(t) {
3742         if(t === undefined) {
3743             t = this._time;
3744         }
3745         return this.func(t, this.begin, this._change, this._duration);
3746     },
3747     setFinish: function(f) {
3748         this._change = f - this.begin;
3749     },
3750     getFinish: function() {
3751         return this.begin + this._change;
3752     },
3753     start: function() {
3754         this.rewind();
3755         this.startEnterFrame();
3756         this.broadcastMessage('onStarted', {
3757             target: this,
3758             type: 'onStarted'
3759         });
3760     },
3761     rewind: function(t) {
3762         this.stop();
3763         this._time = (t === undefined) ? 0 : t;
3764         this.fixTime();
3765         this.update();
3766     },
3767     fforward: function() {
3768         this._time = this._duration;
3769         this.fixTime();
3770         this.update();
3771     },
3772     update: function() {
3773         this.setPosition(this.getPosition(this._time));
3774     },
3775     startEnterFrame: function() {
3776         this.stopEnterFrame();
3777         this.isPlaying = true;
3778         this.onEnterFrame();
3779     },
3780     onEnterFrame: function() {
3781         if(this.isPlaying) {
3782             this.nextFrame();
3783         }
3784     },
3785     nextFrame: function() {
3786         this.setTime((this.getTimer() - this._startTime) / 1000);
3787     },
3788     stop: function() {
3789         this.stopEnterFrame();
3790         this.broadcastMessage('onStopped', {
3791             target: this,
3792             type: 'onStopped'
3793         });
3794     },
3795     stopEnterFrame: function() {
3796         this.isPlaying = false;
3797     },
3798     continueTo: function(finish, duration) {
3799         this.begin = this._pos;
3800         this.setFinish(finish);
3801         if(this._duration != undefined)
3802             this.setDuration(duration);
3803         this.start();
3804     },
3805     resume: function() {
3806         this.fixTime();
3807         this.startEnterFrame();
3808         this.broadcastMessage('onResumed', {
3809             target: this,
3810             type: 'onResumed'
3811         });
3812     },
3813     yoyo: function() {
3814         this.continueTo(this.begin, this._time);
3815     },
3816     addListener: function(o) {
3817         this.removeListener(o);
3818         return this._listeners.push(o);
3819     },
3820     removeListener: function(o) {
3821         var a = this._listeners;
3822         var i = a.length;
3823         while(i--) {
3824             if(a[i] == o) {
3825                 a.splice(i, 1);
3826                 return true;
3827             }
3828         }
3829         return false;
3830     },
3831     broadcastMessage: function() {
3832         var arr = [];
3833         for(var i = 0; i < arguments.length; i++) {
3834             arr.push(arguments[i]);
3835         }
3836         var e = arr.shift();
3837         var a = this._listeners;
3838         var l = a.length;
3839         for(var i = 0; i < l; i++) {
3840             if(a[i][e]) {
3841                 a[i][e].apply(a[i], arr);
3842             }
3843         }
3844     },
3845     fixTime: function() {
3846         this._startTime = this.getTimer() - this._time * 1000;
3847     },
3848     getTimer: function() {
3849         return new Date().getTime() - this._time;
3850     }
3851 };
3852 
3853 Kinetic.Tweens = {
3854     'back-ease-in': function(t, b, c, d, a, p) {
3855         var s = 1.70158;
3856         return c * (t /= d) * t * ((s + 1) * t - s) + b;
3857     },
3858     'back-ease-out': function(t, b, c, d, a, p) {
3859         var s = 1.70158;
3860         return c * (( t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
3861     },
3862     'back-ease-in-out': function(t, b, c, d, a, p) {
3863         var s = 1.70158;
3864         if((t /= d / 2) < 1) {
3865             return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
3866         }
3867         return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
3868     },
3869     'elastic-ease-in': function(t, b, c, d, a, p) {
3870         // added s = 0
3871         var s = 0;
3872         if(t === 0) {
3873             return b;
3874         }
3875         if((t /= d) == 1) {
3876             return b + c;
3877         }
3878         if(!p) {
3879             p = d * 0.3;
3880         }
3881         if(!a || a < Math.abs(c)) {
3882             a = c;
3883             s = p / 4;
3884         }
3885         else {
3886             s = p / (2 * Math.PI) * Math.asin(c / a);
3887         }
3888         return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
3889     },
3890     'elastic-ease-out': function(t, b, c, d, a, p) {
3891         // added s = 0
3892         var s = 0;
3893         if(t === 0) {
3894             return b;
3895         }
3896         if((t /= d) == 1) {
3897             return b + c;
3898         }
3899         if(!p) {
3900             p = d * 0.3;
3901         }
3902         if(!a || a < Math.abs(c)) {
3903             a = c;
3904             s = p / 4;
3905         }
3906         else {
3907             s = p / (2 * Math.PI) * Math.asin(c / a);
3908         }
3909         return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
3910     },
3911     'elastic-ease-in-out': function(t, b, c, d, a, p) {
3912         // added s = 0
3913         var s = 0;
3914         if(t === 0) {
3915             return b;
3916         }
3917         if((t /= d / 2) == 2) {
3918             return b + c;
3919         }
3920         if(!p) {
3921             p = d * (0.3 * 1.5);
3922         }
3923         if(!a || a < Math.abs(c)) {
3924             a = c;
3925             s = p / 4;
3926         }
3927         else {
3928             s = p / (2 * Math.PI) * Math.asin(c / a);
3929         }
3930         if(t < 1) {
3931             return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
3932         }
3933         return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
3934     },
3935     'bounce-ease-out': function(t, b, c, d) {
3936         if((t /= d) < (1 / 2.75)) {
3937             return c * (7.5625 * t * t) + b;
3938         }
3939         else if(t < (2 / 2.75)) {
3940             return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
3941         }
3942         else if(t < (2.5 / 2.75)) {
3943             return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
3944         }
3945         else {
3946             return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
3947         }
3948     },
3949     'bounce-ease-in': function(t, b, c, d) {
3950         return c - Kinetic.Tweens['bounce-ease-out'](d - t, 0, c, d) + b;
3951     },
3952     'bounce-ease-in-out': function(t, b, c, d) {
3953         if(t < d / 2) {
3954             return Kinetic.Tweens['bounce-ease-in'](t * 2, 0, c, d) * 0.5 + b;
3955         }
3956         else {
3957             return Kinetic.Tweens['bounce-ease-out'](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
3958         }
3959     },
3960     // duplicate
3961     /*
3962      strongEaseInOut: function(t, b, c, d) {
3963      return c * (t /= d) * t * t * t * t + b;
3964      },
3965      */
3966     'ease-in': function(t, b, c, d) {
3967         return c * (t /= d) * t + b;
3968     },
3969     'ease-out': function(t, b, c, d) {
3970         return -c * (t /= d) * (t - 2) + b;
3971     },
3972     'ease-in-out': function(t, b, c, d) {
3973         if((t /= d / 2) < 1) {
3974             return c / 2 * t * t + b;
3975         }
3976         return -c / 2 * ((--t) * (t - 2) - 1) + b;
3977     },
3978     'strong-ease-in': function(t, b, c, d) {
3979         return c * (t /= d) * t * t * t * t + b;
3980     },
3981     'strong-ease-out': function(t, b, c, d) {
3982         return c * (( t = t / d - 1) * t * t * t * t + 1) + b;
3983     },
3984     'strong-ease-in-out': function(t, b, c, d) {
3985         if((t /= d / 2) < 1) {
3986             return c / 2 * t * t * t * t * t + b;
3987         }
3988         return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
3989     },
3990     'linear': function(t, b, c, d) {
3991         return c * t / d + b;
3992     },
3993 };
3994