Selkie--Layout--VBox

NAME

Selkie::Layout::VBox - Arrange children top to bottom

SYNOPSIS

use Selkie::Layout::VBox;
use Selkie::Sizing;

my $vbox = Selkie::Layout::VBox.new(sizing => Sizing.flex);
$vbox.add: $header;    # Sizing.fixed(1)
$vbox.add: $body;      # Sizing.flex
$vbox.add: $footer;    # Sizing.fixed(1)

DESCRIPTION

VBox stacks children vertically and allocates rows according to each child's Selkie::Sizing:

  • Fixed children get exactly the rows they ask for.

  • Percent children get n% of the parent's total rows.

  • Flex children share whatever rows are left over, weighted by flex factor.

Columns are set to the full parent width for every child, unless the child says otherwise with cross-sizing β€” see [/Cross-axis alignment](/Cross-axis alignment).

VBox is a Selkie::Container, so it inherits add, remove, clear, and focusable-descendants handling. All children must compose Selkie::Widget.

Gaps

gap reserves rows between children β€” the idiomatic way to give a stack some breathing room without padding every child by hand:

my $form = Selkie::Layout::VBox.new(sizing => Sizing.flex, gap => 1);
$form.add: $name-row;      # Sizing.fixed(1)
$form.add: $email-row;     # Sizing.fixed(1)
$form.add: $submit;        # Sizing.fixed(1)

$form.set-gap(2);          # marks dirty; relayout on the next render

The gutters come off the top of the row budget before any child is sized, so Sizing.percent resolves against the content box (rows minus gutters) β€” see Selkie::Layout::Allocate. Three of the rules are worth knowing by heart:

  • There is never a leading or trailing gap; gap is strictly between children.

  • A child allocated zero rows is parked, and no gap is placed beside it. Collapsing a child with Sizing.fixed(0) therefore removes its gutter as well, instead of leaving a double gap behind.

  • Gap rows are left unpainted, so the VBox's own plane base shows through. If you want a visible rule between panes rather than empty space, use Selkie::Layout::Split (which owns a divider) or add a one-row Text child.

gap defaults to 0, which allocates and positions exactly as VBox always has.

Selkie::Layout::Split deliberately has no gap: its two panes are separated by a divider row/column it draws and drags itself, and a second, invisible gutter next to that would be a footgun rather than a feature.

Cross-axis alignment

A VBox stacks rows, so its cross axis is columns. align-items says where a child sits horizontally; the child's cross-sizing says how wide it is:

my $page = Selkie::Layout::VBox.new(
    sizing      => Sizing.flex,
    gap         => 1,
    align-items => CrossCenter,
);

# A 40-column card, centred in however wide the page happens to be.
$page.add: Selkie::Widget::Border.new(
    title        => 'Sign in',
    sizing       => Sizing.fixed(9),
    cross-sizing => Sizing.fixed(40),
);

# …and one child that opts out of the container's rule.
$page.add: my $footer = Selkie::Widget::Text.new(
    text         => 'v1.2.0',
    sizing       => Sizing.fixed(1),
    cross-sizing => Sizing.fixed(10),
    align-self   => CrossEnd,
);

The rules, in full:

  • cross-sizing is a Selkie::Sizing resolved against the box's width: fixed is a column count, percent a share of the width, flex (and the default, undefined) the whole width.

  • align-items defaults to CrossFill and align-self is undefined by default, so a plain VBox lays out exactly as it always has β€” full-width children at column 0.

  • CrossFill and CrossStart both place a child at column 0; they differ only in that CrossFill is the "no opinion" value, and a child that declares a cross-sizing keeps it under either.

  • A child that resolves to zero columns is parked β€” a zero-width plane can't be resized, and a stale one would paint over its neighbours β€” but it keeps its rows and its gutter. Collapsing a child on the cross axis must not reflow the stack on the main one; sizing =E<gt> Sizing.fixed(0) is still how you take a child out of the flow entirely.

  • Alignment and gap never interact: the gutters come off the row budget, the alignment maths only ever sees columns.

EXAMPLES

Classic three-pane stack

my $root = Selkie::Layout::VBox.new(sizing => Sizing.flex);

$root.add: Selkie::Widget::Text.new(
    text   => ' Selkie App',
    sizing => Sizing.fixed(1),
    style  => Selkie::Style.new(fg => 0x7AA2F7, bold => True),
);

$root.add: $main-content;   # sizing => Sizing.flex β€” fills middle

$root.add: Selkie::Widget::Text.new(
    text   => ' Ctrl+Q: quit',
    sizing => Sizing.fixed(1),
    style  => Selkie::Style.new(fg => 0x666666),
);

Weighted distribution

my $vbox = Selkie::Layout::VBox.new(sizing => Sizing.flex);
$vbox.add: $preview;   # Sizing.flex(2) β€” gets two-thirds
$vbox.add: $output;    # Sizing.flex    β€” gets one-third

SEE ALSO

has UInt $.gap

Rows of empty space reserved between adjacent children. Defaults to 0 β€” no gutter, and layout identical to a gap-free VBox. The total reservation is gap-reserve from Selkie::Layout::Allocate: children collapsed to Sizing.fixed(0) don't get a gutter.

method set-gap

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 horizontally β€” the VBox's cross axis. Defaults to CrossFill: every child is given the box's full width at column 0, exactly as VBox has always laid out. A child overrides this for itself with align-self, and controls how wide it is with cross-sizing. See Selkie::Align.

method set-align-items

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

method render() returns Mu

Perform layout and render each child. Called automatically by the render cycle. The layout pass allocates rows according to every child's Sizing: fixed first, then percent, then flex shares the rest.

method handle-resize

method handle-resize(
    Int $rows where { ... },
    Int $cols where { ... }
) returns Mu

Re-layout children when the parent resizes. Re-runs the same fixed β†’ percent β†’ flex allocation as the initial layout, so children's relative sizing is preserved across resizes. Idempotent on no-size changes.

Selkie v0.11.1

High-level TUI framework built on Notcurses

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

Notcurses::Native:ver<0.4.1+>:auth<zef:apogee>

Test Dependencies

Provides

  • Selkie
  • Selkie::Align
  • Selkie::Alpha
  • Selkie::App
  • Selkie::App::Internal::Animation
  • Selkie::App::Internal::Dispatch
  • Selkie::App::Internal::ErrorLog
  • Selkie::App::Internal::FocusTree
  • Selkie::App::Internal::HitTest
  • Selkie::App::Internal::IdleBudget
  • Selkie::App::Internal::OverlayTree
  • Selkie::App::Internal::RenderLoop
  • Selkie::App::Internal::ScreenModalLifecycle
  • Selkie::App::Internal::Terminal
  • Selkie::App::Internal::TerminalSequences
  • Selkie::BorderStyle
  • Selkie::Container
  • Selkie::EffectiveBounds
  • Selkie::Event
  • Selkie::Gradient
  • Selkie::Layout::Allocate
  • Selkie::Layout::HBox
  • Selkie::Layout::Split
  • Selkie::Layout::VBox
  • Selkie::Plot::Palette
  • Selkie::Plot::Scaler
  • Selkie::Plot::Ticks
  • Selkie::ScreenManager
  • Selkie::Sizing
  • Selkie::Store
  • Selkie::Style
  • Selkie::Test::Focus
  • Selkie::Test::Keys
  • Selkie::Test::Snapshot
  • Selkie::Test::Snapshot::Harness
  • Selkie::Test::Store
  • Selkie::Test::Supply
  • Selkie::Test::Tree
  • Selkie::Theme
  • Selkie::Trace
  • Selkie::Tree
  • Selkie::Tween
  • Selkie::Widget
  • Selkie::Widget::Axis
  • Selkie::Widget::BarChart
  • Selkie::Widget::Border
  • Selkie::Widget::Button
  • Selkie::Widget::CardList
  • Selkie::Widget::Checkbox
  • Selkie::Widget::CommandPalette
  • Selkie::Widget::ConfirmModal
  • Selkie::Widget::FileBrowser
  • Selkie::Widget::FocusableByDefault
  • Selkie::Widget::GradientFill
  • Selkie::Widget::Heatmap
  • Selkie::Widget::HelpOverlay
  • Selkie::Widget::Histogram
  • Selkie::Widget::Image
  • Selkie::Widget::Legend
  • Selkie::Widget::LineChart
  • Selkie::Widget::ListView
  • Selkie::Widget::Modal
  • Selkie::Widget::MultiLineInput
  • Selkie::Widget::PasswordStrength
  • Selkie::Widget::Plot
  • Selkie::Widget::ProgressBar
  • Selkie::Widget::RadioGroup
  • Selkie::Widget::RichText
  • Selkie::Widget::RichText::Span
  • Selkie::Widget::ScatterPlot
  • Selkie::Widget::ScrollView
  • Selkie::Widget::Select
  • Selkie::Widget::Sparkline
  • Selkie::Widget::Spinner
  • Selkie::Widget::TabBar
  • Selkie::Widget::Table
  • Selkie::Widget::Text
  • Selkie::Widget::TextInput
  • Selkie::Widget::TextInput::HighlightSpan
  • Selkie::Widget::TextStream
  • Selkie::Widget::Toast
  • Selkie::Widget::ViewportedCardList

Documentation

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.

Built with Podlite β€” the markup and publishing tools behind this site.