* Implemented Min/Max/PreferredSize().

* Reworked the internal layout. _ValidateLayoutData() computes and caches the
  layout related data and all other methods just use those values. Now, in
  layout-aware mode the class should properly work not only when using the
  layout items. And when using layout items, the class does actually do
  internal layout; it was basically good luck that it worked in the tests,
  before. Vertical resizing is supported, too. 
* We do a few mean tricks to get the probably mostly preferred layout behavior:
  By default our own explicit max width and that of the menu bar layout item is
  set to unlimited and the horizontal menu bar alignment to left aligned. This
  allows to horizontally resize a BMenuField beyond its preferred size,
  although both label and menu bar have a limited max width. The user can, of
  course, override those explicit sizes/alignments to get a different behavior,
  if desired.
* Fixed invalidation in SetDivider(). When having the focus, the left and top
  border of the blue frame were not invalidated.
* The label is no longer drawn at vertical position font ascent + descent
  + leading + 2 (not sure how this calculation was supposed to work), but
  vertically centers the label around the ascent. With big fonts the label is
  shown a bit too far to the bottom. Not sure how to fix this in a generic way.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21466 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold 2007-06-19 23:38:00 +00:00
parent ec592aa9e5
commit b321303cf9
2 changed files with 254 additions and 98 deletions

View File

@ -28,7 +28,7 @@ class BMenuField : public BView {
BMessage* message,
uint32 flags = B_WILL_DRAW | B_NAVIGABLE);
BMenuField(const char* label,
BMenu* menu, BMessage* message);
BMenu* menu, BMessage* message = NULL);
BMenuField(BMessage* data);
virtual ~BMenuField();
@ -77,20 +77,31 @@ class BMenuField : public BView {
virtual void ResizeToPreferred();
virtual void GetPreferredSize(float* width, float* height);
BLayoutItem* CreateLabelLayoutItem();
BLayoutItem* CreateMenuBarLayoutItem();
virtual BSize MinSize();
virtual BSize MaxSize();
virtual BSize PreferredSize();
virtual void InvalidateLayout(bool descendants = false);
BLayoutItem* CreateLabelLayoutItem();
BLayoutItem* CreateMenuBarLayoutItem();
/*----- Private or reserved -----------------------------------------*/
virtual status_t Perform(perform_code d, void* arg);
protected:
virtual void DoLayout();
private:
class LabelLayoutItem;
class MenuBarLayoutItem;
struct LayoutData;
friend class _BMCMenuBar_;
friend class LabelLayoutItem;
friend class MenuBarLayoutItem;
friend class LayoutData;
virtual void _ReservedMenuField1();
virtual void _ReservedMenuField2();
@ -108,22 +119,22 @@ class BMenuField : public BView {
void _InitMenuBar(BMenu* menu,
BRect frame, bool fixedSize);
void _ValidateLayoutData();
char* fLabel;
BMenu* fMenu;
BMenuBar* fMenuBar;
alignment fAlign;
float fDivider;
float fStringWidth;
bool fEnabled;
bool fSelected;
bool fTransition;
bool fFixedSizeMB;
thread_id fMenuTaskID;
BLayoutItem* fLabelLayoutItem;
BLayoutItem* fMenuBarLayoutItem;
LayoutData* fLayoutData;
uint32 _reserved[1];
uint32 _reserved[3];
};
#endif // _MENU_FIELD_H

View File

@ -66,6 +66,28 @@ private:
};
struct BMenuField::LayoutData {
LayoutData()
: label_layout_item(NULL),
menu_bar_layout_item(NULL),
previous_height(-1),
valid(false)
{
}
LabelLayoutItem* label_layout_item;
MenuBarLayoutItem* menu_bar_layout_item;
float previous_height; // used in FrameResized() for
// invalidation
font_height font_info;
float label_width;
float label_height;
BSize min;
BSize menu_bar_min;
bool valid;
};
// #pragma mark -
@ -168,6 +190,8 @@ BMenuField::~BMenuField()
status_t dummy;
if (fMenuTaskID >= 0)
wait_for_thread(fMenuTaskID, &dummy);
delete fLayoutData;
}
@ -258,9 +282,6 @@ BMenuField::AttachedToWindow()
SetViewColor(Parent()->ViewColor());
SetLowColor(Parent()->ViewColor());
}
if (fLabel)
fStringWidth = StringWidth(fLabel);
}
@ -327,7 +348,7 @@ BMenuField::MakeFocus(bool state)
BView::MakeFocus(state);
if (Window())
Invalidate(); // TODO: use fStringWidth
Invalidate(); // TODO: use fLayoutData->label_width
}
@ -387,6 +408,15 @@ void
BMenuField::FrameResized(float newWidth, float newHeight)
{
BView::FrameResized(newWidth, newHeight);
if (newHeight != fLayoutData->previous_height && Label()) {
// The height changed, which means the label has to move and we
// probably also invalidate a part of the borders around the menu bar.
// So don't be shy and invalidate the whole thing.
Invalidate();
}
fLayoutData->previous_height = newHeight;
}
@ -423,11 +453,8 @@ BMenuField::SetLabel(const char *label)
fLabel = strdup(label);
if (Window()) {
if (Window())
Invalidate();
if (fLabel)
fStringWidth = StringWidth(fLabel);
}
InvalidateLayout();
}
@ -489,23 +516,25 @@ BMenuField::SetDivider(float divider)
fDivider = divider;
BRect dirty(fMenuBar->Frame());
if (Flags() & B_SUPPORTS_LAYOUT) {
// We should never get here, since layout support means, we also
// layout the divider, and don't use this method at all.
Relayout();
} else {
BRect dirty(fMenuBar->Frame());
fMenuBar->MoveTo(fDivider + 1, kVMargin);
fMenuBar->MoveTo(fDivider + 1, kVMargin);
if (fFixedSizeMB) {
fMenuBar->ResizeTo(Bounds().Width() - fDivider + 1 - 2,
dirty.Height());
if (fFixedSizeMB) {
fMenuBar->ResizeTo(Bounds().Width() - fDivider + 1 - 2,
dirty.Height());
}
dirty = dirty | fMenuBar->Frame();
dirty.InsetBy(-kVMargin, -kVMargin);
Invalidate(dirty);
}
dirty = dirty | fMenuBar->Frame();
dirty.left -= 1;
dirty.top -= 1;
dirty.right += 2;
dirty.bottom += 2;
Invalidate(dirty);
}
@ -563,57 +592,64 @@ BMenuField::ResizeToPreferred()
void
BMenuField::GetPreferredSize(float *_width, float *_height)
{
fMenuBar->GetPreferredSize(_width, _height);
_ValidateLayoutData();
if (_width) {
// the width is already the menu bars preferred width
// add the room we need to draw the shadow and stuff
*_width += 2 * kVMargin;
if (_width)
*_width = fLayoutData->min.width;
// add the room needed for the label
float labelWidth = fDivider;
if (Label()) {
labelWidth = ceilf(StringWidth(Label()));
if (labelWidth > 0.0) {
// add some room between label and menu bar
labelWidth += 5.0;
}
if (_height)
*_height = fLayoutData->min.height;
}
// have divider override the calculated label width
labelWidth = max_c(labelWidth, fDivider);
}
*_width += labelWidth;
}
BSize
BMenuField::MinSize()
{
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
}
if (_height) {
// the height is already the menu bars preferred height
// add the room we need to draw the shadow and stuff
*_height += 2 * kVMargin;
// see if our label would fit vertically
font_height fh;
GetFontHeight(&fh);
*_height = max_c(*_height, ceilf(fh.ascent + fh.descent));
}
BSize
BMenuField::MaxSize()
{
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), fLayoutData->min);
}
BSize
BMenuField::PreferredSize()
{
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
}
void
BMenuField::InvalidateLayout(bool descendants)
{
fLayoutData->valid = false;
BView::InvalidateLayout(descendants);
}
BLayoutItem*
BMenuField::CreateLabelLayoutItem()
{
if (!fLabelLayoutItem)
fLabelLayoutItem = new LabelLayoutItem(this);
return fLabelLayoutItem;
if (!fLayoutData->label_layout_item)
fLayoutData->label_layout_item = new LabelLayoutItem(this);
return fLayoutData->label_layout_item;
}
BLayoutItem*
BMenuField::CreateMenuBarLayoutItem()
{
if (!fMenuBarLayoutItem)
fMenuBarLayoutItem = new MenuBarLayoutItem(this);
return fMenuBarLayoutItem;
if (!fLayoutData->menu_bar_layout_item)
fLayoutData->menu_bar_layout_item = new MenuBarLayoutItem(this);
return fLayoutData->menu_bar_layout_item;
}
@ -624,6 +660,58 @@ BMenuField::Perform(perform_code d, void *arg)
}
void
BMenuField::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();
// validate current size
BSize size(Bounds().Size());
if (size.width < fLayoutData->min.width)
size.width = fLayoutData->min.width;
if (size.height < fLayoutData->min.height)
size.height = fLayoutData->min.height;
// divider
float divider = 0;
if (fLayoutData->label_layout_item && fLayoutData->menu_bar_layout_item) {
// We have layout items. They define the divider location.
divider = fLayoutData->menu_bar_layout_item->Frame().left
- fLayoutData->label_layout_item->Frame().left;
} else {
if (fLayoutData->label_width > 0)
divider = fLayoutData->label_width + 5;
}
// menu bar
BRect dirty(fMenuBar->Frame());
BRect menuBarFrame(divider + 1, kVMargin, size.width - 2,
size.height - kVMargin);
// place the menu bar and set the divider
BLayoutUtils::AlignInFrame(fMenuBar, menuBarFrame);
fDivider = divider;
// invalidate dirty region
dirty = dirty | fMenuBar->Frame();
dirty.InsetBy(-kVMargin, -kVMargin);
Invalidate(dirty);
}
void BMenuField::_ReservedMenuField1() {}
void BMenuField::_ReservedMenuField2() {}
void BMenuField::_ReservedMenuField3() {}
@ -643,14 +731,12 @@ BMenuField::InitObject(const char *label)
fMenu = NULL;
fMenuBar = NULL;
fAlign = B_ALIGN_LEFT;
fStringWidth = 0;
fEnabled = true;
fSelected = false;
fTransition = false;
fFixedSizeMB = false;
fMenuTaskID = -1;
fLabelLayoutItem = NULL;
fMenuBarLayoutItem = NULL;
fLayoutData = new LayoutData;
SetLabel(label);
@ -658,6 +744,9 @@ BMenuField::InitObject(const char *label)
fDivider = (float)floor(Frame().Width() / 2.0f);
else
fDivider = 0;
// default to unlimited maximum width
SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
}
@ -679,22 +768,21 @@ BMenuField::InitObject2()
void
BMenuField::DrawLabel(BRect bounds, BRect update)
{
font_height fh;
GetFontHeight(&fh);
_ValidateLayoutData();
font_height& fh = fLayoutData->font_info;
if (Label()) {
SetLowColor(ViewColor());
float y = (float)ceil(fh.ascent + fh.descent + fh.leading) + 2.0f;
// horizontal alignment
float x;
switch (fAlign) {
case B_ALIGN_RIGHT:
x = fDivider - StringWidth(Label()) - 3.0f;
x = fDivider - fLayoutData->label_width - 3.0f;
break;
case B_ALIGN_CENTER:
x = fDivider - StringWidth(Label()) / 2.0f;
x = fDivider - fLayoutData->label_width / 2.0f;
break;
default:
@ -702,6 +790,9 @@ BMenuField::DrawLabel(BRect bounds, BRect update)
break;
}
// vertical alignment -- center the ascent
float y = floor((Bounds().Height() + fh.ascent) / 2);
SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
IsEnabled() ? B_DARKEN_MAX_TINT : B_DISABLED_LABEL_TINT));
DrawString(Label(), BPoint(x, y));
@ -760,13 +851,24 @@ BMenuField::MenuTask(void *arg)
void
BMenuField::_UpdateFrame()
{
if (fLabelLayoutItem && fMenuBarLayoutItem) {
BRect labelFrame = fLabelLayoutItem->Frame();
BRect menuFrame = fMenuBarLayoutItem->Frame();
if (fLayoutData->label_layout_item && fLayoutData->menu_bar_layout_item) {
BRect labelFrame = fLayoutData->label_layout_item->Frame();
BRect menuFrame = fLayoutData->menu_bar_layout_item->Frame();
// update divider
fDivider = menuFrame.left - labelFrame.left;
// update our frame
MoveTo(labelFrame.left, labelFrame.top);
BSize oldSize = Bounds().Size();
ResizeTo(menuFrame.left + menuFrame.Width() - labelFrame.left,
menuFrame.top + menuFrame.Height() - labelFrame.top);
SetDivider(menuFrame.left - labelFrame.left);
BSize newSize = Bounds().Size();
// If the size changes, ResizeTo() will trigger a relayout, otherwise
// we need to do that explicitly.
if (newSize != oldSize)
Relayout();
}
}
@ -781,6 +883,10 @@ BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize)
frame.top + kVMargin, frame.right, frame.bottom - kVMargin),
fixedSize, this);
// by default align the menu bar left in the available space
fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
B_ALIGN_VERTICAL_UNSET));
AddChild(fMenuBar);
fMenuBar->AddItem(menu);
@ -788,6 +894,53 @@ BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize)
}
void
BMenuField::_ValidateLayoutData()
{
if (fLayoutData->valid)
return;
// cache font height
font_height& fh = fLayoutData->font_info;
GetFontHeight(&fh);
fLayoutData->label_width = (Label() ? ceilf(StringWidth(Label())) : 0);
fLayoutData->label_height = ceilf(fh.ascent + fh.descent);
// compute the minimal divider
float divider = 0;
if (fLayoutData->label_width > 0)
divider = fLayoutData->label_width + 5;
// If we shan't do real layout, we let the current divider take influence.
if (!(Flags() & B_SUPPORTS_LAYOUT))
divider = max_c(divider, fDivider);
// get the minimal (== preferred) menu bar size
fLayoutData->menu_bar_min = fMenuBar->MinSize();
// compute our minimal (== preferred) size
// TODO: The layout is a bit broken. A one pixel wide border is draw around
// the menu bar to give it it's look. When the view has the focus,
// additionally a one pixel wide blue frame is drawn around it. In order
// to be able to easily visually align the menu bar with the text view of
// a text control, the divider must ignore the focus frame, though. Hence
// we add one less pixel to our width.
BSize min(fLayoutData->menu_bar_min);
min.width += 2 * kVMargin - 1;
min.height += 2 * kVMargin;
if (fLayoutData->label_width > 0)
min.width += fLayoutData->label_width + 5;
if (fLayoutData->label_height > min.height)
min.height = fLayoutData->label_height;
fLayoutData->min = min;
fLayoutData->valid = true;
}
// #pragma mark -
@ -837,17 +990,13 @@ BMenuField::LabelLayoutItem::View()
BSize
BMenuField::LabelLayoutItem::BaseMinSize()
{
// TODO: Cache the info. Might be too expensive for this call.
const char* label = fParent->Label();
if (!label)
fParent->_ValidateLayoutData();
if (!fParent->Label())
return BSize(-1, -1);
BSize size;
fParent->GetPreferredSize(NULL, &size.height);
size.width = fParent->StringWidth(label) + 2 * 3 - 1;
return size;
return BSize(fParent->fLayoutData->label_width + 5,
fParent->fLayoutData->label_height);
}
@ -879,6 +1028,9 @@ BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMenuField* parent)
: fParent(parent),
fFrame()
{
// by default the part left of the divider shall have an unlimited maximum
// width
SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
}
@ -921,14 +1073,12 @@ BMenuField::MenuBarLayoutItem::View()
BSize
BMenuField::MenuBarLayoutItem::BaseMinSize()
{
// TODO: Cache the info. Might be too expensive for this call.
BSize size;
fParent->fMenuBar->GetPreferredSize(&size.width, &size.height);
fParent->_ValidateLayoutData();
// BMenuField draws additional lines around BMenuBar,
// one line on left and top side, two on right and bottom
size.width += 3;
size.height += 3;
// TODO: Cf. the TODO in _ValidateLayoutData().
BSize size = fParent->fLayoutData->menu_bar_min;
size.width += 2 * kVMargin - 1;
size.height += 2 * kVMargin;
return size;
}
@ -937,19 +1087,14 @@ BMenuField::MenuBarLayoutItem::BaseMinSize()
BSize
BMenuField::MenuBarLayoutItem::BaseMaxSize()
{
BSize size(BaseMinSize());
size.width = B_SIZE_UNLIMITED;
return size;
return BaseMinSize();
}
BSize
BMenuField::MenuBarLayoutItem::BasePreferredSize()
{
BSize size(BaseMinSize());
// puh, no idea...
size.width = 100;
return size;
return BaseMinSize();
}