2 * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de>.
3 * Copyright 2015, Rene Gollent, rene@gollent.com.
4 * All rights reserved. Distributed under the terms of the MIT License.
8 #include "SplitLayout.h"
13 #include <ControlLook.h>
14 #include <LayoutItem.h>
15 #include <LayoutUtils.h>
19 #include "OneElementLayouter.h"
20 #include "SimpleLayouter.h"
28 const char* const kItemCollapsibleField
= "BSplitLayout:item:collapsible";
29 const char* const kItemWeightField
= "BSplitLayout:item:weight";
30 const char* const kSpacingField
= "BSplitLayout:spacing";
31 const char* const kSplitterSizeField
= "BSplitLayout:splitterSize";
32 const char* const kIsVerticalField
= "BSplitLayout:vertical";
33 const char* const kInsetsField
= "BSplitLayout:insets";
37 class BSplitLayout::ItemLayoutInfo
{
49 layoutFrame(0, 0, -1, -1),
59 class BSplitLayout::ValueRange
{
61 int32 sumValue
; // including spacing
71 class BSplitLayout::SplitterItem
: public BLayoutItem
{
73 SplitterItem(BSplitLayout
* layout
)
81 virtual BSize
MinSize()
83 if (fLayout
->Orientation() == B_HORIZONTAL
)
84 return BSize(fLayout
->SplitterSize() - 1, -1);
86 return BSize(-1, fLayout
->SplitterSize() - 1);
89 virtual BSize
MaxSize()
91 if (fLayout
->Orientation() == B_HORIZONTAL
)
92 return BSize(fLayout
->SplitterSize() - 1, B_SIZE_UNLIMITED
);
94 return BSize(B_SIZE_UNLIMITED
, fLayout
->SplitterSize() - 1);
97 virtual BSize
PreferredSize()
102 virtual BAlignment
Alignment()
104 return BAlignment(B_ALIGN_HORIZONTAL_CENTER
, B_ALIGN_VERTICAL_CENTER
);
107 virtual void SetExplicitMinSize(BSize size
)
112 virtual void SetExplicitMaxSize(BSize size
)
117 virtual void SetExplicitPreferredSize(BSize size
)
122 virtual void SetExplicitAlignment(BAlignment alignment
)
127 virtual bool IsVisible()
132 virtual void SetVisible(bool visible
)
138 virtual BRect
Frame()
143 virtual void SetFrame(BRect frame
)
149 BSplitLayout
* fLayout
;
157 BSplitLayout::BSplitLayout(orientation orientation
, float spacing
)
159 fOrientation(orientation
),
165 fSpacing(BControlLook::ComposeSpacing(spacing
)),
173 fHorizontalLayouter(NULL
),
174 fVerticalLayouter(NULL
),
175 fHorizontalLayoutInfo(NULL
),
176 fVerticalLayoutInfo(NULL
),
178 fHeightForWidthItems(),
179 fHeightForWidthVerticalLayouter(NULL
),
180 fHeightForWidthHorizontalLayoutInfo(NULL
),
184 fCachedHeightForWidthWidth(-2),
185 fHeightForWidthVerticalLayouterWidth(-2),
186 fCachedMinHeightForWidth(-1),
187 fCachedMaxHeightForWidth(-1),
188 fCachedPreferredHeightForWidth(-1),
190 fDraggingStartPoint(),
191 fDraggingStartValue(0),
192 fDraggingCurrentValue(0),
193 fDraggingSplitterIndex(-1)
198 BSplitLayout::BSplitLayout(BMessage
* from
)
200 BAbstractLayout(BUnarchiver::PrepareArchive(from
)),
201 fOrientation(B_HORIZONTAL
),
207 fSpacing(be_control_look
->DefaultItemSpacing()),
215 fHorizontalLayouter(NULL
),
216 fVerticalLayouter(NULL
),
217 fHorizontalLayoutInfo(NULL
),
218 fVerticalLayoutInfo(NULL
),
220 fHeightForWidthItems(),
221 fHeightForWidthVerticalLayouter(NULL
),
222 fHeightForWidthHorizontalLayoutInfo(NULL
),
226 fCachedHeightForWidthWidth(-2),
227 fHeightForWidthVerticalLayouterWidth(-2),
228 fCachedMinHeightForWidth(-1),
229 fCachedMaxHeightForWidth(-1),
230 fCachedPreferredHeightForWidth(-1),
232 fDraggingStartPoint(),
233 fDraggingStartValue(0),
234 fDraggingCurrentValue(0),
235 fDraggingSplitterIndex(-1)
237 BUnarchiver
unarchiver(from
);
240 status_t err
= from
->FindBool(kIsVerticalField
, &isVertical
);
242 unarchiver
.Finish(err
);
245 fOrientation
= (isVertical
) ? B_VERTICAL
: B_HORIZONTAL
;
248 err
= from
->FindRect(kInsetsField
, &insets
);
250 unarchiver
.Finish(err
);
253 SetInsets(insets
.left
, insets
.top
, insets
.right
, insets
.bottom
);
255 err
= from
->FindFloat(kSplitterSizeField
, &fSplitterSize
);
257 err
= from
->FindFloat(kSpacingField
, &fSpacing
);
259 unarchiver
.Finish(err
);
263 BSplitLayout::~BSplitLayout()
269 BSplitLayout::SetInsets(float left
, float top
, float right
, float bottom
)
274 fBottomInset
= bottom
;
281 BSplitLayout::GetInsets(float* left
, float* top
, float* right
,
289 *right
= fRightInset
;
291 *bottom
= fBottomInset
;
296 BSplitLayout::Spacing() const
303 BSplitLayout::SetSpacing(float spacing
)
305 spacing
= BControlLook::ComposeSpacing(spacing
);
306 if (spacing
!= fSpacing
) {
315 BSplitLayout::Orientation() const
322 BSplitLayout::SetOrientation(orientation orientation
)
324 if (orientation
!= fOrientation
) {
325 fOrientation
= orientation
;
333 BSplitLayout::SplitterSize() const
335 return fSplitterSize
;
340 BSplitLayout::SetSplitterSize(float size
)
342 if (size
!= fSplitterSize
) {
343 fSplitterSize
= size
;
351 BSplitLayout::AddView(BView
* child
)
353 return BAbstractLayout::AddView(child
);
358 BSplitLayout::AddView(int32 index
, BView
* child
)
360 return BAbstractLayout::AddView(index
, child
);
365 BSplitLayout::AddView(BView
* child
, float weight
)
367 return AddView(-1, child
, weight
);
372 BSplitLayout::AddView(int32 index
, BView
* child
, float weight
)
374 BLayoutItem
* item
= AddView(index
, child
);
376 SetItemWeight(item
, weight
);
383 BSplitLayout::AddItem(BLayoutItem
* item
)
385 return BAbstractLayout::AddItem(item
);
390 BSplitLayout::AddItem(int32 index
, BLayoutItem
* item
)
392 return BAbstractLayout::AddItem(index
, item
);
397 BSplitLayout::AddItem(BLayoutItem
* item
, float weight
)
399 return AddItem(-1, item
, weight
);
404 BSplitLayout::AddItem(int32 index
, BLayoutItem
* item
, float weight
)
406 bool success
= AddItem(index
, item
);
408 SetItemWeight(item
, weight
);
415 BSplitLayout::ItemWeight(int32 index
) const
417 if (index
< 0 || index
>= CountItems())
420 return ItemWeight(ItemAt(index
));
425 BSplitLayout::ItemWeight(BLayoutItem
* item
) const
427 if (ItemLayoutInfo
* info
= _ItemLayoutInfo(item
))
434 BSplitLayout::SetItemWeight(int32 index
, float weight
, bool invalidateLayout
)
436 if (index
< 0 || index
>= CountItems())
439 BLayoutItem
* item
= ItemAt(index
);
440 SetItemWeight(item
, weight
);
442 if (fHorizontalLayouter
) {
443 int32 visibleIndex
= fVisibleItems
.IndexOf(item
);
444 if (visibleIndex
>= 0) {
445 if (fOrientation
== B_HORIZONTAL
)
446 fHorizontalLayouter
->SetWeight(visibleIndex
, weight
);
448 fVerticalLayouter
->SetWeight(visibleIndex
, weight
);
452 if (invalidateLayout
)
458 BSplitLayout::SetItemWeight(BLayoutItem
* item
, float weight
)
460 if (ItemLayoutInfo
* info
= _ItemLayoutInfo(item
))
461 info
->weight
= weight
;
466 BSplitLayout::IsCollapsible(int32 index
) const
468 return _ItemLayoutInfo(ItemAt(index
))->isCollapsible
;
473 BSplitLayout::SetCollapsible(bool collapsible
)
475 SetCollapsible(0, CountItems() - 1, collapsible
);
480 BSplitLayout::SetCollapsible(int32 index
, bool collapsible
)
482 SetCollapsible(index
, index
, collapsible
);
487 BSplitLayout::SetCollapsible(int32 first
, int32 last
, bool collapsible
)
489 for (int32 i
= first
; i
<= last
; i
++)
490 _ItemLayoutInfo(ItemAt(i
))->isCollapsible
= collapsible
;
495 BSplitLayout::IsItemCollapsed(int32 index
) const
497 return !_ItemLayoutInfo(ItemAt(index
))->isVisible
;
502 BSplitLayout::SetItemCollapsed(int32 index
, bool collapsed
)
504 ItemAt(index
)->SetVisible(!collapsed
);
506 InvalidateLayout(true);
511 BSplitLayout::BaseMinSize()
515 return _AddInsets(fMin
);
520 BSplitLayout::BaseMaxSize()
524 return _AddInsets(fMax
);
529 BSplitLayout::BasePreferredSize()
533 return _AddInsets(fPreferred
);
538 BSplitLayout::BaseAlignment()
540 return BAbstractLayout::BaseAlignment();
545 BSplitLayout::HasHeightForWidth()
549 return !fHeightForWidthItems
.IsEmpty();
554 BSplitLayout::GetHeightForWidth(float width
, float* min
, float* max
,
557 if (!HasHeightForWidth())
560 float innerWidth
= _SubtractInsets(BSize(width
, 0)).width
;
561 _InternalGetHeightForWidth(innerWidth
, false, min
, max
, preferred
);
562 _AddInsets(min
, max
, preferred
);
567 BSplitLayout::LayoutInvalidated(bool children
)
569 delete fHorizontalLayouter
;
570 delete fVerticalLayouter
;
571 delete fHorizontalLayoutInfo
;
572 delete fVerticalLayoutInfo
;
574 fHorizontalLayouter
= NULL
;
575 fVerticalLayouter
= NULL
;
576 fHorizontalLayoutInfo
= NULL
;
577 fVerticalLayoutInfo
= NULL
;
579 _InvalidateCachedHeightForWidth();
581 fLayoutValid
= false;
586 BSplitLayout::DoLayout()
590 // layout the elements
591 BSize size
= _SubtractInsets(LayoutArea().Size());
592 fHorizontalLayouter
->Layout(fHorizontalLayoutInfo
, size
.width
);
594 Layouter
* verticalLayouter
;
595 if (HasHeightForWidth()) {
596 float minHeight
, maxHeight
, preferredHeight
;
597 _InternalGetHeightForWidth(size
.width
, true, &minHeight
, &maxHeight
,
599 size
.height
= max_c(size
.height
, minHeight
);
600 verticalLayouter
= fHeightForWidthVerticalLayouter
;
602 verticalLayouter
= fVerticalLayouter
;
604 verticalLayouter
->Layout(fVerticalLayoutInfo
, size
.height
);
606 float xOffset
= fLeftInset
;
607 float yOffset
= fTopInset
;
608 float splitterWidth
= 0; // pixel counts, no distances
609 float splitterHeight
= 0; //
612 if (fOrientation
== B_HORIZONTAL
) {
613 splitterWidth
= fSplitterSize
;
614 splitterHeight
= size
.height
+ 1;
617 splitterWidth
= size
.width
+ 1;
618 splitterHeight
= fSplitterSize
;
622 int itemCount
= CountItems();
623 for (int i
= 0; i
< itemCount
; i
++) {
624 // layout the splitter
626 SplitterItem
* splitterItem
= _SplitterItemAt(i
- 1);
628 _LayoutItem(splitterItem
, BRect(xOffset
, yOffset
,
629 xOffset
+ splitterWidth
- 1, yOffset
+ splitterHeight
- 1),
632 if (fOrientation
== B_HORIZONTAL
)
633 xOffset
+= splitterWidth
+ xSpacing
;
635 yOffset
+= splitterHeight
+ ySpacing
;
639 BLayoutItem
* item
= ItemAt(i
);
640 int32 visibleIndex
= fVisibleItems
.IndexOf(item
);
641 if (visibleIndex
< 0) {
642 _LayoutItem(item
, BRect(), false);
646 // get the dimensions of the item
647 float width
= fHorizontalLayoutInfo
->ElementSize(visibleIndex
);
648 float height
= fVerticalLayoutInfo
->ElementSize(visibleIndex
);
650 // place the component
651 _LayoutItem(item
, BRect(xOffset
, yOffset
, xOffset
+ width
,
652 yOffset
+ height
), true);
654 if (fOrientation
== B_HORIZONTAL
)
655 xOffset
+= width
+ xSpacing
+ 1;
657 yOffset
+= height
+ ySpacing
+ 1;
665 BSplitLayout::SplitterItemFrame(int32 index
) const
667 if (SplitterItem
* item
= _SplitterItemAt(index
))
668 return item
->Frame();
674 BSplitLayout::IsAboveSplitter(const BPoint
& point
) const
676 return _SplitterItemAt(point
) != NULL
;
681 BSplitLayout::StartDraggingSplitter(BPoint point
)
683 StopDraggingSplitter();
685 // Layout must be valid. Bail out, if it isn't.
689 // Things shouldn't be draggable, if we have a >= max layout.
690 BSize size
= _SubtractInsets(LayoutArea().Size());
691 if ((fOrientation
== B_HORIZONTAL
&& size
.width
>= fMax
.width
)
692 || (fOrientation
== B_VERTICAL
&& size
.height
>= fMax
.height
)) {
697 if (_SplitterItemAt(point
, &index
) != NULL
) {
698 fDraggingStartPoint
= Owner()->ConvertToScreen(point
);
699 fDraggingStartValue
= _SplitterValue(index
);
700 fDraggingCurrentValue
= fDraggingStartValue
;
701 fDraggingSplitterIndex
= index
;
711 BSplitLayout::DragSplitter(BPoint point
)
713 if (fDraggingSplitterIndex
< 0)
716 point
= Owner()->ConvertToScreen(point
);
719 if (fOrientation
== B_HORIZONTAL
)
720 valueDiff
= int32(point
.x
- fDraggingStartPoint
.x
);
722 valueDiff
= int32(point
.y
- fDraggingStartPoint
.y
);
724 return _SetSplitterValue(fDraggingSplitterIndex
,
725 fDraggingStartValue
+ valueDiff
);
730 BSplitLayout::StopDraggingSplitter()
732 if (fDraggingSplitterIndex
< 0)
735 // update the item weights
736 _UpdateSplitterWeights();
738 fDraggingSplitterIndex
= -1;
745 BSplitLayout::DraggedSplitter() const
747 return fDraggingSplitterIndex
;
752 BSplitLayout::Archive(BMessage
* into
, bool deep
) const
754 BArchiver
archiver(into
);
755 status_t err
= BAbstractLayout::Archive(into
, deep
);
758 err
= into
->AddBool(kIsVerticalField
, fOrientation
== B_VERTICAL
);
761 BRect
insets(fLeftInset
, fTopInset
, fRightInset
, fBottomInset
);
762 err
= into
->AddRect(kInsetsField
, insets
);
766 err
= into
->AddFloat(kSplitterSizeField
, fSplitterSize
);
769 err
= into
->AddFloat(kSpacingField
, fSpacing
);
771 return archiver
.Finish(err
);
776 BSplitLayout::Instantiate(BMessage
* from
)
778 if (validate_instantiation(from
, "BSplitLayout"))
779 return new(std::nothrow
) BSplitLayout(from
);
785 BSplitLayout::ItemArchived(BMessage
* into
, BLayoutItem
* item
, int32 index
) const
787 ItemLayoutInfo
* info
= _ItemLayoutInfo(item
);
789 status_t err
= into
->AddFloat(kItemWeightField
, info
->weight
);
791 err
= into
->AddBool(kItemCollapsibleField
, info
->isCollapsible
);
798 BSplitLayout::ItemUnarchived(const BMessage
* from
,
799 BLayoutItem
* item
, int32 index
)
801 ItemLayoutInfo
* info
= _ItemLayoutInfo(item
);
802 status_t err
= from
->FindFloat(kItemWeightField
, index
, &info
->weight
);
805 bool* collapsible
= &info
->isCollapsible
;
806 err
= from
->FindBool(kItemCollapsibleField
, index
, collapsible
);
813 BSplitLayout::ItemAdded(BLayoutItem
* item
, int32 atIndex
)
815 ItemLayoutInfo
* itemInfo
= new(nothrow
) ItemLayoutInfo();
819 if (CountItems() > 1) {
820 SplitterItem
* splitter
= new(nothrow
) SplitterItem(this);
821 ItemLayoutInfo
* splitterInfo
= new(nothrow
) ItemLayoutInfo();
822 if (!splitter
|| !splitterInfo
|| !fSplitterItems
.AddItem(splitter
)) {
828 splitter
->SetLayoutData(splitterInfo
);
829 SetItemWeight(splitter
, 0);
832 item
->SetLayoutData(itemInfo
);
833 SetItemWeight(item
, 1);
839 BSplitLayout::ItemRemoved(BLayoutItem
* item
, int32 atIndex
)
841 if (fSplitterItems
.CountItems() > 0) {
842 SplitterItem
* splitterItem
= (SplitterItem
*)fSplitterItems
.RemoveItem(
843 fSplitterItems
.CountItems() - 1);
844 delete _ItemLayoutInfo(splitterItem
);
848 delete _ItemLayoutInfo(item
);
849 item
->SetLayoutData(NULL
);
854 BSplitLayout::_InvalidateCachedHeightForWidth()
856 delete fHeightForWidthVerticalLayouter
;
857 delete fHeightForWidthHorizontalLayoutInfo
;
859 fHeightForWidthVerticalLayouter
= NULL
;
860 fHeightForWidthHorizontalLayoutInfo
= NULL
;
862 fCachedHeightForWidthWidth
= -2;
863 fHeightForWidthVerticalLayouterWidth
= -2;
867 BSplitLayout::SplitterItem
*
868 BSplitLayout::_SplitterItemAt(const BPoint
& point
, int32
* index
) const
870 int32 splitterCount
= fSplitterItems
.CountItems();
871 for (int32 i
= 0; i
< splitterCount
; i
++) {
872 SplitterItem
* splitItem
= _SplitterItemAt(i
);
873 BRect frame
= splitItem
->Frame();
874 if (frame
.Contains(point
)) {
884 BSplitLayout::SplitterItem
*
885 BSplitLayout::_SplitterItemAt(int32 index
) const
887 return (SplitterItem
*)fSplitterItems
.ItemAt(index
);
892 BSplitLayout::_GetSplitterValueRange(int32 index
, ValueRange
& range
)
894 ItemLayoutInfo
* previousInfo
= _ItemLayoutInfo(ItemAt(index
));
895 ItemLayoutInfo
* nextInfo
= _ItemLayoutInfo(ItemAt(index
+ 1));
896 if (fOrientation
== B_HORIZONTAL
) {
897 range
.previousMin
= (int32
)previousInfo
->min
.width
+ 1;
898 range
.previousMax
= (int32
)previousInfo
->max
.width
+ 1;
899 range
.previousSize
= previousInfo
->layoutFrame
.IntegerWidth() + 1;
900 range
.nextMin
= (int32
)nextInfo
->min
.width
+ 1;
901 range
.nextMax
= (int32
)nextInfo
->max
.width
+ 1;
902 range
.nextSize
= nextInfo
->layoutFrame
.IntegerWidth() + 1;
904 range
.previousMin
= (int32
)previousInfo
->min
.height
+ 1;
905 range
.previousMax
= (int32
)previousInfo
->max
.height
+ 1;
906 range
.previousSize
= previousInfo
->layoutFrame
.IntegerHeight() + 1;
907 range
.nextMin
= (int32
)nextInfo
->min
.height
+ 1;
908 range
.nextMax
= (int32
)nextInfo
->max
.height
+ 1;
909 range
.nextSize
= (int32
)nextInfo
->layoutFrame
.IntegerHeight() + 1;
912 range
.sumValue
= range
.previousSize
+ range
.nextSize
;
913 if (previousInfo
->isVisible
)
914 range
.sumValue
+= (int32
)fSpacing
;
915 if (nextInfo
->isVisible
)
916 range
.sumValue
+= (int32
)fSpacing
;
921 BSplitLayout::_SplitterValue(int32 index
) const
923 ItemLayoutInfo
* info
= _ItemLayoutInfo(ItemAt(index
));
924 if (info
&& info
->isVisible
) {
925 if (fOrientation
== B_HORIZONTAL
)
926 return info
->layoutFrame
.IntegerWidth() + 1 + (int32
)fSpacing
;
928 return info
->layoutFrame
.IntegerHeight() + 1 + (int32
)fSpacing
;
935 BSplitLayout::_LayoutItem(BLayoutItem
* item
, BRect frame
, bool visible
)
937 // update the layout frame
938 ItemLayoutInfo
* info
= _ItemLayoutInfo(item
);
939 info
->isVisible
= visible
;
941 info
->layoutFrame
= frame
;
943 info
->layoutFrame
= BRect(0, 0, -1, -1);
946 info
->min
= item
->MinSize();
947 info
->max
= item
->MaxSize();
949 if (item
->HasHeightForWidth()) {
950 BSize size
= _SubtractInsets(LayoutArea().Size());
951 float minHeight
, maxHeight
;
952 item
->GetHeightForWidth(size
.width
, &minHeight
, &maxHeight
, NULL
);
953 info
->min
.height
= max_c(info
->min
.height
, minHeight
);
954 info
->max
.height
= min_c(info
->max
.height
, maxHeight
);
959 item
->AlignInFrame(frame
);
964 BSplitLayout::_LayoutItem(BLayoutItem
* item
, ItemLayoutInfo
* info
)
966 // update the visibility of the item
967 bool isVisible
= item
->IsVisible();
968 bool visibilityChanged
= (info
->isVisible
!= isVisible
);
969 if (visibilityChanged
)
970 item
->SetVisible(info
->isVisible
);
972 // nothing more to do, if the item is not visible
973 if (!info
->isVisible
)
976 item
->AlignInFrame(info
->layoutFrame
);
978 // if the item became visible, we need to update its internal layout
979 if (visibilityChanged
&&
980 (fOrientation
!= B_HORIZONTAL
|| !HasHeightForWidth())) {
981 item
->Relayout(true);
987 BSplitLayout::_SetSplitterValue(int32 index
, int32 value
)
989 // if both items are collapsed, nothing can be dragged
990 BLayoutItem
* previousItem
= ItemAt(index
);
991 BLayoutItem
* nextItem
= ItemAt(index
+ 1);
992 ItemLayoutInfo
* previousInfo
= _ItemLayoutInfo(previousItem
);
993 ItemLayoutInfo
* nextInfo
= _ItemLayoutInfo(nextItem
);
994 ItemLayoutInfo
* splitterInfo
= _ItemLayoutInfo(_SplitterItemAt(index
));
995 bool previousVisible
= previousInfo
->isVisible
;
996 bool nextVisible
= nextInfo
->isVisible
;
997 if (!previousVisible
&& !nextVisible
)
1001 _GetSplitterValueRange(index
, range
);
1003 value
= max_c(min_c(value
, range
.sumValue
), -(int32
)fSpacing
);
1005 int32 previousSize
= value
- (int32
)fSpacing
;
1006 int32 nextSize
= range
.sumValue
- value
- (int32
)fSpacing
;
1008 // Note: While this collapsed-check is mathmatically correct (i.e. we
1009 // collapse an item, if it would become smaller than half its minimum
1010 // size), we might want to change it, since for the user it looks like
1011 // collapsing happens earlier. The reason being that the only visual mark
1012 // the user has is the mouse cursor which indeed hasn't crossed the middle
1014 bool previousCollapsed
= (previousSize
<= range
.previousMin
/ 2)
1015 && previousInfo
->isCollapsible
;
1016 bool nextCollapsed
= (nextSize
<= range
.nextMin
/ 2)
1017 && nextInfo
->isCollapsible
;
1018 if (previousCollapsed
&& nextCollapsed
) {
1019 // we cannot collapse both items; we have to decide for one
1020 if (previousSize
< nextSize
) {
1021 // collapse previous
1022 nextCollapsed
= false;
1023 nextSize
= range
.sumValue
- (int32
)fSpacing
;
1026 previousCollapsed
= false;
1027 previousSize
= range
.sumValue
- (int32
)fSpacing
;
1031 if (previousCollapsed
|| nextCollapsed
) {
1032 // one collapsed item -- check whether that violates the constraints
1034 int32 availableSpace
= range
.sumValue
- (int32
)fSpacing
;
1035 if (previousCollapsed
) {
1036 if (availableSpace
< range
.nextMin
1037 || availableSpace
> range
.nextMax
) {
1038 // we cannot collapse the previous item
1039 previousCollapsed
= false;
1042 if (availableSpace
< range
.previousMin
1043 || availableSpace
> range
.previousMax
) {
1044 // we cannot collapse the next item
1045 nextCollapsed
= false;
1050 if (!(previousCollapsed
|| nextCollapsed
)) {
1051 // no collapsed item -- check whether there is a close solution
1052 previousSize
= value
- (int32
)fSpacing
;
1053 nextSize
= range
.sumValue
- value
- (int32
)fSpacing
;
1055 if (range
.previousMin
+ range
.nextMin
+ 2 * fSpacing
> range
.sumValue
) {
1056 // we don't have enough space to uncollapse both items
1057 int32 availableSpace
= range
.sumValue
- (int32
)fSpacing
;
1058 if (previousSize
< nextSize
&& availableSpace
>= range
.nextMin
1059 && availableSpace
<= range
.nextMax
1060 && previousInfo
->isCollapsible
) {
1061 previousCollapsed
= true;
1062 } else if (availableSpace
>= range
.previousMin
1063 && availableSpace
<= range
.previousMax
1064 && nextInfo
->isCollapsible
) {
1065 nextCollapsed
= true;
1066 } else if (availableSpace
>= range
.nextMin
1067 && availableSpace
<= range
.nextMax
1068 && previousInfo
->isCollapsible
) {
1069 previousCollapsed
= true;
1071 if (previousSize
< nextSize
&& previousInfo
->isCollapsible
) {
1072 previousCollapsed
= true;
1073 } else if (nextInfo
->isCollapsible
) {
1074 nextCollapsed
= true;
1076 // Neither item is collapsible although there's not enough
1077 // space: Give them both their minimum size.
1078 previousSize
= range
.previousMin
;
1079 nextSize
= range
.nextMin
;
1084 // there is enough space for both items
1085 // make sure the min constraints are satisfied
1086 if (previousSize
< range
.previousMin
) {
1087 previousSize
= range
.previousMin
;
1088 nextSize
= range
.sumValue
- previousSize
- 2 * (int32
)fSpacing
;
1089 } else if (nextSize
< range
.nextMin
) {
1090 nextSize
= range
.nextMin
;
1091 previousSize
= range
.sumValue
- nextSize
- 2 * (int32
)fSpacing
;
1094 // if we can, also satisfy the max constraints
1095 if (range
.previousMax
+ range
.nextMax
+ 2 * (int32
)fSpacing
1096 >= range
.sumValue
) {
1097 if (previousSize
> range
.previousMax
) {
1098 previousSize
= range
.previousMax
;
1099 nextSize
= range
.sumValue
- previousSize
1100 - 2 * (int32
)fSpacing
;
1101 } else if (nextSize
> range
.nextMax
) {
1102 nextSize
= range
.nextMax
;
1103 previousSize
= range
.sumValue
- nextSize
1104 - 2 * (int32
)fSpacing
;
1110 // compute the size for one collapsed item; for none collapsed item we
1111 // already have correct values
1112 if (previousCollapsed
|| nextCollapsed
) {
1113 int32 availableSpace
= range
.sumValue
- (int32
)fSpacing
;
1114 if (previousCollapsed
) {
1116 nextSize
= availableSpace
;
1118 previousSize
= availableSpace
;
1123 int32 newValue
= previousSize
+ (previousCollapsed
? 0 : (int32
)fSpacing
);
1124 if (newValue
== fDraggingCurrentValue
) {
1129 // something changed: we need to recompute the layout
1130 int32 baseOffset
= -fDraggingCurrentValue
;
1131 // offset to the current splitter position
1132 int32 splitterOffset
= baseOffset
+ newValue
;
1133 int32 nextOffset
= splitterOffset
+ (int32
)fSplitterSize
+ (int32
)fSpacing
;
1135 BRect
splitterFrame(splitterInfo
->layoutFrame
);
1136 if (fOrientation
== B_HORIZONTAL
) {
1137 // horizontal layout
1139 float left
= splitterFrame
.left
+ baseOffset
;
1140 previousInfo
->layoutFrame
.Set(
1143 left
+ previousSize
- 1,
1144 splitterFrame
.bottom
);
1147 left
= splitterFrame
.left
+ nextOffset
;
1148 nextInfo
->layoutFrame
.Set(
1151 left
+ nextSize
- 1,
1152 splitterFrame
.bottom
);
1155 splitterInfo
->layoutFrame
.left
+= splitterOffset
;
1156 splitterInfo
->layoutFrame
.right
+= splitterOffset
;
1160 float top
= splitterFrame
.top
+ baseOffset
;
1161 previousInfo
->layoutFrame
.Set(
1164 splitterFrame
.right
,
1165 top
+ previousSize
- 1);
1168 top
= splitterFrame
.top
+ nextOffset
;
1169 nextInfo
->layoutFrame
.Set(
1172 splitterFrame
.right
,
1173 top
+ nextSize
- 1);
1176 splitterInfo
->layoutFrame
.top
+= splitterOffset
;
1177 splitterInfo
->layoutFrame
.bottom
+= splitterOffset
;
1180 previousInfo
->isVisible
= !previousCollapsed
;
1181 nextInfo
->isVisible
= !nextCollapsed
;
1183 bool heightForWidth
= (fOrientation
== B_HORIZONTAL
&& HasHeightForWidth());
1185 // If the item visibility is to be changed, we need to update the splitter
1186 // values now, since the visibility change will cause an invalidation.
1187 if (previousVisible
!= previousInfo
->isVisible
1188 || nextVisible
!= nextInfo
->isVisible
|| heightForWidth
) {
1189 _UpdateSplitterWeights();
1192 // If we have height for width items, we need to invalidate the previous
1193 // and the next item. Actually we would only need to invalidate height for
1194 // width items, but since non height for width items might be aligned with
1195 // height for width items, we need to trigger a layout that creates a
1196 // context that spans all aligned items.
1197 // We invalidate already here, so that changing the items' size won't cause
1198 // an immediate relayout.
1199 if (heightForWidth
) {
1200 previousItem
->InvalidateLayout();
1201 nextItem
->InvalidateLayout();
1205 _LayoutItem(previousItem
, previousInfo
);
1206 _LayoutItem(_SplitterItemAt(index
), splitterInfo
);
1207 _LayoutItem(nextItem
, nextInfo
);
1209 fDraggingCurrentValue
= newValue
;
1215 BSplitLayout::ItemLayoutInfo
*
1216 BSplitLayout::_ItemLayoutInfo(BLayoutItem
* item
) const
1218 return (ItemLayoutInfo
*)item
->LayoutData();
1223 BSplitLayout::_UpdateSplitterWeights()
1225 int32 count
= CountItems();
1226 for (int32 i
= 0; i
< count
; i
++) {
1228 if (fOrientation
== B_HORIZONTAL
)
1229 weight
= _ItemLayoutInfo(ItemAt(i
))->layoutFrame
.Width() + 1;
1231 weight
= _ItemLayoutInfo(ItemAt(i
))->layoutFrame
.Height() + 1;
1233 SetItemWeight(i
, weight
, false);
1236 // Just updating the splitter weights is fine in principle. The next
1237 // LayoutItems() will use the correct values. But, if our orientation is
1238 // vertical, the cached height for width info needs to be flushed, or the
1239 // obsolete cached values will be used.
1240 if (fOrientation
== B_VERTICAL
)
1241 _InvalidateCachedHeightForWidth();
1246 BSplitLayout::_ValidateMinMax()
1248 if (fHorizontalLayouter
!= NULL
)
1251 fLayoutValid
= false;
1253 fVisibleItems
.MakeEmpty();
1254 fHeightForWidthItems
.MakeEmpty();
1256 _InvalidateCachedHeightForWidth();
1258 // filter the visible items
1259 int32 itemCount
= CountItems();
1260 for (int32 i
= 0; i
< itemCount
; i
++) {
1261 BLayoutItem
* item
= ItemAt(i
);
1262 if (item
->IsVisible())
1263 fVisibleItems
.AddItem(item
);
1265 // Add "height for width" items even, if they aren't visible. Otherwise
1266 // we may get our parent into trouble, since we could change from
1267 // "height for width" to "not height for width".
1268 if (item
->HasHeightForWidth())
1269 fHeightForWidthItems
.AddItem(item
);
1271 itemCount
= fVisibleItems
.CountItems();
1273 // create the layouters
1274 Layouter
* itemLayouter
= new SimpleLayouter(itemCount
, 0);
1276 if (fOrientation
== B_HORIZONTAL
) {
1277 fHorizontalLayouter
= itemLayouter
;
1278 fVerticalLayouter
= new OneElementLayouter();
1280 fHorizontalLayouter
= new OneElementLayouter();
1281 fVerticalLayouter
= itemLayouter
;
1284 // tell the layouters about our constraints
1285 if (itemCount
> 0) {
1286 for (int32 i
= 0; i
< itemCount
; i
++) {
1287 BLayoutItem
* item
= (BLayoutItem
*)fVisibleItems
.ItemAt(i
);
1288 BSize min
= item
->MinSize();
1289 BSize max
= item
->MaxSize();
1290 BSize preferred
= item
->PreferredSize();
1292 fHorizontalLayouter
->AddConstraints(i
, 1, min
.width
, max
.width
,
1294 fVerticalLayouter
->AddConstraints(i
, 1, min
.height
, max
.height
,
1297 float weight
= ItemWeight(item
);
1298 fHorizontalLayouter
->SetWeight(i
, weight
);
1299 fVerticalLayouter
->SetWeight(i
, weight
);
1303 fMin
.width
= fHorizontalLayouter
->MinSize();
1304 fMin
.height
= fVerticalLayouter
->MinSize();
1305 fMax
.width
= fHorizontalLayouter
->MaxSize();
1306 fMax
.height
= fVerticalLayouter
->MaxSize();
1307 fPreferred
.width
= fHorizontalLayouter
->PreferredSize();
1308 fPreferred
.height
= fVerticalLayouter
->PreferredSize();
1310 fHorizontalLayoutInfo
= fHorizontalLayouter
->CreateLayoutInfo();
1311 if (fHeightForWidthItems
.IsEmpty())
1312 fVerticalLayoutInfo
= fVerticalLayouter
->CreateLayoutInfo();
1314 ResetLayoutInvalidation();
1319 BSplitLayout::_InternalGetHeightForWidth(float width
, bool realLayout
,
1320 float* minHeight
, float* maxHeight
, float* preferredHeight
)
1322 if ((realLayout
&& fHeightForWidthVerticalLayouterWidth
!= width
)
1323 || (!realLayout
&& fCachedHeightForWidthWidth
!= width
)) {
1324 // The general strategy is to clone the vertical layouter, which only
1325 // knows the general min/max constraints, do a horizontal layout for the
1326 // given width, and add the children's height for width constraints to
1327 // the cloned vertical layouter. If this method is invoked internally,
1328 // we keep the cloned vertical layouter, for it will be used for doing
1329 // the layout. Otherwise we just drop it after we've got the height for
1332 // clone the vertical layouter and get the horizontal layout info to be used
1333 LayoutInfo
* horizontalLayoutInfo
= NULL
;
1334 Layouter
* verticalLayouter
= fVerticalLayouter
->CloneLayouter();
1336 horizontalLayoutInfo
= fHorizontalLayoutInfo
;
1337 delete fHeightForWidthVerticalLayouter
;
1338 fHeightForWidthVerticalLayouter
= verticalLayouter
;
1339 delete fVerticalLayoutInfo
;
1340 fVerticalLayoutInfo
= verticalLayouter
->CreateLayoutInfo();
1341 fHeightForWidthVerticalLayouterWidth
= width
;
1343 if (fHeightForWidthHorizontalLayoutInfo
== NULL
) {
1344 delete fHeightForWidthHorizontalLayoutInfo
;
1345 fHeightForWidthHorizontalLayoutInfo
1346 = fHorizontalLayouter
->CreateLayoutInfo();
1348 horizontalLayoutInfo
= fHeightForWidthHorizontalLayoutInfo
;
1351 // do the horizontal layout (already done when doing this for the real
1354 fHorizontalLayouter
->Layout(horizontalLayoutInfo
, width
);
1356 // add the children's height for width constraints
1357 int32 count
= fHeightForWidthItems
.CountItems();
1358 for (int32 i
= 0; i
< count
; i
++) {
1359 BLayoutItem
* item
= (BLayoutItem
*)fHeightForWidthItems
.ItemAt(i
);
1360 int32 index
= fVisibleItems
.IndexOf(item
);
1362 float itemMinHeight
, itemMaxHeight
, itemPreferredHeight
;
1363 item
->GetHeightForWidth(
1364 horizontalLayoutInfo
->ElementSize(index
),
1365 &itemMinHeight
, &itemMaxHeight
, &itemPreferredHeight
);
1366 verticalLayouter
->AddConstraints(index
, 1, itemMinHeight
,
1367 itemMaxHeight
, itemPreferredHeight
);
1371 // get the height for width info
1372 fCachedHeightForWidthWidth
= width
;
1373 fCachedMinHeightForWidth
= verticalLayouter
->MinSize();
1374 fCachedMaxHeightForWidth
= verticalLayouter
->MaxSize();
1375 fCachedPreferredHeightForWidth
= verticalLayouter
->PreferredSize();
1379 *minHeight
= fCachedMinHeightForWidth
;
1381 *maxHeight
= fCachedMaxHeightForWidth
;
1382 if (preferredHeight
)
1383 *preferredHeight
= fCachedPreferredHeightForWidth
;
1388 BSplitLayout::_SplitterSpace() const
1390 int32 splitters
= fSplitterItems
.CountItems();
1392 if (splitters
> 0) {
1393 space
= (fVisibleItems
.CountItems() + splitters
- 1) * fSpacing
1394 + splitters
* fSplitterSize
;
1402 BSplitLayout::_AddInsets(BSize size
)
1404 size
.width
= BLayoutUtils::AddDistances(size
.width
,
1405 fLeftInset
+ fRightInset
- 1);
1406 size
.height
= BLayoutUtils::AddDistances(size
.height
,
1407 fTopInset
+ fBottomInset
- 1);
1409 float spacing
= _SplitterSpace();
1410 if (fOrientation
== B_HORIZONTAL
)
1411 size
.width
= BLayoutUtils::AddDistances(size
.width
, spacing
- 1);
1413 size
.height
= BLayoutUtils::AddDistances(size
.height
, spacing
- 1);
1420 BSplitLayout::_AddInsets(float* minHeight
, float* maxHeight
,
1421 float* preferredHeight
)
1423 float insets
= fTopInset
+ fBottomInset
- 1;
1424 if (fOrientation
== B_VERTICAL
)
1425 insets
+= _SplitterSpace();
1427 *minHeight
= BLayoutUtils::AddDistances(*minHeight
, insets
);
1429 *maxHeight
= BLayoutUtils::AddDistances(*maxHeight
, insets
);
1430 if (preferredHeight
)
1431 *preferredHeight
= BLayoutUtils::AddDistances(*preferredHeight
, insets
);
1436 BSplitLayout::_SubtractInsets(BSize size
)
1438 size
.width
= BLayoutUtils::SubtractDistances(size
.width
,
1439 fLeftInset
+ fRightInset
- 1);
1440 size
.height
= BLayoutUtils::SubtractDistances(size
.height
,
1441 fTopInset
+ fBottomInset
- 1);
1443 float spacing
= _SplitterSpace();
1444 if (fOrientation
== B_HORIZONTAL
)
1445 size
.width
= BLayoutUtils::SubtractDistances(size
.width
, spacing
- 1);
1447 size
.height
= BLayoutUtils::SubtractDistances(size
.height
, spacing
- 1);