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.