2 * Copyright 2001-2017 Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT license.
6 * Stephan Aßmus, superstippi@gmx.de
7 * DarkWyrm, bpmagic@columbus.rr.com
8 * Axel Dörfler, axeld@pinc-software.de
9 * Marc Flerackers, mflerackers@androme.be
10 * John Scipione, jscipione@gmail.com
20 #include <ControlLook.h>
22 #include <LayoutUtils.h>
26 #include <binary_compatibility/Interface.h>
29 struct BBox::LayoutData
{
35 BRect label_box
; // label box (label string or label view); in case
36 // of a label string not including descent
37 BRect insets
; // insets induced by border and label
42 bool valid
; // validity the other fields
46 BBox::BBox(BRect frame
, const char* name
, uint32 resizingMode
, uint32 flags
,
49 BView(frame
, name
, resizingMode
, flags
| B_WILL_DRAW
| B_FRAME_EVENTS
),
56 BBox::BBox(const char* name
, uint32 flags
, border_style border
, BView
* child
)
58 BView(name
, flags
| B_WILL_DRAW
| B_FRAME_EVENTS
),
68 BBox::BBox(border_style border
, BView
* child
)
70 BView(NULL
, B_WILL_DRAW
| B_FRAME_EVENTS
| B_NAVIGABLE_JUMP
),
80 BBox::BBox(BMessage
* archive
)
83 fStyle(B_FANCY_BORDER
)
98 BBox::Instantiate(BMessage
* archive
)
100 if (validate_instantiation(archive
, "BBox"))
101 return new BBox(archive
);
108 BBox::Archive(BMessage
* archive
, bool deep
) const
110 status_t ret
= BView::Archive(archive
, deep
);
112 if (fLabel
&& ret
== B_OK
)
113 ret
= archive
->AddString("_label", fLabel
);
115 if (fLabelView
&& ret
== B_OK
)
116 ret
= archive
->AddBool("_lblview", true);
118 if (fStyle
!= B_FANCY_BORDER
&& ret
== B_OK
)
119 ret
= archive
->AddInt32("_style", fStyle
);
126 BBox::SetBorder(border_style border
)
128 if (border
== fStyle
)
135 if (Window() != NULL
&& LockLooper()) {
149 //! This function is not part of the R5 API and is not yet finalized yet
151 BBox::TopBorderOffset()
153 _ValidateLayoutData();
155 if (fLabel
!= NULL
|| fLabelView
!= NULL
)
156 return fLayoutData
->label_box
.Height() / 2;
162 //! This function is not part of the R5 API and is not yet finalized yet
166 _ValidateLayoutData();
168 BRect
frame(Bounds());
169 frame
.left
+= fLayoutData
->insets
.left
;
170 frame
.top
+= fLayoutData
->insets
.top
;
171 frame
.right
-= fLayoutData
->insets
.right
;
172 frame
.bottom
-= fLayoutData
->insets
.bottom
;
179 BBox::SetLabel(const char* string
)
184 fLabel
= strdup(string
);
194 BBox::SetLabel(BView
* viewLabel
)
199 fLabelView
= viewLabel
;
200 fLabelView
->MoveTo(10.0f
, 0.0f
);
201 AddChild(fLabelView
, ChildAt(0));
221 BBox::LabelView() const
228 BBox::Draw(BRect updateRect
)
230 _ValidateLayoutData();
234 BRect labelBox
= BRect(0, 0, 0, 0);
235 if (fLabel
!= NULL
) {
236 labelBox
= fLayoutData
->label_box
;
237 BRegion
update(updateRect
);
238 update
.Exclude(labelBox
);
240 ConstrainClippingRegion(&update
);
241 } else if (fLabelView
!= NULL
)
242 labelBox
= fLabelView
->Bounds();
246 _DrawFancy(labelBox
);
250 _DrawPlain(labelBox
);
258 ConstrainClippingRegion(NULL
);
260 font_height fontHeight
;
261 GetFontHeight(&fontHeight
);
263 SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
264 DrawString(fLabel
, BPoint(10.0f
, ceilf(fontHeight
.ascent
)));
272 BBox::AttachedToWindow()
276 // Force low color to match view color for proper label drawing.
277 float viewTint
= B_NO_TINT
;
278 float lowTint
= B_NO_TINT
;
280 if (LowUIColor(&lowTint
) != ViewUIColor(&viewTint
) || viewTint
!= lowTint
)
281 SetLowUIColor(ViewUIColor(), viewTint
);
282 else if (LowColor() != ViewColor())
283 SetLowColor(ViewColor());
285 if (ViewColor() == B_TRANSPARENT_COLOR
)
288 // The box could have been resized in the mean time
289 fBounds
= Bounds().OffsetToCopy(0, 0);
294 BBox::DetachedFromWindow()
296 BView::DetachedFromWindow();
303 BView::AllAttached();
310 BView::AllDetached();
315 BBox::FrameResized(float width
, float height
)
317 BRect
bounds(Bounds());
319 // invalidate the regions that the app_server did not
320 // (for removing the previous or drawing the new border)
321 if (fStyle
!= B_NO_BORDER
) {
322 // TODO: this must be made part of the be_control_look stuff!
323 int32 borderSize
= fStyle
== B_PLAIN_BORDER
? 0 : 2;
326 BRect
invalid(bounds
);
327 if (fBounds
.Width() < bounds
.Width()) {
329 invalid
.left
= bounds
.left
+ fBounds
.right
- borderSize
;
330 invalid
.right
= bounds
.left
+ fBounds
.right
;
333 } else if (fBounds
.Width() > bounds
.Width()) {
335 invalid
.left
= bounds
.left
+ bounds
.right
- borderSize
;
342 if (fBounds
.Height() < bounds
.Height()) {
344 invalid
.top
= bounds
.top
+ fBounds
.bottom
- borderSize
;
345 invalid
.bottom
= bounds
.top
+ fBounds
.bottom
;
348 } else if (fBounds
.Height() > bounds
.Height()) {
350 invalid
.top
= bounds
.top
+ bounds
.bottom
- borderSize
;
356 fBounds
.right
= width
;
357 fBounds
.bottom
= height
;
362 BBox::MessageReceived(BMessage
* message
)
364 BView::MessageReceived(message
);
369 BBox::MouseDown(BPoint point
)
371 BView::MouseDown(point
);
376 BBox::MouseUp(BPoint point
)
378 BView::MouseUp(point
);
383 BBox::WindowActivated(bool active
)
385 BView::WindowActivated(active
);
390 BBox::MouseMoved(BPoint point
, uint32 transit
, const BMessage
* message
)
392 BView::MouseMoved(point
, transit
, message
);
397 BBox::FrameMoved(BPoint newLocation
)
399 BView::FrameMoved(newLocation
);
404 BBox::ResolveSpecifier(BMessage
* message
, int32 index
, BMessage
* specifier
,
405 int32 what
, const char* property
)
407 return BView::ResolveSpecifier(message
, index
, specifier
, what
, property
);
412 BBox::ResizeToPreferred()
415 GetPreferredSize(&width
, &height
);
417 // make sure the box don't get smaller than it already is
418 if (width
< Bounds().Width())
419 width
= Bounds().Width();
420 if (height
< Bounds().Height())
421 height
= Bounds().Height();
423 BView::ResizeTo(width
, height
);
428 BBox::GetPreferredSize(float* _width
, float* _height
)
430 _ValidateLayoutData();
433 *_width
= fLayoutData
->preferred
.width
;
435 *_height
= fLayoutData
->preferred
.height
;
440 BBox::MakeFocus(bool focused
)
442 BView::MakeFocus(focused
);
447 BBox::GetSupportedSuites(BMessage
* message
)
449 return BView::GetSupportedSuites(message
);
454 BBox::Perform(perform_code code
, void* _data
)
457 case PERFORM_CODE_MIN_SIZE
:
458 ((perform_data_min_size
*)_data
)->return_value
461 case PERFORM_CODE_MAX_SIZE
:
462 ((perform_data_max_size
*)_data
)->return_value
465 case PERFORM_CODE_PREFERRED_SIZE
:
466 ((perform_data_preferred_size
*)_data
)->return_value
467 = BBox::PreferredSize();
469 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
470 ((perform_data_layout_alignment
*)_data
)->return_value
471 = BBox::LayoutAlignment();
473 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
474 ((perform_data_has_height_for_width
*)_data
)->return_value
475 = BBox::HasHeightForWidth();
477 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
479 perform_data_get_height_for_width
* data
480 = (perform_data_get_height_for_width
*)_data
;
481 BBox::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
485 case PERFORM_CODE_SET_LAYOUT
:
487 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
488 BBox::SetLayout(data
->layout
);
491 case PERFORM_CODE_LAYOUT_INVALIDATED
:
493 perform_data_layout_invalidated
* data
494 = (perform_data_layout_invalidated
*)_data
;
495 BBox::LayoutInvalidated(data
->descendants
);
498 case PERFORM_CODE_DO_LAYOUT
:
505 return BView::Perform(code
, _data
);
512 _ValidateLayoutData();
514 BSize size
= (GetLayout() ? GetLayout()->MinSize() : fLayoutData
->min
);
515 if (size
.width
< fLayoutData
->min
.width
)
516 size
.width
= fLayoutData
->min
.width
;
517 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size
);
524 _ValidateLayoutData();
526 BSize size
= (GetLayout() ? GetLayout()->MaxSize() : fLayoutData
->max
);
527 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size
);
532 BBox::PreferredSize()
534 _ValidateLayoutData();
536 BSize size
= (GetLayout() ? GetLayout()->PreferredSize()
537 : fLayoutData
->preferred
);
538 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size
);
543 BBox::LayoutAlignment()
545 _ValidateLayoutData();
547 BAlignment alignment
= (GetLayout() ? GetLayout()->Alignment()
548 : fLayoutData
->alignment
);
549 return BLayoutUtils::ComposeAlignment(ExplicitAlignment(), alignment
);
554 BBox::LayoutInvalidated(bool descendants
)
556 fLayoutData
->valid
= false;
563 // Bail out, if we shan't do layout.
564 if (!(Flags() & B_SUPPORTS_LAYOUT
))
567 BLayout
* layout
= GetLayout();
569 // If the user set a layout, let the base class version call its
570 // hook. In case when we have BView as a label, remove it from child list
571 // so it won't be layouted with the rest of views and add it again
573 if (layout
!= NULL
) {
575 RemoveChild(fLabelView
);
579 if (fLabelView
!= NULL
) {
580 DisableLayoutInvalidation();
581 // don't trigger a relayout
582 AddChild(fLabelView
, ChildAt(0));
583 EnableLayoutInvalidation();
587 _ValidateLayoutData();
589 // Even if the user set a layout, restore label view to it's
592 // layout the label view
593 if (fLabelView
!= NULL
) {
594 fLabelView
->MoveTo(fLayoutData
->label_box
.LeftTop());
595 fLabelView
->ResizeTo(fLayoutData
->label_box
.Size());
598 // If we have layout return here and do not layout the child
603 BView
* child
= _Child();
605 BRect
frame(Bounds());
606 frame
.left
+= fLayoutData
->insets
.left
;
607 frame
.top
+= fLayoutData
->insets
.top
;
608 frame
.right
-= fLayoutData
->insets
.right
;
609 frame
.bottom
-= fLayoutData
->insets
.bottom
;
611 if ((child
->Flags() & B_SUPPORTS_LAYOUT
) != 0)
612 BLayoutUtils::AlignInFrame(child
, frame
);
614 child
->MoveTo(frame
.LeftTop());
619 void BBox::_ReservedBox1() {}
620 void BBox::_ReservedBox2() {}
624 BBox::operator=(const BBox
&)
631 BBox::_InitObject(BMessage
* archive
)
633 fBounds
= Bounds().OffsetToCopy(0, 0);
637 fLayoutData
= new LayoutData
;
641 BFont
font(be_bold_font
);
643 if (!archive
|| !archive
->HasString("_fname"))
644 flags
= B_FONT_FAMILY_AND_STYLE
;
646 if (!archive
|| !archive
->HasFloat("_fflt"))
647 flags
|= B_FONT_SIZE
;
650 SetFont(&font
, flags
);
652 if (archive
!= NULL
) {
654 if (archive
->FindString("_label", &string
) == B_OK
)
660 if (archive
->FindBool("_style", &fancy
) == B_OK
)
661 fStyle
= fancy
? B_FANCY_BORDER
: B_PLAIN_BORDER
;
662 else if (archive
->FindInt32("_style", &style
) == B_OK
)
663 fStyle
= (border_style
)style
;
666 if (archive
->FindBool("_lblview", &hasLabelView
) == B_OK
)
667 fLabelView
= ChildAt(0);
675 BBox::_DrawPlain(BRect labelBox
)
677 BRect rect
= Bounds();
678 rect
.top
+= TopBorderOffset();
682 lightTint
= B_LIGHTEN_1_TINT
;
683 shadowTint
= B_DARKEN_1_TINT
;
685 if (rect
.Height() == 0.0 || rect
.Width() == 0.0) {
687 rgb_color shadow
= tint_color(ViewColor(), B_DARKEN_2_TINT
);
689 SetHighColor(shadow
);
690 StrokeLine(rect
.LeftTop(),rect
.RightBottom());
693 rgb_color light
= tint_color(ViewColor(), lightTint
);
694 rgb_color shadow
= tint_color(ViewColor(), shadowTint
);
697 AddLine(BPoint(rect
.left
, rect
.bottom
),
698 BPoint(rect
.left
, rect
.top
), light
);
699 AddLine(BPoint(rect
.left
+ 1.0f
, rect
.top
),
700 BPoint(rect
.right
, rect
.top
), light
);
701 AddLine(BPoint(rect
.left
+ 1.0f
, rect
.bottom
),
702 BPoint(rect
.right
, rect
.bottom
), shadow
);
703 AddLine(BPoint(rect
.right
, rect
.bottom
- 1.0f
),
704 BPoint(rect
.right
, rect
.top
+ 1.0f
), shadow
);
711 BBox::_DrawFancy(BRect labelBox
)
713 BRect rect
= Bounds();
714 rect
.top
+= TopBorderOffset();
716 rgb_color base
= ViewColor();
717 if (rect
.Height() == 1.0) {
718 // used as horizontal separator
719 be_control_look
->DrawGroupFrame(this, rect
, rect
, base
,
720 BControlLook::B_TOP_BORDER
);
721 } else if (rect
.Width() == 1.0) {
722 // used as vertical separator
723 be_control_look
->DrawGroupFrame(this, rect
, rect
, base
,
724 BControlLook::B_LEFT_BORDER
);
727 be_control_look
->DrawGroupFrame(this, rect
, rect
, base
);
738 } else if (fLabelView
) {
739 fLabelView
->RemoveSelf();
749 for (int32 i
= 0; BView
* view
= ChildAt(i
); i
++) {
750 if (view
!= fLabelView
)
759 BBox::_ValidateLayoutData()
761 if (fLayoutData
->valid
)
764 // compute the label box, width and height
766 float labelHeight
= 0; // height of the label (pixel count)
768 // leave 6 pixels of the frame, and have a gap of 4 pixels between
769 // the frame and the text on either side
770 font_height fontHeight
;
771 GetFontHeight(&fontHeight
);
772 fLayoutData
->label_box
.Set(6.0f
, 0, 14.0f
+ StringWidth(fLabel
),
773 ceilf(fontHeight
.ascent
));
774 labelHeight
= ceilf(fontHeight
.ascent
+ fontHeight
.descent
) + 1;
775 } else if (fLabelView
) {
776 // the label view is placed at (0, 10) at its preferred size
777 BSize size
= fLabelView
->PreferredSize();
778 fLayoutData
->label_box
.Set(10, 0, 10 + size
.width
, size
.height
);
779 labelHeight
= size
.height
+ 1;
786 fLayoutData
->insets
.Set(1, 1, 1, 1);
789 fLayoutData
->insets
.Set(3, 3, 3, 3);
793 fLayoutData
->insets
.Set(0, 0, 0, 0);
797 // if there's a label, the top inset will be dictated by the label
798 if (label
&& labelHeight
> fLayoutData
->insets
.top
)
799 fLayoutData
->insets
.top
= labelHeight
;
801 // total number of pixel the border adds
802 float addWidth
= fLayoutData
->insets
.left
+ fLayoutData
->insets
.right
;
803 float addHeight
= fLayoutData
->insets
.top
+ fLayoutData
->insets
.bottom
;
805 // compute the minimal width induced by the label
808 minWidth
= fLayoutData
->label_box
.right
+ fLayoutData
->insets
.right
;
810 minWidth
= addWidth
- 1;
812 BAlignment
alignment(B_ALIGN_HORIZONTAL_CENTER
, B_ALIGN_VERTICAL_CENTER
);
814 // finally consider the child constraints, if we shall support layout
815 BView
* child
= _Child();
816 if (child
&& (child
->Flags() & B_SUPPORTS_LAYOUT
)) {
817 BSize min
= child
->MinSize();
818 BSize max
= child
->MaxSize();
819 BSize preferred
= child
->PreferredSize();
821 min
.width
+= addWidth
;
822 min
.height
+= addHeight
;
823 preferred
.width
+= addWidth
;
824 preferred
.height
+= addHeight
;
825 max
.width
= BLayoutUtils::AddDistances(max
.width
, addWidth
- 1);
826 max
.height
= BLayoutUtils::AddDistances(max
.height
, addHeight
- 1);
828 if (min
.width
< minWidth
)
829 min
.width
= minWidth
;
830 BLayoutUtils::FixSizeConstraints(min
, max
, preferred
);
832 fLayoutData
->min
= min
;
833 fLayoutData
->max
= max
;
834 fLayoutData
->preferred
= preferred
;
836 BAlignment childAlignment
= child
->LayoutAlignment();
837 if (childAlignment
.horizontal
== B_ALIGN_USE_FULL_WIDTH
)
838 alignment
.horizontal
= B_ALIGN_USE_FULL_WIDTH
;
839 if (childAlignment
.vertical
== B_ALIGN_USE_FULL_HEIGHT
)
840 alignment
.vertical
= B_ALIGN_USE_FULL_HEIGHT
;
842 fLayoutData
->alignment
= alignment
;
844 fLayoutData
->min
.Set(minWidth
, addHeight
- 1);
845 fLayoutData
->max
.Set(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
);
846 fLayoutData
->preferred
= fLayoutData
->min
;
847 fLayoutData
->alignment
= alignment
;
850 fLayoutData
->valid
= true;
851 ResetLayoutInvalidation();
856 B_IF_GCC_2(InvalidateLayout__4BBoxb
, _ZN4BBox16InvalidateLayoutEb
)(
857 BBox
* box
, bool descendants
)
859 perform_data_layout_invalidated data
;
860 data
.descendants
= descendants
;
862 box
->Perform(PERFORM_CODE_LAYOUT_INVALIDATED
, &data
);