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

Haiku BSplitView: Pane Weights, Constraints, and Collapse

Design Haiku resizable panes with BSplitView orientation, relative weights, minimum sizes, collapsibility, persistence, and keyboard verification.

BSplitView is a container whose children are arranged horizontally or vertically with draggable splitters between them. Users can allocate more space to one pane while keeping the other panes in the same view. It is useful for an inspector beside a document, a list above details, or a tool panel beside a canvas.

The splitter is a user-controlled layout boundary, not a promise that panes always keep a fixed ratio. Weights are relative, while each child also has minimum, maximum, and preferred-size constraints. A layout that works at a large desktop width can become infeasible when the window is narrow, fonts are enlarged, or one pane contains long localized strings. Design pane constraints and collapse policy before tuning initial weights.

Choose orientation and add through the layout

The constructor accepts an orientation such as B_HORIZONTAL or B_VERTICAL and a spacing value. A split view manages its own split layout; add pane views with the AddChild(BView*, float weight) overload so they become layout items. The separate AddChild(BView*, BView* sibling) overload is documented as bypassing the layout system and should not be used for ordinary panes.

#include <SplitView.h>
#include <View.h>

BSplitView* split = new BSplitView(B_HORIZONTAL);
BView* navigator = new BView("navigator", 0);
BView* detail = new BView("detail", 0);

if (!split->AddChild(navigator, 0.30f)) {
    // Handle failure; ownership transfers only on successful addition.
}
if (!split->AddChild(detail, 0.70f)) {
    // Also remove/dispose of the successfully added pane as appropriate.
}

The example only shows the parent-child arrangement. Each pane should have its own responsive layout and useful size constraints before the outer window is shown. AddChild returns bool; check it and follow the documented ownership transfer only after success. If the second insertion fails, the first pane is already managed by the split view, so cleanup should happen through the container’s ownership path rather than deleting the same child twice.

Weights are relative to all other items: they influence how available space is distributed. A 0.30/0.70 initial choice communicates the intended starting balance, but it does not guarantee those exact proportions after constraints, user dragging, resize, or collapse. Avoid describing weight as a hard width in pixels. When restoring user preferences, validate persisted weights and fall back to sensible defaults if values are missing, negative, infinite, or no longer match the current pane count.

The orientation defines the split axis. Horizontal means panes lie side-by-side; vertical means panes stack. Do not name panes “left” and “right” if the same component can switch to a vertical arrangement at a narrow breakpoint. Keep semantic pane identity separate from index, since inserting or removing a pane shifts the zero-based indexes used by methods such as ItemWeight() and SetCollapsible(index, ...).

Minimum sizes and infeasible layouts

Every pane contributes min, max, and preferred size through its view/layout. If the combined minimum sizes plus insets, gaps, and splitters exceed the available extent, no weight can make the layout fit. Decide which content may scroll, truncate, reflow, or collapse. A navigation tree may have a smaller but nonzero minimum width; a secondary inspector may be allowed to collapse; a document canvas may need to remain visible at all times.

Use SetInsets() for the outer padding, SetSpacing() for space between child components, and SetSplitterSize() for splitter geometry. These settings affect available pane size. Do not imitate padding by adding arbitrary margins to every child and then also set outer insets; duplicate spacing becomes especially visible in nested layouts. Use default spacing unless a specific product layout requires different geometry and has been checked against native control metrics.

If a child has an unconstrained maximum size and an unexpectedly large minimum size, inspect the child’s layout contract rather than compensating only with weights. A split view distributes space among panes; it cannot repair inconsistent constraints inside them. Add scroll views around content that legitimately exceeds the window, and keep controls reachable when the pane is reduced.

Collapsible panes and access to controls

SetCollapsible() can apply to all items, an individual index, or an inclusive index range. A collapsible pane may be dragged fully out of view; a non-collapsible pane remains at least partly visible. SetItemCollapsed() can set collapse state programmatically, and IsItemCollapsed() reports it. Choose collapse intentionally: users should have a visible affordance or documented gesture to restore a hidden pane.

Do not make a critical action available only inside a pane that can disappear without another route to it. If a pane contains the only close, save, or safety action, make it non-collapsible or surface that action elsewhere. Collapsed state should not be confused with disabled state; a hidden pane’s controls are not interactable, and keyboard navigation should not unexpectedly move into them.

If the app has a compact window mode, test the transition between expanded and collapsed panes. Keep the semantic active selection and current document when the view is hidden. When a collapsed pane returns, it should retain the expected state rather than silently reconstructing from stale data. If programmatically collapsing an item during resize, do not fight the user’s splitter drag on every layout pass; distinguish an app-requested breakpoint transition from an interactive resize.

For keyboard and accessibility, verify whether splitters expose an operable keyboard interaction in the exact Haiku release you target. Do not assume a draggable mouse divider alone is sufficient for all users. Provide alternate commands or a size control when pane proportions are important, and ensure focus can move between visible pane contents without becoming trapped. The splitter’s visual affordance should be discoverable and retain adequate contrast at normal and high-contrast settings.

Persist state by pane identity

Store user layout preferences as model data: schema version, semantic pane identifiers, relative weights, and intentional collapsed states. Do not serialize raw child pointers or assume a view’s current index remains stable across software versions. On restore, map known pane IDs to the current child set, ignore removed IDs, supply defaults for new panes, clamp invalid weights, and normalize the remaining values if your own format expects normalized proportions.

Persist after a completed user interaction or a debounced resize, not on every mouse movement. A splitter can generate many intermediate size changes. Excessive writes cause unnecessary disk I/O and can preserve accidental transient values just before shutdown. Apply a short debounce and flush when the window closes or settings are explicitly committed. If the app stores preferences in a BMessage, check each field lookup and validate types before applying them.

Also keep the saved layout independent from current display scale. A proportion is more portable across screen resolutions than a fixed pixel width, but min/max constraints and content lengths still matter. On a smaller display, allow the layout to degrade gracefully and preserve semantic defaults when old saved data no longer fits.

Update views safely

The split view owns the layout relationship for successful additions. Reconfigure it through the window’s normal Interface Kit ownership path. Do not mutate children from a worker thread while the window looper may be drawing or processing a drag. A background task can calculate whether a pane should exist, then send a result to the window thread, where the child is inserted or removed and the current semantic pane selection is repaired.

When changing orientation at runtime, check that child layouts provide suitable constraints in both axes. A pane optimized for a 250-pixel-wide sidebar may not work as a short top row. Avoid repeatedly destroying and rebuilding the split view to flip orientation if its user-resized state should survive; update the existing container and persist state in the appropriate orientation-specific model.

Verify the full size range

Test at the minimum window size, normal desktop size, and a very wide size. Drag every divider to both extremes, exercise collapsed panes, resize while a pane has long content, and switch orientations if the application supports it. Confirm child minimum sizes do not make the entire window exceed its intended limits. Check for clipped buttons, inaccessible scrollbars, and panes that become too narrow to display their own controls.

Add a test harness that logs pane order, weights, collapsed flags, and resulting frames after each layout pass. Assert that every non-collapsible pane retains a nonzero visible extent and that a restored preference maps to the correct semantic child after an item insertion. Test persistence with a removed pane ID and malformed weight. Verify that successful AddChild transfers ownership and failed additions do not leak or double-delete views.

Test mouse, keyboard, and assistive navigation separately. A screenshot proves only the current geometry; it does not prove that a hidden pane can be reopened or that keyboard focus remains sensible. Test translated strings and enlarged fonts because they change child minimum sizes. Record the Haiku release and screen configuration used for the check if pane sizing is part of a support contract.

BSplitView supplies draggable, weighted pane layout. The application still owns semantic pane identity, feasible constraints, collapse policy, persistence, asynchronous updates, and accessible alternatives to dragging.

Related:

Sources:

Comments