1 sc_require("views/view"); 2 sc_require('views/view/layout_style'); 3 4 /** Select a horizontal layout for various views.*/ 5 SC.LAYOUT_HORIZONTAL = 'sc-layout-horizontal'; 6 7 /** Select a vertical layout for various views.*/ 8 SC.LAYOUT_VERTICAL = 'sc-layout-vertical'; 9 10 /** 11 Layout properties to take up the full width of a parent view. 12 */ 13 SC.FULL_WIDTH = { left: 0, right: 0 }; 14 15 /** 16 Layout properties to take up the full height of a parent view. 17 */ 18 SC.FULL_HEIGHT = { top: 0, bottom: 0 }; 19 20 /** 21 Layout properties to center. Note that you must also specify a width and 22 height for this to work. 23 */ 24 SC.ANCHOR_CENTER = { centerX: 0, centerY: 0 }; 25 26 /** 27 Layout property for width, height 28 */ 29 30 SC.LAYOUT_AUTO = 'auto'; 31 32 // Regexes representating valid values for rotation and scale layout properties 33 SC._ROTATION_VALUE_REGEX = /^\-?\d+(\.\d*)?(rad|deg)$/; 34 SC._SCALE_VALUE_REGEX = /^\d+(,\d+){0,2}$/; 35 36 SC.View.reopen( 37 /** @scope SC.View.prototype */ { 38 39 // ------------------------------------------------------------------------ 40 // Properties 41 // 42 43 /* @private Internal variable used to check for layout changes that resize. */ 44 _sc_previousLayout: null, 45 46 /** 47 The view's background color. Only recommended for use during prototyping and in views 48 where the background color may change arbitrarily, for example in connection with an 49 instance of `SC.Color`. Otherwise you should use CSS and `classNames` or 50 `classNameBindings`. 51 52 If set at create time, will be added to the view's layer. For dynamic background colors, 53 you must add `backgroundColor` to the view's `displayProperties`. 54 55 @type String 56 @default null 57 */ 58 backgroundColor: null, 59 60 /** 61 The frame of the view including the borders and scale 62 */ 63 borderFrame: function () { 64 var frame = this.get('frame'), 65 ret = null; 66 67 if (frame) { 68 /*jshint eqnull:true */ 69 var layout = this.get('layout'), 70 defaultValue = layout.border == null ? 0 : layout.border, 71 borderTop = this._sc_explicitValueFor(layout.borderTop, defaultValue), 72 borderRight = this._sc_explicitValueFor(layout.borderRight, defaultValue), 73 borderBottom = this._sc_explicitValueFor(layout.borderBottom, defaultValue), 74 borderLeft = this._sc_explicitValueFor(layout.borderLeft, defaultValue); 75 76 ret = { 77 x: frame.x, 78 y: frame.y, 79 width: frame.width, 80 height: frame.height 81 }; 82 83 var scale = frame.scale; 84 /*jshint eqnull:true*/ 85 if (scale != null) { 86 var scaledBorderTop = borderTop * scale, 87 scaledBorderRight = borderRight * scale, 88 scaledBorderBottom = borderBottom * scale, 89 scaledBorderLeft = borderLeft * scale; 90 91 ret.scale = scale; 92 ret.x -= scaledBorderLeft; 93 ret.y -= scaledBorderTop; 94 ret.width += scaledBorderLeft + scaledBorderRight; 95 ret.height += scaledBorderTop + scaledBorderBottom; 96 } else { 97 ret.x -= borderLeft; 98 ret.y -= borderTop; 99 ret.width += borderLeft + borderRight; 100 ret.height += borderTop + borderBottom; 101 } 102 103 if (frame.transformOriginX != null) { 104 ret.transformOriginX = frame.transformOriginX; 105 } 106 107 if (frame.transformOriginY != null) { 108 ret.transformOriginY = frame.transformOriginY; 109 } 110 } 111 112 return ret; 113 }.property('frame').cacheable(), 114 115 116 /** 117 Set this property to YES whenever the view needs to layout its child 118 views. Normally this property is set automatically whenever the layout 119 property for a child view changes. 120 121 @type Boolean 122 */ 123 childViewsNeedLayout: NO, 124 125 /** 126 The child view layout plugin to use when laying out child views. 127 128 You can set this property to a child layout plugin object to 129 automatically set and adjust the layouts of this view's child views 130 according to some specific layout style. For instance, SproutCore includes 131 two such plugins, SC.View.VERTICAL_STACK and SC.View.HORIZONTAL_STACK. 132 133 SC.View.VERTICAL_STACK will arrange child views in order in a vertical 134 stack, which only requires that the height of each child view be specified. 135 Likewise, SC.View.HORIZONTAL_STACK does the same in the horizontal 136 direction, which requires that the width of each child view be specified. 137 138 Where child layout plugins are extremely useful, besides simplifying 139 the amount of layout code you need to write, is that they can update the 140 layouts automatically as things change. For more details and examples, 141 please see the documentation for SC.View.VERTICAL_STACK and 142 SC.View.HORIZONTAL_STACK. 143 144 To define your own child view layout plugin, simply create an object that 145 conforms to the SC.ChildViewLayoutProtocol protocol. 146 147 **Note** This should only be set once and is not bindable. 148 149 @type Object 150 @default null 151 */ 152 childViewLayout: null, 153 154 /** 155 The options for the given child view layout plugin. 156 157 These options are specific to the current child layout plugin being used and 158 are used to modify the applied layouts. For example, SC.View.VERTICAL_STACK 159 accepts options like: 160 161 childViewLayoutOptions: { 162 paddingAfter: 20, 163 paddingBefore: 20, 164 spacing: 10 165 } 166 167 To determine what options may be used for a given plugin and to see what the 168 default options are, please refer to the documentation for the child layout 169 plugin being used. 170 171 @type Object 172 @default null 173 */ 174 childViewLayoutOptions: null, 175 176 /** @private The explicit layout of the view, computed from the layout using the explicit position. */ 177 explicitLayout: function () { 178 var layout = this.get('layout'), 179 ret = null; 180 181 if (layout) { 182 ret = this._sc_computeExplicitLayout(layout); 183 } 184 185 return ret; 186 }.property('layout').cacheable(), 187 188 /** 189 Walks like a duck. Is `true` to indicate that a view has layout support. 190 */ 191 hasLayout: true, 192 193 /** 194 Whether the view and its child views should be monitored for changes that 195 affect the current child view layout. 196 197 When `true` and using a childViewLayout plugin, the view and its child views 198 will be observed for any changes that would affect the layout of all the 199 child views. For example, if `isChildViewLayout` is true and using 200 SC.View.VERTICAL_STACK, if any child view's height or visibility changes 201 all of the child views will be re-adjusted. 202 203 If you only want to automatically layout the child views once, you can 204 set this to `false` to improve performance. 205 206 @type Boolean 207 @default true 208 */ 209 isChildViewLayoutLive: true, 210 211 /** 212 Returns whether the height is 'fixed' or not. A fixed height is defined on the layout 213 as an integer number of pixels. Fixed widths are therefore unaffected by changes 214 to their parent view's height. 215 216 @field 217 @returns {Boolean} YES if fixed, NO otherwise 218 @test in layout 219 */ 220 isFixedHeight: function() { 221 var layout = this.get('layout'); 222 223 // Height is fixed if it has a height and it isn't SC.LAYOUT_AUTO or a percent. 224 return (layout.height !== undefined) && 225 !SC.isPercentage(layout.height) && 226 (layout.height !== SC.LAYOUT_AUTO); 227 }.property('layout').cacheable(), 228 229 /** 230 Returns whether the layout is 'fixed' or not. A fixed layout means a 231 fixed left & top position and fixed width & height. Fixed layouts are 232 therefore unaffected by changes to their parent view's layout. 233 234 @returns {Boolean} YES if fixed, NO otherwise 235 @test in layoutStyle 236 */ 237 isFixedLayout: function () { 238 return this.get('isFixedPosition') && this.get('isFixedSize'); 239 }.property('isFixedPosition', 'isFixedSize').cacheable(), 240 241 /** 242 Returns whether the position is 'fixed' or not. A fixed position means a 243 fixed left & top position within its parent's frame. Fixed positions are 244 therefore unaffected by changes to their parent view's size. 245 246 @field 247 @returns {Boolean} YES if fixed, NO otherwise 248 @test in layoutStyle 249 */ 250 isFixedPosition: function () { 251 var explicitLayout = this.get('explicitLayout'), 252 left = explicitLayout.left, 253 top = explicitLayout.top, 254 hasFixedLeft, 255 hasFixedTop; 256 257 // Position is fixed if it has left + top, but not as percentages and not as SC.LAYOUT_AUTO. 258 hasFixedLeft = left !== undefined && !SC.isPercentage(left) && left !== SC.LAYOUT_AUTO; 259 hasFixedTop = top !== undefined && !SC.isPercentage(top) && top !== SC.LAYOUT_AUTO; 260 261 return hasFixedLeft && hasFixedTop; 262 }.property('explicitLayout').cacheable(), 263 264 /** 265 Returns whether the size is 'fixed' or not. A fixed size means a fixed 266 width and height. Fixed sizes are therefore unaffected by changes to their 267 parent view's size. 268 269 @field 270 @returns {Boolean} YES if fixed, NO otherwise 271 @test in layout 272 */ 273 isFixedSize: function () { 274 return this.get('isFixedHeight') && this.get('isFixedWidth'); 275 }.property('isFixedWidth', 'isFixedHeight').cacheable(), 276 277 /** 278 Returns whether the width is 'fixed' or not. A fixed width is defined on the layout 279 as an integer number of pixels. Fixed widths are therefore unaffected by changes 280 to their parent view's width. 281 282 @field 283 @returns {Boolean} YES if fixed, NO otherwise 284 @test in layout 285 */ 286 isFixedWidth: function() { 287 var layout = this.get('layout'); 288 289 // Width is fixed if it has a width and it isn't SC.LAYOUT_AUTO or a percent. 290 return (layout.width !== undefined) && 291 !SC.isPercentage(layout.width) && 292 (layout.width !== SC.LAYOUT_AUTO); 293 }.property('layout').cacheable(), 294 295 /** 296 Set the layout to a hash of layout properties to describe in detail how your view 297 should be positioned on screen. Like most application development environments, 298 your views are laid out absolutely, relative to their parent view. 299 300 You can define your layout using combinations of the following positional properties: 301 302 - left 303 - top 304 - right 305 - bottom 306 - height 307 - width 308 - centerX: offset from center, horizontally 309 - centerY: offset from center, vertically 310 - minWidth 311 - minHeight 312 - maxWidth 313 - maxHeight 314 - scale: once positioned, scales the view in place. 315 - transformOriginX, transformOriginY: defines the point (as a decimal percentage) around which 316 your view will scale. (Also impacts rotation; see below.) 317 318 They are processed by SproutCore's layout engine and used to position the view's element onscreen. They are 319 also reliably and speedily processed into a scaled rectangle (with x, y, height, width, scale and origin 320 values) available on the frame property. See documentation on it and the clippingFrame property for more. 321 322 Most of these properties take integer numbers of pixels, for example { left: 10 }, or fractional 323 percentages like { left 0.25 }. Exceptions include scale, which takes a scale factor (e.g. { scale: 324 2 } doubles the view's size), and transformOriginX/Y which take a decimal percent, and default to 0.5 325 (the center of the view). 326 327 It's possible to define very sophisticated layouts with these properties alone. For example, you 328 can define a view which takes up the full screen until it reaches a certain width, and aligns to 329 the left thereafter, with { left: 0, right: 0, maxWidth: 400 }. (If you need the flexibility to 330 assign entirely different layouts at different screen or window sizes, see the Design Modes 331 documentation under SC.Application.) 332 333 Certain layout combinations are nonsensical and of course should be avoided. For example, you 334 can use left + right or left + width, but not left + right + width. 335 336 If your view has a CSS border, it's important that you specify its thickness in the layout hash, 337 using one or more of the following border properties, as well as in your CSS. This is an unfortunate 338 bit of repetition, but it's necessary to allow SproutCore to adjust the layout to compensate. (HTML 339 positions borders outside of the body of an element; SproutCore positions them inside their rectangles.) 340 341 - border: border thickness on all sides 342 - borderTop: top border thickness 343 - borderRight: right border thickness 344 - borderBottom: bottom border thickness 345 - borderLeft: bottom left thickness 346 347 You can also use the following layout properties, which don't impact your view's frame. 348 349 - opacity: the opacity of the view 350 - rotate: once positioned, rotates the view in place. 351 - zIndex: position above or below other views (Not recommended. Control sibling view 352 overlay with childView order (later views draw above earlier views) where possible.) 353 354 To change a layout property, you should use the adjust method, which handles some particulars for you. 355 356 @type Object 357 @default { top: 0, left: 0, bottom: 0, right: 0 } 358 @test in layoutStyle 359 */ 360 layout: { top: 0, left: 0, bottom: 0, right: 0 }, 361 362 /** 363 The view responsible for laying out this view. The default version 364 returns the current parent view. 365 */ 366 layoutView: function () { 367 return this.get('parentView'); 368 }.property('parentView').cacheable(), 369 370 /** 371 The transition plugin to use when this view is moved or resized by adjusting 372 its layout. 373 374 SC.CoreView uses a pluggable transition architecture where the transition 375 setup, execution and cleanup can be handled by a plugin. This allows you 376 to create complex transition animations and share them across all your views 377 with only a single line of code. 378 379 There are a number of pre-built transition adjust plugins available in 380 the SproutCore foundation framework: 381 382 SC.View.SMOOTH_ADJUST 383 SC.View.BOUNCE_ADJUST 384 SC.View.SPRING_ADJUST 385 386 To create a custom transition plugin simply create a regular JavaScript 387 object that conforms to the SC.ViewTransitionProtocol protocol. 388 389 NOTE: When creating custom transition adjust plugins, be aware that SC.View 390 will not call the `setup` method of the plugin, only the `run` method. 391 392 @type Object (SC.ViewTransitionProtocol) 393 @default null 394 @since Version 1.10 395 */ 396 transitionAdjust: null, 397 398 /** 399 The options for the given `transitionAdjust` plugin. 400 401 These options are specific to the current transition plugin used and are 402 used to modify the transition animation. To determine what options 403 may be used for a given plugin and to see what the default options are, 404 see the documentation for the transition plugin being used. 405 406 Most transitions will accept a duration and timing option, but may 407 also use other options. For example, SC.View.BOUNCE_ADJUST accepts options 408 like: 409 410 transitionAdjustOptions: { 411 bounciness: 0.5, // how much the adjustment should bounce back each time 412 bounces: 4, // the number of bounces 413 duration: 0.25, 414 delay: 1 415 } 416 417 @type Object 418 @default null 419 @since Version 1.10 420 */ 421 transitionAdjustOptions: null, 422 423 /** 424 Activates use of brower's static layout. To activate, set this property to YES. 425 426 @type Boolean 427 @default NO 428 */ 429 useStaticLayout: NO, 430 431 // ------------------------------------------------------------------------ 432 // Methods 433 // 434 435 /** @private */ 436 _sc_adjustForBorder: function (frame, layout) { 437 /*jshint eqnull:true */ 438 var defaultValue = layout.border == null ? 0 : layout.border, 439 borderTop = this._sc_explicitValueFor(layout.borderTop, defaultValue), 440 borderLeft = this._sc_explicitValueFor(layout.borderLeft, defaultValue), 441 borderBottom = this._sc_explicitValueFor(layout.borderBottom, defaultValue), 442 borderRight = this._sc_explicitValueFor(layout.borderRight, defaultValue); 443 444 frame.x += borderLeft; // The border on the left pushes the frame to the right 445 frame.y += borderTop; // The border on the top pushes the frame down 446 frame.width -= (borderLeft + borderRight); // Border takes up space 447 frame.height -= (borderTop + borderBottom); // Border takes up space 448 449 return frame; 450 }, 451 452 /** @private */ 453 _sc_adjustForScale: function (frame, layout) { 454 455 // Scale not supported on this platform, ignore the layout values. 456 if (!SC.platform.supportsCSSTransforms) { 457 frame.scale = 1; 458 frame.transformOriginX = frame.transformOriginY = 0.5; 459 460 // Use scale. 461 } else { 462 463 // Get the scale and transform origins, if not provided. (Note inlining of SC.none for performance) 464 /*jshint eqnull:true*/ 465 var scale = layout.scale, 466 oX = layout.transformOriginX, 467 oY = layout.transformOriginY; 468 469 // If the scale is set and isn't 1, do some calculations. 470 if (scale != null && scale !== 1) { 471 // Scale the rect. 472 frame = SC.scaleRect(frame, scale, oX, oY); 473 474 // Add the scale and original unscaled height and width. 475 frame.scale = scale; 476 } 477 478 // If the origin is set and isn't 0.5, include it. 479 if (oX != null && oX !== 0.5) { 480 frame.transformOriginX = oX; 481 } 482 483 // If the origin is set and isn't 0.5, include it. 484 if (oY != null && oY !== 0.5) { 485 frame.transformOriginY = oY; 486 } 487 } 488 489 // Make sure width/height are never < 0. 490 if (frame.height < 0) frame.height = 0; 491 if (frame.width < 0) frame.width = 0; 492 493 return frame; 494 }, 495 496 /** @private Apply the adjustment to a clone of the layout (cloned unless newLayout is passed in) */ 497 _sc_applyAdjustment: function (key, newValue, layout, newLayout) { 498 var animateLayout = this._animateLayout; 499 500 // If a call to animate occurs in the same run loop, the animation layout 501 // would still be applied in the next run loop, potentially overriding this 502 // adjustment. So we need to cancel the animation layout. 503 if (animateLayout) { 504 if (newValue === null) { 505 delete animateLayout[key]; 506 } else { 507 animateLayout[key] = newValue; 508 } 509 510 if (this._pendingAnimations && this._pendingAnimations[key]) { 511 // Adjusting a value that was about to be animated cancels the animation. 512 delete this._pendingAnimations[key]; 513 } 514 515 } 516 517 // Ignore undefined values or values equal to the current value. 518 /*jshint eqeqeq:false*/ 519 if (newValue !== undefined && layout[key] != newValue) { // coerced so '100' == 100 520 // Only clone the layout if it is not given. 521 if (!newLayout) newLayout = SC.clone(this.get('layout')); 522 523 if (newValue === null) { 524 delete newLayout[key]; 525 } else { 526 newLayout[key] = newValue; 527 } 528 } 529 530 return newLayout; 531 }, 532 533 /** @private */ 534 _sc_checkForResize: function (previousLayout, currentLayout) { 535 // Did our layout change in a way that could cause us to have changed size? If 536 // not, then there's no need to invalidate the frames of our child views. 537 var didResizeHeight = true, 538 didResizeWidth = true, 539 didResize = true; 540 541 // We test the new layout to see if we believe it will affect the view's frame. 542 // Since all the child view frames may depend on the parent's frame, it's 543 // best only to notify a frame change when it actually happens. 544 /*jshint eqnull:true*/ 545 546 // Simple test: Width is defined and hasn't changed. 547 // Complex test: No defined width, left or right haven't changed. 548 if (previousLayout != null && 549 ((previousLayout.width != null && 550 previousLayout.width === currentLayout.width) || 551 (previousLayout.width == null && 552 currentLayout.width == null && 553 previousLayout.left === currentLayout.left && 554 previousLayout.right === currentLayout.right))) { 555 didResizeWidth = false; 556 } 557 558 // Simple test: Height is defined and hasn't changed. 559 // Complex test: No defined height, top or bottom haven't changed. 560 if (!didResizeWidth && 561 ((previousLayout.height != null && 562 previousLayout.height === currentLayout.height) || 563 (previousLayout.height == null && 564 currentLayout.height == null && 565 previousLayout.top === currentLayout.top && 566 previousLayout.bottom === currentLayout.bottom))) { 567 didResizeHeight = false; 568 } 569 570 // Border test: Even if the width & height haven't changed, a change in a border would be a resize. 571 if (!didResizeHeight && !didResizeWidth) { 572 didResize = !(previousLayout.border === currentLayout.border && 573 previousLayout.borderTop === currentLayout.borderTop && 574 previousLayout.borderLeft === currentLayout.borderLeft && 575 previousLayout.borderBottom === currentLayout.borderBottom && 576 previousLayout.borderRight === currentLayout.borderRight); 577 } 578 579 return didResize; 580 }, 581 582 /** @private Called when the child view layout plugin or options change. */ 583 _cvl_childViewLayoutDidChange: function () { 584 this.set('childViewsNeedLayout', true); 585 586 // Filter the input channel. 587 this.invokeOnce(this.layoutChildViewsIfNeeded); 588 }, 589 590 /** @private Called when the child views change. */ 591 _cvl_childViewsDidChange: function () { 592 this._cvl_teardownChildViewsLiveLayout(); 593 this._cvl_setupChildViewsLiveLayout(); 594 595 this.set('childViewsNeedLayout', true); 596 597 // Filter the input channel. 598 this.invokeOnce(this.layoutChildViewsIfNeeded); 599 }, 600 601 /** @private Add observers to the child views for automatic child view layout. */ 602 _cvl_setupChildViewsLiveLayout: function () { 603 var childViewLayout = this.childViewLayout, 604 childViews, 605 childLayoutProperties = childViewLayout.childLayoutProperties || []; 606 607 // Create a reference to the current child views so that we can clean them if they change. 608 childViews = this._cvl_childViews = this.get('childViews'); 609 for (var i = 0, len = childLayoutProperties.length; i < len; i++) { 610 var observedProperty = childLayoutProperties[i]; 611 612 for (var j = 0, jlen = childViews.get('length'); j < jlen; j++) { 613 var childView = childViews.objectAt(j); 614 if (!childView.get('useAbsoluteLayout') && !childView.get('useStaticLayout')) { 615 childView.addObserver(observedProperty, this, this._cvl_childViewLayoutDidChange); 616 } 617 } 618 } 619 }, 620 621 /** @private Remove observers from the child views for automatic child view layout. */ 622 _cvl_teardownChildViewsLiveLayout: function () { 623 var childViewLayout = this.childViewLayout, 624 childViews = this._cvl_childViews || [], 625 childLayoutProperties = childViewLayout.childLayoutProperties || []; 626 627 for (var i = 0, len = childLayoutProperties.length; i < len; i++) { 628 var observedProperty = childLayoutProperties[i]; 629 630 for (var j = 0, jlen = childViews.get('length'); j < jlen; j++) { 631 var childView = childViews.objectAt(j); 632 if (!childView.get('useAbsoluteLayout') && !childView.get('useStaticLayout')) { 633 childView.removeObserver(observedProperty, this, this._cvl_childViewLayoutDidChange); 634 } 635 } 636 } 637 }, 638 639 /** @private Computes the explicit layout. */ 640 _sc_computeExplicitLayout: function (layout) { 641 var ret = SC.copy(layout); 642 643 /* jshint eqnull:true */ 644 var hasBottom = (layout.bottom != null); 645 var hasRight = (layout.right != null); 646 var hasLeft = (layout.left != null); 647 var hasTop = (layout.top != null); 648 var hasCenterX = (layout.centerX != null); 649 var hasCenterY = (layout.centerY != null); 650 var hasHeight = (layout.height != null); // || (layout.maxHeight != null) 651 var hasWidth = (layout.width != null); // || (layout.maxWidth != null) 652 653 /*jshint eqnull:true */ 654 // Left + Top take precedence (left & right & width becomes left & width). 655 delete ret.right; // Right will be set if needed below. 656 delete ret.bottom; // Bottom will be set if needed below. 657 658 if (hasLeft) { 659 ret.left = layout.left; 660 } else if (!hasCenterX && !(hasWidth && hasRight)) { 661 ret.left = 0; 662 } 663 664 if (hasRight && !(hasLeft && hasWidth)) { 665 ret.right = layout.right; 666 } else if (!hasCenterX && !hasWidth) { 667 ret.right = 0; 668 } 669 670 //@if(debug) 671 // Debug-only warning when layout isn't valid. 672 // UNUSED: This is too noisy for certain views that adjust their own layouts based on top of the default layout. 673 // if (hasRight && hasLeft && hasWidth) { 674 // SC.warn("Developer Warning: When setting `width` in the layout, you must only set `left` or `right`, but not both: %@".fmt(this)); 675 // } 676 //@endif 677 678 if (hasTop) { 679 ret.top = layout.top; 680 } else if (!hasCenterY && !(hasHeight && hasBottom)) { 681 ret.top = 0; 682 } 683 684 if (hasBottom && !(hasTop && hasHeight)) { 685 ret.bottom = layout.bottom; 686 } else if (!hasCenterY && !hasHeight) { 687 ret.bottom = 0; 688 } 689 690 //@if(debug) 691 // Debug-only warning when layout isn't valid. 692 // UNUSED: This is too noisy for certain views that adjust their own layouts based on top of the default layout. 693 // if (hasBottom && hasTop && hasHeight) { 694 // SC.warn("Developer Warning: When setting `height` in the layout, you must only set `top` or `bottom`, but not both: %@".fmt(this)); 695 // } 696 //@endif 697 698 // CENTERS 699 if (hasCenterX) { 700 ret.centerX = layout.centerX; 701 702 //@if(debug) 703 // Debug-only warning when layout isn't valid. 704 if (hasCenterX && !hasWidth) { 705 SC.warn("Developer Warning: When setting `centerX` in the layout, you must also define the `width`: %@".fmt(this)); 706 } 707 //@endif 708 } 709 710 if (hasCenterY) { 711 ret.centerY = layout.centerY; 712 713 //@if(debug) 714 // Debug-only warning when layout isn't valid. 715 if (hasCenterY && !hasHeight) { 716 SC.warn("Developer Warning: When setting `centerY` in the layout, you must also define the `height`: %@".fmt(this)); 717 } 718 //@endif 719 } 720 721 // BORDERS 722 // Apply border first, so that the more specific borderX values will override it next. 723 var border = layout.border; 724 if (border != null) { 725 ret.borderTop = border; 726 ret.borderRight = border; 727 ret.borderBottom = border; 728 ret.borderLeft = border; 729 delete ret.border; 730 } 731 732 // Override generic border with more specific borderX. 733 if (layout.borderTop != null) { 734 ret.borderTop = layout.borderTop; 735 } 736 if (layout.borderRight != null) { 737 ret.borderRight = layout.borderRight; 738 } 739 if (layout.borderBottom != null) { 740 ret.borderBottom = layout.borderBottom; 741 } 742 if (layout.borderLeft != null) { 743 ret.borderLeft = layout.borderLeft; 744 } 745 746 return ret; 747 }, 748 749 /** @private */ 750 _sc_convertFrameFromViewHelper: function (frame, fromView, targetView) { 751 var myX = frame.x, myY = frame.y, myWidth = frame.width, myHeight = frame.height, view, f; 752 753 // first, walk up from the view of the frame, up to the top level 754 if (fromView) { 755 view = fromView; 756 //Note: Intentional assignment of variable f 757 while (view && (f = view.get('frame'))) { 758 759 // if scale != 1, then multiple by the scale (going from view to parent) 760 if (f.scale && f.scale !== 1) { 761 myX *= f.scale; 762 myY *= f.scale; 763 myWidth *= f.scale; 764 myHeight *= f.scale; 765 } 766 767 myX += f.x; 768 myY += f.y; 769 770 view = view.get('layoutView'); 771 } 772 } 773 774 // now, we'll walk down from the top level to the target view 775 776 // construct an array of view ancestry, from 777 // the top level view down to the target view 778 if (targetView) { 779 var viewAncestors = []; 780 view = targetView; 781 782 while (view && view.get('frame')) { 783 viewAncestors.unshift(view); 784 view = view.get('layoutView'); 785 } 786 787 // now walk the frame from 788 for (var i = 0; i < viewAncestors.length; i++ ) { 789 view = viewAncestors[i]; 790 f = view.get('frame'); 791 792 myX -= f.x; 793 myY -= f.y; 794 795 if (f.scale && f.scale !== 1) { 796 myX /= f.scale; 797 myY /= f.scale; 798 myWidth /= f.scale; 799 myHeight /= f.scale; 800 } 801 } 802 } 803 804 return { x: myX, y: myY, width: myWidth, height: myHeight }; 805 }, 806 807 /** @private */ 808 _sc_explicitValueFor: function (givenValue, impliedValue) { 809 return givenValue === undefined ? impliedValue : givenValue; 810 }, 811 812 /** @private Attempts to run a transition adjust, ensuring any showing transitions are stopped in place. */ 813 _sc_transitionAdjust: function (layout) { 814 var transitionAdjust = this.get('transitionAdjust'), 815 options = this.get('transitionAdjustOptions') || {}; 816 817 // Execute the adjusting transition. 818 transitionAdjust.run(this, options, layout); 819 }, 820 821 /** @private 822 Invoked by other views to notify this view that its frame has changed. 823 824 This notifies the view that its frame property has changed, 825 then notifies its child views that their clipping frames may have changed. 826 */ 827 _sc_viewFrameDidChange: function () { 828 this.notifyPropertyChange('frame'); 829 830 // Notify the children that their clipping frame may have changed. Top-down, because a child's 831 // clippingFrame is dependent on its parent's frame. 832 this._callOnChildViews('_sc_clippingFrameDidChange'); 833 }, 834 835 /** 836 This convenience method will take the current layout, apply any changes 837 you pass and set it again. It is more convenient than having to do this 838 yourself sometimes. 839 840 You can pass just a key/value pair or a hash with several pairs. You can 841 also pass a null value to delete a property. 842 843 This method will avoid actually setting the layout if the value you pass 844 does not edit the layout. 845 846 @param {String|Hash} key 847 @param {Object} value 848 @returns {SC.View} receiver 849 */ 850 adjust: function (key, value) { 851 if (key === undefined) { return this; } // FAST PATH! Nothing to do. 852 853 var layout = this.get('layout'), 854 newLayout; 855 856 // Normalize arguments. 857 if (SC.typeOf(key) === SC.T_STRING) { 858 newLayout = this._sc_applyAdjustment(key, value, layout); 859 } else { 860 for (var aKey in key) { 861 if (!key.hasOwnProperty(aKey)) { continue; } 862 863 newLayout = this._sc_applyAdjustment(aKey, key[aKey], layout, newLayout); 864 } 865 } 866 867 // now set adjusted layout 868 if (newLayout) { 869 var transitionAdjust = this.get('transitionAdjust'); 870 871 if (this.get('viewState') & SC.CoreView.IS_SHOWN && transitionAdjust) { 872 // Run the adjust transition. 873 this._sc_transitionAdjust(newLayout); 874 } else { 875 this.set('layout', newLayout); 876 } 877 } 878 879 return this; 880 }, 881 882 /** */ 883 computeParentDimensions: function (frame) { 884 var parentView = this.get('parentView'), 885 parentFrame = (parentView) ? parentView.get('frame') : null, 886 ret; 887 888 if (parentFrame) { 889 ret = { 890 width: parentFrame.width, 891 height: parentFrame.height 892 }; 893 } else if (frame) { 894 ret = { 895 width: (frame.left || 0) + (frame.width || 0) + (frame.right || 0), 896 height: (frame.top || 0) + (frame.height || 0) + (frame.bottom || 0) 897 }; 898 } else { 899 ret = { 900 width: 0, 901 height: 0 902 }; 903 } 904 905 return ret; 906 }, 907 908 /** 909 Converts a frame from the receiver's offset to the target offset. Both 910 the receiver and the target must belong to the same pane. If you pass 911 null, the conversion will be to the pane level. 912 913 Note that the context of a view's frame is the view's parent frame. In 914 other words, if you want to convert the frame of your view to the global 915 frame, then you should do: 916 917 var pv = this.get('parentView'), frame = this.get('frame'); 918 var newFrame = pv ? pv.convertFrameToView(frame, null) : frame; 919 920 @param {Rect} frame the source frame 921 @param {SC.View} targetView the target view to convert to 922 @returns {Rect} converted frame 923 @test in convertFrames 924 */ 925 convertFrameToView: function (frame, targetView) { 926 return this._sc_convertFrameFromViewHelper(frame, this, targetView); 927 }, 928 929 /** 930 Converts a frame offset in the coordinates of another view system to the 931 receiver's view. 932 933 Note that the convext of a view's frame is relative to the view's 934 parentFrame. For example, if you want to convert the frame of view that 935 belongs to another view to the receiver's frame you would do: 936 937 var frame = view.get('frame'); 938 var newFrame = this.convertFrameFromView(frame, view.get('parentView')); 939 940 @param {Rect} frame the source frame 941 @param {SC.View} targetView the target view to convert to 942 @returns {Rect} converted frame 943 @test in converFrames 944 */ 945 convertFrameFromView: function (frame, targetView) { 946 return this._sc_convertFrameFromViewHelper(frame, targetView, this); 947 }, 948 949 /** @private */ 950 didTransitionAdjust: function () {}, 951 952 /** 953 This method is called whenever a property changes that invalidates the 954 layout of the view. Changing the layout will do this automatically, but 955 you can add others if you want. 956 957 Implementation Note: In a traditional setup, we would simply observe 958 'layout' here, but as described above in the documentation for our custom 959 implementation of propertyDidChange(), this method must always run 960 immediately after 'layout' is updated to avoid the potential for stale 961 (incorrect) cached 'frame' values. 962 963 @returns {SC.View} receiver 964 */ 965 layoutDidChange: function () { 966 var currentLayout = this.get('layout'); 967 968 // Handle old style rotation. 969 if (!SC.none(currentLayout.rotate)) { 970 if (SC.none(currentLayout.rotateZ) && SC.platform.get('supportsCSS3DTransforms')) { 971 currentLayout.rotateZ = currentLayout.rotate; 972 delete currentLayout.rotate; 973 } 974 } 975 976 // Optimize notifications depending on if we resized or just moved. 977 var didResize = this._sc_checkForResize(this._sc_previousLayout, currentLayout); 978 979 // Cache the last layout to fine-tune notifications when the layout changes. 980 // NOTE: Do this before continuing so that any adjustments that occur in viewDidResize or from 981 // _sc_viewFrameDidChange (say to the position after a resize), don't result in _sc_checkForResize 982 // running against the old _sc_previousLayout. 983 this._sc_previousLayout = currentLayout; 984 985 if (didResize) { 986 this.viewDidResize(); 987 } else { 988 // Even if we didn't resize, our frame sould have changed. 989 // TODO: consider checking for position changes by testing the resulting frame against the cached frame. This is difficult to do. 990 this._sc_viewFrameDidChange(); 991 } 992 993 // Notify layoutView/parentView, unless we are transitioning. 994 var layoutView = this.get('layoutView'); 995 if (layoutView) { 996 layoutView.set('childViewsNeedLayout', YES); 997 layoutView.layoutDidChangeFor(this); 998 999 // Check if childViewsNeedLayout is still true. 1000 if (layoutView.get('childViewsNeedLayout')) { 1001 layoutView.invokeOnce(layoutView.layoutChildViewsIfNeeded); 1002 } 1003 } else { 1004 this.invokeOnce(this.updateLayout); 1005 } 1006 1007 return this; 1008 }, 1009 1010 /** 1011 One of two methods that are invoked whenever one of your childViews 1012 layout changes. This method is invoked every time a child view's layout 1013 changes to give you a chance to record the information about the view. 1014 1015 Since this method may be called many times during a single run loop, you 1016 should keep this method pretty short. The other method called when layout 1017 changes, layoutChildViews(), is invoked only once at the end of 1018 the run loop. You should do any expensive operations (including changing 1019 a childView's actual layer) in this other method. 1020 1021 Note that if as a result of running this method you decide that you do not 1022 need your layoutChildViews() method run later, you can set the 1023 childViewsNeedsLayout property to NO from this method and the layout 1024 method will not be called layer. 1025 1026 @param {SC.View} childView the view whose layout has changed. 1027 @returns {void} 1028 */ 1029 layoutDidChangeFor: function (childView) { 1030 var set = this._needLayoutViews; 1031 1032 // Track this view. 1033 if (!set) set = this._needLayoutViews = SC.CoreSet.create(); 1034 set.add(childView); 1035 }, 1036 1037 /** 1038 Called your layout method if the view currently needs to layout some 1039 child views. 1040 1041 @param {Boolean} force if true assume view is visible even if it is not. 1042 @returns {SC.View} receiver 1043 @test in layoutChildViews 1044 */ 1045 layoutChildViewsIfNeeded: function (force) { 1046 if (this.get('childViewsNeedLayout')) { 1047 this.layoutChildViews(force); 1048 1049 this.set('childViewsNeedLayout', NO); 1050 } 1051 1052 return this; 1053 }, 1054 1055 /** 1056 Applies the current layout to the layer. This method is usually only 1057 called once per runloop. You can override this method to provide your 1058 own layout updating method if you want, though usually the better option 1059 is to override the layout method from the parent view. 1060 1061 The default implementation of this method simply calls the updateLayout() 1062 method on the views that need layout. 1063 1064 @param {Boolean} force Force the update to the layer's layout style immediately even if the view is not in a shown state. Otherwise the style will be updated when the view returns to a shown state. 1065 @returns {void} 1066 */ 1067 layoutChildViews: function (force) { 1068 var childViewLayout = this.childViewLayout, 1069 set, len, i; 1070 1071 // Allow the child view layout plugin to layout all child views. 1072 if (childViewLayout) { 1073 // Adjust all other child views right now. 1074 // Note: this will add the affected child views to the set so they will be updated only once in this run loop 1075 childViewLayout.layoutChildViews(this); 1076 } 1077 1078 // Retreive these values after they may have been updated by adjustments by 1079 // the childViewLayout plugin. 1080 set = this._needLayoutViews; 1081 if (set) { 1082 for (i = 0, len = set.length; i < len; ++i) { 1083 set[i].updateLayout(force); 1084 } 1085 1086 set.clear(); // reset & reuse 1087 } 1088 }, 1089 1090 /** 1091 This method may be called on your view whenever the parent view resizes. 1092 1093 The default version of this method will reset the frame and then call 1094 viewDidResize() if its size may have changed. You will not usually override 1095 this method, but you may override the viewDidResize() method. 1096 1097 @param {Frame} parentFrame the parent view's current frame. 1098 @returns {void} 1099 @test in viewDidResize 1100 */ 1101 parentViewDidResize: function (parentFrame) { 1102 // Determine if our position may have changed. 1103 var positionMayHaveChanged = !this.get('isFixedPosition'); 1104 1105 // Figure out if our size may have changed. 1106 var isStatic = this.get('useStaticLayout'), 1107 // Figure out whether our height may have changed. 1108 parentHeight = parentFrame ? parentFrame.height : 0, 1109 parentHeightDidChange = parentHeight !== this._scv_parentHeight, 1110 isFixedHeight = this.get('isFixedHeight'), 1111 heightMayHaveChanged = isStatic || (parentHeightDidChange && !isFixedHeight), 1112 // Figure out whether our width may have changed. 1113 parentWidth = parentFrame ? parentFrame.width : 0, 1114 parentWidthDidChange = parentWidth !== this._scv_parentWidth, 1115 isFixedWidth = this.get('isFixedWidth'), 1116 widthMayHaveChanged = isStatic || (parentWidthDidChange && !isFixedWidth); 1117 1118 // Update the cached parent frame. 1119 this._scv_parentHeight = parentHeight; 1120 this._scv_parentWidth = parentWidth; 1121 1122 // If our height or width changed, our resulting frame change may impact our child views. 1123 if (heightMayHaveChanged || widthMayHaveChanged) { 1124 this.viewDidResize(); 1125 } 1126 // If our size didn't change but our position did, our frame will change, but it won't impact our child 1127 // views' frames. (Note that the _sc_viewFrameDidChange call is made by viewDidResize above.) 1128 else if (positionMayHaveChanged) { 1129 this._sc_viewFrameDidChange(); 1130 } 1131 }, 1132 1133 /** 1134 The 'frame' property depends on the 'layout' property as well as the 1135 parent view's frame. In order to properly invalidate any cached values, 1136 we need to invalidate the cache whenever 'layout' changes. However, 1137 observing 'layout' does not guarantee that; the observer might not be run 1138 before all other observers. 1139 1140 In order to avoid any window of opportunity where the cached frame could 1141 be invalid, we need to force layoutDidChange() to immediately run 1142 whenever 'layout' is set. 1143 */ 1144 propertyDidChange: function (key, value, _keepCache) { 1145 //@if(debug) 1146 // Debug mode only property validation. 1147 if (key === 'layout') { 1148 // If a layout value is accidentally set to NaN, this can result in infinite loops. Help the 1149 // developer out by failing early so that they can follow the stack trace to the problem. 1150 for (var property in value) { 1151 if (!value.hasOwnProperty(property)) { continue; } 1152 1153 var layoutValue = value[property]; 1154 if (isNaN(layoutValue) && (layoutValue !== SC.LAYOUT_AUTO) && 1155 !SC._ROTATION_VALUE_REGEX.exec(layoutValue) && !SC._SCALE_VALUE_REGEX.exec(layoutValue)) { 1156 throw new Error("SC.View layout property set to invalid value, %@: %@.".fmt(property, layoutValue)); 1157 } 1158 } 1159 } 1160 //@endif 1161 1162 // To allow layout to be a computed property, we check if any property has 1163 // changed and if layout is dependent on the property. 1164 var layoutChange = false; 1165 if (typeof this.layout === "function" && this._kvo_dependents) { 1166 var dependents = this._kvo_dependents[key]; 1167 if (dependents && dependents.indexOf('layout') !== -1) { layoutChange = true; } 1168 } 1169 1170 // If the key is 'layout', we need to call layoutDidChange() immediately 1171 // so that if the frame has changed any cached values (for both this view 1172 // and any child views) can be appropriately invalidated. 1173 if (key === 'layout' || layoutChange) { 1174 this.layoutDidChange(); 1175 } 1176 1177 // Resume notification as usual. 1178 return sc_super(); 1179 }, 1180 1181 /** 1182 */ 1183 // propertyWillChange: function (key) { 1184 // // To allow layout to be a computed property, we check if any property has 1185 // // changed and if layout is dependent on the property. 1186 // var layoutChange = false; 1187 // if (typeof this.layout === "function" && this._kvo_dependents) { 1188 // var dependents = this._kvo_dependents[key]; 1189 // if (dependents && dependents.indexOf('layout') !== -1) { layoutChange = true; } 1190 // } 1191 1192 // if (key === 'layout' || layoutChange) { 1193 // this._sc_previousLayout = this.get('layout'); 1194 // } 1195 1196 // return sc_super(); 1197 // }, 1198 1199 /** 1200 Attempt to scroll the view to visible. This will walk up the parent 1201 view hierarchy looking looking for a scrollable view. It will then 1202 call scrollToVisible() on it. 1203 1204 Returns YES if an actual scroll took place, no otherwise. 1205 1206 @returns {Boolean} 1207 */ 1208 scrollToVisible: function () { 1209 var pv = this.get('parentView'); 1210 while (pv && !pv.get('isScrollable')) { pv = pv.get('parentView'); } 1211 1212 // found view, first make it scroll itself visible then scroll this. 1213 if (pv) { 1214 pv.scrollToVisible(); 1215 return pv.scrollToVisible(this); 1216 } else { 1217 return NO; 1218 } 1219 }, 1220 1221 /** 1222 This method is invoked on your view when the view resizes due to a layout 1223 change or potentially due to the parent view resizing (if your view’s size 1224 depends on the size of your parent view). You can override this method 1225 to implement your own layout if you like, such as performing a grid 1226 layout. 1227 1228 The default implementation simply notifies about the change to 'frame' and 1229 then calls parentViewDidResize on all of your children. 1230 1231 @returns {void} 1232 */ 1233 viewDidResize: function () { 1234 this._sc_viewFrameDidChange(); 1235 1236 // Also notify our children. 1237 var cv = this.childViews, 1238 frame = this.get('frame'), 1239 len, idx, view; 1240 for (idx = 0; idx < (len = cv.length); ++idx) { 1241 view = cv[idx]; 1242 view.tryToPerform('parentViewDidResize', frame); 1243 } 1244 }, 1245 1246 // Implementation note: As a general rule, paired method calls, such as 1247 // beginLiveResize/endLiveResize that are called recursively on the tree 1248 // should reverse the order when doing the final half of the call. This 1249 // ensures that the calls are propertly nested for any cleanup routines. 1250 // 1251 // -> View A.beginXXX() 1252 // -> View B.beginXXX() 1253 // -> View C.beginXXX() 1254 // -> View D.beginXXX() 1255 // 1256 // ...later on, endXXX methods are called in reverse order of beginXXX... 1257 // 1258 // <- View D.endXXX() 1259 // <- View C.endXXX() 1260 // <- View B.endXXX() 1261 // <- View A.endXXX() 1262 // 1263 // See the two methods below for an example implementation. 1264 1265 /** 1266 Call this method when you plan to begin a live resize. This will 1267 notify the receiver view and any of its children that are interested 1268 that the resize is about to begin. 1269 1270 @returns {SC.View} receiver 1271 @test in viewDidResize 1272 */ 1273 beginLiveResize: function () { 1274 // call before children have been notified... 1275 if (this.willBeginLiveResize) this.willBeginLiveResize(); 1276 1277 // notify children in order 1278 var ary = this.get('childViews'), len = ary.length, idx, view; 1279 for (idx = 0; idx < len; ++idx) { 1280 view = ary[idx]; 1281 if (view.beginLiveResize) view.beginLiveResize(); 1282 } 1283 return this; 1284 }, 1285 1286 /** 1287 Call this method when you are finished with a live resize. This will 1288 notify the receiver view and any of its children that are interested 1289 that the live resize has ended. 1290 1291 @returns {SC.View} receiver 1292 @test in viewDidResize 1293 */ 1294 endLiveResize: function () { 1295 // notify children in *reverse* order 1296 var ary = this.get('childViews'), len = ary.length, idx, view; 1297 for (idx = len - 1; idx >= 0; --idx) { // loop backwards 1298 view = ary[idx]; 1299 if (view.endLiveResize) view.endLiveResize(); 1300 } 1301 1302 // call *after* all children have been notified... 1303 if (this.didEndLiveResize) this.didEndLiveResize(); 1304 return this; 1305 }, 1306 1307 /** 1308 Invoked by the layoutChildViews method to update the layout on a 1309 particular view. This method creates a render context and calls the 1310 renderLayout() method, which is probably what you want to override instead 1311 of this. 1312 1313 You will not usually override this method, but you may call it if you 1314 implement layoutChildViews() in a view yourself. 1315 1316 @param {Boolean} force Force the update to the layer's layout style immediately even if the view is not in a shown state. Otherwise the style will be updated when the view returns to a shown state. 1317 @returns {SC.View} receiver 1318 @test in layoutChildViews 1319 */ 1320 updateLayout: function (force) { 1321 this._doUpdateLayout(force); 1322 1323 return this; 1324 }, 1325 1326 /** 1327 Default method called by the layout view to actually apply the current 1328 layout to the layer. The default implementation simply assigns the 1329 current layoutStyle to the layer. This method is also called whenever 1330 the layer is first created. 1331 1332 @param {SC.RenderContext} the render context 1333 @returns {void} 1334 @test in layoutChildViews 1335 */ 1336 renderLayout: function (context) { 1337 context.setStyle(this.get('layoutStyle')); 1338 }, 1339 1340 // ------------------------------------------------------------------------ 1341 // Statechart 1342 // 1343 1344 /** @private Update this view's layout action. */ 1345 _doUpdateLayout: function (force) { 1346 var isRendered = this.get('_isRendered'), 1347 isVisibleInWindow = this.get('isVisibleInWindow'), 1348 handled = true; 1349 1350 if (isRendered) { 1351 if (isVisibleInWindow || force) { 1352 // Only in the visible states do we allow updates without being forced. 1353 this._doUpdateLayoutStyle(); 1354 } else { 1355 // Otherwise mark the view as needing an update when we enter a shown state again. 1356 this._layoutStyleNeedsUpdate = true; 1357 } 1358 } else { 1359 handled = false; 1360 } 1361 1362 return handled; 1363 }, 1364 1365 /** @private */ 1366 _doUpdateLayoutStyle: function () { 1367 var layer = this.get('layer'), 1368 layoutStyle = this.get('layoutStyle'); 1369 1370 for (var styleName in layoutStyle) { 1371 layer.style[styleName] = layoutStyle[styleName]; 1372 } 1373 1374 // Reset that an update is required. 1375 this._layoutStyleNeedsUpdate = false; 1376 1377 // Notify updated. 1378 this._updatedLayout(); 1379 }, 1380 1381 /** @private Override: Notify on attached (avoids notify of frame changed). */ 1382 _notifyDidAttach: function () { 1383 // If we are using static layout then we don't know the frame until appended to the document. 1384 if (this.get('useStaticLayout')) { 1385 // We call viewDidResize so that it calls parentViewDidResize on all child views. 1386 this.viewDidResize(); 1387 } 1388 1389 // Notify. 1390 if (this.didAppendToDocument) { this.didAppendToDocument(); } 1391 }, 1392 1393 /** @private Override: The 'adopted' event (uses isFixedSize so our childViews are notified if our frame changes). */ 1394 _adopted: function (beforeView) { 1395 // If our size depends on our parent, it will have changed on adoption. 1396 var isFixedSize = this.get('isFixedSize'); 1397 if (isFixedSize) { 1398 // Even if our size is fixed, our frame may have changed (in particular if the anchor is not top/left) 1399 this._sc_viewFrameDidChange(); 1400 } else { 1401 this.viewDidResize(); 1402 } 1403 1404 sc_super(); 1405 }, 1406 1407 /** @private Extension: The 'orphaned' event (uses isFixedSize so our childViews are notified if our frame changes). */ 1408 _orphaned: function () { 1409 sc_super(); 1410 1411 if (!this.isDestroyed) { 1412 // If our size depends on our parent, it will have changed on orphaning. 1413 var isFixedSize = this.get('isFixedSize'); 1414 if (isFixedSize) { 1415 // Even if our size is fixed, our frame may have changed (in particular if the anchor is not top/left) 1416 this._sc_viewFrameDidChange(); 1417 } else { 1418 this.viewDidResize(); 1419 } 1420 } 1421 }, 1422 1423 /** @private Extension: The 'updatedContent' event. */ 1424 _updatedContent: function () { 1425 sc_super(); 1426 1427 // If this view uses static layout, then notify that the frame (likely) 1428 // changed. 1429 if (this.get('useStaticLayout')) { this.viewDidResize(); } 1430 }, 1431 1432 /** @private The 'updatedLayout' event. */ 1433 _updatedLayout: function () { 1434 // Notify. 1435 this.didRenderAnimations(); 1436 } 1437 1438 }); 1439 1440 SC.View.mixin( 1441 /** @scope SC.View */ { 1442 1443 /** 1444 Convert any layout to a Top, Left, Width, Height layout 1445 */ 1446 convertLayoutToAnchoredLayout: function (layout, parentFrame) { 1447 var ret = {top: 0, left: 0, width: parentFrame.width, height: parentFrame.height}, 1448 pFW = parentFrame.width, pFH = parentFrame.height, //shortHand for parentDimensions 1449 lR = layout.right, 1450 lL = layout.left, 1451 lT = layout.top, 1452 lB = layout.bottom, 1453 lW = layout.width, 1454 lH = layout.height, 1455 lcX = layout.centerX, 1456 lcY = layout.centerY; 1457 1458 // X Conversion 1459 // handle left aligned and left/right 1460 if (!SC.none(lL)) { 1461 if (SC.isPercentage(lL)) ret.left = lL * pFW; 1462 else ret.left = lL; 1463 if (lW !== undefined) { 1464 if (lW === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO; 1465 else if (SC.isPercentage(lW)) ret.width = lW * pFW; 1466 else ret.width = lW; 1467 } else { 1468 if (lR && SC.isPercentage(lR)) ret.width = pFW - ret.left - (lR * pFW); 1469 else ret.width = pFW - ret.left - (lR || 0); 1470 } 1471 1472 // handle right aligned 1473 } else if (!SC.none(lR)) { 1474 1475 // if no width, calculate it from the parent frame 1476 if (SC.none(lW)) { 1477 ret.left = 0; 1478 if (lR && SC.isPercentage(lR)) ret.width = pFW - (lR * pFW); 1479 else ret.width = pFW - (lR || 0); 1480 1481 // If has width, calculate the left anchor from the width and right and parent frame 1482 } else { 1483 if (lW === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO; 1484 else { 1485 if (SC.isPercentage(lW)) ret.width = lW * pFW; 1486 else ret.width = lW; 1487 if (SC.isPercentage(lR)) ret.left = pFW - (ret.width + lR); 1488 else ret.left = pFW - (ret.width + lR); 1489 } 1490 } 1491 1492 // handle centered 1493 } else if (!SC.none(lcX)) { 1494 if (lW && SC.isPercentage(lW)) ret.width = (lW * pFW); 1495 else ret.width = (lW || 0); 1496 ret.left = ((pFW - ret.width) / 2); 1497 if (SC.isPercentage(lcX)) ret.left = ret.left + lcX * pFW; 1498 else ret.left = ret.left + lcX; 1499 1500 // if width defined, assume left of zero 1501 } else if (!SC.none(lW)) { 1502 ret.left = 0; 1503 if (lW === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO; 1504 else { 1505 if (SC.isPercentage(lW)) ret.width = lW * pFW; 1506 else ret.width = lW; 1507 } 1508 1509 // fallback, full width. 1510 } else { 1511 ret.left = 0; 1512 ret.width = 0; 1513 } 1514 1515 // handle min/max 1516 if (layout.minWidth !== undefined) ret.minWidth = layout.minWidth; 1517 if (layout.maxWidth !== undefined) ret.maxWidth = layout.maxWidth; 1518 1519 // Y Conversion 1520 // handle left aligned and top/bottom 1521 if (!SC.none(lT)) { 1522 if (SC.isPercentage(lT)) ret.top = lT * pFH; 1523 else ret.top = lT; 1524 if (lH !== undefined) { 1525 if (lH === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO; 1526 else if (SC.isPercentage(lH)) ret.height = lH * pFH; 1527 else ret.height = lH; 1528 } else { 1529 ret.height = pFH - ret.top; 1530 if (lB && SC.isPercentage(lB)) ret.height = ret.height - (lB * pFH); 1531 else ret.height = ret.height - (lB || 0); 1532 } 1533 1534 // handle bottom aligned 1535 } else if (!SC.none(lB)) { 1536 1537 // if no height, calculate it from the parent frame 1538 if (SC.none(lH)) { 1539 ret.top = 0; 1540 if (lB && SC.isPercentage(lB)) ret.height = pFH - (lB * pFH); 1541 else ret.height = pFH - (lB || 0); 1542 1543 // If has height, calculate the top anchor from the height and bottom and parent frame 1544 } else { 1545 if (lH === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO; 1546 else { 1547 if (SC.isPercentage(lH)) ret.height = lH * pFH; 1548 else ret.height = lH; 1549 ret.top = pFH - ret.height; 1550 if (SC.isPercentage(lB)) ret.top = ret.top - (lB * pFH); 1551 else ret.top = ret.top - lB; 1552 } 1553 } 1554 1555 // handle centered 1556 } else if (!SC.none(lcY)) { 1557 if (lH && SC.isPercentage(lH)) ret.height = (lH * pFH); 1558 else ret.height = (lH || 0); 1559 ret.top = ((pFH - ret.height) / 2); 1560 if (SC.isPercentage(lcY)) ret.top = ret.top + lcY * pFH; 1561 else ret.top = ret.top + lcY; 1562 1563 // if height defined, assume top of zero 1564 } else if (!SC.none(lH)) { 1565 ret.top = 0; 1566 if (lH === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO; 1567 else if (SC.isPercentage(lH)) ret.height = lH * pFH; 1568 else ret.height = lH; 1569 1570 // fallback, full height. 1571 } else { 1572 ret.top = 0; 1573 ret.height = 0; 1574 } 1575 1576 if (ret.top) ret.top = Math.floor(ret.top); 1577 if (ret.bottom) ret.bottom = Math.floor(ret.bottom); 1578 if (ret.left) ret.left = Math.floor(ret.left); 1579 if (ret.right) ret.right = Math.floor(ret.right); 1580 if (ret.width !== SC.LAYOUT_AUTO) ret.width = Math.floor(ret.width); 1581 if (ret.height !== SC.LAYOUT_AUTO) ret.height = Math.floor(ret.height); 1582 1583 // handle min/max 1584 if (layout.minHeight !== undefined) ret.minHeight = layout.minHeight; 1585 if (layout.maxHeight !== undefined) ret.maxHeight = layout.maxHeight; 1586 1587 return ret; 1588 }, 1589 1590 /** 1591 For now can only convert Top/Left/Width/Height to a Custom Layout 1592 */ 1593 convertLayoutToCustomLayout: function (layout, layoutParams, parentFrame) { 1594 // TODO: [EG] Create Top/Left/Width/Height to a Custom Layout conversion 1595 } 1596 1597 }); 1598