1 goog.provide('lime.Node');
  2 
  3 
  4 goog.require('goog.events.EventTarget');
  5 goog.require('goog.math.Box');
  6 goog.require('goog.math.Coordinate');
  7 goog.require('goog.math.Size');
  8 goog.require('goog.math.Vec2');
  9 goog.require('lime');
 10 goog.require('lime.DirtyObject');
 11 
 12 goog.require('lime.Renderer.CANVAS');
 13 goog.require('lime.Renderer.DOM');
 14 
 15 
 16 /**
 17 * Node. Abstract drawable object in lime.
 18 * @constructor
 19 * @implements lime.DirtyObject
 20 * extends goog.events.EventTarget
 21 */
 22 lime.Node = function() {
 23     goog.events.EventTarget.call(this);
 24 
 25     this.children_ = [];
 26 
 27     this.parent_ = null;
 28 
 29     /** type {Object.<number, number>} */
 30     this.transitionsAdd_ = {};
 31     this.transitionsActive_ = {};
 32     this.transitionsActiveSet_ = {};
 33     /** type {Object.<number, number>} */
 34     this.transitionsClear_ = {};
 35 
 36     /**
 37      * Node has been added to DOM tree
 38      * @type {boolean}
 39      * @protected
 40      */
 41     this.inTree_ = false;
 42 
 43     this.director_ = null;
 44 
 45     this.scene_ = null;
 46 
 47     /**
 48      * Hash of active event handlers
 49      * @type {Object}
 50      * @private
 51      */
 52     this.eventHandlers_ = {};
 53 
 54     this.setScale(1);
 55 
 56     this.setPosition(0, 0);
 57 
 58     this.setSize(0, 0);
 59 
 60     this.quality_ = 1.0;
 61 
 62     this.setAnchorPoint(0.5, 0.5);
 63 
 64     this.setRotation(0);
 65 
 66     this.setAutoResize(lime.AutoResize.NONE);
 67 
 68     this.opacity_ = 1;
 69 
 70     this.setMask(null);
 71 
 72     this.setRenderer(this.supportedRenderers[0].getType());
 73 
 74     this.setDirty(lime.Dirty.LAYOUT);
 75 
 76 };
 77 goog.inherits(lime.Node, goog.events.EventTarget);
 78 
 79 /**
 80  * Supported renderers for Node
 81  * @type {Array.<lime.Renderer>}
 82  */
 83 lime.Node.prototype.supportedRenderers = [
 84     lime.Renderer.DOM,
 85     lime.Renderer.CANVAS
 86 ];
 87 
 88 /**
 89  * Set renderer for the node. Renderer defines what lower
 90  * level technology will be used for drawing node on screen
 91  * @param {lime.Renderer} value Renderer object.
 92  * @return {lime.Node} Node itself.
 93  */
 94 lime.Node.prototype.setRenderer = function(value) {
 95     if (!this.renderer || this.renderer.getType() != value) {
 96         var index = -1;
 97         for (var i = 0; i < this.supportedRenderers.length; i++) {
 98             if (this.supportedRenderers[i].getType() == value) {
 99                 index = i;
100                 break;
101             }
102         }
103         if (index == -1) return this; //not supported
104 
105         this.renderer = this.supportedRenderers[i];
106         this.setDirty(lime.Dirty.LAYOUT);
107         for (var i = 0, child; child = this.children_[i]; i++) {
108             child.setRenderer(value);
109         }
110     }
111     return this;
112 };
113 
114 /**
115  * Does node require DOM element for drawing?
116  * @return {boolean} True if DOM is required.
117  */
118 lime.Node.prototype.needsDomElement = function() {
119     return !(this.parent_ &&
120         this.parent_.renderer.getType() == lime.Renderer.CANVAS);
121 };
122 
123 /**
124  * Return deepest element in DOM tree that is used
125  * for drawing the Node.
126  * @return {Element} Deepest DOM element.
127  */
128 lime.Node.prototype.getDeepestDomElement = function() {
129     return this.getDeepestParentWithDom().domElement;
130 };
131 
132 /**
133  * Return deepest parent node that requires DOM element
134  * for drawing on screen.
135  * @return {lime.Node} Deepest parent.
136  */
137 lime.Node.prototype.getDeepestParentWithDom = function() {
138     if (this.needsDomElement()) {
139         this.updateDomElement();
140         return this;
141     }
142     else {
143         if (this.parent_)
144         return this.parent_.getDeepestParentWithDom();
145     }
146     return null;
147 };
148 
149 /**
150  * Return array of parent nodes until scene object
151  * @private
152  * @return {Array.<lime.Node>} Array of parents.
153  */
154 lime.Node.prototype.getParentStack_ = function() {
155     if (!this.parent_ || this instanceof lime.Scene) return [];
156     var r = this.parent_.children_.indexOf(this);
157     var a = this.parent_.getParentStack_();
158     a.push(r);
159     return a;
160 };
161 
162 /**
163  * Compare two node positions in the tree
164  * @param {lime.Node} n1 First node.
165  * @param {lime.Node} n2 Second node.
166  * @return {number} Comparison result.
167  */
168 lime.Node.compareNode = function(n1, n2) {
169     if (n1 == n2) return 0;
170 
171     var s1 = n1.getParentStack_();
172     var s2 = n2.getParentStack_();
173 
174     var i = 0;
175     while (true) {
176         if (s1.length <= i) return 1;
177         if (s2.length <= i) return -1;
178 
179         if (s1[i] == s2[i]) {
180             i++;
181         }
182         else {
183             return s1[i] > s2[i] ? -1 : 1;
184         }
185     }
186 };
187 
188 /**
189  * @private
190  */
191 lime.Node.prototype.customEvent_ = false;
192 
193 /**
194  * Return a bitmask of dirty values that need to be updated before next drawing
195  * The bitmask parts are values of lime.Dirty enum
196  * @return {number} Dirty propertiest bitmask.
197  */
198 lime.Node.prototype.getDirty = function() {
199     return this.dirty_;
200 };
201 
202 /**
203  * Sets a dirty value true. This means that object needs that
204  * kind of updates before next draw.
205  * @param {number} value Values to be added to the bitmask.
206  * @param {number=} opt_pass Pass number (0-1).
207  * @param {boolean=} opt_nextframe Register for next frame.
208  * @return {lime.Node} Node itself.
209  */
210 lime.Node.prototype.setDirty = function(value, opt_pass, opt_nextframe) {
211 
212     if (value && !this.dirty_) {
213         lime.setObjectDirty(this, opt_pass, opt_nextframe);
214     }
215     var old = this.dirty_;
216     this.dirty_ |= value;
217 
218     if (value == lime.Dirty.LAYOUT) {
219         for (var i = 0, child; child = this.children_[i]; i++) {
220             if (child instanceof lime.Node)
221             child.setDirty(lime.Dirty.LAYOUT);
222         }
223     }
224     if (!goog.isDef(this.dirty_) || !value) {
225         this.dirty_ = 0;
226         lime.clearObjectDirty(this, opt_pass, opt_nextframe);
227     }
228     if(value && this.maskTarget_){
229         this.mSet = false;
230         this.maskTarget_.setDirty(~0);
231     }
232     
233     return this;
234 };
235 
236 
237 /**
238  * Returns scale vector for the element. 1,1 means no scale.
239  * @return {goog.math.Vec2} scale vector.
240  */
241 lime.Node.prototype.getScale = function() {
242     return this.scale_;
243 };
244 /**
245  * Sets new scale vector for element. This function also accepts
246  * 2 numbers or 1 number that would be coverted to vector before use
247  * @param {(goog.math.Vec2|number)} value New scale vector.
248  * @param {number=} opt_y Optionaly set scale using x,y.
249  * @return {lime.Node} Node itself.
250  */
251 lime.Node.prototype.setScale = function(value, opt_y) {
252     if (arguments.length == 1 && goog.isNumber(value)) {
253         this.scale_ = new goog.math.Vec2(value, value);
254     }
255     else if (arguments.length == 2) {
256         this.scale_ = new goog.math.Vec2(arguments[0], arguments[1]);
257     }
258     else {
259         this.scale_ = value;
260     }
261     if (this.transitionsActive_[lime.Transition.SCALE]) return this;
262     return this.setDirty(lime.Dirty.SCALE);
263 };
264 
265 /**
266  * Returns element's position coordinate
267  * @return {goog.math.Coordinate} Current position coordinate.
268  */
269 lime.Node.prototype.getPosition = function() {
270     return this.position_;
271 };
272 
273 /**
274  * Sets new position for element. Also accepts 2 numbers(x and y value)
275  * @param {(goog.math.Coordinate|number)} value Position coordinate.
276  * @param {number=} opt_y Optionaly set position using x,y.
277  * @return {lime.Node} object itself.
278  */
279 lime.Node.prototype.setPosition = function(value, opt_y) {
280     if (arguments.length == 2) {
281         this.position_ = new goog.math.Coordinate(arguments[0], arguments[1]);
282     }
283     else {
284         this.position_ = value;
285     }
286     if (this.transitionsActive_[lime.Transition.POSITION]) return this;
287     return this.setDirty(lime.Dirty.POSITION);
288 };
289 
290 /**
291  * Returns element used as a mask for current element
292  * @return {lime.Node} Mask node.
293  */
294 lime.Node.prototype.getMask = function() {
295     return this.mask_;
296 };
297 /**
298  * Sets element as a mask for current element
299  * @param {lime.Node} value Mask node.
300  * @return {lime.Node} object itself.
301  */
302 lime.Node.prototype.setMask = function(value) {
303     if (value == this.mask_) return this;
304 
305     if(this.mask_){
306         this.mask_.releaseDependencies();
307         delete this.mask_.maskTarget_;
308     }
309 
310     this.mask_ = value;
311     
312     if(this.mask_){
313         this.mask_.setupDependencies();
314         this.mask_.maskTarget_ = this;
315     }
316 
317     return this.setDirty(lime.Dirty.CONTENT);
318 };
319 
320 /**
321  * Returns anchor point for the element.
322  * @return {goog.math.Vec2} Anchorpoint vector.
323  */
324 lime.Node.prototype.getAnchorPoint = function() {
325     return this.anchorPoint_;
326 };
327 
328 /**
329  * Sets elements anchor point to new value. Anchor point is used
330  * when positioning the element to position coordinate. [0,0] means
331  * top left corner, [1,1] bottom right, [.5,.5] means that element
332  * is position by the center. You can also pass in 2 numbers.
333  * @param {(goog.math.Vec2|number)} value AnchorPoint vector.
334  * @param {number=} opt_y Optionaly set anchorpoint with x,y.
335  * @return {lime.Node} object itself.
336  */
337 lime.Node.prototype.setAnchorPoint = function(value, opt_y) {
338     if (arguments.length == 2) {
339         this.anchorPoint_ = new goog.math.Vec2(arguments[0], arguments[1]);
340     }
341     else {
342         this.anchorPoint_ = value;
343     }
344     return this.setDirty(lime.Dirty.POSITION);
345 };
346 
347 /**
348  * Returns rotation angle for element in degrees
349  * @return {number} Rotation angle.
350  */
351 lime.Node.prototype.getRotation = function() {
352     return (this.rotation_ = this.rotation_ % 360);
353 };
354 
355 /**
356  * Rotates element to specific angle in degrees
357  * @param {number} value New rotation angle.
358  * @return {lime.Node} object itself.
359  */
360 lime.Node.prototype.setRotation = function(value) {
361 
362     this.rotation_ = value;
363 
364     if (this.transitionsActive_[lime.Transition.ROTATION]) return this;
365 
366     return this.setDirty(lime.Dirty.POSITION);
367 };
368 
369 
370 /**
371  * Returns true if element currently not visible
372  * @return {boolean} True if node is hidden.
373  */
374 lime.Node.prototype.getHidden = function() {
375     return this.hidden_;
376 };
377 /**
378  * Sets if element is visible or not
379  * @param {boolean} value Hide(true) or show(false).
380  * @return {lime.Node} object itself.
381  */
382 lime.Node.prototype.setHidden = function(value) {
383     this.hidden_ = value;
384     this.setDirty(lime.Dirty.VISIBILITY);
385     this.autoHide_ = 0;
386     return this;
387 };
388 
389 /**
390  * Returns elements dimension
391  * @return {goog.math.Size} Current element dimensions.
392  */
393 lime.Node.prototype.getSize = function() {
394     return this.size_;
395 };
396 
397 /**
398  * Sets dimensions for element. This funciton also
399  * accepts 2 numbers: width,height
400  * @param {(goog.math.Size|number)} value Elements new size.
401  * @param {number=} opt_height Optionaly use widht,height as parameter.
402  * @return {lime.Node} object itself.
403  */
404 lime.Node.prototype.setSize = function(value, opt_height) {
405     var oldSize = this.size_,
406         newval,
407         scale;
408     if (arguments.length == 2) {
409         newval = new goog.math.Size(arguments[0], arguments[1]);
410     }
411     else {
412         newval = value;
413     }
414     //todo:clear this mess
415     var ap2 = this.getAnchorPoint();
416    if (oldSize && this.children_.length) {
417         for (var i = 0; i < this.children_.length; i++) {
418             var c = this.children_[i];
419             if (c.getAutoResize) {
420                 var ar = c.getAutoResize();
421                 if (ar == lime.AutoResize.NONE) continue;
422                 var b = c.getBoundingBox();
423                 var fixed = oldSize.width;
424                 var c1 = b.left + ap2.x * oldSize.width;
425                 var c2 = b.right - b.left;
426                 var c3 = fixed - b.right - ap2.x * oldSize.width;
427                 if (ar & lime.AutoResize.LEFT) fixed -= c1;
428                 if (ar & lime.AutoResize.WIDTH) fixed -= c2;
429                 if (ar & lime.AutoResize.RIGHT) fixed -= c3;
430                 if (fixed != oldSize.width) {
431                     scale = (newval.width - fixed) /
432                         (oldSize.width - fixed);
433                     if (ar & lime.AutoResize.LEFT) c1 *= scale;
434                     if (ar & lime.AutoResize.WIDTH) c2 *= scale;
435                 }
436                 fixed = oldSize.height;
437                 var r1 = b.top + ap2.y * oldSize.height;
438                 var r2 = b.bottom - b.top;
439                 var r3 = fixed - b.bottom - ap2.y * oldSize.height;
440                 if (ar & lime.AutoResize.TOP) fixed -= r1;
441                 if (ar & lime.AutoResize.HEIGHT) fixed -= r2;
442                 if (ar & lime.AutoResize.BOTTOM) fixed -= r3;
443                 if (fixed != oldSize.height) {
444                     scale = (newval.height - fixed) /
445                         (oldSize.height - fixed);
446                     if (ar & lime.AutoResize.TOP) r1 *= scale;
447                     if (ar & lime.AutoResize.HEIGHT) r2 *= scale;
448                 }
449 
450                 var ap = c.getAnchorPoint();
451                 c.setSize(c2, r2);
452                 c.setPosition(c1 + ap.x * c2 - ap2.x * newval.width,
453                               r1 + ap.y * r2 - ap2.y * newval.height);
454             }
455 
456         }
457 
458     }
459     this.size_ = newval;
460     return this.setDirty(lime.Dirty.SCALE);
461 };
462 
463 /**
464  * Returns elements quality value.
465  * @return {number} Quality value.
466  */
467 lime.Node.prototype.getQuality = function() {
468     return this.quality_;
469 };
470 /**
471  * Sets quality value used while drawing. Not all rendermodes can draw
472  * more effectively on lower quality. 1.0 full quality, 0.5 half quality.
473  * Setting this walue larger than 1.0 almost never does anything good.
474  * @param {number} value New quality value.
475  * @return {lime.Node} object itself.
476  */
477 lime.Node.prototype.setQuality = function(value) {
478     if (this.quality_ != value) {
479         this.quality_ = value;
480         this.setDirty(lime.Dirty.SCALE);
481         this.calcRelativeQuality();
482     }
483     return this;
484 };
485 
486 /**
487  * Return cumulative quality value relative to screen full quality.
488  * @return {number} Quality value.
489  */
490 lime.Node.prototype.getRelativeQuality = function(){
491     if(!this.relativeQuality_)
492         this.calcRelativeQuality();
493         
494     return this.relativeQuality_;
495 }
496 
497 /**
498  * Calculates relative quality change from the
499  * parent objects quality
500  */
501 lime.Node.prototype.calcRelativeQuality = function() {
502     var rq = goog.isDef(this.relativeQuality_) ?
503         this.relativeQuality_ : this.quality_;
504 
505     if (this.parent_ && this.parent_.relativeQuality_)
506         rq = this.quality_ * this.parent_.relativeQuality_;
507 
508     if (rq != this.relativeQuality_) {
509         this.relativeQuality_ = rq;
510         for (var i = 0, child; child = this.children_[i]; i++) {
511             if (child instanceof lime.Node)
512             child.calcRelativeQuality();
513         }
514         this.setDirty(lime.Dirty.SCALE);
515     }
516 };
517 
518 /**
519  * Returns autoresize rules bitmask.
520  * @return {number} Autoresize bitmask.
521  */
522 lime.Node.prototype.getAutoResize = function() {
523     return this.autoResize_;
524 };
525 /**
526  * Sets new autoresixe rules. NOT IMPLEMENTED!
527  * @param {number} value New autoresize bitmask.
528  * @return {lime.Node} object itself.
529  */
530 lime.Node.prototype.setAutoResize = function(value) {
531     this.autoResize_ = value;
532     return this.setDirty(lime.Dirty.ALL);
533 };
534 
535 
536 /**
537  * Convert screen coordinate to node coordinate space
538  * @param {goog.math.Coordinate} coord Screen coordinate.
539  * @return {goog.math.Coordinate} Local coordinate.
540  */
541 lime.Node.prototype.screenToLocal = function(coord) {
542     if (!this.inTree_) return coord;
543     var newcoord = this.getParent().screenToLocal(coord);
544 
545     return this.parentToLocal(newcoord);
546 };
547 
548 /**
549  * Covert coordinate form parent node space to
550  * current node space
551  * @param {goog.math.Coordinate} coord Parent coordinate.
552  * @return {goog.math.Coordinate} Local coordinate.
553  */
554 lime.Node.prototype.parentToLocal = function(coord) {
555     if (!this.getParent()) return null;
556 
557     coord.x -= this.position_.x;
558     coord.y -= this.position_.y;
559 
560     coord.x /= this.scale_.x;
561     coord.y /= this.scale_.y;
562 
563     if (this.rotation_ != 0) {
564         var c2 = coord.clone(),
565             rot = this.rotation_ * Math.PI / 180,
566             cos = Math.cos(rot),
567             sin = Math.sin(rot);
568         coord.x = cos * c2.x - sin * c2.y;
569         coord.y = cos * c2.y + sin * c2.x;
570     }
571 
572     return coord;
573 };
574 
575 /**
576  * Convert coordinate in node space to screen coordinate
577  * @param {goog.math.Coordinate} coord Local coordinate.
578  * @return {goog.math.Coordinate} Screen coordinate.
579  */
580 lime.Node.prototype.localToScreen = function(coord) {
581     if (!this.inTree_) return coord;
582 
583     return this.getParent().localToScreen(this.localToParent(coord));
584 };
585 
586 /**
587  * Convert coordinate from current node space to
588  * parent node space
589  * @param {goog.math.Coordinate} coord Local coordinate.
590  * @return {goog.math.Coordinate} Parent coordinate.
591  */
592 lime.Node.prototype.localToParent = function(coord) {
593     if (!this.getParent()) return coord;
594     var newcoord = coord.clone();
595 
596     if (this.rotation_ != 0) {
597         var rot = -this.rotation_ * Math.PI / 180,
598             cos = Math.cos(rot),
599             sin = Math.sin(rot);
600         newcoord.x = cos * coord.x - sin * coord.y;
601         newcoord.y = cos * coord.y + sin * coord.x;
602     }
603 
604     newcoord.x *= this.scale_.x;
605     newcoord.y *= this.scale_.y;
606 
607     newcoord.x += this.position_.x;
608     newcoord.y += this.position_.y;
609 
610     return newcoord;
611 };
612 
613 /**
614  * Convert coordinate in node space to other nodes coordinate space
615  * @param {goog.math.Coordinate} coord Local coordinate.
616  * @param {lime.Node} node Node for new coordinate space.
617  * @return {goog.math.Coordinate} Coordinate in node space.
618  */
619 lime.Node.prototype.localToNode = function(coord, node) {
620     // Todo: this can be optimized.
621     // maybe with goog.dom.findCommonAncestor but probably even more
622     if (!this.inTree_) return coord;
623     return node.screenToLocal(this.localToScreen(coord));
624 
625 };
626 
627 /**
628  * Returns the opacity value of the Node. 0.0=100% trasparent, 1.0=100% opaque
629  * @return {number} Opacity value.
630  */
631 lime.Node.prototype.getOpacity = function() {
632     return this.opacity_;
633 };
634 
635 /**
636  * Sets the opacity value of the Node object
637  * @param {number} value New opacity value(0-1).
638  * @return {lime.Node} The node object itself.
639  */
640 lime.Node.prototype.setOpacity = function(value) {
641     this.opacity_ = value;
642 
643     var hidden = this.getHidden();
644     if (this.opacity_ == 0 && !hidden) {
645         this.setHidden(true);
646         this.autoHide_ = 1;
647     }
648     else if (this.opacity_ != 0 && hidden && this.autoHide_) {
649         this.setHidden(false);
650     }
651 
652     if (goog.isDef(this.transitionsActive_[lime.Transition.OPACITY])){
653         return this;
654     }
655 
656     this.setDirty(lime.Dirty.ALPHA);
657     return this;
658 };
659 
660 
661 /**
662  * Create DOM element to render the node
663  */
664 lime.Node.prototype.createDomElement = function() {
665 
666     var newTagName =
667         this.renderer.getType() == lime.Renderer.CANVAS ? 'canvas' : 'div';
668     var create = function() {
669         this.domElement = this.rootElement =
670             this.containerElement = goog.dom.createDom(newTagName);
671         if (this.domClassName)
672             goog.dom.classes.add(this.domElement, this.domClassName);
673         this.dirty_ |= ~0;
674     };
675 
676     if (this.domElement) {
677         var curtag = this.domElement.tagName.toLowerCase();
678         if (curtag != newTagName) {
679             var oldEl = this.rootElement;
680             create.call(this);
681             if (oldEl.parentNode)
682                 oldEl.parentNode.replaceChild(this.rootElement, oldEl);
683             //return true;
684         }
685     }
686     else {
687         create.call(this);
688         //return true;
689     }
690 
691 };
692 
693 /**
694  * Update DOM element connected to the node
695  */
696 lime.Node.prototype.updateDomElement = function() {
697     if (this.needsDomElement()) {
698         this.createDomElement();
699     }
700     else {
701         this.removeDomElement();
702     }
703     //return false;
704 };
705 
706 /**
707  * Remove DOM element connected to teh node
708  */
709 lime.Node.prototype.removeDomElement = function() {
710     if (this.rootElement) {
711         goog.dom.removeNode(this.rootElement);
712         delete this.domElement;
713         delete this.rootElement;
714         delete this.containerElement;
715         //return true;
716     }
717 };
718 
719 /**
720  * Update node's layout (tree relations)
721  */
722 lime.Node.prototype.updateLayout = function() {
723    // debugger;
724     this.dirty_ &= ~lime.Dirty.LAYOUT;
725     //var didupdate = this.updateDomElement();
726     this.updateDomElement();
727 
728     if (this.parent_ && (this.parent_.dirty_ & lime.Dirty.LAYOUT)) {
729         this.parent_.updateLayout();
730         return;
731     }
732 
733     if (this.needsDomElement()) {
734 
735         for (var i = 0, child; child = this.children_[i]; i++) {
736             if (child instanceof lime.Node)
737             child.updateLayout();
738         }
739 
740         this.renderer.updateLayout.call(this);
741 
742     }
743 
744 };
745 
746 /**
747  * Update modified dirty parameters to visible elements properties
748  * @param {number=} opt_pass Pass number.
749  */
750 lime.Node.prototype.update = function(opt_pass) {
751  // if (!this.renderer) return;
752     var property,
753         value;
754    var pass = opt_pass || 0;
755 
756    var uid = goog.getUid(this);
757    if (this.dirty_ & lime.Dirty.LAYOUT) {
758        this.updateLayout();
759    }
760     
761    var do_draw = this.renderer.getType() == lime.Renderer.DOM || pass;
762 
763     if (do_draw) {
764         
765         //clear transitions in the queue
766         for (var i in this.transitionsClear_) {
767             delete this.transitionsActive_[i];
768             delete this.transitionsActiveSet_[i];
769             property = lime.Node.getPropertyForTransition(parseInt(i, 10));
770             lime.style.clearTransition(this.domElement, property);
771             if (this.domElement != this.containerElement) {
772                 lime.style.clearTransition(this.continerElement, property);
773             }
774         }
775         
776         // predraw is a check that elements are correctly drawn before the
777         // transition. if not then transition is started in the next frame not now.
778         var only_predraw = 0;
779         for (i in this.transitionsAdd_) {
780             value = this.transitionsAdd_[i];
781             
782             // 3rd is an "already_activated" flag
783             if (!value[3]) {
784                 value[3] = 1;
785                 
786             if (i == lime.Transition.POSITION &&
787                 this.positionDrawn_ != this.position_) {
788                  this.setDirty(lime.Dirty.POSITION, 0, true);
789                  only_predraw = 1;
790             }
791 
792             if (i == lime.Transition.SCALE &&
793                 this.scaleDrawn_ != this.scale_) {
794                 this.setDirty(lime.Dirty.SCALE, 0, true);
795                 only_predraw = 1;
796             }
797 
798             if (i == lime.Transition.OPACITY &&
799                 this.opacityDrawn_ != this.opacity_) {
800                 this.setDirty(lime.Dirty.ALPHA, 0, true);
801                 only_predraw = 1;
802             }
803             if (i == lime.Transition.ROTATION &&
804                 this.rotationDrawn_ != this.rotation_) {
805                 this.setDirty(lime.Dirty.ROTATION, 0, true);
806                 only_predraw = 1;
807             }
808 
809             }
810         }
811         
812         // activate the transitions
813         if(!only_predraw)
814         for (i in this.transitionsAdd_) {
815             value = this.transitionsAdd_[i];
816             property = lime.Node.getPropertyForTransition(parseInt(i, 10));
817             
818             if(this.renderer.getType()==lime.Renderer.DOM || property!='opacity'){
819             
820             this.transitionsActive_[i] = value[0];
821             lime.style.setTransition(this.domElement,
822                 property, value[1], value[2]);
823                 
824             if (this.domElement != this.containerElement &&
825                 property == lime.style.transformProperty) {
826 
827                 lime.style.setTransition(this.containerElement,
828                     property, value[1], value[2]);
829 
830             }
831             }
832             delete this.transitionsAdd_[i];
833         }
834 
835         // cache last drawn values to for predraw check
836         this.positionDrawn_ = this.position_;
837         this.scaleDrawn_ = this.scale_;
838         this.opacityDrawn_ = this.opacity_;
839         this.rotationDrawn_ = this.rotation_;
840 
841 
842         this.transitionsClear_ = {};
843 
844     }
845 
846 
847     if (pass) {
848         this.renderer.drawCanvas.call(this);
849     }
850     else {
851         if (this.renderer.getType() == lime.Renderer.CANVAS) {
852             var parent = this.getDeepestParentWithDom();
853             parent.redraw_ = 1;
854             if (parent == this && this.dirty_ == lime.Dirty.POSITION && !this.mask_) {
855                 parent.redraw_ = 0;
856             }
857             lime.setObjectDirty(this.getDeepestParentWithDom(), 1);
858         }
859         
860         // dom draw happens here
861         this.renderer.update.call(this);
862 
863     }
864     
865     // set flags that transitions have been draw.
866     if(do_draw)
867     for (i in this.transitionsActive_) {
868         if (this.transitionsActive_[i]) {
869             this.transitionsActiveSet_[i] = true;
870         }
871     }
872     
873     if(this.dependencies_){
874         for(var i=0;i<this.dependencies_.length;i++){
875             this.dependencies_[i].setDirty(lime.Dirty.ALL);
876         }
877     }
878 
879     //clear dirty
880     this.setDirty(0, pass);
881 
882 };
883 
884 /**
885  * Return CSS property name for transition constant
886  * @param {number} transition Transition constant.
887  * @return {string} Property name.
888  */
889 lime.Node.getPropertyForTransition = function(transition) {
890     return transition == lime.Transition.OPACITY ?
891         'opacity' : lime.style.transformProperty;
892 };
893 
894 
895 /**
896  * Return the parent object. Returns null in not in tree
897  * @return {lime.Node} Parent node.
898  */
899 lime.Node.prototype.getParent = function() {
900     return this.parent_ ? this.parent_ : null;
901 };
902 
903 /**
904  * Append element to the end of childrens array
905  * @param {lime.Node|Element|Node} child Child node.
906  * @param {number=} opt_pos Position of new child.
907  * @return {lime.Node} obejct itself.
908  */
909 lime.Node.prototype.appendChild = function(child, opt_pos) {
910     
911     if (child instanceof lime.Node && child.getParent()) {
912         child.getParent().removeChild(child);
913     }
914     else if(child.parentNode){
915         goog.dom.removeNode(/** @type {Node} */ (child));
916     }
917     
918     child.parent_ = this;
919     
920     if (opt_pos == undefined) {
921         this.children_.push(child);
922     }
923     else {
924         goog.array.insertAt(this.children_, child, opt_pos);
925     }
926     if (this.renderer.getType() != lime.Renderer.DOM) {
927 
928         child.setRenderer(this.renderer.getType());
929     }
930     if (child instanceof lime.Node) {
931         child.calcRelativeQuality();
932         if (this.inTree_) child.wasAddedToTree();
933     }
934     return this.setDirty(lime.Dirty.LAYOUT);
935 };
936 
937 /**
938  * Return number of childnodes current element has.
939  * @return {number} Number of children.
940  */
941 lime.Node.prototype.getNumberOfChildren = function(){
942     return this.children_.length;
943 }
944 
945 /**
946  * Return the child at defined index.
947  * @param {number} index Child index.
948  * @return {lime.Node|Element|null} Child element.
949  */
950 lime.Node.prototype.getChildAt = function(index){
951     if(index>=0 && this.getNumberOfChildren()>index) {
952         return this.children_[index];
953     }else {
954         return null;
955     }
956 };
957 
958 /**
959  * Return the index position of a child.
960  * @param {lime.Node|Element} child Child to search.
961  * @return {number} Index number.
962  */
963 lime.Node.prototype.getChildIndex = function(child){
964     return this.children_.indexOf(child);
965 };
966 
967 /**
968  * Remove element from the childrens array
969  * @param {lime.Node|Element} child Child node.
970  * @return {lime.Node} object itself.
971  */
972 lime.Node.prototype.removeChild = function(child) {
973     return this.removeChildAt(this.getChildIndex(child));
974 };
975 
976 /**
977  * Remove element at a given index from the childrens array
978  * @param {number} index Index of element to remove.
979  * @return {lime.Node} object itself.
980  */
981 lime.Node.prototype.removeChildAt = function(index){
982     if(index>=0 && this.getNumberOfChildren()>index){
983         var child = this.getChildAt(index);
984         if(child.maskTarget_){
985             child.maskTarget_.setMask(null);
986         }
987         if(child instanceof lime.Node){
988             if (this.inTree_)
989                 child.wasRemovedFromTree();
990             child.removeDomElement();
991             child.parent_ = null;
992         }
993         else 
994             goog.dom.removeNode(child);
995         
996         this.children_.splice(index, 1);
997         return this.setDirty(lime.Dirty.LAYOUT);
998     }
999     return this;
1000 };
1001 
1002 /**
1003  * Removes all children of a node.
1004  * @return {lime.Node} object itself.
1005  */
1006 lime.Node.prototype.removeAllChildren = function(){
1007     while(this.getNumberOfChildren()){
1008         this.removeChildAt(0);
1009     }
1010     return this;
1011 };
1012 
1013 /**
1014  * Move a child to another index in the childrens array.
1015  * @param {lime.Node} child Child node.
1016  * @param {number} index New index for the child.
1017  * @return {lime.Node} object itself.
1018  */
1019 lime.Node.prototype.setChildIndex = function(child,index){
1020     var oldindex = this.getChildIndex(child);
1021     if(oldindex!=-1 && oldindex!=index){
1022         this.children_.splice(oldindex,1);
1023         goog.array.insertAt(this.children_, child, index);
1024         if(this.getDirector())
1025         this.getDirector().eventDispatcher.updateDispatchOrder(child);
1026         return this.setDirty(lime.Dirty.LAYOUT);
1027     }
1028     return this;
1029 };
1030 
1031 /**
1032  * @inheritDoc
1033  */
1034 lime.Node.prototype.addEventListener = function(type, handler, 
1035         opt_capture, opt_handlerScope) {
1036 
1037     // Bypass all mouse events on touchscreen devices
1038     if (lime.userAgent.SUPPORTS_TOUCH &&
1039         type.substring(0, 5) == 'mouse') return;
1040 
1041     // First element defines if events are registered with DOM 1=yes/0=no
1042     // Second element defines how many listeners have been set
1043     if (!goog.isDef(this.eventHandlers_[type])) {
1044         this.eventHandlers_[type] = [0, 0];
1045     }
1046 
1047     if (this.inTree_ && this.eventHandlers_[type][0] == 0) {
1048         this.eventHandlers_[type][0] = 1;
1049         this.getDirector().eventDispatcher.register(this, type);
1050     }
1051     this.eventHandlers_[type][1]++;
1052 
1053 };
1054 
1055 /**
1056  * @inheritDoc
1057  */
1058 lime.Node.prototype.removeEventListener = function(
1059     type, handler, opt_capture, opt_handlerScope) {
1060 
1061     // Bypass all mouse events on touchscreen devices
1062     if (lime.userAgent.SUPPORTS_TOUCH &&
1063         type.substring(0, 5) == 'mouse') return;
1064 
1065     if (this.inTree_ && this.eventHandlers_[type][1] == 1) {
1066         this.eventHandlers_[type][0] = 0;
1067         this.getDirector().eventDispatcher.release(this, type);
1068     }
1069     this.eventHandlers_[type][1]--;
1070     if (!this.eventHandlers_[type][1]) delete this.eventHandlers_[type];
1071 
1072 };
1073 
1074 /**
1075  * Return the Director instance related to the node.
1076  * Returns null if no director connection.
1077  * @return {lime.Director} Current director.
1078  */
1079 lime.Node.prototype.getDirector = function() {
1080      if (!this.inTree_) return null;
1081 
1082      return this.director_;
1083 };
1084 
1085 /**
1086  * Returns the Scene instance related to the node.
1087  * Returns null if no scene connection.
1088  * @return {lime.Scene} Current scene.
1089  */
1090 lime.Node.prototype.getScene = function() {
1091     if (!this.inTree_) return null;
1092 
1093     return this.scene_;
1094 };
1095 
1096 /**
1097  * Handle removing Node from DOM tree
1098  */
1099 lime.Node.prototype.wasRemovedFromTree = function() {
1100     var child;
1101     
1102     if(!this.dependencySet_){
1103         this.removeDependency(this.getParent());
1104     }
1105 
1106     // Call remove for all children
1107     for (var i = 0; child = this.children_[i]; i++) {
1108         if (child instanceof lime.Node) {
1109             child.wasRemovedFromTree();
1110         }
1111     }
1112     // Unregister Event listeners
1113     for (var type in this.eventHandlers_) {
1114         this.eventHandlers_[type][0] = 0;
1115 
1116        if (!this.getDirector()) debugger;
1117         this.getDirector().eventDispatcher.release(this, type);
1118     }
1119     
1120     this.getDirector().eventDispatcher.updateDispatchOrder(this);
1121 
1122     this.inTree_ = false;
1123     this.director_ = null;
1124     this.scene_ = null;
1125     
1126 };
1127 
1128 /**
1129  * Handle adding Node to the DOM tree
1130  */
1131 lime.Node.prototype.wasAddedToTree = function() {
1132     this.inTree_ = true;
1133     this.director_ = this.parent_.getDirector();
1134     this.scene_ = this.parent_.getScene();
1135 
1136     // Notify all children
1137     for (var i = 0, child; child = this.children_[i]; i++) {
1138         if (child instanceof lime.Node) {
1139             child.wasAddedToTree();
1140         }
1141     }
1142     // Register Event Listeners
1143     for (var type in this.eventHandlers_) {
1144         this.eventHandlers_[type][0] = 1;
1145         this.getDirector().eventDispatcher.register(this, type);
1146     }
1147     
1148     if(this.dependencySet_){
1149         this.setupDependencies();
1150     }
1151     this.getDirector().eventDispatcher.updateDispatchOrder(this);
1152 };
1153 
1154 lime.Node.prototype.setupDependencies = function(){
1155    this.dependencySet_ = true;
1156    if(this.inTree_){
1157        this.addDependency(this.getParent());
1158    }
1159 }
1160 
1161 lime.Node.prototype.addDependency = function(other){
1162     if(!other.dependencies_) other.dependencies_ = [];
1163     
1164     goog.array.insert(other.dependencies_,this);
1165     if(!other && !(other.getParent() instanceof lime.Scene)){
1166         this.addDependency(other.getParent());
1167     }
1168     
1169 }
1170 
1171 lime.Node.prototype.removeDependency = function(other){
1172     if(!other || !other.dependencies_) return;
1173 
1174     goog.array.remove(other.dependencies_,this);
1175     this.removeDependency(other.getParent());
1176 }
1177 
1178 lime.Node.prototype.releaseDependencies = function(){
1179     delete this.dependencySet_;
1180     this.removeDependency(this.getParent());
1181 }
1182 
1183 /**
1184  * Returns bounding box for element is elements own coordinate space
1185  * @return {goog.math.Box} Contents frame in node space.
1186  */
1187 lime.Node.prototype.getFrame = function() {
1188     var s = this.getSize(), a = this.getAnchorPoint();
1189 
1190     return new goog.math.Box(
1191         -s.height * a.y,        //top
1192         s.width * (1 - a.x),    //right
1193         s.height * (1 - a.y),   //bottom
1194         -s.width * a.x          //left
1195         );
1196 };
1197 
1198 /**
1199  * Returns bounding box for element in parents coordinate space.
1200  * @param {goog.math.Box} opt_frame Optional frame box for element.
1201  * @return {goog.math.Box} Bounding box.
1202  */
1203 lime.Node.prototype.getBoundingBox = function(opt_frame) {
1204     var frame = opt_frame || this.getFrame();
1205     var tl = new goog.math.Coordinate(frame.left, frame.top);
1206     var tr = new goog.math.Coordinate(frame.right, frame.top);
1207     var bl = new goog.math.Coordinate(frame.left, frame.bottom);
1208     var br = new goog.math.Coordinate(frame.right, frame.bottom);
1209 
1210     tl = this.localToParent(tl);
1211     tr = this.localToParent(tr);
1212     bl = this.localToParent(bl);
1213     br = this.localToParent(br);
1214 
1215     return new goog.math.Box(
1216         Math.floor(Math.min(tl.y, tr.y, bl.y, br.y)),
1217         Math.ceil(Math.max(tl.x, tr.x, bl.x, br.x)),
1218         Math.ceil(Math.max(tl.y, tr.y, bl.y, br.y)),
1219         Math.floor(Math.min(tl.x, tr.x, bl.x, br.x))
1220     );
1221 
1222 };
1223 
1224 /**
1225  * Return box containing current element and all its children
1226  * @return {goog.math.Box} Bounding box.
1227  */
1228 lime.Node.prototype.measureContents = function() {
1229 
1230     var frame = this.getFrame();
1231     if (frame.left == frame.right && this.children_.length) {
1232         frame = this.children_[0].getBoundingBox(
1233                     this.children_[0].measureContents());
1234     }
1235 
1236 
1237     for (var i = 0, child; child = this.children_[i]; i++) {
1238         if (child.isMask != 1)
1239         frame.expandToInclude(child.getBoundingBox(child.measureContents()));
1240     }
1241 
1242     return frame;
1243 
1244 };
1245 
1246 /**
1247  * Register transition property. Use through animations.
1248  * @param {number} property Transition property constant.
1249  * @param {*} value New value.
1250  * @param {number} duration Transition duration.
1251  * @param {Array.<*>} ease Easing function.
1252  */
1253 lime.Node.prototype.addTransition = function(property, value, duration, ease) {
1254     this.transitionsAdd_[property] = [value, duration, ease, 0];
1255 };
1256 
1257 /**
1258  * Clear previously set transition
1259  * @param {number} property Transition property.
1260  */
1261 lime.Node.prototype.clearTransition = function(property) {
1262     this.transitionsClear_[property] = 1;
1263 };
1264 
1265 /**
1266  * Checks if event should fire on element based on the position.
1267  * Before returning true this function should set the position property
1268  * of the event to the hit position in elements coordinate space
1269  * @param {lime.events.Event} e Event object.
1270  * @return {boolean} If node should handle the event.
1271  */
1272 lime.Node.prototype.hitTest = function(e) {
1273     var coord = this.screenToLocal(e.screenPosition);
1274     if (this.getFrame().contains(coord)) {
1275         e.position = coord;
1276         return true;
1277     }
1278     return false;
1279 };
1280 
1281 /**
1282  * Add Node to action targets list and start the animation
1283  * @param {lime.animation.Animation} action Animation to run.
1284  */
1285 lime.Node.prototype.runAction = function(action) {
1286     action.addTarget(this);
1287     action.play();
1288 };
1289