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