From caf8aba2c926b8ebaa3eb8db3aa7b5ff77048f2b Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Fri, 8 Jun 2007 23:40:16 +0000 Subject: [PATCH] * Added layout-friendly constructors and implemented Min/Max/PreferredSize(), and DoLayout(). When the B_SUPPORTS_LAYOUT view flag is set (as is by default when using one of the new constructors) the BBox completely manages one true child (the first child that is not the label view). * Centralized the layout related computation in new method _ValidateLayoutData(). The computed infos are cached in a new private LayoutData structure. * GetPreferredSize() was broken in several respects. It does now return the same result as PreferredSize(). If B_SUPPORTS_LAYOUT is not set, these are the sums of the insets induces by the frame and the label. I.e. those values can for instance be added to the child's preferred size to compute the preferred size of the compound. Not sure, if the Haiku-only TopBorderOffset() and InnerFrame() functions still make sense. With layout management they're actually superfluous. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21356 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/os/interface/Box.h | 23 ++- src/kits/interface/Box.cpp | 339 ++++++++++++++++++++++++++++--------- 2 files changed, 276 insertions(+), 86 deletions(-) diff --git a/headers/os/interface/Box.h b/headers/os/interface/Box.h index eca9488820..24bf907e05 100644 --- a/headers/os/interface/Box.h +++ b/headers/os/interface/Box.h @@ -12,10 +12,17 @@ class BBox : public BView { public: BBox(BRect frame, const char *name = NULL, - uint32 resizingMode = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 resizingMode = B_FOLLOW_LEFT + | B_FOLLOW_TOP, uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, border_style border = B_FANCY_BORDER); + BBox(const char* name, + uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS + | B_NAVIGABLE_JUMP, + border_style border = B_FANCY_BORDER, + BView* child = NULL); + BBox(border_style border, BView* child); virtual ~BBox(); /* Archiving */ @@ -60,7 +67,16 @@ class BBox : public BView { virtual status_t Perform(perform_code d, void* arg); + virtual BSize MinSize(); + virtual BSize MaxSize(); + virtual BSize PreferredSize(); + + virtual void InvalidateLayout(bool descendants = false); + virtual void DoLayout(); + private: + struct LayoutData; + virtual void _ReservedBox1(); virtual void _ReservedBox2(); @@ -71,11 +87,14 @@ class BBox : public BView { void _DrawFancy(BRect labelBox); void _ClearLabel(); + BView* _Child() const; + void _ValidateLayoutData(); + char* fLabel; BRect fBounds; border_style fStyle; BView* fLabelView; - BRect* fLabelBox; + LayoutData* fLayoutData; }; #endif // _BOX_H diff --git a/src/kits/interface/Box.cpp b/src/kits/interface/Box.cpp index d091e193d4..1551ff6039 100644 --- a/src/kits/interface/Box.cpp +++ b/src/kits/interface/Box.cpp @@ -11,18 +11,37 @@ #include -#include -#include #include #include #include +#include +#include +#include +#include + + +struct BBox::LayoutData { + LayoutData() + : valid(false) + { + } + + BRect label_box; // label box (label string or label view); in case + // of a label string not including descent + BRect insets; // insets induced by border and label + BSize min; + BSize max; + BSize preferred; + bool valid; // validity the other fields +}; + BBox::BBox(BRect frame, const char *name, uint32 resizingMode, uint32 flags, border_style border) - : BView(frame, name, resizingMode, flags | B_FRAME_EVENTS), - fStyle(border) + : BView(frame, name, resizingMode, flags | B_WILL_DRAW | B_FRAME_EVENTS), + fStyle(border) { SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); @@ -31,6 +50,36 @@ BBox::BBox(BRect frame, const char *name, uint32 resizingMode, uint32 flags, } +BBox::BBox(const char* name, uint32 flags, border_style border, BView* child) + : BView(BRect(0, 0, -1, -1), name, B_FOLLOW_NONE, + flags | B_WILL_DRAW | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT), + fStyle(border) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + _InitObject(); + + if (child) + AddChild(child); +} + + +BBox::BBox(border_style border, BView* child) + : BView(BRect(0, 0, -1, -1), NULL, B_FOLLOW_NONE, + B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP | B_SUPPORTS_LAYOUT), + fStyle(border) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + _InitObject(); + + if (child) + AddChild(child); +} + + BBox::BBox(BMessage *archive) : BView(archive) { @@ -41,6 +90,8 @@ BBox::BBox(BMessage *archive) BBox::~BBox() { _ClearLabel(); + + delete fLayoutData; } @@ -75,8 +126,13 @@ BBox::Archive(BMessage *archive, bool deep) const void BBox::SetBorder(border_style border) { + if (border == fStyle) + return; + fStyle = border; + InvalidateLayout(); + if (Window() != NULL && LockLooper()) { Invalidate(); UnlockLooper(); @@ -95,14 +151,12 @@ BBox::Border() const float BBox::TopBorderOffset() { - float labelHeight = 0; + _ValidateLayoutData(); - if (fLabel != NULL) - labelHeight = fLabelBox->Height(); - else if (fLabelView != NULL) - labelHeight = fLabelView->Bounds().Height(); + if (fLabel != NULL || fLabelView != NULL) + return fLayoutData->label_box.Height() / 2; - return ceilf(labelHeight / 2.0f); + return 0; } @@ -110,24 +164,15 @@ BBox::TopBorderOffset() BRect BBox::InnerFrame() { - float borderSize = Border() == B_FANCY_BORDER ? 2.0f - : Border() == B_PLAIN_BORDER ? 1.0f : 0.0f; - float labelHeight = 0.0f; + _ValidateLayoutData(); - if (fLabel != NULL) { - // fLabelBox doesn't contain the font's descent, but we want it here - font_height fontHeight; - GetFontHeight(&fontHeight); + BRect frame(Bounds()); + frame.left += fLayoutData->insets.left; + frame.top += fLayoutData->insets.top; + frame.right -= fLayoutData->insets.right; + frame.bottom -= fLayoutData->insets.bottom; - labelHeight = ceilf(fontHeight.ascent + fontHeight.descent); - } else if (fLabelView != NULL) - labelHeight = fLabelView->Bounds().Height(); - - BRect rect = Bounds().InsetByCopy(borderSize, borderSize); - if (labelHeight) - rect.top = Bounds().top + labelHeight; - - return rect; + return frame; } @@ -136,17 +181,10 @@ BBox::SetLabel(const char *string) { _ClearLabel(); - if (string) { - font_height fontHeight; - GetFontHeight(&fontHeight); - + if (string) fLabel = strdup(string); - // leave 6 pixels of the frame, and have a gap of 4 pixels between - // the frame and the text on both sides - fLabelBox = new BRect(6.0f, 0, StringWidth(string) + 14.0f, - ceilf(fontHeight.ascent)); - } + InvalidateLayout(); if (Window()) Invalidate(); @@ -164,6 +202,8 @@ BBox::SetLabel(BView *viewLabel) AddChild(fLabelView, ChildAt(0)); } + InvalidateLayout(); + if (Window()) Invalidate(); @@ -188,11 +228,13 @@ BBox::LabelView() const void BBox::Draw(BRect updateRect) { + _ValidateLayoutData(); + PushState(); BRect labelBox = BRect(0, 0, 0, 0); if (fLabel != NULL) { - labelBox = *fLabelBox; + labelBox = fLayoutData->label_box; BRegion update(updateRect); update.Exclude(labelBox); @@ -376,54 +418,12 @@ BBox::ResizeToPreferred() void BBox::GetPreferredSize(float *_width, float *_height) { - float width, height; - bool label = true; - - // acount for label - if (fLabelView) { - fLabelView->GetPreferredSize(&width, &height); - width += 10.0; - // the label view is placed 10 pixels from the left - } else if (fLabel) { - font_height fh; - GetFontHeight(&fh); - width += ceilf(StringWidth(fLabel)); - height += ceilf(fh.ascent + fh.descent); - } else { - label = false; - width = 0; - height = 0; - } - - // acount for border - switch (fStyle) { - case B_NO_BORDER: - break; - case B_PLAIN_BORDER: - // label: (1 pixel for border + 1 pixel for padding) * 2 - // no label: (1 pixel for border) * 2 + 1 pixel for padding - width += label ? 4 : 3; - // label: 1 pixel for bottom border + 1 pixel for padding - // no label: (1 pixel for border) * 2 + 1 pixel for padding - height += label ? 2 : 3; - break; - case B_FANCY_BORDER: - // label: (2 pixel for border + 1 pixel for padding) * 2 - // no label: (2 pixel for border) * 2 + 1 pixel for padding - width += label ? 6 : 5; - // label: 2 pixel for bottom border + 1 pixel for padding - // no label: (2 pixel for border) * 2 + 1 pixel for padding - height += label ? 3 : 5; - break; - } - // NOTE: children are ignored, you can use BBox::GetPreferredSize() - // to get the minimum size of this object, then add the size - // of your child(ren) plus inner padding for the final size + _ValidateLayoutData(); if (_width) - *_width = width; + *_width = fLayoutData->preferred.width; if (_height) - *_height = height; + *_height = fLayoutData->preferred.height; } @@ -448,6 +448,80 @@ BBox::Perform(perform_code d, void *arg) } +BSize +BBox::MinSize() +{ + _ValidateLayoutData(); + + BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min); + return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); +} + + +BSize +BBox::MaxSize() +{ + _ValidateLayoutData(); + + BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max); + return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); +} + + +BSize +BBox::PreferredSize() +{ + _ValidateLayoutData(); + + BSize size = (GetLayout() ? GetLayout()->PreferredSize() + : fLayoutData->preferred); + return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); +} + + +void +BBox::InvalidateLayout(bool descendants) +{ + fLayoutData->valid = false; + BView::InvalidateLayout(descendants); +} + + +void +BBox::DoLayout() +{ + // Bail out, if we shan't do layout. + if (!(Flags() & B_SUPPORTS_LAYOUT)) + return; + + // If the user set a layout, we let the base class version call its + // hook. + if (GetLayout()) { + BView::DoLayout(); + return; + } + + _ValidateLayoutData(); + + // layout the label view + if (fLabelView) { + fLabelView->MoveTo(fLayoutData->label_box.LeftTop()); + fLabelView->ResizeTo(fLayoutData->label_box.Size()); + } + + // layout the child + if (BView* child = _Child()) { + BRect frame(Bounds()); + frame.left += fLayoutData->insets.left; + frame.top += fLayoutData->insets.top; + frame.right -= fLayoutData->insets.right; + frame.bottom -= fLayoutData->insets.bottom; + + BLayoutUtils::AlignInFrame(child, frame); + } +} + + void BBox::_ReservedBox1() {} void BBox::_ReservedBox2() {} @@ -466,7 +540,7 @@ BBox::_InitObject(BMessage* archive) fLabel = NULL; fLabelView = NULL; - fLabelBox = NULL; + fLayoutData = new LayoutData; int32 flags = 0; @@ -588,13 +662,110 @@ BBox::_ClearLabel() fBounds.top = 0; if (fLabel) { - delete fLabelBox; free(fLabel); fLabel = NULL; - fLabelBox = NULL; } else if (fLabelView) { fLabelView->RemoveSelf(); delete fLabelView; fLabelView = NULL; } } + + +BView* +BBox::_Child() const +{ + for (int32 i = 0; BView* view = ChildAt(i); i++) { + if (view != fLabelView) + return view; + } + + return NULL; +} + + +void +BBox::_ValidateLayoutData() +{ + if (fLayoutData->valid) + return; + + // compute the label box, width and height + bool label = true; + float labelHeight = 0; // height of the label (pixel count) + if (fLabel) { + // leave 6 pixels of the frame, and have a gap of 4 pixels between + // the frame and the text on either side + font_height fontHeight; + GetFontHeight(&fontHeight); + fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel), + ceilf(fontHeight.ascent)); + labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1; + } else if (fLabelView) { + // the label view is placed at (0, 10) at its preferred size + BSize size = fLabelView->PreferredSize(); + fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height); + labelHeight = size.height + 1; + } else { + label = false; + } + + // border + switch (fStyle) { + case B_PLAIN_BORDER: + fLayoutData->insets.Set(1, 1, 1, 1); + break; + case B_FANCY_BORDER: + fLayoutData->insets.Set(2, 2, 2, 2); + break; + case B_NO_BORDER: + default: + fLayoutData->insets.Set(0, 0, 0, 0); + break; + } + + // if there's a label, the top inset will be dictated by the label + if (label && labelHeight > fLayoutData->insets.top) + fLayoutData->insets.top = labelHeight; + + // total number of pixel the border adds + float addWidth = fLayoutData->insets.left + fLayoutData->insets.right; + float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom; + + // compute the minimal width induced by the label + float minWidth; + if (label) + minWidth = fLayoutData->label_box.right + fLayoutData->insets.right; + else + minWidth = addWidth - 1; + + // finally consider the child constraints, if we shall support layout + BView* child = _Child(); + if (child && (Flags() & B_SUPPORTS_LAYOUT)) { + BSize min = child->MinSize(); + BSize max = child->MaxSize(); + BSize preferred = child->PreferredSize(); + + min.width += addWidth; + min.height += addHeight; + preferred.width += addWidth; + preferred.height += addHeight; + max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1); + max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1); + + if (min.width < minWidth) + min.width = minWidth; + BLayoutUtils::FixSizeConstraints(min, max, preferred); + + fLayoutData->min = min; + fLayoutData->max = max; + fLayoutData->preferred = preferred; + } else { + fLayoutData->min.Set(minWidth, addHeight - 1); + fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); + fLayoutData->preferred = fLayoutData->min; + } + + fLayoutData->valid = true; +} +