HBox
NAME
Selkie::Layout::HBox - Arrange children left to right
SYNOPSIS
use Selkie::Layout::HBox;
use Selkie::Sizing;
my $row = Selkie::Layout::HBox.new(sizing => Sizing.fixed(1));
$row.add: $label; # Sizing.fixed(8)
$row.add: $input; # Sizing.flex
$row.add: $button; # Sizing.fixed(10)
DESCRIPTION
HBox arranges children horizontally. Allocation follows the same
three-pass sizing rule as Selkie::Layout::VBox, but operates on
columns instead of rows.
Children get the full parent height unless they say otherwise with
cross-sizing ā see /Cross-axis alignment.
Gaps
gap reserves columns between children ā the least fiddly way to
space out a button row or keep a sidebar off its neighbour:
my $buttons = Selkie::Layout::HBox.new(sizing => Sizing.fixed(1), gap => 2);
$buttons.add: Selkie::Widget::Button.new(label => 'Cancel', sizing => Sizing.flex);
$buttons.add: Selkie::Widget::Button.new(label => 'OK', sizing => Sizing.flex);
$buttons.set-gap(4); # marks dirty; relayout on the next render
Note that this replaces the old spacer-child idiom (an empty Text
with Sizing.fixed(2) between the buttons): a gap is not a widget, so
it costs no plane and paints nothing.
Gutters come off the top of the column budget before any child is
sized, so Sizing.percent resolves against the content box (columns
minus gutters) ā see Selkie::Layout::Allocate. The rules match
Selkie::Layout::VBox exactly: no leading or trailing gap, no gap
beside a child allocated zero columns (it's parked), and gap cells stay
unpainted so the HBox's own plane base shows through.
gap defaults to 0, which allocates and positions exactly as HBox
always has. Selkie::Layout::Split deliberately has no gap ā it
separates its panes with a divider it draws and drags itself.
Cross-axis alignment
An HBox lays out columns, so its cross axis is rows: align-items
places children vertically, and a child's cross-sizing decides how
tall it is. This is the natural way to vertically centre a one-row
label beside a taller pane:
my $row = Selkie::Layout::HBox.new(
sizing => Sizing.flex,
gap => 2,
align-items => CrossCenter,
);
$row.add: Selkie::Widget::Text.new(
text => 'Status:',
sizing => Sizing.fixed(10),
cross-sizing => Sizing.fixed(1), # one row, vertically centred
);
$row.add: $detail-pane; # full height ā no cross-sizing
# One child pinned to the bottom regardless of the row's policy.
$row.add: my $stamp = Selkie::Widget::Text.new(
text => '12:04',
sizing => Sizing.fixed(5),
cross-sizing => Sizing.fixed(1),
align-self => CrossEnd,
);
The rules are Selkie::Layout::VBox's with the axes swapped:
CrossFill (the default) plus an undefined cross-sizing is the
historical full-height layout at row 0; align-self beats
align-items; a child resolving to zero rows is parked but keeps its
columns and its gutter, so a cross-axis collapse never reflows the row;
and gap, which only ever touches columns here, is entirely
orthogonal.
EXAMPLES
Three-column main layout
The classic file-manager pattern: sidebar + main + details.
my $columns = Selkie::Layout::HBox.new(sizing => Sizing.flex);
$columns.add: $sidebar; # Sizing.fixed(20)
$columns.add: $main-content; # Sizing.flex
$columns.add: $details; # Sizing.fixed(30)
A button row
my $buttons = Selkie::Layout::HBox.new(sizing => Sizing.fixed(1));
$buttons.add: Selkie::Widget::Button.new(label => 'Cancel', sizing => Sizing.flex);
$buttons.add: Selkie::Widget::Text.new(text => '', sizing => Sizing.fixed(2)); # spacer
$buttons.add: Selkie::Widget::Button.new(label => 'OK', sizing => Sizing.flex);
SEE ALSO
Selkie::Layout::VBox ā vertical version of the same layout
Selkie::Layout::Split ā two-pane split with a divider
Selkie::Sizing ā the sizing model
Selkie::Align ā the
CrossAlignvaluesalign-itemstakesSelkie::Layout::Allocate ā the shared allocation and cross-axis maths