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; +} +