1 /* 2 * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include <TwoDimensionalLayout.h> 7 8 #include <stdio.h> 9 10 #include <LayoutContext.h> 11 #include <LayoutItem.h> 12 #include <LayoutUtils.h> 13 #include <List.h> 14 #include <View.h> 15 16 #include "ComplexLayouter.h" 17 #include "OneElementLayouter.h" 18 #include "SimpleLayouter.h" 19 20 21 // Some words of explanation: 22 // 23 // This class is the base class for BLayouts that organize their items 24 // on a grid, with each item covering one or more grid cells (always a 25 // rectangular area). The derived classes only need to implement the 26 // hooks reporting the constraints for the items and additional constraints 27 // for the rows and columns. This class does all the layouting. 28 // 29 // The basic idea of the layout process is simple. The horizontal and the 30 // vertical dimensions are laid out independently and the items are set to the 31 // resulting locations and sizes. The "height for width" feature makes the 32 // height depend on the width, which makes things a bit more complicated. 33 // The horizontal dimension must be laid out first and and the results are 34 // fed into the vertical layout process. 35 // 36 // The AlignLayoutWith() feature, which allows to align layouts for different 37 // views with each other, causes the need for the three inner *Layouter classes. 38 // For each set of layouts aligned with each other with respect to one 39 // dimension that dimension must be laid out together. The class responsible 40 // is CompoundLayouter; one instance exists per such set. The derived class 41 // VerticalCompoundLayouter is a specialization for the vertical dimension 42 // which additionally takes care of the "height for width" feature. Per layout 43 // a single LocalLayouter exists, which comprises the required glue layout 44 // code and serves as a proxy for the layout, providing service methods 45 // needed by the CompoundLayouter. 46 47 // TODO: Check for memory leaks! 48 49 //#define DEBUG_LAYOUT 50 51 // CompoundLayouter 52 class BTwoDimensionalLayout::CompoundLayouter { 53 public: 54 CompoundLayouter(enum orientation orientation); 55 virtual ~CompoundLayouter(); 56 57 orientation Orientation(); 58 59 virtual Layouter* GetLayouter(bool minMax); 60 61 LayoutInfo* GetLayoutInfo(); 62 63 void AddLocalLayouter(LocalLayouter* localLayouter); 64 void RemoveLocalLayouter( 65 LocalLayouter* localLayouter); 66 67 void AbsorbCompoundLayouter(CompoundLayouter* other); 68 69 virtual void InvalidateLayout(); 70 bool IsMinMaxValid(); 71 void ValidateMinMax(); 72 void Layout(float size, LocalLayouter* localLayouter, 73 BLayoutContext* context); 74 75 protected: 76 virtual void DoLayout(float size, 77 LocalLayouter* localLayouter, 78 BLayoutContext* context); 79 80 Layouter* fLayouter; 81 LayoutInfo* fLayoutInfo; 82 orientation fOrientation; 83 BList fLocalLayouters; 84 BLayoutContext* fLayoutContext; 85 float fLastLayoutSize; 86 87 void _PrepareItems(); 88 89 int32 _CountElements(); 90 bool _HasMultiElementItems(); 91 92 void _AddConstraints(Layouter* layouter); 93 94 float _Spacing(); 95 }; 96 97 // VerticalCompoundLayouter 98 class BTwoDimensionalLayout::VerticalCompoundLayouter 99 : public CompoundLayouter, private BLayoutContextListener { 100 public: 101 VerticalCompoundLayouter(); 102 103 virtual Layouter* GetLayouter(bool minMax); 104 105 virtual void InvalidateLayout(); 106 107 void InvalidateHeightForWidth(); 108 109 void InternalGetHeightForWidth( 110 LocalLayouter* localLayouter, 111 BLayoutContext* context, 112 bool realLayout, float* minHeight, 113 float* maxHeight, float* preferredHeight); 114 115 protected: 116 virtual void DoLayout(float size, 117 LocalLayouter* localLayouter, 118 BLayoutContext* context); 119 120 private: 121 Layouter* fHeightForWidthLayouter; 122 float fCachedMinHeightForWidth; 123 float fCachedMaxHeightForWidth; 124 float fCachedPreferredHeightForWidth; 125 BLayoutContext* fHeightForWidthLayoutContext; 126 127 bool _HasHeightForWidth(); 128 129 bool _SetHeightForWidthLayoutContext( 130 BLayoutContext* context); 131 132 // BLayoutContextListener 133 virtual void LayoutContextLeft(BLayoutContext* context); 134 }; 135 136 // LocalLayouter 137 class BTwoDimensionalLayout::LocalLayouter : private BLayoutContextListener { 138 public: 139 LocalLayouter(BTwoDimensionalLayout* layout); 140 141 // interface for the BTwoDimensionalLayout class 142 143 BSize MinSize(); 144 BSize MaxSize(); 145 BSize PreferredSize(); 146 147 void InvalidateLayout(); 148 void Layout(BSize size); 149 150 BRect ItemFrame(Dimensions itemDimensions); 151 152 void ValidateMinMax(); 153 154 void DoHorizontalLayout(float width); 155 156 void InternalGetHeightForWidth(float width, 157 float* minHeight, float* maxHeight, 158 float* preferredHeight); 159 160 void AlignWith(LocalLayouter* other, 161 enum orientation orientation); 162 163 164 // interface for the compound layout context 165 166 void PrepareItems( 167 CompoundLayouter* compoundLayouter); 168 int32 CountElements( 169 CompoundLayouter* compoundLayouter); 170 bool HasMultiElementItems( 171 CompoundLayouter* compoundLayouter); 172 173 void AddConstraints( 174 CompoundLayouter* compoundLayouter, 175 Layouter* layouter); 176 177 float Spacing(CompoundLayouter* compoundLayouter); 178 179 bool HasHeightForWidth(); 180 181 bool AddHeightForWidthConstraints( 182 VerticalCompoundLayouter* compoundLayouter, 183 Layouter* layouter, 184 BLayoutContext* context); 185 void SetHeightForWidthConstraintsAdded(bool added); 186 187 void SetCompoundLayouter( 188 CompoundLayouter* compoundLayouter, 189 enum orientation orientation); 190 191 void InternalInvalidateLayout( 192 CompoundLayouter* compoundLayouter); 193 194 // implementation private 195 private: 196 BTwoDimensionalLayout* fLayout; 197 CompoundLayouter* fHLayouter; 198 VerticalCompoundLayouter* fVLayouter; 199 BList fHeightForWidthItems; 200 201 // active layout context when doing last horizontal layout 202 BLayoutContext* fHorizontalLayoutContext; 203 float fHorizontalLayoutWidth; 204 bool fHeightForWidthConstraintsAdded; 205 206 void _SetHorizontalLayoutContext( 207 BLayoutContext* context, float width); 208 209 // BLayoutContextListener 210 virtual void LayoutContextLeft(BLayoutContext* context); 211 }; 212 213 214 // #pragma mark - 215 216 217 // constructor 218 BTwoDimensionalLayout::BTwoDimensionalLayout() 219 : fLeftInset(0), 220 fRightInset(0), 221 fTopInset(0), 222 fBottomInset(0), 223 fHSpacing(0), 224 fVSpacing(0), 225 fLocalLayouter(new LocalLayouter(this)) 226 { 227 } 228 229 // destructor 230 BTwoDimensionalLayout::~BTwoDimensionalLayout() 231 { 232 delete fLocalLayouter; 233 } 234 235 // SetInsets 236 void 237 BTwoDimensionalLayout::SetInsets(float left, float top, float right, 238 float bottom) 239 { 240 fLeftInset = left; 241 fTopInset = top; 242 fRightInset = right; 243 fBottomInset = bottom; 244 245 InvalidateLayout(); 246 } 247 248 // GetInsets 249 void 250 BTwoDimensionalLayout::GetInsets(float* left, float* top, float* right, 251 float* bottom) const 252 { 253 if (left) 254 *left = fLeftInset; 255 if (top) 256 *top = fTopInset; 257 if (right) 258 *right = fRightInset; 259 if (bottom) 260 *bottom = fBottomInset; 261 } 262 263 // AlignLayoutWith 264 void 265 BTwoDimensionalLayout::AlignLayoutWith(BTwoDimensionalLayout* other, 266 enum orientation orientation) 267 { 268 if (!other || other == this) 269 return; 270 271 fLocalLayouter->AlignWith(other->fLocalLayouter, orientation); 272 273 InvalidateLayout(); 274 } 275 276 // MinSize 277 BSize 278 BTwoDimensionalLayout::MinSize() 279 { 280 _ValidateMinMax(); 281 return AddInsets(fLocalLayouter->MinSize()); 282 } 283 284 // MaxSize 285 BSize 286 BTwoDimensionalLayout::MaxSize() 287 { 288 _ValidateMinMax(); 289 return AddInsets(fLocalLayouter->MaxSize()); 290 } 291 292 // PreferredSize 293 BSize 294 BTwoDimensionalLayout::PreferredSize() 295 { 296 _ValidateMinMax(); 297 return AddInsets(fLocalLayouter->PreferredSize()); 298 } 299 300 // Alignment 301 BAlignment 302 BTwoDimensionalLayout::Alignment() 303 { 304 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 305 } 306 307 // GetHeightForWidth 308 bool 309 BTwoDimensionalLayout::HasHeightForWidth() 310 { 311 _ValidateMinMax(); 312 return fLocalLayouter->HasHeightForWidth(); 313 } 314 315 // GetHeightForWidth 316 void 317 BTwoDimensionalLayout::GetHeightForWidth(float width, float* min, float* max, 318 float* preferred) 319 { 320 if (!HasHeightForWidth()) 321 return; 322 323 float outerSpacing = fLeftInset + fRightInset - 1; 324 fLocalLayouter->InternalGetHeightForWidth(BLayoutUtils::SubtractDistances( 325 width, outerSpacing), min, max, preferred); 326 AddInsets(min, max, preferred); 327 } 328 329 // InvalidateLayout 330 void 331 BTwoDimensionalLayout::InvalidateLayout() 332 { 333 BLayout::InvalidateLayout(); 334 335 fLocalLayouter->InvalidateLayout(); 336 } 337 338 // LayoutView 339 void 340 BTwoDimensionalLayout::LayoutView() 341 { 342 _ValidateMinMax(); 343 344 // layout the horizontal/vertical elements 345 BSize size = SubtractInsets(View()->Frame().Size()); 346 #ifdef DEBUG_LAYOUT 347 printf("BTwoDimensionalLayout::LayoutView(%p): size: (%.1f, %.1f)\n", 348 View(), size.width, size.height); 349 #endif 350 351 fLocalLayouter->Layout(size); 352 353 // layout the items 354 int itemCount = CountItems(); 355 for (int i = 0; i < itemCount; i++) { 356 BLayoutItem* item = ItemAt(i); 357 if (item->IsVisible()) { 358 Dimensions itemDimensions; 359 GetItemDimensions(item, &itemDimensions); 360 BRect frame = fLocalLayouter->ItemFrame(itemDimensions); 361 frame.left += fLeftInset; 362 frame.top += fTopInset; 363 frame.right += fLeftInset; 364 frame.bottom += fTopInset; 365 { 366 #ifdef DEBUG_LAYOUT 367 printf(" frame for item %2d (view: %p): ", i, item->View()); 368 frame.PrintToStream(); 369 #endif 370 //BSize min(item->MinSize()); 371 //BSize max(item->MaxSize()); 372 //printf(" min: (%.1f, %.1f), max: (%.1f, %.1f)\n", min.width, min.height, 373 // max.width, max.height); 374 //if (item->HasHeightForWidth()) { 375 //float minHeight, maxHeight, preferredHeight; 376 //item->GetHeightForWidth(frame.Width(), &minHeight, &maxHeight, 377 // &preferredHeight); 378 //printf(" hfw: min: %.1f, max: %.1f, pref: %.1f\n", minHeight, maxHeight, 379 // preferredHeight); 380 //} 381 } 382 383 item->AlignInFrame(frame); 384 } 385 //else 386 //printf(" item %2d not visible", i); 387 } 388 } 389 390 // AddInsets 391 BSize 392 BTwoDimensionalLayout::AddInsets(BSize size) 393 { 394 size.width = BLayoutUtils::AddDistances(size.width, 395 fLeftInset + fRightInset - 1); 396 size.height = BLayoutUtils::AddDistances(size.height, 397 fTopInset + fBottomInset - 1); 398 return size; 399 } 400 401 // AddInsets 402 void 403 BTwoDimensionalLayout::AddInsets(float* minHeight, float* maxHeight, 404 float* preferredHeight) 405 { 406 float insets = fTopInset + fBottomInset - 1; 407 if (minHeight) 408 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets); 409 if (maxHeight) 410 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets); 411 if (preferredHeight) 412 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets); 413 } 414 415 // SubtractInsets 416 BSize 417 BTwoDimensionalLayout::SubtractInsets(BSize size) 418 { 419 size.width = BLayoutUtils::SubtractDistances(size.width, 420 fLeftInset + fRightInset - 1); 421 size.height = BLayoutUtils::SubtractDistances(size.height, 422 fTopInset + fBottomInset - 1); 423 return size; 424 } 425 426 // PrepareItems 427 void 428 BTwoDimensionalLayout::PrepareItems(enum orientation orientation) 429 { 430 } 431 432 // HasMultiColumnItems 433 bool 434 BTwoDimensionalLayout::HasMultiColumnItems() 435 { 436 return false; 437 } 438 439 // HasMultiRowItems 440 bool 441 BTwoDimensionalLayout::HasMultiRowItems() 442 { 443 return false; 444 } 445 446 // _ValidateMinMax 447 void 448 BTwoDimensionalLayout::_ValidateMinMax() 449 { 450 fLocalLayouter->ValidateMinMax(); 451 452 if (BView* view = View()) 453 view->ResetLayoutInvalidation(); 454 } 455 456 // _CurrentLayoutContext 457 BLayoutContext* 458 BTwoDimensionalLayout::_CurrentLayoutContext() 459 { 460 BView* view = View(); 461 return (view ? view->LayoutContext() : NULL); 462 } 463 464 465 // #pragma mark - CompoundLayouter 466 467 // constructor 468 BTwoDimensionalLayout::CompoundLayouter::CompoundLayouter( 469 enum orientation orientation) 470 : fLayouter(NULL), 471 fLayoutInfo(NULL), 472 fOrientation(orientation), 473 fLocalLayouters(10), 474 fLayoutContext(NULL), 475 fLastLayoutSize(-1) 476 { 477 } 478 479 // destructor 480 BTwoDimensionalLayout::CompoundLayouter::~CompoundLayouter() 481 { 482 } 483 484 // Orientation 485 orientation 486 BTwoDimensionalLayout::CompoundLayouter::Orientation() 487 { 488 return fOrientation; 489 } 490 491 // GetLayouter 492 Layouter* 493 BTwoDimensionalLayout::CompoundLayouter::GetLayouter(bool minMax) 494 { 495 return fLayouter; 496 } 497 498 // GetLayoutInfo 499 LayoutInfo* 500 BTwoDimensionalLayout::CompoundLayouter::GetLayoutInfo() 501 { 502 return fLayoutInfo; 503 } 504 505 // AddLocalLayouter 506 void 507 BTwoDimensionalLayout::CompoundLayouter::AddLocalLayouter( 508 LocalLayouter* localLayouter) 509 { 510 if (localLayouter) { 511 if (!fLocalLayouters.HasItem(localLayouter)) { 512 fLocalLayouters.AddItem(localLayouter); 513 InvalidateLayout(); 514 } 515 } 516 } 517 518 // RemoveLocalLayouter 519 void 520 BTwoDimensionalLayout::CompoundLayouter::RemoveLocalLayouter( 521 LocalLayouter* localLayouter) 522 { 523 if (fLocalLayouters.RemoveItem(localLayouter)) 524 InvalidateLayout(); 525 } 526 527 // AbsorbCompoundLayouter 528 void 529 BTwoDimensionalLayout::CompoundLayouter::AbsorbCompoundLayouter( 530 CompoundLayouter* other) 531 { 532 if (other == this) 533 return; 534 535 int32 count = other->fLocalLayouters.CountItems(); 536 for (int32 i = 0; i < count; i++) { 537 LocalLayouter* layouter 538 = (LocalLayouter*)other->fLocalLayouters.ItemAt(i); 539 AddLocalLayouter(layouter); 540 layouter->SetCompoundLayouter(this, fOrientation); 541 } 542 543 InvalidateLayout(); 544 } 545 546 // InvalidateLayout 547 void 548 BTwoDimensionalLayout::CompoundLayouter::InvalidateLayout() 549 { 550 if (!fLayouter) 551 return; 552 553 delete fLayouter; 554 delete fLayoutInfo; 555 556 fLayouter = NULL; 557 fLayoutInfo = NULL; 558 fLayoutContext = NULL; 559 560 // notify all local layouters to invalidate the respective views 561 int32 count = fLocalLayouters.CountItems(); 562 for (int32 i = 0; i < count; i++) { 563 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 564 layouter->InternalInvalidateLayout(this); 565 } 566 } 567 568 // IsMinMaxValid 569 bool 570 BTwoDimensionalLayout::CompoundLayouter::IsMinMaxValid() 571 { 572 return (fLayouter != NULL); 573 } 574 575 // ValidateMinMax 576 void 577 BTwoDimensionalLayout::CompoundLayouter::ValidateMinMax() 578 { 579 if (IsMinMaxValid()) 580 return; 581 582 fLastLayoutSize = -1; 583 584 // create the layouter 585 _PrepareItems(); 586 587 int elementCount = _CountElements(); 588 589 if (elementCount <= 1) 590 fLayouter = new OneElementLayouter(); 591 else if (_HasMultiElementItems()) 592 fLayouter = new ComplexLayouter(elementCount, _Spacing()); 593 else 594 fLayouter = new SimpleLayouter(elementCount, _Spacing()); 595 596 // tell the layouter about our constraints 597 // TODO: We should probably ignore local layouters whose view is hidden. It's a bit tricky to find 598 // out, whether the view is hidden, though, since this doesn't necessarily mean only hidden 599 // relative to the parent, but hidden relative to a common parent. 600 _AddConstraints(fLayouter); 601 602 fLayoutInfo = fLayouter->CreateLayoutInfo(); 603 } 604 605 // Layout 606 void 607 BTwoDimensionalLayout::CompoundLayouter::Layout(float size, 608 LocalLayouter* localLayouter, BLayoutContext* context) 609 { 610 ValidateMinMax(); 611 612 if (context != fLayoutContext || fLastLayoutSize != size) { 613 DoLayout(size, localLayouter, context); 614 fLayoutContext = context; 615 fLastLayoutSize = size; 616 } 617 } 618 619 // DoLayout 620 void 621 BTwoDimensionalLayout::CompoundLayouter::DoLayout(float size, 622 LocalLayouter* localLayouter, BLayoutContext* context) 623 { 624 fLayouter->Layout(fLayoutInfo, size); 625 } 626 627 // _PrepareItems 628 void 629 BTwoDimensionalLayout::CompoundLayouter::_PrepareItems() 630 { 631 int32 count = fLocalLayouters.CountItems(); 632 for (int32 i = 0; i < count; i++) { 633 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 634 layouter->PrepareItems(this); 635 } 636 } 637 638 // _CountElements 639 int32 640 BTwoDimensionalLayout::CompoundLayouter::_CountElements() 641 { 642 int32 elementCount = 0; 643 int32 count = fLocalLayouters.CountItems(); 644 for (int32 i = 0; i < count; i++) { 645 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 646 int32 layouterCount = layouter->CountElements(this); 647 elementCount = max_c(elementCount, layouterCount); 648 } 649 650 return elementCount; 651 } 652 653 // _HasMultiElementItems 654 bool 655 BTwoDimensionalLayout::CompoundLayouter::_HasMultiElementItems() 656 { 657 int32 count = fLocalLayouters.CountItems(); 658 for (int32 i = 0; i < count; i++) { 659 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 660 if (layouter->HasMultiElementItems(this)) 661 return true; 662 } 663 664 return false; 665 } 666 667 // _AddConstraints 668 void 669 BTwoDimensionalLayout::CompoundLayouter::_AddConstraints(Layouter* layouter) 670 { 671 int32 count = fLocalLayouters.CountItems(); 672 for (int32 i = 0; i < count; i++) { 673 LocalLayouter* localLayouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 674 localLayouter->AddConstraints(this, layouter); 675 } 676 } 677 678 // _Spacing 679 float 680 BTwoDimensionalLayout::CompoundLayouter::_Spacing() 681 { 682 if (!fLocalLayouters.IsEmpty()) 683 return ((LocalLayouter*)fLocalLayouters.ItemAt(0))->Spacing(this); 684 return 0; 685 } 686 687 688 // #pragma mark - VerticalCompoundLayouter 689 690 691 // constructor 692 BTwoDimensionalLayout::VerticalCompoundLayouter::VerticalCompoundLayouter() 693 : CompoundLayouter(B_VERTICAL), 694 fHeightForWidthLayouter(NULL), 695 fCachedMinHeightForWidth(0), 696 fCachedMaxHeightForWidth(0), 697 fCachedPreferredHeightForWidth(0), 698 fHeightForWidthLayoutContext(NULL) 699 { 700 } 701 702 // GetLayouter 703 Layouter* 704 BTwoDimensionalLayout::VerticalCompoundLayouter::GetLayouter(bool minMax) 705 { 706 return (minMax || !_HasHeightForWidth() 707 ? fLayouter : fHeightForWidthLayouter); 708 } 709 710 // InvalidateLayout 711 void 712 BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateLayout() 713 { 714 CompoundLayouter::InvalidateLayout(); 715 716 InvalidateHeightForWidth(); 717 } 718 719 // InvalidateHeightForWidth 720 void 721 BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateHeightForWidth() 722 { 723 if (fHeightForWidthLayouter != NULL) { 724 delete fHeightForWidthLayouter; 725 fHeightForWidthLayouter = NULL; 726 727 // also make sure we're not reusing the old layout info 728 fLastLayoutSize = -1; 729 730 int32 count = fLocalLayouters.CountItems(); 731 for (int32 i = 0; i < count; i++) { 732 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 733 layouter->SetHeightForWidthConstraintsAdded(false); 734 } 735 } 736 } 737 738 // InternalGetHeightForWidth 739 void 740 BTwoDimensionalLayout::VerticalCompoundLayouter::InternalGetHeightForWidth( 741 LocalLayouter* localLayouter, BLayoutContext* context, bool realLayout, 742 float* minHeight, float* maxHeight, float* preferredHeight) 743 { 744 bool updateCachedInfo = false; 745 746 if (_SetHeightForWidthLayoutContext(context) 747 || fHeightForWidthLayouter == NULL) { 748 // Either the layout context changed or we haven't initialized the 749 // height for width layouter yet. We create it and init it now. 750 751 // clone the vertical layouter 752 delete fHeightForWidthLayouter; 753 delete fLayoutInfo; 754 fHeightForWidthLayouter = fLayouter->CloneLayouter(); 755 fLayoutInfo = fHeightForWidthLayouter->CreateLayoutInfo(); 756 757 // add the children's height for width constraints 758 int32 count = fLocalLayouters.CountItems(); 759 for (int32 i = 0; i < count; i++) { 760 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 761 if (layouter->HasHeightForWidth()) { 762 layouter->AddHeightForWidthConstraints(this, 763 fHeightForWidthLayouter, context); 764 } 765 } 766 767 updateCachedInfo = true; 768 769 // get the height for width info 770 fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize(); 771 fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize(); 772 fCachedPreferredHeightForWidth 773 = fHeightForWidthLayouter->PreferredSize(); 774 775 } else if (localLayouter->HasHeightForWidth()) { 776 // There is a height for width layouter and it has been initialized 777 // in the current layout context. So we just add the height for width 778 // constraints of the calling local layouter, if they haven't been 779 // added yet. 780 updateCachedInfo = localLayouter->AddHeightForWidthConstraints(this, 781 fHeightForWidthLayouter, context); 782 } 783 784 // update cached height for width info, if something changed 785 if (updateCachedInfo) { 786 // get the height for width info 787 fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize(); 788 fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize(); 789 fCachedPreferredHeightForWidth 790 = fHeightForWidthLayouter->PreferredSize(); 791 } 792 793 if (minHeight) 794 *minHeight = fCachedMinHeightForWidth; 795 if (maxHeight) 796 *maxHeight = fCachedMaxHeightForWidth; 797 if (preferredHeight) 798 *preferredHeight = fCachedPreferredHeightForWidth; 799 } 800 801 // DoLayout 802 void 803 BTwoDimensionalLayout::VerticalCompoundLayouter::DoLayout(float size, 804 LocalLayouter* localLayouter, BLayoutContext* context) 805 { 806 Layouter* layouter; 807 if (_HasHeightForWidth()) { 808 float minHeight, maxHeight, preferredHeight; 809 InternalGetHeightForWidth(localLayouter, context, true, &minHeight, 810 &maxHeight, &preferredHeight); 811 size = max_c(size, minHeight); 812 layouter = fHeightForWidthLayouter; 813 } else 814 layouter = fLayouter; 815 816 layouter->Layout(fLayoutInfo, size); 817 } 818 819 // _HasHeightForWidth 820 bool 821 BTwoDimensionalLayout::VerticalCompoundLayouter::_HasHeightForWidth() 822 { 823 int32 count = fLocalLayouters.CountItems(); 824 for (int32 i = 0; i < count; i++) { 825 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 826 if (layouter->HasHeightForWidth()) 827 return true; 828 } 829 830 return false; 831 } 832 833 // _SetHeightForWidthLayoutContext 834 bool 835 BTwoDimensionalLayout::VerticalCompoundLayouter 836 ::_SetHeightForWidthLayoutContext(BLayoutContext* context) 837 { 838 if (context == fHeightForWidthLayoutContext) 839 return false; 840 841 if (fHeightForWidthLayoutContext != NULL) { 842 fHeightForWidthLayoutContext->RemoveListener(this); 843 fHeightForWidthLayoutContext = NULL; 844 } 845 846 // We can ignore the whole context business, if we have no more than one 847 // local layouter. We use the layout context only to recognize when calls 848 // of different local layouters belong to the same context. 849 if (fLocalLayouters.CountItems() <= 1) 850 return false; 851 852 fHeightForWidthLayoutContext = context; 853 854 if (fHeightForWidthLayoutContext != NULL) 855 fHeightForWidthLayoutContext->AddListener(this); 856 857 InvalidateHeightForWidth(); 858 859 return true; 860 } 861 862 // LayoutContextLeft 863 void 864 BTwoDimensionalLayout::VerticalCompoundLayouter::LayoutContextLeft( 865 BLayoutContext* context) 866 { 867 fHeightForWidthLayoutContext = NULL; 868 } 869 870 871 // #pragma mark - LocalLayouter 872 873 874 // constructor 875 BTwoDimensionalLayout::LocalLayouter::LocalLayouter( 876 BTwoDimensionalLayout* layout) 877 : fLayout(layout), 878 fHLayouter(new CompoundLayouter(B_HORIZONTAL)), 879 fVLayouter(new VerticalCompoundLayouter), 880 fHeightForWidthItems(), 881 fHorizontalLayoutContext(NULL), 882 fHorizontalLayoutWidth(0), 883 fHeightForWidthConstraintsAdded(false) 884 { 885 fHLayouter->AddLocalLayouter(this); 886 fVLayouter->AddLocalLayouter(this); 887 } 888 889 // MinSize 890 BSize 891 BTwoDimensionalLayout::LocalLayouter::MinSize() 892 { 893 return BSize(fHLayouter->GetLayouter(true)->MinSize(), 894 fVLayouter->GetLayouter(true)->MinSize()); 895 } 896 897 // MaxSize 898 BSize 899 BTwoDimensionalLayout::LocalLayouter::MaxSize() 900 { 901 return BSize(fHLayouter->GetLayouter(true)->MaxSize(), 902 fVLayouter->GetLayouter(true)->MaxSize()); 903 } 904 905 // PreferredSize 906 BSize 907 BTwoDimensionalLayout::LocalLayouter::PreferredSize() 908 { 909 return BSize(fHLayouter->GetLayouter(true)->PreferredSize(), 910 fVLayouter->GetLayouter(true)->PreferredSize()); 911 } 912 913 // InvalidateLayout 914 void 915 BTwoDimensionalLayout::LocalLayouter::InvalidateLayout() 916 { 917 fHLayouter->InvalidateLayout(); 918 fVLayouter->InvalidateLayout(); 919 } 920 921 // Layout 922 void 923 BTwoDimensionalLayout::LocalLayouter::Layout(BSize size) 924 { 925 DoHorizontalLayout(size.width); 926 fVLayouter->Layout(size.height, this, fLayout->_CurrentLayoutContext()); 927 } 928 929 // ItemFrame 930 BRect 931 BTwoDimensionalLayout::LocalLayouter::ItemFrame(Dimensions itemDimensions) 932 { 933 LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo(); 934 LayoutInfo* vLayoutInfo = fVLayouter->GetLayoutInfo(); 935 float x = hLayoutInfo->ElementLocation(itemDimensions.x); 936 float y = vLayoutInfo->ElementLocation(itemDimensions.y); 937 float width = hLayoutInfo->ElementRangeSize(itemDimensions.x, 938 itemDimensions.width); 939 float height = vLayoutInfo->ElementRangeSize(itemDimensions.y, 940 itemDimensions.height); 941 return BRect(x, y, x + width, y + height); 942 } 943 944 // ValidateMinMax 945 void 946 BTwoDimensionalLayout::LocalLayouter::ValidateMinMax() 947 { 948 if (fHLayouter->IsMinMaxValid() && fVLayouter->IsMinMaxValid()) 949 return; 950 951 if (!fHLayouter->IsMinMaxValid()) 952 fHeightForWidthItems.MakeEmpty(); 953 954 _SetHorizontalLayoutContext(NULL, -1); 955 956 fHLayouter->ValidateMinMax(); 957 fVLayouter->ValidateMinMax(); 958 } 959 960 // DoHorizontalLayout 961 void 962 BTwoDimensionalLayout::LocalLayouter::DoHorizontalLayout(float width) 963 { 964 BLayoutContext* context = fLayout->_CurrentLayoutContext(); 965 if (fHorizontalLayoutContext != context 966 || width != fHorizontalLayoutWidth) { 967 _SetHorizontalLayoutContext(context, width); 968 fHLayouter->Layout(width, this, context); 969 fVLayouter->InvalidateHeightForWidth(); 970 } 971 } 972 973 // InternalGetHeightForWidth 974 void 975 BTwoDimensionalLayout::LocalLayouter::InternalGetHeightForWidth(float width, 976 float* minHeight, float* maxHeight, float* preferredHeight) 977 { 978 DoHorizontalLayout(width); 979 fVLayouter->InternalGetHeightForWidth(this, fHorizontalLayoutContext, false, 980 minHeight, maxHeight, preferredHeight); 981 } 982 983 // AlignWith 984 void 985 BTwoDimensionalLayout::LocalLayouter::AlignWith(LocalLayouter* other, 986 enum orientation orientation) 987 { 988 if (orientation == B_HORIZONTAL) 989 other->fHLayouter->AbsorbCompoundLayouter(fHLayouter); 990 else 991 other->fVLayouter->AbsorbCompoundLayouter(fVLayouter); 992 } 993 994 // PrepareItems 995 void 996 BTwoDimensionalLayout::LocalLayouter::PrepareItems( 997 CompoundLayouter* compoundLayouter) 998 { 999 fLayout->PrepareItems(compoundLayouter->Orientation()); 1000 } 1001 1002 // CountElements 1003 int32 1004 BTwoDimensionalLayout::LocalLayouter::CountElements( 1005 CompoundLayouter* compoundLayouter) 1006 { 1007 if (compoundLayouter->Orientation() == B_HORIZONTAL) 1008 return fLayout->InternalCountColumns(); 1009 else 1010 return fLayout->InternalCountRows(); 1011 } 1012 1013 // HasMultiElementItems 1014 bool 1015 BTwoDimensionalLayout::LocalLayouter::HasMultiElementItems( 1016 CompoundLayouter* compoundLayouter) 1017 { 1018 if (compoundLayouter->Orientation() == B_HORIZONTAL) 1019 return fLayout->HasMultiColumnItems(); 1020 else 1021 return fLayout->HasMultiRowItems(); 1022 } 1023 1024 // AddConstraints 1025 void 1026 BTwoDimensionalLayout::LocalLayouter::AddConstraints( 1027 CompoundLayouter* compoundLayouter, Layouter* layouter) 1028 { 1029 enum orientation orientation = compoundLayouter->Orientation(); 1030 int itemCount = fLayout->CountItems(); 1031 if (itemCount > 0) { 1032 for (int i = 0; i < itemCount; i++) { 1033 BLayoutItem* item = fLayout->ItemAt(i); 1034 if (item->IsVisible()) { 1035 Dimensions itemDimensions; 1036 fLayout->GetItemDimensions(item, &itemDimensions); 1037 1038 BSize min = item->MinSize(); 1039 BSize max = item->MaxSize(); 1040 BSize preferred = item->PreferredSize(); 1041 1042 if (orientation == B_HORIZONTAL) { 1043 layouter->AddConstraints( 1044 itemDimensions.x, 1045 itemDimensions.width, 1046 min.width, 1047 max.width, 1048 preferred.width); 1049 1050 if (item->HasHeightForWidth()) 1051 fHeightForWidthItems.AddItem(item); 1052 1053 } else { 1054 layouter->AddConstraints( 1055 itemDimensions.y, 1056 itemDimensions.height, 1057 min.height, 1058 max.height, 1059 preferred.height); 1060 } 1061 } 1062 } 1063 1064 // add column/row constraints 1065 ColumnRowConstraints constraints; 1066 int elementCount = CountElements(compoundLayouter); 1067 for (int element = 0; element < elementCount; element++) { 1068 fLayout->GetColumnRowConstraints(orientation, element, 1069 &constraints); 1070 layouter->SetWeight(element, constraints.weight); 1071 layouter->AddConstraints(element, 1, constraints.min, 1072 constraints.max, constraints.min); 1073 } 1074 } 1075 } 1076 1077 // Spacing 1078 float 1079 BTwoDimensionalLayout::LocalLayouter::Spacing( 1080 CompoundLayouter* compoundLayouter) 1081 { 1082 return (compoundLayouter->Orientation() == B_HORIZONTAL 1083 ? fLayout->fHSpacing : fLayout->fVSpacing); 1084 } 1085 1086 // HasHeightForWidth 1087 bool 1088 BTwoDimensionalLayout::LocalLayouter::HasHeightForWidth() 1089 { 1090 return !fHeightForWidthItems.IsEmpty(); 1091 } 1092 1093 // AddHeightForWidthConstraints 1094 bool 1095 BTwoDimensionalLayout::LocalLayouter::AddHeightForWidthConstraints( 1096 VerticalCompoundLayouter* compoundLayouter, Layouter* layouter, 1097 BLayoutContext* context) 1098 { 1099 if (context != fHorizontalLayoutContext) 1100 return false; 1101 1102 if (fHeightForWidthConstraintsAdded) 1103 return false; 1104 1105 LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo(); 1106 1107 // add the children's height for width constraints 1108 int32 itemCount = fHeightForWidthItems.CountItems(); 1109 for (int32 i = 0; i < itemCount; i++) { 1110 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i); 1111 Dimensions itemDimensions; 1112 fLayout->GetItemDimensions(item, &itemDimensions); 1113 1114 float minHeight, maxHeight, preferredHeight; 1115 item->GetHeightForWidth( 1116 hLayoutInfo->ElementRangeSize(itemDimensions.x, 1117 itemDimensions.width), 1118 &minHeight, &maxHeight, &preferredHeight); 1119 layouter->AddConstraints( 1120 itemDimensions.y, 1121 itemDimensions.height, 1122 minHeight, 1123 maxHeight, 1124 preferredHeight); 1125 } 1126 1127 SetHeightForWidthConstraintsAdded(true); 1128 1129 return true; 1130 } 1131 1132 // SetHeightForWidthConstraintsAdded 1133 void 1134 BTwoDimensionalLayout::LocalLayouter::SetHeightForWidthConstraintsAdded( 1135 bool added) 1136 { 1137 fHeightForWidthConstraintsAdded = added; 1138 } 1139 1140 // SetCompoundLayouter 1141 void 1142 BTwoDimensionalLayout::LocalLayouter::SetCompoundLayouter( 1143 CompoundLayouter* compoundLayouter, enum orientation orientation) 1144 { 1145 if (orientation == B_HORIZONTAL) 1146 fHLayouter = compoundLayouter; 1147 else 1148 fVLayouter = (VerticalCompoundLayouter*)compoundLayouter; 1149 1150 InternalInvalidateLayout(compoundLayouter); 1151 } 1152 1153 // InternalInvalidateLayout 1154 void 1155 BTwoDimensionalLayout::LocalLayouter::InternalInvalidateLayout( 1156 CompoundLayouter* compoundLayouter) 1157 { 1158 _SetHorizontalLayoutContext(NULL, -1); 1159 1160 fLayout->BLayout::InvalidateLayout(); 1161 } 1162 1163 // _SetHorizontalLayoutContext 1164 void 1165 BTwoDimensionalLayout::LocalLayouter::_SetHorizontalLayoutContext( 1166 BLayoutContext* context, float width) 1167 { 1168 if (context != fHorizontalLayoutContext) { 1169 if (fHorizontalLayoutContext != NULL) 1170 fHorizontalLayoutContext->RemoveListener(this); 1171 1172 fHorizontalLayoutContext = context; 1173 1174 if (fHorizontalLayoutContext != NULL) 1175 fHorizontalLayoutContext->AddListener(this); 1176 } 1177 1178 fHorizontalLayoutWidth = width; 1179 } 1180 1181 // LayoutContextLeft 1182 void 1183 BTwoDimensionalLayout::LocalLayouter::LayoutContextLeft(BLayoutContext* context) 1184 { 1185 fHorizontalLayoutContext = NULL; 1186 fHorizontalLayoutWidth = -1; 1187 } 1188