1 /* 2 * Copyright © 2009 Apple Inc. All rights reserved. 3 */ 4 5 /** 6 * The keyboard identifier for the backspace key. 7 * @constant 8 * @type int 9 */ 10 const KEYBOARD_BACKSPACE = 8; 11 /** 12 * The keyboard identifier for the left key. 13 * @constant 14 * @type int 15 */ 16 const KEYBOARD_LEFT = 37; 17 /** 18 * The keyboard identifier for the right key. 19 * @constant 20 * @type int 21 */ 22 const KEYBOARD_RIGHT = 39; 23 /** 24 * The keyboard identifier for the up key. 25 * @constant 26 * @type int 27 */ 28 const KEYBOARD_UP = 38; 29 /** 30 * The keyboard identifier for the down key. 31 * @constant 32 * @type int 33 */ 34 const KEYBOARD_DOWN = 40; 35 /** 36 * The keyboard identifier for the return key. 37 * @constant 38 * @type int 39 */ 40 const KEYBOARD_RETURN = 13; 41 42 /** 43 * Indicates whether TuneKit is running on Apple TV. 44 * @constant 45 * @type bool 46 */ 47 const IS_APPLE_TV = (window.iTunes !== undefined && window.iTunes.platform == 'AppleTV'); 48 /** 49 * The Apple TV-specific iTunes system sounds interface. 50 * @constant 51 * @type bool 52 * @private 53 */ 54 const ATV_SOUNDS = IS_APPLE_TV ? window.iTunes.getSystemSounds() : null; 55 56 /** 57 * The move sound of the Apple TV user interface. 58 * @constant 59 * @type Object 60 */ 61 const SOUND_MOVED = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundScrollStart : new Audio('../TuneKit/sounds/SelectionChange.aif'); 62 /** 63 * The selection sound of the Apple TV user interface. 64 * @constant 65 * @type Object 66 */ 67 const SOUND_ACTIVATED = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundSelect : new Audio('../TuneKit/sounds/Selection.aif'); 68 /** 69 * The limit sound of the Apple TV user interface. 70 * @constant 71 * @type Object 72 */ 73 const SOUND_LIMIT = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundScrollLimit : new Audio('../TuneKit/sounds/Limit.aif'); 74 /** 75 * The exit sound of the Apple TV user interface. 76 * @constant 77 * @type Object 78 */ 79 const SOUND_EXIT = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundExit : new Audio('../TuneKit/sounds/Exit.aif'); 80 81 /** 82 * @class 83 * @name TKUtils 84 * 85 * @since TuneKit 1.0 86 */ 87 function TKUtils () { 88 }; 89 90 /* ==================== SOUNDS ==================== */ 91 92 /** 93 * Plays a sound. 94 * 95 * @param {Object} sound The sound to play, which is either an <code>audio</code> element or an iTunes system sound identifier on Apple TV. 96 */ 97 TKUtils.playSound = function (sound) { 98 if (IS_APPLE_TV) { 99 ATV_SOUNDS.playSystemSound(sound); 100 } 101 else { 102 sound.play(); 103 } 104 }; 105 106 /* ==================== TRANSFORMS SHORTHANDS ==================== */ 107 108 /** 109 * Prints a <code>translate3d()</code> command that can be used as input for a <code>-webkit-transform</code> property. 110 * 111 * @param {int} tx The x coordinate for the translation. 112 * @param {int} ty The y coordinate for the translation 113 * 114 * @returns {String} The <code>translate3d()</code> command 115 */ 116 TKUtils.t = function (tx, ty) { 117 return 'translate3d(' + tx + 'px, ' + ty + 'px, 0)'; 118 }; 119 120 /** 121 * Creates a CSS string representation for a number in pixels. 122 * 123 * @param {number} value The value to be converted. 124 * 125 * @returns {String} A CSS string representation for <code>value</code> in pixels. 126 */ 127 TKUtils.px = function (value) { 128 return value + 'px'; 129 }; 130 131 /* ==================== Array ==================== */ 132 133 /** 134 * Copies all properties from one object onto another. 135 * 136 * @param {Object} sourceObject The object from which we will copy properties. 137 * @param {Object} targetObject The array onto which we will copy properties. 138 */ 139 TKUtils.copyPropertiesFromSourceToTarget = function (source, target) { 140 for (var property in source) { 141 target[property] = source[property]; 142 } 143 }; 144 145 /* ==================== Delegates ==================== */ 146 147 /** 148 * Indicates whether an object is a <code>Function</code>. 149 * 150 * @param {Object} object The object purported to be a <code>Function</code>. 151 * 152 * @returns {bool} Whether the object is a <code>Function</code>. 153 */ 154 TKUtils.objectIsFunction = function (object) { 155 return (typeof object == 'function'); 156 }; 157 158 /** 159 * Indicates whether an object is <code>undefined</code>. 160 * 161 * @param {Object} object The object purported to be <code>undefined</code>. 162 * 163 * @returns {bool} Whether the object is <code>undefined</code>. 164 */ 165 TKUtils.objectIsUndefined = function (object) { 166 return (object === undefined); 167 }; 168 169 /** 170 * Indicates whether an object is a string literal or a <code>String</code> instance. 171 * 172 * @param {Object} object The object purported to be a string literal or a <code>String</code> instance. 173 * 174 * @returns {bool} Whether the object is a string literal or a <code>String</code> instance. 175 */ 176 TKUtils.objectIsString = function (object) { 177 return (typeof object == 'string' || object instanceof String); 178 }; 179 180 /** 181 * Indicates whether an object is an <code>Array</code>. 182 * 183 * @param {Object} object The object purported to be an <code>Array</code>. 184 * 185 * @returns {bool} Whether the object is an <code>Array</code>. 186 */ 187 TKUtils.objectIsArray = function (object) { 188 return (object instanceof Array); 189 }; 190 191 /** 192 * Indicates whether an object implements a given method, useful to check if a delegate 193 * object implements a given delegate method. 194 * 195 * @param {Object} object The object purported to implement a given method. 196 * @param {String} methodNameAsString The method name as a <code>String</code>. 197 * 198 * @returns {bool} Whether the object implements the given method. 199 */ 200 TKUtils.objectHasMethod = function (object, methodNameAsString) { 201 return ( object !== null && 202 !this.objectIsUndefined(object) && 203 !this.objectIsUndefined(object[methodNameAsString]) && 204 this.objectIsFunction(object[methodNameAsString]) 205 ); 206 }; 207 208 /* ==================== INIT ==================== */ 209 210 /** 211 * Sets up the .displayNames for all functions defined on the specified class, including its prototype. 212 * 213 * @param {Object} class The class. 214 * @param {String} className The class name as a string, in case it can not be derived from <code>class</code>. Optional. 215 */ 216 TKUtils.setupDisplayNames = function (object, className) { 217 var class_name = className || object.name; 218 for (var i in object) { 219 // make sure we don't touch properties that were synthetized 220 if (object.__lookupGetter__(i)) { 221 continue; 222 } 223 var prop = object[i]; 224 if (TKUtils.objectIsFunction(prop)) { 225 prop.displayName = TKUtils.createDisplayName(class_name, i); 226 } 227 } 228 for (var i in object.prototype) { 229 // make sure we don't touch properties that were synthetized 230 if (object.prototype.__lookupGetter__(i)) { 231 continue; 232 } 233 var prop = object.prototype[i]; 234 if (TKUtils.objectIsFunction(prop)) { 235 prop.displayName = TKUtils.createDisplayName(class_name, i); 236 } 237 } 238 }; 239 240 TKUtils.createDisplayName = function (className, methodName) { 241 return className + '.' + methodName + '()'; 242 }; 243 244 TKUtils.buildElement = function (elementData) { 245 // nothing to do if we don't have useful data 246 if (!elementData || !elementData.type) { 247 return null; 248 } 249 // 250 var element = null; 251 switch (elementData.type) { 252 case "emptyDiv": 253 element = document.createElement("div"); 254 break; 255 case "container": 256 element = document.createElement("div"); 257 for (var i=0; i < elementData.children.length; i++) { 258 element.appendChild(TKUtils.buildElement(elementData.children[i])); 259 } 260 break; 261 case "image": 262 element = document.createElement("img"); 263 element.src = elementData.src; 264 break; 265 case "text": 266 element = document.createElement("div"); 267 var p = document.createElement("p"); 268 p.innerText = elementData.text; 269 element.appendChild(p); 270 break; 271 default: 272 element = document.createElement(elementData.type); 273 element.innerHTML = elementData.content; 274 } 275 // add optional id 276 if (elementData.id) { 277 element.id = elementData.id; 278 } 279 // add optional class 280 if (elementData.className) { 281 element.className = elementData.className; 282 } 283 284 // wrap in optional link 285 if (elementData.link){ 286 var subElement = element; 287 element = document.createElement("a"); 288 element.href = elementData.link; 289 element.target = "_blank"; 290 element.appendChild(subElement); 291 } 292 293 return element; 294 }; 295 296 /** 297 * Creates a DOM event. 298 * 299 * @param {String} eventType The event type. 300 * @param {Element} relatedTarget The optional related target for this event. 301 * 302 * @returns {Event} The event. 303 */ 304 TKUtils.createEvent = function (eventType, relatedTarget) { 305 var event = document.createEvent('Event'); 306 event.initEvent(eventType, true, true); 307 event.relatedTarget = relatedTarget; 308 return event; 309 }; 310 311 /** 312 * Indicates whether a node is in some other node's subtree. 313 * 314 * @param {Node} childNode The alleged child node. 315 * @param {Node} allegedParentNode The alleged parent node. 316 * 317 * @returns {bool} Whether <code>childNode</code> is a child of <code>allegedParentNode</code>. 318 */ 319 TKUtils.isNodeChildOfOtherNode = function (childNode, allegedParentNode) { 320 var node = childNode.parentNode; 321 while (node !== null) { 322 if (node === allegedParentNode) { 323 return true; 324 break; 325 } 326 node = node.parentNode; 327 } 328 return false; 329 }; 330 331 TKUtils.setupDisplayNames(TKUtils, 'TKUtils'); 332 333 /* ==================== TKRect ==================== */ 334 335 /** 336 * The top left corner of a rectangle. 337 * @constant 338 * @type int 339 * @private 340 */ 341 const TKRectTopLeftCorner = 0; 342 /** 343 * The middle point on the top edge of a rectangle. 344 * @constant 345 * @type int 346 * @private 347 */ 348 const TKRectMiddleOfTopEdge = 1; 349 /** 350 * The top right corner of a rectangle. 351 * @constant 352 * @type int 353 * @private 354 */ 355 const TKRectTopRightCorner = 2; 356 /** 357 * The middle point on the right edge of a rectangle. 358 * @constant 359 * @type int 360 * @private 361 */ 362 const TKRectMiddleOfRightEdge = 3; 363 /** 364 * The bottom right corner of a rectangle. 365 * @constant 366 * @type int 367 * @private 368 */ 369 const TKRectBottomRightCorner = 4; 370 /** 371 * The middle point on the bottom edge of a rectangle. 372 * @constant 373 * @type int 374 * @private 375 */ 376 const TKRectMiddleOfBottomEdge = 5; 377 /** 378 * The bottom left corner of a rectangle. 379 * @constant 380 * @type int 381 * @private 382 */ 383 const TKRectBottomLeftCorner = 6; 384 /** 385 * The middle point on the left edge of a rectangle. 386 * @constant 387 * @type int 388 * @private 389 */ 390 const TKRectMiddleOfLeftEdge = 7; 391 /** 392 * The center of a rectangle. 393 * @constant 394 * @type int 395 * @private 396 */ 397 const TKRectCenter = 8; 398 /** 399 * The top edge of a rectangle. 400 * @constant 401 * @type int 402 * @private 403 */ 404 const TKRectTopEdge = 9; 405 /** 406 * The right edge of a rectangle. 407 * @constant 408 * @type int 409 * @private 410 */ 411 const TKRectRightEdge = 10; 412 /** 413 * The bottom edge of a rectangle. 414 * @constant 415 * @type int 416 * @private 417 */ 418 const TKRectBottomEdge = 11; 419 /** 420 * The left edge of a rectangle. 421 * @constant 422 * @type int 423 * @private 424 */ 425 const TKRectLeftEdge = 12; 426 427 /** 428 * @class 429 * 430 * <p>The <code>TKRect</code> provides some utilities to deal with a rectangle data type, allowing to obtain coordinates of the rectangle's points 431 * of interest as {@link TKPoint} objects and its edges as {@link TKSegment} objects, or obtaining a rectangle resulting in the union of several others.</p> 432 * 433 * @since TuneKit 1.0 434 * 435 * @param {float} x The x coordinate. 436 * @param {float} y The y coordinate. 437 * @param {float} width The width. 438 * @param {float} height The height. 439 */ 440 function TKRect (x, y, width, height) { 441 /** 442 * The x coordinate. 443 * @type float 444 */ 445 this.x = x || 0; 446 /** 447 * The y coordinate. 448 * @type float 449 */ 450 this.y = y || 0; 451 /** 452 * The width. 453 * @type float 454 */ 455 this.width = width || 0; 456 /** 457 * The height. 458 * @type float 459 */ 460 this.height = height || 0; 461 }; 462 463 /** 464 * @private 465 * Provides the coordinates of a given point of interest. 466 * 467 * @param {int} index The point of interest. 468 * 469 * @returns {TKPoint} The point at the given point of interest. 470 */ 471 TKRect.prototype.pointAtPosition = function (index) { 472 var point; 473 if (index == TKRectTopLeftCorner) { 474 point = new TKPoint(this.x, this.y); 475 } 476 else if (index == TKRectMiddleOfTopEdge) { 477 point = new TKPoint(this.x + this.width / 2, this.y); 478 } 479 else if (index == TKRectTopRightCorner) { 480 point = new TKPoint(this.x + this.width, this.y); 481 } 482 else if (index == TKRectMiddleOfRightEdge) { 483 point = new TKPoint(this.x + this.width, this.y + this.height / 2); 484 } 485 else if (index == TKRectBottomRightCorner) { 486 point = new TKPoint(this.x + this.width, this.y + this.height); 487 } 488 else if (index == TKRectMiddleOfBottomEdge) { 489 point = new TKPoint(this.x + this.width / 2, this.y + this.height); 490 } 491 else if (index == TKRectBottomLeftCorner) { 492 point = new TKPoint(this.x, this.y + this.height); 493 } 494 else if (index == TKRectMiddleOfLeftEdge) { 495 point = new TKPoint(this.x, this.y + this.height / 2); 496 } 497 else if (index == TKRectCenter) { 498 point = new TKPoint(this.x + this.width / 2, this.y + this.height / 2); 499 } 500 return point; 501 }; 502 503 /** 504 * @private 505 * Provides the segment for a given edge. 506 * 507 * @param {int} index The edge. 508 * 509 * @returns {TKSegment} The segment for the given edge. 510 */ 511 TKRect.prototype.edge = function (index) { 512 var edge; 513 if (index == TKRectTopEdge) { 514 edge = new TKSegment(this.pointAtPosition(TKRectTopLeftCorner), this.pointAtPosition(TKRectTopRightCorner)); 515 } 516 else if (index == TKRectRightEdge) { 517 edge = new TKSegment(this.pointAtPosition(TKRectTopRightCorner), this.pointAtPosition(TKRectBottomRightCorner)); 518 } 519 else if (index == TKRectBottomEdge) { 520 edge = new TKSegment(this.pointAtPosition(TKRectBottomLeftCorner), this.pointAtPosition(TKRectBottomRightCorner)); 521 } 522 else if (index == TKRectLeftEdge) { 523 edge = new TKSegment(this.pointAtPosition(TKRectTopLeftCorner), this.pointAtPosition(TKRectBottomLeftCorner)); 524 } 525 return edge; 526 }; 527 528 /** 529 * Returns a {@link TKRect} from a rectangle returned by the <code>Node.getBoundingClientRect</code> method. 530 * 531 * @param {ClientRect} rect The CSS client rectangle. 532 * 533 * @returns {TKRect} The equivalent rectangle as a TuneKit data type. 534 */ 535 TKRect.rectFromClientRect = function (rect) { 536 return new TKRect(rect.left, rect.top, rect.width, rect.height); 537 }; 538 539 /** 540 * @private 541 * Returns a {@link TKRect} encompassing the union of a list of other rectangles. 542 * 543 * @param {Array} rects The various rectangles we'd like the union of. 544 * 545 * @returns {TKRect} The rectangle encompassing the union of a list of the provided rectangles. 546 */ 547 TKRect.rectFromUnionOfRects = function (rects) { 548 if (rects.length < 1) { 549 return new TKRect(); 550 } 551 var union = rects[0]; 552 var rect; 553 for (var i = 1; i < rects.length; i++) { 554 rect = rects[i]; 555 union.x = Math.min(union.x, rect.x); 556 union.y = Math.min(union.y, rect.y); 557 union.width = Math.max(union.width, rect.x + rect.width); 558 union.height = Math.max(union.height, rect.y + rect.height); 559 } 560 return union; 561 }; 562 563 /* ==================== TKPoint ==================== */ 564 565 /** 566 * @private 567 * @class 568 * 569 * <p>The <code>TKPoint</code> provides a TuneKit data type to deal with points in 2D space and some utilities to work with them, such as figuring out 570 * the distance between two points.</p> 571 * 572 * @since TuneKit 1.0 573 * 574 * @param {float} x The x coordinate. 575 * @param {float} y The y coordinate. 576 */ 577 function TKPoint (x, y) { 578 /** 579 * The x coordinate. 580 * @type float 581 */ 582 this.x = x || 0; 583 /** 584 * The y coordinate. 585 * @type float 586 */ 587 this.y = y || 0; 588 }; 589 590 /** 591 * Provides the distance between this point and another. 592 * 593 * @param {TKPoint} aPoint The point to which we'd like to figure out the distance. 594 * 595 * @returns {float} The distance between the receiver the provided point. 596 */ 597 TKPoint.prototype.distanceToPoint = function (aPoint) { 598 return Math.sqrt(Math.pow(aPoint.x - this.x, 2) + Math.pow(aPoint.y - this.y, 2)); 599 }; 600 601 /* ==================== TKSegment ==================== */ 602 603 /** 604 * @class 605 * @private 606 * 607 * <p>The <code>TKSegment</code> provides a TuneKit data type to deal with segments in 2D space and some utilities to work with them, such as figuring out 608 * the shortest distance between a segment and a point.</p> 609 * 610 * @since TuneKit 1.0 611 * 612 * @param {TKPoint} a The first extremity of the segment. 613 * @param {TKPoint} b The other extremity of the segment. 614 */ 615 function TKSegment (a, b) { 616 /** 617 * The first extremity of the segment. 618 * @type TKPoint 619 */ 620 this.a = a; 621 /** 622 * The other extremity of the segment. 623 * @type TKPoint 624 */ 625 this.b = b; 626 this.ab = new TKPoint(b.x - a.x, b.y - a.y); 627 /** 628 * The segment's length. 629 * @type float 630 */ 631 this.length = b.distanceToPoint(a); 632 }; 633 634 // XXX: this only deals with horizontal and vertical lines 635 TKSegment.prototype.crossPoint = function (c) { 636 return (this.a.y == this.b.y) ? new TKPoint(c.x, this.a.y) : new TKPoint(this.a.x, c.y); 637 }; 638 639 /** 640 * Computes the shortest distance between this segment and the given point. 641 * 642 * @param {TKPoint} aPoint 643 * 644 * @returns {float} The shortest distance between this segment and the given point. 645 */ 646 TKSegment.prototype.distanceToPoint = function (aPoint) { 647 var d; 648 var cross_point = this.crossPoint(aPoint); 649 // is it inside the segment? 650 if (cross_point.distanceToPoint(this.a) + cross_point.distanceToPoint(this.b) == this.length) { 651 d = aPoint.distanceToPoint(cross_point); 652 } 653 else { 654 d = Math.min(aPoint.distanceToPoint(this.a), aPoint.distanceToPoint(this.b)); 655 } 656 return d; 657 }; 658 659 /* ================= Debugging ======================== */ 660 661 var DEBUG = false; 662 663 function debug(msg) { 664 if (window.DEBUG !== undefined && window.DEBUG) { 665 console.log("DEBUG: " + msg); 666 } 667 }; 668