layout

package
v0.0.0-...-6710c34 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package layout partitions terminal screen space into rectangular regions using a constraint-based solver.

Under the hood, it relies on the Cassowary constraint solver algorithm to resolve competing size requirements. Each constraint carries a priority, so the solver can pick the best trade-off when not every requirement fits.

How It Works

A Layout takes the available area and a list of constraints (Len, Ratio, Percent, Fill, Min, Max) and produces a set of non-overlapping rectangles. The solver tries to honour every constraint; when that is impossible it relaxes lower-priority ones first.

You are not required to use Layout at all. If you prefer manual control, you can compute gamma.Rectangle values yourself with plain arithmetic.

Acknowledgements

This implementation is heavily based on Ratatui source code and is roughly 1:1 translation from Rust with some minor API adjustments.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Constraint

type Constraint interface {
	// contains filtered or unexported methods
}

Constraint describes how a single segment of a Layout should be sized.

Each constraint type expresses a different kind of sizing rule: fixed (Len), proportional (Percent, Ratio), bounded (Min, Max), or greedy (Fill). Proportional constraints are evaluated against the full area being split rather than the remaining space after fixed constraints have been applied.

When the solver cannot satisfy every constraint, it resolves conflicts according to the following priority order (highest first):

type Direction

type Direction int

Direction controls whether a Layout arranges its segments horizontally (left to right) or vertically (top to bottom).

const (
	// DirectionVertical - layout segments are arranged top to bottom (default).
	DirectionVertical Direction = iota
	// DirectionHorizontal - layout segments are arranged side by side (left to right).
	DirectionHorizontal
)

type Fill

type Fill int

Fill distributes remaining space proportionally among all Fill segments according to their respective weights.

A Fill segment only expands into space left over after higher-priority constraints have been satisfied. Multiple Fill segments share that leftover in proportion to their integer values.

Examples

[Fill(1), Fill(2), Fill(3)]

┌──────┐┌───────────────┐┌───────────────────────┐
│ 8 px ││     17 px     ││         25 px         │
└──────┘└───────────────┘└───────────────────────┘

[Fill(1), Percent(50), Fill(1)]

┌───────────┐┌───────────────────────┐┌──────────┐
│   13 px   ││         25 px         ││   12 px  │
└───────────┘└───────────────────────┘└──────────┘

func (Fill) String

func (f Fill) String() string

type Flex

type Flex int

Flex controls how leftover space is distributed once every segment's constraint has been resolved. It is analogous to the CSS justify-content property and is used together with Layout.

const (

	// FlexStart pushes segments to the leading edge of the area, leaving
	// any surplus space at the trailing edge.
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐
	// 	│  Percent(20) ││    Length(20)    ││     Fixed(20)    │
	// 	└──────────────┘└──────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌──────20 px───────┐┌──────20 px───────┐
	// 	│      Max(20)     ││      Max(20)     │
	// 	└──────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌──────20 px───────┐
	// 	│      Max(20)     │
	// 	└──────────────────┘
	FlexStart Flex = iota

	// FlexLegacy fills the entire area by assigning surplus space to the
	// lowest-priority trailing segment. This reproduces the original
	// Ratatui/tui-rs layout behaviour.
	//
	// The examples below show how surplus space is allocated for different
	// constraint combinations. Recall the priority order (highest first):
	//
	// 	- [Min]
	// 	- [Max]
	// 	- [Len]
	// 	- [Percent]
	// 	- [Ratio]
	// 	- [Fill]
	//
	// With all-[Len] constraints the surplus goes to the final segment.
	//
	// 	<----------------------------------- 80 px ------------------------------------>
	// 	┌──────20 px───────┐┌──────20 px───────┐┌────────────────40 px─────────────────┐
	// 	│    Length(20)    ││    Length(20)    ││              Length(20)              │
	// 	└──────────────────┘└──────────────────┘└──────────────────────────────────────┘
	// 	                                        ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^
	//
	// [Fill] has the lowest priority, so it always absorbs surplus.
	//
	// 	<----------------------------------- 80 px ------------------------------------>
	// 	┌──────20 px───────┐┌──────20 px───────┐┌──────20 px───────┐┌──────20 px───────┐
	// 	│      Fill(0)     ││      Max(20)     ││    Length(20)    ││     Length(20)   │
	// 	└──────────────────┘└──────────────────┘└──────────────────┘└──────────────────┘
	// 	^^^^^^ EXCESS ^^^^^^
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌──────────────────────────60 px───────────────────────────┐┌──────20 px───────┐
	// 	│                          Min(20)                         ││      Max(20)     │
	// 	└──────────────────────────────────────────────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌────────────────────────────────────80 px─────────────────────────────────────┐
	// 	│                                    Max(20)                                   │
	// 	└──────────────────────────────────────────────────────────────────────────────┘
	FlexLegacy

	// FlexEnd pushes segments to the trailing edge of the area, leaving
	// surplus space at the leading edge.
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                        ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐
	// 	                        │  Percent(20) ││    Length(20)    ││     Length(20)   │
	// 	                        └──────────────┘└──────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                                        ┌──────20 px───────┐┌──────20 px───────┐
	// 	                                        │      Max(20)     ││      Max(20)     │
	// 	                                        └──────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                                                            ┌──────20 px───────┐
	// 	                                                            │      Max(20)     │
	// 	                                                            └──────────────────┘
	FlexEnd

	// FlexCenter places segments in the middle of the area, distributing
	// surplus space equally before the first and after the last segment.
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	            ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐
	// 	            │  Percent(20) ││    Length(20)    ││     Length(20)   │
	// 	            └──────────────┘└──────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                    ┌──────20 px───────┐┌──────20 px───────┐
	// 	                    │      Max(20)     ││      Max(20)     │
	// 	                    └──────────────────┘└──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                              ┌──────20 px───────┐
	// 	                              │      Max(20)     │
	// 	                              └──────────────────┘
	FlexCenter

	// FlexSpaceBetween distributes surplus space equally between adjacent
	// segments, with no space before the first or after the last.
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌────16 px─────┐            ┌──────20 px───────┐            ┌──────20 px───────┐
	// 	│  Percent(20) │            │    Length(20)    │            │     Length(20)   │
	// 	└──────────────┘            └──────────────────┘            └──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌──────20 px───────┐                                        ┌──────20 px───────┐
	// 	│      Max(20)     │                                        │      Max(20)     │
	// 	└──────────────────┘                                        └──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	┌────────────────────────────────────80 px─────────────────────────────────────┐
	// 	│                                    Max(20)                                   │
	// 	└──────────────────────────────────────────────────────────────────────────────┘
	FlexSpaceBetween

	// FlexSpaceEvenly distributes surplus space so that every gap
	// (including before the first and after the last segment) is the same width.
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	      ┌────16 px─────┐      ┌──────20 px───────┐      ┌──────20 px───────┐
	// 	      │  Percent(20) │      │    Length(20)    │      │     Length(20)   │
	// 	      └──────────────┘      └──────────────────┘      └──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	             ┌──────20 px───────┐              ┌──────20 px───────┐
	// 	             │      Max(20)     │              │      Max(20)     │
	// 	             └──────────────────┘              └──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                              ┌──────20 px───────┐
	// 	                              │      Max(20)     │
	// 	                              └──────────────────┘
	FlexSpaceEvenly

	// FlexSpaceAround places equal space on both sides of each segment.
	// Adjacent segments therefore have twice the gap of the outer edges.
	//
	// # Examples
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	    ┌────16 px─────┐       ┌──────20 px───────┐       ┌──────20 px───────┐
	// 	    │  Percent(20) │       │    Length(20)    │       │     Length(20)   │
	// 	    └──────────────┘       └──────────────────┘       └──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	     ┌──────20 px───────┐                      ┌──────20 px───────┐
	// 	     │      Max(20)     │                      │      Max(20)     │
	// 	     └──────────────────┘                      └──────────────────┘
	//
	// 	<------------------------------------80 px------------------------------------->
	// 	                              ┌──────20 px───────┐
	// 	                              │      Max(20)     │
	// 	                              └──────────────────┘
	FlexSpaceAround
)

func (Flex) String

func (f Flex) String() string

type Layout

type Layout struct {
	Direction   Direction
	Constraints []Constraint
	Padding     Padding
	// Spacing is the gap between adjacent segments, measured in cells.
	// A negative value causes segments to overlap by that many cells.
	Spacing int
	Flex    Flex
}

Layout splits a rectangular area into smaller rectangles using a set of constraints. It is the primary building block for structuring terminal user interfaces.

Fields:

  • Direction: whether segments flow vertically or horizontally.
  • Constraints: the sizing rules (Len, Ratio, Percent, Fill, Min, Max).
  • Padding: inset applied to the outer area before solving.
  • Flex: strategy for distributing leftover space among segments.
  • Spacing: gap (or overlap, if negative) between adjacent segments.

Internally, sizes are resolved by a Cassowary linear-constraint solver that satisfies as many rules as it can, preferring higher-priority constraints when trade-offs are necessary.

func Horizontal

func Horizontal(constraints ...Constraint) Layout

Horizontal is shorthand for New(DirectionHorizontal, constraints...).

func New

func New(direction Direction, constraints ...Constraint) Layout

New returns a Layout configured with the given direction and constraints.

func Vertical

func Vertical(constraints ...Constraint) Layout

Vertical is shorthand for New(DirectionVertical, constraints...).

func (Layout) Split

func (l Layout) Split(area gamma.Rectangle) Splitted

Split partitions the area into content rectangles according to the layout's direction and constraints.

Because every constraint is evaluated against the total area, mixing relative constraints (Percent, Ratio) with absolute ones (Min, Max, Len) can produce ambiguous results. For example, splitting 100 cells as [Min(20), Percent(50), Percent(50)] will not necessarily yield [20, 40, 40].

func (Layout) SplitWithSpacers

func (l Layout) SplitWithSpacers(area gamma.Rectangle) (segments, spacers Splitted)

SplitWithSpacers divides the given area into content segments and the gaps (spacers) between them. It returns both slices; use Layout.Split if you only need the content rectangles.

func (Layout) WithConstraints

func (l Layout) WithConstraints(constraints ...Constraint) Layout

WithConstraints returns a shallow copy of the layout with the given constraints appended to its existing list.

func (Layout) WithDirection

func (l Layout) WithDirection(direction Direction) Layout

WithDirection returns a shallow copy of the layout using the specified direction.

func (Layout) WithFlex

func (l Layout) WithFlex(flex Flex) Layout

WithFlex returns a shallow copy of the layout using the specified flex strategy.

func (Layout) WithPadding

func (l Layout) WithPadding(padding Padding) Layout

WithPadding returns a shallow copy of the layout using the specified padding.

func (Layout) WithSpacing

func (l Layout) WithSpacing(spacing int) Layout

WithSpacing returns a shallow copy of the layout using the specified spacing value.

type Len

type Len int

Len fixes the segment to exactly the given number of cells.

Examples

[Len(20), Len(20)]

┌──────────────────┐┌──────────────────┐
│       20 px      ││       20 px      │
└──────────────────┘└──────────────────┘

[Len(20), Len(30)]

┌──────────────────┐┌────────────────────────────┐
│       20 px      ││            30 px           │
└──────────────────┘└────────────────────────────┘

func (Len) String

func (l Len) String() string

type Max

type Max int

Max caps the segment at the given number of cells.

Examples

[Percent(0), Max(20)]

┌────────────────────────────┐┌──────────────────┐
│            30 px           ││       20 px      │
└────────────────────────────┘└──────────────────┘

[Percent(0), Max(10)]

┌──────────────────────────────────────┐┌────────┐
│                 40 px                ││  10 px │
└──────────────────────────────────────┘└────────┘

func (Max) String

func (m Max) String() string

type Min

type Min int

Min ensures the segment is no smaller than the given number of cells.

Examples

[Percent(100), Min(20)]

┌────────────────────────────┐┌──────────────────┐
│            30 px           ││       20 px      │
└────────────────────────────┘└──────────────────┘

[Percent(100), Min(10)]

┌──────────────────────────────────────┐┌────────┐
│                 40 px                ││  10 px │
└──────────────────────────────────────┘└────────┘

func (Min) String

func (m Min) String() string

type Padding

type Padding struct {
	Top, Right, Bottom, Left int
}

Padding defines the inset applied to a Layout's outer area before solving.

func Pad

func Pad(sides ...int) Padding

Pad builds a Padding value from a variable number of sides, following the same shorthand convention as CSS:

  • 0 args: all sides zero.
  • 1 arg: uniform on every side.
  • 2 args: first is top/bottom, second is left/right.
  • 4 args: top, right, bottom, left.

Any other count causes a panic.

type Percent

type Percent int

Percent sizes the segment as a fraction of the total area.

The integer value is treated as a percentage (0-100+) and multiplied by the total area; the result is rounded to the nearest cell.

Because only whole integers are accepted, some fractions (e.g. 1/3) cannot be represented exactly. Consider Ratio or Fill instead.

Examples

[Percent(75), Fill(1)]

┌────────────────────────────────────┐┌──────────┐
│                38 px               ││   12 px  │
└────────────────────────────────────┘└──────────┘

[Percent(50), Fill(1)]

┌───────────────────────┐┌───────────────────────┐
│         25 px         ││         25 px         │
└───────────────────────┘└───────────────────────┘

func (Percent) String

func (p Percent) String() string

type Ratio

type Ratio struct{ Num, Den int }

Ratio sizes the segment as a numerator/denominator fraction of the total area.

The fraction is converted to a float, multiplied by the area, and rounded to the nearest cell.

Examples

[Ratio(1, 2) ; 2]

┌───────────────────────┐┌───────────────────────┐
│         25 px         ││         25 px         │
└───────────────────────┘└───────────────────────┘

[Ratio(1, 4) ; 4]

┌───────────┐┌──────────┐┌───────────┐┌──────────┐
│   13 px   ││   12 px  ││   13 px   ││   12 px  │
└───────────┘└──────────┘└───────────┘└──────────┘

func (Ratio) String

func (r Ratio) String() string

type Splitted

type Splitted []gamma.Rectangle

Splitted holds the rectangles produced by a Layout.Split call.

func (Splitted) Assign

func (s Splitted) Assign(areas ...*gamma.Rectangle)

Assign stores each resulting rectangle into the corresponding pointer.

Nil pointers are silently skipped.

Panics when len(areas) exceeds the number of rectangles in Splitted.

Examples

var top, bottom gamma.Rectangle

layout.New(layout.Fill(1), layout.Len(1)).
	Split(area).
    Assign(&top, &bottom)

Jump to

Keyboard shortcuts

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