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