How to Build Resizable Haiku Interfaces with BLayout Instead of Fixed Coordinates
A step-by-step Haiku Layout API workflow for intrinsic sizes, nested groups, spacing, constraints, localization, resizing, and maintainable UI code.
Fixed pixel frames can make a Haiku window look correct on one machine and fail after font changes, localization, content growth, or resizing. The Layout API instead lets views report minimum, preferred, and maximum sizes while layout objects distribute available space. The result is not “automatic design”; it is an explicit hierarchy of constraints that the toolkit can recompute.
Start with semantic groups
Sketch the relationships before writing coordinates. A settings window might contain a vertical group with an explanatory label, a grid of labels and fields, and a trailing horizontal group for action buttons. The hierarchy should describe reading order and resizing behavior, not merely reproduce a screenshot.
BLayoutBuilder provides a concise way to construct common group and grid layouts. The exact builder overloads can vary with the target API, so compile against the shipped headers, but the pattern is stable:
BTextControl* server = new BTextControl("Server:", "", nullptr);
BTextControl* port = new BTextControl("Port:", "443", nullptr);
BButton* cancel = new BButton("Cancel", new BMessage(kCancel));
BButton* save = new BButton("Save", new BMessage(kSave));
SetLayout(new BGroupLayout(B_VERTICAL));
BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING)
.SetInsets(B_USE_WINDOW_SPACING)
.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
.Add(server->CreateLabelLayoutItem(), 0, 0)
.Add(server->CreateTextViewLayoutItem(), 1, 0)
.Add(port->CreateLabelLayoutItem(), 0, 1)
.Add(port->CreateTextViewLayoutItem(), 1, 1)
.End()
.AddGlue()
.AddGroup(B_HORIZONTAL)
.AddGlue()
.Add(cancel)
.Add(save)
.End();
Use system spacing constants instead of inventing many nearly identical pixel values. Insets define the window edge; item spacing defines relationships inside a group. AddGlue() consumes flexible space and is useful for trailing buttons, but excessive glue can make the intended constraint difficult to understand.
Let controls declare intrinsic size
Standard controls calculate useful sizes from their font, label, border, and content. Avoid calling ResizeTo() afterward, because manual frames fight the layout manager. If a custom BView participates in layout, implement the sizing methods or set explicit size constraints that reflect real content.
Distinguish a minimum that prevents clipping from a preferred size that feels comfortable. A maximum of “unlimited” lets a field expand; a fixed maximum may be appropriate for a compact icon or button. Give the content view a layout first, call ResizeToPreferred() for an initial window size, then set a sensible minimum derived from GetLayout()->MinSize() rather than a magic constant.
Test pressure, growth, and localization
Resize the window to its minimum and beyond its preferred dimensions. Verify which fields grow, whether scroll views receive space, and whether buttons remain reachable. Replace labels with deliberately long translations and increase the system font size. A layout that survives only English defaults is still frame-dependent in disguise.
Dynamic content requires invalidation. When a custom view’s intrinsic requirements change, invalidate its layout so ancestors recompute. Do not recursively resize the whole window on every update; preserve user-selected size and allow the layout hierarchy to allocate current space.
Keyboard traversal and accessibility follow the semantic tree more naturally when related controls are created and added in a coherent order. Ensure labels really label controls, the default button is intentional, and a narrow window does not hide the only way to cancel an operation.
Alignment across nested groups deserves an explicit test. Grid columns can align labels inside one grid, but two separate grids do not automatically share column widths. If sections must align, place them in one suitable grid or connect their layout items through the API supported by the target Haiku version. Avoid measuring a translated label manually and copying that width into another view; the font, language, and control decoration can change after the value was calculated.
Finally, inspect the interface with real data: empty strings, validation errors, long paths, and translated labels. The Layout API provides a solver; good results come from accurate intrinsic sizes, a legible hierarchy, system metrics, and explicit decisions about which regions may stretch.
Related:
- How to Build a Haiku Replicant That Can Live on the Desktop or Deskbar
- Fixing a Haiku UEFI Installation That Boots Only from the Installer Media
Sources: