layout

package
v0.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package layout is a tiny declarative engine for composing Bubble Tea view strings. Every layout is a tree of Node; each Node knows how to render itself into a given geom.Rect. VStack and HStack split their allotment among Fixed- and Flex-sized children; ZStack overlays children.

The point is to remove hand-written "m.h-2" math from callers. You describe what goes where and let the stack engine divide the pixels.

Typical use:

root := layout.VStack(
    layout.Fixed(1, layout.RenderFunc(func(r geom.Rect) string { ... })),
    layout.Flex(1, body),
    layout.Fixed(1, layout.RenderFunc(func(r geom.Rect) string { ... })),
)
return root.Render(geom.New(0, 0, termW, termH))

Rects, not sizes

A Node receives a geom.Rect rather than a bare (width, height): it learns where it sits in absolute terminal coordinates, not merely how big it is. Components store the rect they were handed and use it to answer mouse events without any marker injection into the rendered string — see pkg/geom.

The root of a render calls geom.NextGen once per frame and seeds its rect with geom.New; every child inherits that generation as the rect propagates down. A nested root (pkg/tab rendering its active body's layout) must not call NextGen — it renders inside an existing frame and passes the generation from the rect it was given.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Item

type Item struct {
	// contains filtered or unexported fields
}

Item is a child of VStack or HStack. Construct with Fixed or Flex.

func Fixed

func Fixed(size int, node Node) Item

Fixed reserves an exact number of cells (rows in VStack, columns in HStack) for node.

func Flex

func Flex(weight int, node Node) Item

Flex asks for a proportional share of the space remaining after all Fixed items are accounted for. Weight picks the share ratio: Flex(2, ...) + Flex(1, ...) gives the first twice as much as the second. A non-positive weight is clamped to 1.

type Node

type Node interface {
	Render(r geom.Rect) string
}

Node is the unit of layout — something that can render itself into a given rect. Components participate by being wrapped in Sized (or any adapter that yields a Node).

func Bar

func Bar(s Sizer) Node

Bar wraps a single-row component (breadcrumb.Model, statusbar.Model, …) as a Node. It behaves exactly as Sized — the name documents intent, since a bar is expected to sit in a Fixed(1, …) slot and render one row.

func Center

func Center(naturalW, naturalH int, child Node) Node

Center renders child at its natural size (given by naturalW/naturalH) padded to the parent's rect with surrounding whitespace. Useful as the overlay layer inside a ZStack.

The child's rect is offset to where lipgloss.Place will actually draw it, so a centered modal hit-tests against its visible position rather than the parent's origin.

func HStack

func HStack(items ...Item) Node

HStack stacks items left to right within its allotted width.

func Sized

func Sized(s Sizer) Node

Sized wraps a component as a Node, handing it its rect at render time. Pass a pointer: &m.list, &m.body.

func VStack

func VStack(items ...Item) Node

VStack stacks items top to bottom within its allotted height.

func ZStack

func ZStack(base, overlay Node) Node

ZStack layers overlay on top of base. Both are rendered into the full rect. Compositing happens cell-by-cell: cells that are spaces in the overlay pass the base through; non-space cells replace base at that column. Empty overlay rows pass through entirely.

In practice this means a centered modal drawn with Center(...) only blots out the modal's bounding box; pane borders and content to the left and right of the modal stay visible. Wide characters and ANSI styles are handled via x/ansi cell-aware cutting.

Both layers receive the same rect, so a component in the base still believes it owns cells the overlay covers. Occlusion is the host screen's job: while a modal is up, forward messages to the modal alone (see the confirm and alert examples) so the covered components never see the click.

type RenderFunc

type RenderFunc func(r geom.Rect) string

RenderFunc adapts a render-into-rect function into a Node. It is the escape hatch for content the component adapters don't cover: close over whatever you need, render at r.W by r.H, and return the string.

func (RenderFunc) Render

func (f RenderFunc) Render(r geom.Rect) string

Render satisfies Node.

type Sizer

type Sizer interface {
	SetRect(r geom.Rect)
	View() string
}

Sizer is satisfied by any component the layout engine can place: it accepts the rect it should occupy and renders into it. Every interactive component in tuilib satisfies this, as do the single-row bars.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL