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