Selkie.git | docs/api/ | Selkie--Layout--HBox.md
NAME
====
Selkie::Layout::HBox - Arrange children left to right
SYNOPSIS
========
```raku
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](Selkie--Layout--VBox.md), but operates on columns instead of rows.
Children get the full parent height unless they say otherwise with `cross-sizing` — see [/Cross-axis alignment](/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:
```raku
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](Selkie--Layout--Allocate.md). The rules match [Selkie::Layout::VBox](Selkie--Layout--VBox.md) 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](Selkie--Layout--Split.md) 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:
```raku
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](Selkie--Layout--VBox.md)'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.
```raku
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
------------
```raku
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](Selkie--Layout--VBox.md) — vertical version of the same layout
* [Selkie::Layout::Split](Selkie--Layout--Split.md) — two-pane split with a divider
* [Selkie::Sizing](Selkie--Sizing.md) — the sizing model
* [Selkie::Align](Selkie--Align.md) — the `CrossAlign` values `align-items` takes
* [Selkie::Layout::Allocate](Selkie--Layout--Allocate.md) — the shared allocation and cross-axis maths
### has UInt $.gap
Columns of empty space reserved between adjacent children. Defaults to 0 — no gutter, and layout identical to a gap-free HBox. The total reservation is `gap-reserve` from [Selkie::Layout::Allocate](Selkie--Layout--Allocate.md): children collapsed to `Sizing.fixed(0)` don't get a gutter.
### method set-gap
```raku
method set-gap(
Int:D $g where { ... }
) returns Nil
```
Change the inter-child gutter and mark the box dirty so the next render re-runs the allocation. `0` restores the gap-free layout.
### has CrossAlign $.align-items
Where children sit vertically — the HBox's **cross** axis. Defaults to `CrossFill`: every child is given the box's full height at row 0, exactly as HBox has always laid out. A child overrides this for itself with `align-self`, and controls *how tall* it is with `cross-sizing`. See [Selkie::Align](Selkie--Align.md).
### method set-align-items
```raku
method set-align-items(
CrossAlign:D $a
) returns Nil
```
Change the cross-axis alignment policy for children that haven't set their own `align-self`, and mark the box dirty so the next render re-runs the layout.
### method render
```raku
method render() returns Mu
```
Perform layout and render each child. Called automatically by the render cycle. Columns are allocated using the same three-pass strategy as `VBox`, applied to the width axis.
### method handle-resize
```raku
method handle-resize(
Int $rows where { ... },
Int $cols where { ... }
) returns Mu
```
Resize this container's own plane. Children are re-laid-out in `render`, not here. See VBox for the rationale.