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 <SplitLayout.h> 7 8 #include <stdio.h> 9 10 #include <LayoutItem.h> 11 #include <LayoutUtils.h> 12 #include <View.h> 13 14 #include "OneElementLayouter.h" 15 #include "SimpleLayouter.h" 16 17 // ItemLayoutInfo 18 class BSplitLayout::ItemLayoutInfo { 19 public: 20 float weight; 21 BRect layoutFrame; 22 BSize min; 23 BSize max; 24 bool isVisible; 25 bool isCollapsible; 26 27 ItemLayoutInfo() 28 : weight(1.0f), 29 layoutFrame(0, 0, -1, -1), 30 min(), 31 max(), 32 isVisible(true), 33 isCollapsible(true) 34 { 35 } 36 }; 37 38 // ValueRange 39 class BSplitLayout::ValueRange { 40 public: 41 int32 sumValue; // including spacing 42 int32 previousMin; 43 int32 previousMax; 44 int32 previousSize; 45 int32 nextMin; 46 int32 nextMax; 47 int32 nextSize; 48 }; 49 50 // SplitterItem 51 class BSplitLayout::SplitterItem : public BLayoutItem { 52 public: 53 SplitterItem(BSplitLayout* layout) 54 : fLayout(layout), 55 fFrame() 56 { 57 } 58 59 virtual BSize MinSize() 60 { 61 if (fLayout->Orientation() == B_HORIZONTAL) 62 return BSize(fLayout->SplitterSize() - 1, -1); 63 else 64 return BSize(-1, fLayout->SplitterSize() - 1); 65 } 66 67 virtual BSize MaxSize() 68 { 69 if (fLayout->Orientation() == B_HORIZONTAL) 70 return BSize(fLayout->SplitterSize() - 1, B_SIZE_UNLIMITED); 71 else 72 return BSize(B_SIZE_UNLIMITED, fLayout->SplitterSize() - 1); 73 } 74 75 virtual BSize PreferredSize() 76 { 77 return MinSize(); 78 } 79 80 virtual BAlignment Alignment() 81 { 82 return BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER); 83 } 84 85 virtual void SetExplicitMinSize(BSize size) 86 { 87 // not allowed 88 } 89 90 virtual void SetExplicitMaxSize(BSize size) 91 { 92 // not allowed 93 } 94 95 virtual void SetExplicitPreferredSize(BSize size) 96 { 97 // not allowed 98 } 99 100 virtual void SetExplicitAlignment(BAlignment alignment) 101 { 102 // not allowed 103 } 104 105 virtual bool IsVisible() 106 { 107 return true; 108 } 109 110 virtual void SetVisible(bool visible) 111 { 112 // not allowed 113 } 114 115 116 virtual BRect Frame() 117 { 118 return fFrame; 119 } 120 121 virtual void SetFrame(BRect frame) 122 { 123 fFrame = frame; 124 } 125 126 private: 127 BSplitLayout* fLayout; 128 BRect fFrame; 129 }; 130 131 132 // #pragma mark - 133 134 135 // constructor 136 BSplitLayout::BSplitLayout(enum orientation orientation, 137 float spacing) 138 : fOrientation(orientation), 139 fLeftInset(0), 140 fRightInset(0), 141 fTopInset(0), 142 fBottomInset(0), 143 fSplitterSize(3), 144 fSpacing(spacing), 145 146 fSplitterItems(), 147 fVisibleItems(), 148 fMin(), 149 fMax(), 150 fPreferred(), 151 152 fHorizontalLayouter(NULL), 153 fVerticalLayouter(NULL), 154 fHorizontalLayoutInfo(NULL), 155 fVerticalLayoutInfo(NULL), 156 157 fHeightForWidthItems(), 158 fHeightForWidthVerticalLayouter(NULL), 159 fHeightForWidthHorizontalLayoutInfo(NULL), 160 161 fLayoutValid(false), 162 163 fCachedHeightForWidthWidth(-2), 164 fHeightForWidthVerticalLayouterWidth(-2), 165 fCachedMinHeightForWidth(-1), 166 fCachedMaxHeightForWidth(-1), 167 fCachedPreferredHeightForWidth(-1), 168 169 fDraggingStartPoint(), 170 fDraggingStartValue(0), 171 fDraggingCurrentValue(0), 172 fDraggingSplitterIndex(-1) 173 { 174 } 175 176 // destructor 177 BSplitLayout::~BSplitLayout() 178 { 179 } 180 181 // SetInsets 182 void 183 BSplitLayout::SetInsets(float left, float top, float right, float bottom) 184 { 185 fLeftInset = left; 186 fTopInset = top; 187 fRightInset = right; 188 fBottomInset = bottom; 189 190 InvalidateLayout(); 191 } 192 193 // GetInsets 194 void 195 BSplitLayout::GetInsets(float* left, float* top, float* right, float* bottom) 196 { 197 if (left) 198 *left = fLeftInset; 199 if (top) 200 *top = fTopInset; 201 if (right) 202 *right = fRightInset; 203 if (bottom) 204 *bottom = fBottomInset; 205 } 206 207 // Spacing 208 float 209 BSplitLayout::Spacing() const 210 { 211 return fSpacing; 212 } 213 214 // SetSpacing 215 void 216 BSplitLayout::SetSpacing(float spacing) 217 { 218 if (spacing != fSpacing) { 219 fSpacing = spacing; 220 221 InvalidateLayout(); 222 } 223 } 224 225 // Orientation 226 orientation 227 BSplitLayout::Orientation() const 228 { 229 return fOrientation; 230 } 231 232 // SetOrientation 233 void 234 BSplitLayout::SetOrientation(enum orientation orientation) 235 { 236 if (orientation != fOrientation) { 237 fOrientation = orientation; 238 239 InvalidateLayout(); 240 } 241 } 242 243 // SplitterSize 244 float 245 BSplitLayout::SplitterSize() const 246 { 247 return fSplitterSize; 248 } 249 250 // SetSplitterSize 251 void 252 BSplitLayout::SetSplitterSize(float size) 253 { 254 if (size != fSplitterSize) { 255 fSplitterSize = size; 256 257 InvalidateLayout(); 258 } 259 } 260 261 // AddView 262 BLayoutItem* 263 BSplitLayout::AddView(BView* child) 264 { 265 return BLayout::AddView(child); 266 } 267 268 // AddView 269 BLayoutItem* 270 BSplitLayout::AddView(int32 index, BView* child) 271 { 272 return BLayout::AddView(index, child); 273 } 274 275 // AddView 276 BLayoutItem* 277 BSplitLayout::AddView(BView* child, float weight) 278 { 279 return AddView(-1, child, weight); 280 } 281 282 // AddView 283 BLayoutItem* 284 BSplitLayout::AddView(int32 index, BView* child, float weight) 285 { 286 BLayoutItem* item = AddView(index, child); 287 if (item) 288 SetItemWeight(item, weight); 289 290 return item; 291 } 292 293 // AddItem 294 bool 295 BSplitLayout::AddItem(BLayoutItem* item) 296 { 297 return BLayout::AddItem(item); 298 } 299 300 // AddItem 301 bool 302 BSplitLayout::AddItem(int32 index, BLayoutItem* item) 303 { 304 return BLayout::AddItem(index, item); 305 } 306 307 // AddItem 308 bool 309 BSplitLayout::AddItem(BLayoutItem* item, float weight) 310 { 311 return AddItem(-1, item, weight); 312 } 313 314 // AddItem 315 bool 316 BSplitLayout::AddItem(int32 index, BLayoutItem* item, float weight) 317 { 318 bool success = AddItem(index, item); 319 if (success) 320 SetItemWeight(item, weight); 321 322 return success; 323 } 324 325 // ItemWeight 326 float 327 BSplitLayout::ItemWeight(int32 index) const 328 { 329 if (index < 0 || index >= CountItems()) 330 return 0; 331 332 return ItemWeight(ItemAt(index)); 333 } 334 335 // ItemWeight 336 float 337 BSplitLayout::ItemWeight(BLayoutItem* item) const 338 { 339 if (ItemLayoutInfo* info = _ItemLayoutInfo(item)) 340 return info->weight; 341 return 0; 342 } 343 344 // SetItemWeight 345 void 346 BSplitLayout::SetItemWeight(int32 index, float weight, bool invalidateLayout) 347 { 348 if (index < 0 || index >= CountItems()) 349 return; 350 351 BLayoutItem* item = ItemAt(index); 352 SetItemWeight(item, weight); 353 354 if (fHorizontalLayouter) { 355 int32 visibleIndex = fVisibleItems.IndexOf(item); 356 if (visibleIndex >= 0) { 357 if (fOrientation == B_HORIZONTAL) 358 fHorizontalLayouter->SetWeight(visibleIndex, weight); 359 else 360 fVerticalLayouter->SetWeight(visibleIndex, weight); 361 } 362 } 363 364 if (invalidateLayout) 365 InvalidateLayout(); 366 } 367 368 // SetItemWeight 369 void 370 BSplitLayout::SetItemWeight(BLayoutItem* item, float weight) 371 { 372 if (ItemLayoutInfo* info = _ItemLayoutInfo(item)) 373 info->weight = weight; 374 } 375 376 // SetCollapsible 377 void 378 BSplitLayout::SetCollapsible(bool collapsible) 379 { 380 SetCollapsible(0, CountItems() - 1, collapsible); 381 } 382 383 // SetCollapsible 384 void 385 BSplitLayout::SetCollapsible(int32 index, bool collapsible) 386 { 387 SetCollapsible(index, index, collapsible); 388 } 389 390 // SetCollapsible 391 void 392 BSplitLayout::SetCollapsible(int32 first, int32 last, bool collapsible) 393 { 394 if (first < 0) 395 first = 0; 396 if (last < 0 || last > CountItems()) 397 last = CountItems() - 1; 398 399 for (int32 i = first; i <= last; i++) 400 _ItemLayoutInfo(ItemAt(i))->isCollapsible = collapsible; 401 } 402 403 // MinSize 404 BSize 405 BSplitLayout::MinSize() 406 { 407 _ValidateMinMax(); 408 409 return _AddInsets(fMin); 410 } 411 412 // MaxSize 413 BSize 414 BSplitLayout::MaxSize() 415 { 416 _ValidateMinMax(); 417 418 return _AddInsets(fMax); 419 } 420 421 // PreferredSize 422 BSize 423 BSplitLayout::PreferredSize() 424 { 425 _ValidateMinMax(); 426 427 return _AddInsets(fPreferred); 428 } 429 430 // Alignment 431 BAlignment 432 BSplitLayout::Alignment() 433 { 434 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 435 } 436 437 // HasHeightForWidth 438 bool 439 BSplitLayout::HasHeightForWidth() 440 { 441 _ValidateMinMax(); 442 443 return !fHeightForWidthItems.IsEmpty(); 444 } 445 446 // GetHeightForWidth 447 void 448 BSplitLayout::GetHeightForWidth(float width, float* min, float* max, 449 float* preferred) 450 { 451 if (!HasHeightForWidth()) 452 return; 453 454 float innerWidth = _SubtractInsets(BSize(width, 0)).width; 455 _InternalGetHeightForWidth(innerWidth, false, min, max, preferred); 456 _AddInsets(min, max, preferred); 457 } 458 459 // InvalidateLayout 460 void 461 BSplitLayout::InvalidateLayout() 462 { 463 _InvalidateLayout(true); 464 } 465 466 // LayoutView 467 void 468 BSplitLayout::LayoutView() 469 { 470 _ValidateMinMax(); 471 472 // layout the elements 473 BSize size = _SubtractInsets(BSize(View()->Bounds())); 474 fHorizontalLayouter->Layout(fHorizontalLayoutInfo, size.width); 475 476 Layouter* verticalLayouter; 477 if (HasHeightForWidth()) { 478 float minHeight, maxHeight, preferredHeight; 479 _InternalGetHeightForWidth(size.width, true, &minHeight, &maxHeight, 480 &preferredHeight); 481 size.height = max_c(size.height, minHeight); 482 verticalLayouter = fHeightForWidthVerticalLayouter; 483 } else 484 verticalLayouter = fVerticalLayouter; 485 486 verticalLayouter->Layout(fVerticalLayoutInfo, size.height); 487 488 float xOffset = fLeftInset; 489 float yOffset = fTopInset; 490 float splitterWidth = 0; // pixel counts, no distances 491 float splitterHeight = 0; // 492 float xSpacing = 0; 493 float ySpacing = 0; 494 if (fOrientation == B_HORIZONTAL) { 495 splitterWidth = fSplitterSize; 496 splitterHeight = size.height + 1; 497 xSpacing = fSpacing; 498 } else { 499 splitterWidth = size.width + 1; 500 splitterHeight = fSplitterSize; 501 ySpacing = fSpacing; 502 } 503 504 int itemCount = CountItems(); 505 for (int i = 0; i < itemCount; i++) { 506 // layout the splitter 507 if (i > 0) { 508 SplitterItem* splitterItem = _SplitterItemAt(i - 1); 509 510 _LayoutItem(splitterItem, BRect(xOffset, yOffset, 511 xOffset + splitterWidth - 1, yOffset + splitterHeight - 1), 512 true); 513 514 if (fOrientation == B_HORIZONTAL) 515 xOffset += splitterWidth + xSpacing; 516 else 517 yOffset += splitterHeight + ySpacing; 518 } 519 520 // layout the item 521 BLayoutItem* item = ItemAt(i); 522 int32 visibleIndex = fVisibleItems.IndexOf(item); 523 if (visibleIndex < 0) { 524 _LayoutItem(item, BRect(), false); 525 continue; 526 } 527 528 // get the dimensions of the item 529 float width = fHorizontalLayoutInfo->ElementSize(visibleIndex); 530 float height = fVerticalLayoutInfo->ElementSize(visibleIndex); 531 532 // place the component 533 _LayoutItem(item, BRect(xOffset, yOffset, xOffset + width, 534 yOffset + height), true); 535 536 if (fOrientation == B_HORIZONTAL) 537 xOffset += width + xSpacing + 1; 538 else 539 yOffset += height + ySpacing + 1; 540 } 541 542 fLayoutValid = true; 543 } 544 545 // SplitterItemFrame 546 BRect 547 BSplitLayout::SplitterItemFrame(int32 index) const 548 { 549 if (SplitterItem* item = _SplitterItemAt(index)) 550 return item->Frame(); 551 return BRect(); 552 } 553 554 // StartDraggingSplitter 555 bool 556 BSplitLayout::StartDraggingSplitter(BPoint point) 557 { 558 StopDraggingSplitter(); 559 560 // Layout must be valid. Bail out, if it isn't. 561 if (!fLayoutValid) 562 return false; 563 564 // Things shouldn't be draggable, if we have a >= max layout. 565 BSize size = _SubtractInsets(BSize(View()->Frame())); 566 if (fOrientation == B_HORIZONTAL && size.width >= fMax.width 567 || fOrientation == B_VERTICAL && size.height >= fMax.height) { 568 return false; 569 } 570 571 int32 splitterCount = fSplitterItems.CountItems(); 572 for (int32 i = 0; i < splitterCount; i++) { 573 SplitterItem* splitItem = _SplitterItemAt(i); 574 BRect frame = splitItem->Frame(); 575 if (frame.Contains(point)) { 576 fDraggingStartPoint = View()->ConvertToScreen(point); 577 fDraggingStartValue = _SplitterValue(i); 578 fDraggingCurrentValue = fDraggingStartValue; 579 fDraggingSplitterIndex = i; 580 581 return true; 582 } 583 } 584 585 return false; 586 } 587 588 // DragSplitter 589 bool 590 BSplitLayout::DragSplitter(BPoint point) 591 { 592 if (fDraggingSplitterIndex < 0) 593 return false; 594 595 point = View()->ConvertToScreen(point); 596 597 int32 valueDiff; 598 if (fOrientation == B_HORIZONTAL) 599 valueDiff = int32(point.x - fDraggingStartPoint.x); 600 else 601 valueDiff = int32(point.y - fDraggingStartPoint.y); 602 603 return _SetSplitterValue(fDraggingSplitterIndex, 604 fDraggingStartValue + valueDiff); 605 } 606 607 // StopDraggingSplitter 608 bool 609 BSplitLayout::StopDraggingSplitter() 610 { 611 if (fDraggingSplitterIndex < 0) 612 return false; 613 614 // update the item weights 615 _UpdateSplitterWeights(); 616 617 fDraggingSplitterIndex = -1; 618 619 return true; 620 } 621 622 // DraggedSplitter 623 int32 624 BSplitLayout::DraggedSplitter() const 625 { 626 return fDraggingSplitterIndex; 627 } 628 629 // ItemAdded 630 void 631 BSplitLayout::ItemAdded(BLayoutItem* item) 632 { 633 if (CountItems() > 1) { 634 SplitterItem* splitterItem = new SplitterItem(this); 635 SetItemWeight(splitterItem, 0); 636 fSplitterItems.AddItem(splitterItem); 637 } 638 639 SetItemWeight(item, 1); 640 } 641 642 // ItemRemoved 643 void 644 BSplitLayout::ItemRemoved(BLayoutItem* item) 645 { 646 if (fSplitterItems.CountItems() > 0) { 647 SplitterItem* splitterItem = (SplitterItem*)fSplitterItems.RemoveItem( 648 fSplitterItems.CountItems() - 1); 649 delete (ItemLayoutInfo*)splitterItem->LayoutData(); 650 delete splitterItem; 651 } 652 653 delete (ItemLayoutInfo*)item->LayoutData(); 654 item->SetLayoutData(NULL); 655 } 656 657 // _InvalidateLayout 658 void 659 BSplitLayout::_InvalidateLayout(bool invalidateView) 660 { 661 if (invalidateView) 662 BLayout::InvalidateLayout(); 663 664 delete fHorizontalLayouter; 665 delete fVerticalLayouter; 666 delete fHorizontalLayoutInfo; 667 delete fVerticalLayoutInfo; 668 669 fHorizontalLayouter = NULL; 670 fVerticalLayouter = NULL; 671 fHorizontalLayoutInfo = NULL; 672 fVerticalLayoutInfo = NULL; 673 674 _InvalidateCachedHeightForWidth(); 675 676 fLayoutValid = false; 677 } 678 679 // _InvalidateCachedHeightForWidth 680 void 681 BSplitLayout::_InvalidateCachedHeightForWidth() 682 { 683 delete fHeightForWidthVerticalLayouter; 684 delete fHeightForWidthHorizontalLayoutInfo; 685 686 fHeightForWidthVerticalLayouter = NULL; 687 fHeightForWidthHorizontalLayoutInfo = NULL; 688 689 fCachedHeightForWidthWidth = -2; 690 fHeightForWidthVerticalLayouterWidth = -2; 691 } 692 693 // _SplitterItemAt 694 BSplitLayout::SplitterItem* 695 BSplitLayout::_SplitterItemAt(int32 index) const 696 { 697 return (SplitterItem*)fSplitterItems.ItemAt(index); 698 } 699 700 // _GetSplitterValueRange 701 void 702 BSplitLayout::_GetSplitterValueRange(int32 index, ValueRange& range) 703 { 704 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(ItemAt(index)); 705 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(ItemAt(index + 1)); 706 if (fOrientation == B_HORIZONTAL) { 707 range.previousMin = (int32)previousInfo->min.width + 1; 708 range.previousMax = (int32)previousInfo->max.width + 1; 709 range.previousSize = previousInfo->layoutFrame.IntegerWidth() + 1; 710 range.nextMin = (int32)nextInfo->min.width + 1; 711 range.nextMax = (int32)nextInfo->max.width + 1; 712 range.nextSize = nextInfo->layoutFrame.IntegerWidth() + 1; 713 } else { 714 range.previousMin = (int32)previousInfo->min.height + 1; 715 range.previousMax = (int32)previousInfo->max.height + 1; 716 range.previousSize = previousInfo->layoutFrame.IntegerHeight() + 1; 717 range.nextMin = (int32)nextInfo->min.height + 1; 718 range.nextMax = (int32)nextInfo->max.height + 1; 719 range.nextSize = (int32)nextInfo->layoutFrame.IntegerHeight() + 1; 720 } 721 722 range.sumValue = range.previousSize + range.nextSize; 723 if (previousInfo->isVisible) 724 range.sumValue += (int32)fSpacing; 725 if (nextInfo->isVisible) 726 range.sumValue += (int32)fSpacing; 727 } 728 729 // _SplitterValue 730 int32 731 BSplitLayout::_SplitterValue(int32 index) const 732 { 733 ItemLayoutInfo* info = _ItemLayoutInfo(ItemAt(index)); 734 if (info && info->isVisible) { 735 if (fOrientation == B_HORIZONTAL) 736 return info->layoutFrame.IntegerWidth() + 1 + (int32)fSpacing; 737 else 738 return info->layoutFrame.IntegerHeight() + 1 + (int32)fSpacing; 739 } else 740 return 0; 741 } 742 743 // _LayoutItem 744 void 745 BSplitLayout::_LayoutItem(BLayoutItem* item, BRect frame, bool visible) 746 { 747 // update the layout frame 748 ItemLayoutInfo* info = _ItemLayoutInfo(item); 749 info->isVisible = visible; 750 if (visible) 751 info->layoutFrame = frame; 752 else 753 info->layoutFrame = BRect(0, 0, -1, -1); 754 755 // update min/max 756 info->min = item->MinSize(); 757 info->max = item->MaxSize(); 758 759 if (item->HasHeightForWidth()) { 760 BSize size = _SubtractInsets(BSize(View()->Frame())); 761 float minHeight, maxHeight; 762 item->GetHeightForWidth(size.width, &minHeight, &maxHeight, NULL); 763 info->min.height = max_c(info->min.height, minHeight); 764 info->max.height = min_c(info->max.height, maxHeight); 765 } 766 767 // layout the item 768 if (visible) 769 item->AlignInFrame(frame); 770 } 771 772 // _LayoutItem 773 void 774 BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info) 775 { 776 // update the visibility of the item 777 bool isVisible = item->IsVisible(); 778 bool visibilityChanged = (info->isVisible != isVisible); 779 if (visibilityChanged) 780 item->SetVisible(info->isVisible); 781 782 // nothing more to do, if the item is not visible 783 if (!info->isVisible) 784 return; 785 786 item->AlignInFrame(info->layoutFrame); 787 788 // if the item became visible, we need to update its internal layout 789 if (visibilityChanged) { 790 if (BView* itemView = item->View()) 791 itemView->Layout(false); 792 } 793 } 794 795 // _SetSplitterValue 796 bool 797 BSplitLayout::_SetSplitterValue(int32 index, int32 value) 798 { 799 // if both items are collapsed, nothing can be dragged 800 BLayoutItem* previousItem = ItemAt(index); 801 BLayoutItem* nextItem = ItemAt(index + 1); 802 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(previousItem); 803 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(nextItem); 804 ItemLayoutInfo* splitterInfo = _ItemLayoutInfo(_SplitterItemAt(index)); 805 bool previousVisible = previousInfo->isVisible; 806 bool nextVisible = nextInfo->isVisible; 807 if (!previousVisible && !nextVisible) 808 return false; 809 810 ValueRange range; 811 _GetSplitterValueRange(index, range); 812 813 value = max_c(min_c(value, range.sumValue), -(int32)fSpacing); 814 815 int32 previousSize = value - (int32)fSpacing; 816 int32 nextSize = range.sumValue - value - (int32)fSpacing; 817 818 // Note: While this collapsed-check is mathmatically correct (i.e. we 819 // collapse an item, if it would become smaller than half its minimum 820 // size), we might want to change it, since for the user it looks like 821 // collapsing happens earlier. The reason being that the only visual mark 822 // the user has is the mouse cursor which indeed hasn't crossed the middle 823 // of the item yet. 824 bool previousCollapsed = (previousSize <= range.previousMin / 2) 825 && previousInfo->isCollapsible; 826 bool nextCollapsed = (nextSize <= range.nextMin / 2) 827 && nextInfo->isCollapsible; 828 if (previousCollapsed && nextCollapsed) { 829 // we cannot collapse both items; we have to decide for one 830 if (previousSize < nextSize) { 831 // collapse previous 832 nextCollapsed = false; 833 nextSize = range.sumValue - (int32)fSpacing; 834 } else { 835 // collapse next 836 previousCollapsed = false; 837 previousSize = range.sumValue - (int32)fSpacing; 838 } 839 } 840 841 if (previousCollapsed || nextCollapsed) { 842 // one collapsed item -- check whether that violates the constraints 843 // of the other one 844 int32 availableSpace = range.sumValue - (int32)fSpacing; 845 if (previousCollapsed) { 846 if (availableSpace < range.nextMin 847 || availableSpace > range.nextMax) { 848 // we cannot collapse the previous item 849 previousCollapsed = false; 850 } 851 } else { 852 if (availableSpace < range.previousMin 853 || availableSpace > range.previousMax) { 854 // we cannot collapse the next item 855 nextCollapsed = false; 856 } 857 } 858 } 859 860 if (!(previousCollapsed || nextCollapsed)) { 861 // no collapsed item -- check whether there is a close solution 862 previousSize = value - (int32)fSpacing; 863 nextSize = range.sumValue - value - (int32)fSpacing; 864 865 if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) { 866 // we don't have enough space to uncollapse both items 867 int32 availableSpace = range.sumValue - (int32)fSpacing; 868 if (previousSize < nextSize && availableSpace >= range.nextMin 869 && availableSpace <= range.nextMax 870 && previousInfo->isCollapsible) { 871 previousCollapsed = true; 872 } else if (availableSpace >= range.previousMin 873 && availableSpace <= range.previousMax 874 && nextInfo->isCollapsible) { 875 nextCollapsed = true; 876 } else if (availableSpace >= range.nextMin 877 && availableSpace <= range.nextMax 878 && previousInfo->isCollapsible) { 879 previousCollapsed = true; 880 } else { 881 if (previousSize < nextSize && previousInfo->isCollapsible) { 882 previousCollapsed = true; 883 } else if (nextInfo->isCollapsible) { 884 nextCollapsed = true; 885 } else { 886 // Neither item is collapsible although there's not enough 887 // space: Give them both their minimum size. 888 previousSize = range.previousMin; 889 nextSize = range.nextMin; 890 } 891 } 892 893 } else { 894 // there is enough space for both items 895 // make sure the min constraints are satisfied 896 if (previousSize < range.previousMin) { 897 previousSize = range.previousMin; 898 nextSize = range.sumValue - previousSize - 2 * (int32)fSpacing; 899 } else if (nextSize < range.nextMin) { 900 nextSize = range.nextMin; 901 previousSize = range.sumValue - nextSize - 2 * (int32)fSpacing; 902 } 903 904 // if we can, also satisfy the max constraints 905 if (range.previousMax + range.nextMax + 2 * (int32)fSpacing 906 >= range.sumValue) { 907 if (previousSize > range.previousMax) { 908 previousSize = range.previousMax; 909 nextSize = range.sumValue - previousSize 910 - 2 * (int32)fSpacing; 911 } else if (nextSize > range.nextMax) { 912 nextSize = range.nextMax; 913 previousSize = range.sumValue - nextSize 914 - 2 * (int32)fSpacing; 915 } 916 } 917 } 918 } 919 920 // compute the size for one collapsed item; for none collapsed item we 921 // already have correct values 922 if (previousCollapsed || nextCollapsed) { 923 int32 availableSpace = range.sumValue - (int32)fSpacing; 924 if (previousCollapsed) { 925 previousSize = 0; 926 nextSize = availableSpace; 927 } else { 928 previousSize = availableSpace; 929 nextSize = 0; 930 } 931 } 932 933 int32 newValue = previousSize + (previousCollapsed ? 0 : (int32)fSpacing); 934 if (newValue == fDraggingCurrentValue) { 935 // nothing changed 936 return false; 937 } 938 939 // something changed: we need to recompute the layout 940 int32 baseOffset = -fDraggingCurrentValue; 941 // offset to the current splitter position 942 int32 splitterOffset = baseOffset + newValue; 943 int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing; 944 945 BRect splitterFrame(splitterInfo->layoutFrame); 946 if (fOrientation == B_HORIZONTAL) { 947 // horizontal layout 948 // previous item 949 float left = splitterFrame.left + baseOffset; 950 previousInfo->layoutFrame.Set( 951 left, 952 splitterFrame.top, 953 left + previousSize - 1, 954 splitterFrame.bottom); 955 956 // next item 957 left = splitterFrame.left + nextOffset; 958 nextInfo->layoutFrame.Set( 959 left, 960 splitterFrame.top, 961 left + nextSize - 1, 962 splitterFrame.bottom); 963 964 // splitter 965 splitterInfo->layoutFrame.left += splitterOffset; 966 splitterInfo->layoutFrame.right += splitterOffset; 967 } else { 968 // vertical layout 969 // previous item 970 float top = splitterFrame.top + baseOffset; 971 previousInfo->layoutFrame.Set( 972 splitterFrame.left, 973 top, 974 splitterFrame.right, 975 top + previousSize - 1); 976 977 // next item 978 top = splitterFrame.top + nextOffset; 979 nextInfo->layoutFrame.Set( 980 splitterFrame.left, 981 top, 982 splitterFrame.right, 983 top + nextSize - 1); 984 985 // splitter 986 splitterInfo->layoutFrame.top += splitterOffset; 987 splitterInfo->layoutFrame.bottom += splitterOffset; 988 } 989 990 previousInfo->isVisible = !previousCollapsed; 991 nextInfo->isVisible = !nextCollapsed; 992 993 bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth()); 994 995 // If the item visibility is to be changed, we need to update the splitter 996 // values now, since the visibility change will cause an invalidation. 997 if (previousVisible != previousInfo->isVisible 998 || nextVisible != nextInfo->isVisible || heightForWidth) { 999 _UpdateSplitterWeights(); 1000 } 1001 1002 // If we have height for width items, we need to invalidate the previous 1003 // and the next item. Actually we would only need to invalidate height for 1004 // width items, but since non height for width items might be aligned with 1005 // height for width items, we need to trigger a layout that creates a 1006 // context that spans all aligned items. 1007 // We invalidate already here, so that changing the items' size won't cause 1008 // an immediate relayout. 1009 if (heightForWidth) { 1010 previousItem->InvalidateLayout(); 1011 nextItem->InvalidateLayout(); 1012 } 1013 1014 // do the layout 1015 _LayoutItem(previousItem, previousInfo); 1016 _LayoutItem(_SplitterItemAt(index), splitterInfo); 1017 _LayoutItem(nextItem, nextInfo); 1018 1019 fDraggingCurrentValue = newValue; 1020 1021 return true; 1022 } 1023 1024 // _ItemLayoutInfo 1025 BSplitLayout::ItemLayoutInfo* 1026 BSplitLayout::_ItemLayoutInfo(BLayoutItem* item) const 1027 { 1028 ItemLayoutInfo* info = (ItemLayoutInfo*)item->LayoutData(); 1029 if (!info) { 1030 info = new ItemLayoutInfo(); 1031 item->SetLayoutData(info); 1032 } 1033 1034 return info; 1035 } 1036 1037 // _UpdateSplitterWeights 1038 void 1039 BSplitLayout::_UpdateSplitterWeights() 1040 { 1041 int32 count = CountItems(); 1042 for (int32 i = 0; i < count; i++) { 1043 float weight; 1044 if (fOrientation == B_HORIZONTAL) 1045 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Width() + 1; 1046 else 1047 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Height() + 1; 1048 1049 SetItemWeight(i, weight, false); 1050 } 1051 1052 // Just updating the splitter weights is fine in principle. The next 1053 // LayoutView() will use the correct values. But, if our orientation is 1054 // vertical, the cached height for width info needs to be flushed, or the 1055 // obsolete cached values will be used. 1056 if (fOrientation == B_VERTICAL) 1057 _InvalidateCachedHeightForWidth(); 1058 } 1059 1060 // _ValidateMinMax 1061 void 1062 BSplitLayout::_ValidateMinMax() 1063 { 1064 if (fHorizontalLayouter != NULL) 1065 return; 1066 1067 fLayoutValid = false; 1068 1069 fVisibleItems.MakeEmpty(); 1070 fHeightForWidthItems.MakeEmpty(); 1071 1072 _InvalidateCachedHeightForWidth(); 1073 1074 // filter the visible items 1075 int32 itemCount = CountItems(); 1076 for (int32 i = 0; i < itemCount; i++) { 1077 BLayoutItem* item = ItemAt(i); 1078 if (item->IsVisible()) 1079 fVisibleItems.AddItem(item); 1080 1081 // Add "height for width" items even, if they aren't visible. Otherwise 1082 // we may get our parent into trouble, since we could change from 1083 // "height for width" to "not height for width". 1084 if (item->HasHeightForWidth()) 1085 fHeightForWidthItems.AddItem(item); 1086 } 1087 itemCount = fVisibleItems.CountItems(); 1088 1089 // create the layouters 1090 Layouter* itemLayouter = new SimpleLayouter(itemCount, 0); 1091 1092 if (fOrientation == B_HORIZONTAL) { 1093 fHorizontalLayouter = itemLayouter; 1094 fVerticalLayouter = new OneElementLayouter(); 1095 } else { 1096 fHorizontalLayouter = new OneElementLayouter(); 1097 fVerticalLayouter = itemLayouter; 1098 } 1099 1100 // tell the layouters about our constraints 1101 if (itemCount > 0) { 1102 for (int32 i = 0; i < itemCount; i++) { 1103 BLayoutItem* item = (BLayoutItem*)fVisibleItems.ItemAt(i); 1104 BSize min = item->MinSize(); 1105 BSize max = item->MaxSize(); 1106 BSize preferred = item->PreferredSize(); 1107 1108 fHorizontalLayouter->AddConstraints(i, 1, min.width, max.width, 1109 preferred.width); 1110 fVerticalLayouter->AddConstraints(i, 1, min.height, max.height, 1111 preferred.height); 1112 1113 float weight = ItemWeight(item); 1114 fHorizontalLayouter->SetWeight(i, weight); 1115 fVerticalLayouter->SetWeight(i, weight); 1116 } 1117 } 1118 1119 fMin.width = fHorizontalLayouter->MinSize(); 1120 fMin.height = fVerticalLayouter->MinSize(); 1121 fMax.width = fHorizontalLayouter->MaxSize(); 1122 fMax.height = fVerticalLayouter->MaxSize(); 1123 fPreferred.width = fHorizontalLayouter->PreferredSize(); 1124 fPreferred.height = fVerticalLayouter->PreferredSize(); 1125 1126 fHorizontalLayoutInfo = fHorizontalLayouter->CreateLayoutInfo(); 1127 if (fHeightForWidthItems.IsEmpty()) 1128 fVerticalLayoutInfo = fVerticalLayouter->CreateLayoutInfo(); 1129 } 1130 1131 // _InternalGetHeightForWidth 1132 void 1133 BSplitLayout::_InternalGetHeightForWidth(float width, bool realLayout, 1134 float* minHeight, float* maxHeight, float* preferredHeight) 1135 { 1136 if (realLayout && fHeightForWidthVerticalLayouterWidth != width 1137 || !realLayout && fCachedHeightForWidthWidth != width) { 1138 // The general strategy is to clone the vertical layouter, which only 1139 // knows the general min/max constraints, do a horizontal layout for the 1140 // given width, and add the children's height for width constraints to 1141 // the cloned vertical layouter. If this method is invoked internally, 1142 // we keep the cloned vertical layouter, for it will be used for doing 1143 // the layout. Otherwise we just drop it after we've got the height for 1144 // width info. 1145 1146 // clone the vertical layouter and get the horizontal layout info to be used 1147 LayoutInfo* horizontalLayoutInfo = NULL; 1148 Layouter* verticalLayouter = fVerticalLayouter->CloneLayouter(); 1149 if (realLayout) { 1150 horizontalLayoutInfo = fHorizontalLayoutInfo; 1151 delete fHeightForWidthVerticalLayouter; 1152 fHeightForWidthVerticalLayouter = verticalLayouter; 1153 delete fVerticalLayoutInfo; 1154 fVerticalLayoutInfo = verticalLayouter->CreateLayoutInfo(); 1155 fHeightForWidthVerticalLayouterWidth = width; 1156 } else { 1157 if (fHeightForWidthHorizontalLayoutInfo == NULL) { 1158 delete fHeightForWidthHorizontalLayoutInfo; 1159 fHeightForWidthHorizontalLayoutInfo 1160 = fHorizontalLayouter->CreateLayoutInfo(); 1161 } 1162 horizontalLayoutInfo = fHeightForWidthHorizontalLayoutInfo; 1163 } 1164 1165 // do the horizontal layout (already done when doing this for the real 1166 // layout) 1167 if (!realLayout) 1168 fHorizontalLayouter->Layout(horizontalLayoutInfo, width); 1169 1170 // add the children's height for width constraints 1171 int32 count = fHeightForWidthItems.CountItems(); 1172 for (int32 i = 0; i < count; i++) { 1173 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i); 1174 int32 index = fVisibleItems.IndexOf(item); 1175 if (index >= 0) { 1176 float itemMinHeight, itemMaxHeight, itemPreferredHeight; 1177 item->GetHeightForWidth( 1178 horizontalLayoutInfo->ElementSize(index), 1179 &itemMinHeight, &itemMaxHeight, &itemPreferredHeight); 1180 verticalLayouter->AddConstraints(index, 1, itemMinHeight, 1181 itemMaxHeight, itemPreferredHeight); 1182 } 1183 } 1184 1185 // get the height for width info 1186 fCachedHeightForWidthWidth = width; 1187 fCachedMinHeightForWidth = verticalLayouter->MinSize(); 1188 fCachedMaxHeightForWidth = verticalLayouter->MaxSize(); 1189 fCachedPreferredHeightForWidth = verticalLayouter->PreferredSize(); 1190 } 1191 1192 if (minHeight) 1193 *minHeight = fCachedMinHeightForWidth; 1194 if (maxHeight) 1195 *maxHeight = fCachedMaxHeightForWidth; 1196 if (preferredHeight) 1197 *preferredHeight = fCachedPreferredHeightForWidth; 1198 } 1199 1200 // _SplitterSpace 1201 float 1202 BSplitLayout::_SplitterSpace() const 1203 { 1204 int32 splitters = fSplitterItems.CountItems(); 1205 float space = 0; 1206 if (splitters > 0) { 1207 space = (fVisibleItems.CountItems() + splitters - 1) * fSpacing 1208 + splitters * fSplitterSize; 1209 } 1210 1211 return space; 1212 } 1213 1214 // _AddInsets 1215 BSize 1216 BSplitLayout::_AddInsets(BSize size) 1217 { 1218 size.width = BLayoutUtils::AddDistances(size.width, 1219 fLeftInset + fRightInset - 1); 1220 size.height = BLayoutUtils::AddDistances(size.height, 1221 fTopInset + fBottomInset - 1); 1222 1223 float spacing = _SplitterSpace(); 1224 if (fOrientation == B_HORIZONTAL) 1225 size.width = BLayoutUtils::AddDistances(size.width, spacing - 1); 1226 else 1227 size.height = BLayoutUtils::AddDistances(size.height, spacing - 1); 1228 1229 return size; 1230 } 1231 1232 // _AddInsets 1233 void 1234 BSplitLayout::_AddInsets(float* minHeight, float* maxHeight, 1235 float* preferredHeight) 1236 { 1237 float insets = fTopInset + fBottomInset - 1; 1238 if (fOrientation == B_VERTICAL) 1239 insets += _SplitterSpace(); 1240 if (minHeight) 1241 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets); 1242 if (maxHeight) 1243 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets); 1244 if (preferredHeight) 1245 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets); 1246 } 1247 1248 // _SubtractInsets 1249 BSize 1250 BSplitLayout::_SubtractInsets(BSize size) 1251 { 1252 size.width = BLayoutUtils::SubtractDistances(size.width, 1253 fLeftInset + fRightInset - 1); 1254 size.height = BLayoutUtils::SubtractDistances(size.height, 1255 fTopInset + fBottomInset - 1); 1256 1257 float spacing = _SplitterSpace(); 1258 if (fOrientation == B_HORIZONTAL) 1259 size.width = BLayoutUtils::SubtractDistances(size.width, spacing - 1); 1260 else 1261 size.height = BLayoutUtils::SubtractDistances(size.height, spacing - 1); 1262 1263 return size; 1264 } 1265