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