Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku BLayoutItem: Custom Measurement and Constraint Negotiation

Implement or configure Haiku BLayoutItem measurement with coherent min, preferred, max, alignment, height-for-width, and invalidation behavior.

BLayoutItem is the Interface Kit abstraction for something a BLayout can position and resize. A layout item can wrap a view or represent a non-view object such as a spacer. Most application code should let the standard layout classes own this negotiation. The class becomes especially important when diagnosing why a view refuses to shrink, why a translated label makes a row too wide, or why a custom non-view item consumes unexpected space.

The public API exposes minimum, preferred, and maximum sizes; alignment; visibility; frame access; optional height-for-width behavior; and layout invalidation. Haiku’s own API documentation includes a warning that BLayoutItem is not finalized and may break in a future version. Prefer standard layouts and built-in view wrappers unless a custom item solves a measured problem. If you subclass it, isolate the implementation, build against each supported Haiku target, and do not promise an unchanged ABI.

Treat size values as constraints, not decoration

MinSize() expresses the lower bound the layout should respect, PreferredSize() expresses the item’s requested natural size, and MaxSize() expresses the upper bound. These are not three alternate ways to state one preferred pixel size. A layout uses them with available frame space and sibling constraints to decide how to distribute room. An impossible combination, such as a maximum smaller than the minimum, forces layouts into behavior that may be hard to reason about.

For a view-backed item, explicit minimum, preferred, and maximum values can override or constrain automatic values. The SetExplicitSize() helper exists when all dimensions should be fixed together. Do not set a minimum equal to the current frame simply to stop one specific resize bug; that can make the entire window’s minimum size grow unexpectedly. Find whether the issue comes from font metrics, an unbreakable label, a hidden sibling, or an incorrect layout row.

BLayoutItem* item = formLayout->AddView(control);
if (item == NULL)
    return B_NO_MEMORY;

item->SetExplicitMinSize(BSize(140, 24));
item->SetExplicitPreferredSize(BSize(210, 24));
item->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 24));

This assumes formLayout is an already-owned layout and that the target control and API support these explicit constraints. B_SIZE_UNLIMITED is the public unconstrained-size sentinel; verify its availability in the target SDK. In a production helper, check the return from AddView() and unwind the view/layout tree if insertion fails. Prefer leaving the preferred width to the control when it naturally measures localized content.

Keep alignment separate from size

Alignment() asks where the item wants to be placed inside the frame a layout assigns it. A vertical group may allocate a common column width, while a label asks to align at the left and a compact icon asks to center. Alignment does not mean that the item can violate its min/max constraints, and a wide frame does not imply that the view should stretch to fill it.

An item can make its explicit alignment configurable where that behavior is appropriate. Do not encode parent-specific placement by returning a fixed alignment from a custom item if it should behave differently in another layout. Reuse the layout API’s item alignment controls instead. When a view’s content baseline matters, test baseline placement at the target font and scale; an edge alignment is not equivalent to typographic baseline alignment.

Visibility is also a layout input. IsVisible() and SetVisible() let a layout omit or include an item without necessarily destroying its view. Hiding a view and removing its item are different lifecycle decisions: a hidden item may retain state and return later, while removal changes ownership and invalidates pointers into the old layout. Ensure the parent recalculates after visibility changes and verify whether hidden items still contribute to the exact built-in layout you use.

Model text wrapping with height-for-width

Most items have size constraints that can be evaluated independently in each dimension. A wrapping text view is different: its height depends on the assigned width. HasHeightForWidth() reports this relationship, and GetHeightForWidth(width, min, max, preferred) provides the constraints for a particular width. The default implementation reports no such dependency.

If a custom item wraps text, return consistent height constraints for the width the layout proposes. Check each output pointer before writing to it; the public documentation explicitly recommends comparing them with null. Test narrow, preferred, and wide widths, including long unbreakable words and translated text. Avoid performing expensive document layout from a callback that may be queried repeatedly during resizing. Cache measurements by width and content generation, and invalidate the cache when either changes.

Height-for-width is a negotiation feature, not a command to resize a parent window. The layout still has to fit within its containing view and window. If a preferred height cannot fit, the application must decide whether to allow scrolling, clip content, or constrain the window. Do not report a fictional small minimum height merely to make a window open; users may then be unable to reach content or controls.

Invalidate layout when the inputs change

InvalidateLayout() marks a layout’s measurements or placement as stale; Relayout() requests recalculation, with an optional immediate mode. Use these methods when a custom item’s content, explicit constraints, or visibility changes outside the normal setter path. Avoid calling immediate relayout in every keystroke or paint callback. Batch updates and let the layout recompute once after a group of model changes.

The protected lifecycle hooks (AttachedToLayout(), DetachedFromLayout(), LayoutInvalidated(), and AncestorVisibilityChanged()) are useful for custom items that need to react to hierarchy changes. They are not substitutes for explicit ownership tracking. A detached item can outlive its former layout, so clear parent-specific references when the hook is called and do not keep using LayoutData() as though it belongs to the same owner.

LayoutData() and SetLayoutData() expose a void* slot associated with an item. If an application uses it, define the type, owner, and destructor path in one wrapper. Never store a pointer to a stack object or an object whose lifetime is shorter than the layout item. Since the slot is untyped, ordinary compiler checks cannot catch a stale cast or wrong data type.

Write a custom item only when a view is not enough

A BLayoutItem subclass must provide min, max, preferred size, alignment, explicit constraint setters, visibility methods, frame access, and the lifecycle contract required by its base. That is a substantial interface. If the content can be represented by a BView, use a view-backed item and put custom drawing or measurement in the view. A custom item is appropriate for a non-view spacer, a virtualized layout participant, or a bridge to an external object whose geometry does not belong in a view.

If implementing a custom item, make every reported property derive from one coherent model. Do not cache PreferredSize() independently from MinSize() if updating the text only invalidates one cache. Keep frame assignment and measurement separate: SetFrame() records the actual geometry selected by the layout, while size methods report the constraints that guided that selection.

Implement InvalidateLayout() behavior conservatively. When a descendant changes, determine whether the current item needs to propagate the change upward. Invalidating every ancestor for an unrelated paint-only state change can trigger excess work; failing to invalidate after a genuine size change produces stale clipping and overlapping siblings. Separate “redraw” from “measure/relayout” in the application model.

Test layout negotiation as a state matrix

Test an item at min, preferred, and max sizes; a parent smaller than the aggregate minimum; a parent larger than the aggregate preferred size; invisible and restored states; long localized text; font changes; and runtime constraint updates. For height-for-width, test multiple widths in both shrink and grow directions. Verify that the layout updates frames and redraws without recursive invalidation loops.

When a built-in layout behaves unexpectedly, inspect the actual BLayoutItem values after construction rather than guessing what a control “should” request. Record min, preferred, max, alignment, visibility, frame, and the parent layout at the point the issue occurs. That turns layout debugging into a constraint problem rather than a sequence of arbitrary ResizeTo() calls.

Because the API documentation marks BLayoutItem as not finalized, rebuild custom subclasses against every supported Haiku release and keep tests at the source level. A successful compile is not enough: run geometry and interaction tests on Haiku, because this host does not provide the native Interface Kit runtime. Use standard layout/view abstractions wherever they meet the requirement.

BLayoutItem is the negotiation boundary between content and geometry. Robust layouts report honest constraints, keep alignment separate, model width-dependent height explicitly, invalidate only when measurement changes, and make ownership clear. Custom subclasses should remain small and version-tested because the API is explicitly not finalized.

Related:

Sources:

Comments