layout

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Apr 12, 2026 License: MIT Imports: 4 Imported by: 1

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MeasureText

func MeasureText(content string, wrap WrapMode, maxWidth int) (width, height int)

MeasureText measures text content and returns content-box dimensions. It handles wrapping based on WrapMode and available width.

Parameters:

  • content: The text to measure
  • wrap: Wrapping mode (WrapNone, WrapWord, WrapChar)
  • maxWidth: Maximum width in cells (0 or negative = unbounded)

Returns:

  • width: The width of the widest line (in cells)
  • height: Number of lines (after wrapping)

func PreservesCrossSize

func PreservesCrossSize(node LayoutNode, parentAxis Axis) bool

PreservesCrossSize checks if a node preserves its cross-axis size for the given axis. For a horizontal parent (Row), cross axis is height. For a vertical parent (Column), cross axis is width. Returns false if node doesn't implement SizePreserver.

Types

type Axis

type Axis int

Axis represents the primary direction of a linear layout.

const (
	// Horizontal axis: main-axis runs left-to-right (X), cross-axis runs top-to-bottom (Y).
	// Used by RowNode.
	Horizontal Axis = iota

	// Vertical axis: main-axis runs top-to-bottom (Y), cross-axis runs left-to-right (X).
	// Used by ColumnNode.
	Vertical
)

type BoxModel

type BoxModel struct {
	// Border-box dimensions (content + padding + border)
	Width  int
	Height int

	// Insets (padding and border shrink inward, margin expands outward)
	Padding EdgeInsets
	Border  EdgeInsets // Typically uniform (1,1,1,1) for borders
	Margin  EdgeInsets

	// Scrolling (optional - zero values mean no scrolling)
	// VirtualWidth/Height represent the total scrollable content size.
	// When set, content can extend beyond the visible viewport.
	VirtualWidth  int // Total virtual content width (0 = same as computed ContentWidth)
	VirtualHeight int // Total virtual content height (0 = same as computed ContentHeight)
	ScrollOffsetX int // Horizontal scroll offset
	ScrollOffsetY int // Vertical scroll offset

	// ScrollbarWidth is the space reserved for a vertical scrollbar.
	// This reduces the usable content width when vertical scrolling is enabled.
	ScrollbarWidth int

	// ScrollbarHeight is the space reserved for a horizontal scrollbar.
	// This reduces the usable content height when horizontal scrolling is enabled.
	ScrollbarHeight int
}

BoxModel describes a rectangular region with CSS border-box semantics. Width and Height refer to the border-box (content + padding + border). Content dimensions are computed by subtracting padding and border from the border-box. Margin is added externally to get the margin-box (total allocated space).

The model consists of four nested boxes:

  • Content box: computed as border-box minus padding and border
  • Padding box: border-box minus border
  • Border box: the stored Width/Height dimensions
  • Margin box: border-box plus margin (the outermost boundary)

func (BoxModel) BorderBox

func (b BoxModel) BorderBox() Rect

BorderBox returns the border box as a Rect. The position is relative to the margin box origin.

func (BoxModel) BorderBoxHeight

func (b BoxModel) BorderBoxHeight() int

BorderBoxHeight returns the height of the border box. This is the stored Height value.

func (BoxModel) BorderBoxWidth

func (b BoxModel) BorderBoxWidth() int

BorderBoxWidth returns the width of the border box. This is the stored Width value.

func (BoxModel) BorderOrigin

func (b BoxModel) BorderOrigin() (x, y int)

BorderOrigin returns the offset from the margin box origin to the border origin. This is useful for drawing borders.

func (BoxModel) ClampScrollOffsetX

func (b BoxModel) ClampScrollOffsetX(offset int) int

ClampScrollOffsetX clamps the given offset to valid horizontal scroll bounds.

func (BoxModel) ClampScrollOffsetY

func (b BoxModel) ClampScrollOffsetY(offset int) int

ClampScrollOffsetY clamps the given offset to valid vertical scroll bounds.

func (BoxModel) ContentBox

func (b BoxModel) ContentBox() Rect

ContentBox returns the computed content area as a Rect. Content dimensions are computed by subtracting padding and border from the border-box. Use for: laying out content within the available space. The position is relative to the margin box origin.

func (BoxModel) ContentHeight

func (b BoxModel) ContentHeight() int

ContentHeight computes the content height by subtracting padding and border from the border-box. Returns 0 if insets exceed the border-box height.

func (BoxModel) ContentOrigin

func (b BoxModel) ContentOrigin() (x, y int)

ContentOrigin returns the offset from the margin box origin to the content origin. This is useful for positioning content within a rendered box.

func (BoxModel) ContentWidth

func (b BoxModel) ContentWidth() int

ContentWidth computes the content width by subtracting padding and border from the border-box. Returns 0 if insets exceed the border-box width.

func (BoxModel) EffectiveVirtualHeight

func (b BoxModel) EffectiveVirtualHeight() int

EffectiveVirtualHeight returns the virtual content height. Returns computed ContentHeight() if VirtualHeight is not set (0).

func (BoxModel) EffectiveVirtualWidth

func (b BoxModel) EffectiveVirtualWidth() int

EffectiveVirtualWidth returns the virtual content width. Returns computed ContentWidth() if VirtualWidth is not set (0).

func (BoxModel) IsScrollable

func (b BoxModel) IsScrollable() bool

IsScrollable returns true if scrolling is possible in either direction.

func (BoxModel) IsScrollableX

func (b BoxModel) IsScrollableX() bool

IsScrollableX returns true if horizontal scrolling is possible. This is true when virtual width exceeds the usable content width (accounting for vertical scrollbar space if vertical scrolling is enabled).

func (BoxModel) IsScrollableY

func (b BoxModel) IsScrollableY() bool

IsScrollableY returns true if vertical scrolling is possible. This is true when virtual height exceeds the usable content height (accounting for horizontal scrollbar space if horizontal scrolling is enabled).

func (BoxModel) MarginBox

func (b BoxModel) MarginBox() Rect

MarginBox returns the margin box as a Rect. This is always positioned at (0,0) since it's the outermost boundary.

func (BoxModel) MarginBoxHeight

func (b BoxModel) MarginBoxHeight() int

MarginBoxHeight returns the height of the margin box (border-box plus vertical margin). This is the total height including margin.

func (BoxModel) MarginBoxWidth

func (b BoxModel) MarginBoxWidth() int

MarginBoxWidth returns the width of the margin box (border-box plus horizontal margin). This is the total width including margin.

func (BoxModel) MaxScrollX

func (b BoxModel) MaxScrollX() int

MaxScrollX returns the maximum valid horizontal scroll offset. Returns 0 if not scrollable.

func (BoxModel) MaxScrollY

func (b BoxModel) MaxScrollY() int

MaxScrollY returns the maximum valid vertical scroll offset. Returns 0 if not scrollable.

func (BoxModel) PaddingBox

func (b BoxModel) PaddingBox() Rect

PaddingBox returns the padding box as a Rect. The position is relative to the margin box origin.

func (BoxModel) PaddingBoxHeight

func (b BoxModel) PaddingBoxHeight() int

PaddingBoxHeight returns the height of the padding box (border-box minus border).

func (BoxModel) PaddingBoxWidth

func (b BoxModel) PaddingBoxWidth() int

PaddingBoxWidth returns the width of the padding box (border-box minus border).

func (BoxModel) TotalHorizontalInset

func (b BoxModel) TotalHorizontalInset() int

TotalHorizontalInset returns the total horizontal space taken by all insets.

func (BoxModel) TotalVerticalInset

func (b BoxModel) TotalVerticalInset() int

TotalVerticalInset returns the total vertical space taken by all insets.

func (BoxModel) UsableContentBox

func (b BoxModel) UsableContentBox() Rect

UsableContentBox returns the content area available for child widgets. This subtracts space reserved for scrollbars from the computed content area:

  • Vertical scrollbar (ScrollbarWidth) reduces width when IsScrollableY()
  • Horizontal scrollbar (ScrollbarHeight) reduces height when IsScrollableX()

Use for: laying out children, determining available space for content. The position is relative to the margin box origin.

func (BoxModel) Validate

func (b BoxModel) Validate()

Validate checks that all BoxModel fields have valid values. Panics if any field has an invalid value (negative dimensions, invalid constraints, etc.). Note: Negative margins are allowed (CSS behavior) for overlapping/pull effects.

func (BoxModel) VirtualContentRect

func (b BoxModel) VirtualContentRect() Rect

VirtualContentRect returns the full virtual content area. For non-scrollable boxes, this equals the content dimensions.

func (BoxModel) VisibleContentRect

func (b BoxModel) VisibleContentRect() Rect

VisibleContentRect returns the visible portion of the virtual content. The rect is in virtual content coordinates (not screen coordinates). For non-scrollable boxes, this returns a rect starting at (0,0).

func (BoxModel) WithBorder

func (b BoxModel) WithBorder(border EdgeInsets) BoxModel

WithBorder returns a new BoxModel with the specified border. Panics if any border value is negative.

func (BoxModel) WithClampedScrollOffset

func (b BoxModel) WithClampedScrollOffset() BoxModel

WithClampedScrollOffset returns a new BoxModel with scroll offsets clamped to valid bounds.

func (BoxModel) WithMargin

func (b BoxModel) WithMargin(margin EdgeInsets) BoxModel

WithMargin returns a new BoxModel with the specified margin. Panics if any margin value is negative.

func (BoxModel) WithPadding

func (b BoxModel) WithPadding(padding EdgeInsets) BoxModel

WithPadding returns a new BoxModel with the specified padding. Panics if any padding value is negative.

func (BoxModel) WithScrollOffset

func (b BoxModel) WithScrollOffset(offsetX, offsetY int) BoxModel

WithScrollOffset returns a new BoxModel with the specified scroll offset.

func (BoxModel) WithScrollbarHeight

func (b BoxModel) WithScrollbarHeight(height int) BoxModel

WithScrollbarHeight returns a new BoxModel with the specified horizontal scrollbar height. Panics if height is negative.

func (BoxModel) WithScrollbarWidth

func (b BoxModel) WithScrollbarWidth(width int) BoxModel

WithScrollbarWidth returns a new BoxModel with the specified vertical scrollbar width. Panics if width is negative.

func (BoxModel) WithScrollbars

func (b BoxModel) WithScrollbars(width, height int) BoxModel

WithScrollbars returns a new BoxModel with the specified scrollbar dimensions. Panics if width or height is negative.

func (BoxModel) WithSize

func (b BoxModel) WithSize(width, height int) BoxModel

WithSize returns a new BoxModel with the specified border-box dimensions. Negative values are clamped to 0, since dimensions often come from layout calculations that can legitimately underflow (e.g., when the terminal is resized too small).

func (BoxModel) WithVirtualSize

func (b BoxModel) WithVirtualSize(virtualWidth, virtualHeight int) BoxModel

WithVirtualSize returns a new BoxModel with virtual content dimensions for scrolling. Negative values are clamped to 0, since virtual dimensions may come from layout calculations that can legitimately underflow.

type BoxNode

type BoxNode struct {
	// Fixed size (if MeasureFunc is nil).
	// These are border-box dimensions.
	Width  int
	Height int

	// Node's own min/max constraints.
	// These are merged with parent constraints to form effective constraints.
	// NOTE: 0 means "no constraint" (unconstrained), not "zero size".
	// This is a pragmatic trade-off for API simplicity in TUI contexts.
	// If you need a box that shrinks to zero, use MeasureFunc or set Width/Height directly.
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int

	// Insets
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Expand flags force the box to fill available space on that axis.
	// When true and Width/Height is 0, the box expands to MaxWidth/MaxHeight.
	// This is used when a widget's dimension is Flex() or Percent().
	ExpandWidth  bool
	ExpandHeight bool

	// MeasureFunc for dynamic sizing (overrides Width/Height if set).
	// Receives CONTENT-BOX constraints (available space for content, after subtracting padding/border).
	// Returns CONTENT-BOX dimensions (just the content size, not including padding/border).
	// ComputeLayout adds padding/border back automatically.
	// This keeps MeasureFunc simple - it only measures content, not decoration.
	// Can use constraints.IsTightWidth() etc. to detect forced vs flexible sizing.
	MeasureFunc func(constraints Constraints) (width, height int)
}

BoxNode is a leaf node representing a fixed or measured box. It implements LayoutNode and produces a BoxModel with no children.

func (*BoxNode) ComputeLayout

func (b *BoxNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the BoxNode's layout given parent constraints. It returns a ComputedLayout with the resulting BoxModel and no children.

type ColumnNode

type ColumnNode struct {
	// Spacing is the gap between children.
	Spacing int

	// MainAlign controls vertical distribution of children.
	MainAlign MainAxisAlignment

	// CrossAlign controls horizontal positioning of children.
	CrossAlign CrossAxisAlignment

	// Children to lay out.
	Children []LayoutNode

	// Container's own insets (optional).
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Container's own size constraints (optional, 0 means unconstrained).
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int

	// Expand flags force the container to fill available space on that axis.
	// Used when the widget's dimension is Flex() to mean "fill available space".
	ExpandWidth  bool
	ExpandHeight bool

	// Preserve flags indicate the container should resist stretching on that axis.
	// Used when the widget's dimension is explicitly Auto to mean "fit content, don't stretch".
	PreserveWidth  bool
	PreserveHeight bool
}

ColumnNode lays out children vertically (top to bottom). This is a thin wrapper around LinearNode with Axis=Vertical.

func (*ColumnNode) ComputeLayout

func (c *ColumnNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the column layout by delegating to LinearNode.

func (*ColumnNode) PreservesHeight

func (c *ColumnNode) PreservesHeight() bool

PreservesHeight implements SizePreserver.

func (*ColumnNode) PreservesWidth

func (c *ColumnNode) PreservesWidth() bool

PreservesWidth implements SizePreserver.

type ComputedLayout

type ComputedLayout struct {
	// Box is the computed BoxModel for this node.
	Box BoxModel

	// Children contains the positioned child layouts.
	// nil for leaf nodes.
	Children []PositionedChild
}

ComputedLayout is the result of layout computation. It contains the computed BoxModel for a node and its positioned children (if any).

type Constraints

type Constraints struct {
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int
}

Constraints represents the min/max size constraints passed from parent to child. This allows expressing tight constraints (min == max), loose constraints (min = 0), and range constraints (min < max).

func Loose

func Loose(maxWidth, maxHeight int) Constraints

Loose creates constraints where the node can be any size from 0 up to the given max.

func Tight

func Tight(width, height int) Constraints

Tight creates constraints where the node must be exactly the given size. Both min and max are set to the same value.

func TightHeight

func TightHeight(maxWidth, height int) Constraints

TightHeight creates constraints with a fixed height but flexible width.

func TightWidth

func TightWidth(width, maxHeight int) Constraints

TightWidth creates constraints with a fixed width but flexible height.

func Unbounded

func Unbounded() Constraints

Unbounded creates constraints with no limits. Useful for measuring intrinsic size.

func (Constraints) Constrain

func (c Constraints) Constrain(width, height int) (int, int)

Constrain clamps the given width and height to satisfy these constraints.

func (Constraints) IsTight

func (c Constraints) IsTight() bool

IsTight returns true if both dimensions are tightly constrained (min == max).

func (Constraints) IsTightHeight

func (c Constraints) IsTightHeight() bool

IsTightHeight returns true if height is tightly constrained (min == max).

func (Constraints) IsTightWidth

func (c Constraints) IsTightWidth() bool

IsTightWidth returns true if width is tightly constrained (min == max).

func (Constraints) WithNodeConstraints

func (c Constraints) WithNodeConstraints(minW, maxW, minH, maxH int) Constraints

WithNodeConstraints applies a node's own min/max constraints to parent constraints. Node constraints use 0 to mean "unconstrained".

Precedence rules:

  • Node's explicit MaxWidth/MaxHeight takes precedence (represents user intent like Width: Cells(4))
  • If parent's min exceeds node's explicit max, parent's min is lowered to match
  • Node's min is clamped to parent's max (can't exceed available space)
  • Without explicit node max, parent constraints pass through (e.g., stretch works)

This matches Flutter/CSS behavior where explicit size constraints are respected even when a parent wants to stretch the child.

If the resulting constraints are invalid (min > max), min wins. This handles user configuration errors like MinHeight: 60, MaxHeight: 40.

type CrossAxisAlignment

type CrossAxisAlignment int

CrossAxisAlignment controls how children are positioned along the cross axis.

const (
	// CrossAxisStart aligns children at the start of the cross axis.
	CrossAxisStart CrossAxisAlignment = iota

	// CrossAxisCenter centers children along the cross axis.
	CrossAxisCenter

	// CrossAxisEnd aligns children at the end of the cross axis.
	CrossAxisEnd

	// CrossAxisStretch stretches children to fill the cross axis.
	// Children are re-laid out with tight cross-axis constraints.
	CrossAxisStretch
)

type DockEdge

type DockEdge int

DockEdge specifies which edge a child is docked to.

const (
	DockTop DockEdge = iota
	DockBottom
	DockLeft
	DockRight
)

type DockNode

type DockNode struct {
	// Docked children for each edge.
	// Multiple children on the same edge are processed in order,
	// each consuming space from the remaining area.
	Top    []LayoutNode
	Bottom []LayoutNode
	Left   []LayoutNode
	Right  []LayoutNode

	// Body fills the remaining space after all edges are processed.
	// Optional - if nil, the dock only contains edge children.
	Body LayoutNode

	// DockOrder specifies the order in which edges are processed.
	// If nil or empty, defaults to [DockTop, DockBottom, DockLeft, DockRight].
	// Each edge in this list is processed once, laying out all children for that edge.
	DockOrder []DockEdge

	// Container's own insets (optional).
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Container's own size constraints (optional, 0 means unconstrained).
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int
}

DockNode lays out children by docking them to edges (WPF DockPanel style). Each docked child consumes space from the remaining area in order. The body fills whatever space remains after all edges are processed.

Processing order is determined by DockOrder. If not specified, the default order is: Top, Bottom, Left, Right. This matches WPF's LastChildFill behavior when Body is set.

Example: With Top=[header], Bottom=[footer], Body=content:

  • header takes full width at top, consumes its height
  • footer takes full width at bottom, consumes its height
  • content fills the remaining space in the middle

func (*DockNode) ComputeLayout

func (d *DockNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the layout for this dock container.

type EdgeInsets

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

EdgeInsets represents spacing around the four edges of a box.

func EdgeInsetsAll

func EdgeInsetsAll(value int) EdgeInsets

EdgeInsetsAll creates EdgeInsets with the same value for all sides.

func EdgeInsetsTRBL

func EdgeInsetsTRBL(top, right, bottom, left int) EdgeInsets

EdgeInsetsTRBL creates EdgeInsets with individual values for each side.

func EdgeInsetsXY

func EdgeInsetsXY(horizontal, vertical int) EdgeInsets

EdgeInsetsXY creates EdgeInsets with separate horizontal and vertical values.

func (EdgeInsets) Horizontal

func (e EdgeInsets) Horizontal() int

Horizontal returns the total horizontal inset (Left + Right).

func (EdgeInsets) Vertical

func (e EdgeInsets) Vertical() int

Vertical returns the total vertical inset (Top + Bottom).

type FlexNode

type FlexNode struct {
	// Child is the wrapped layout node.
	Child LayoutNode

	// Flex is the proportion of remaining space this child should receive.
	// Must be > 0. Defaults to 1 if not specified.
	// A child with Flex: 2 gets twice the space of a sibling with Flex: 1.
	Flex float64
}

FlexNode wraps a child with flex behavior for use in LinearNode. It's "transparent" - it doesn't produce its own box, just metadata for the parent.

FlexNode is only meaningful as a direct child of LinearNode (Row/Column). The parent inspects FlexNode.Flex to determine space distribution, then layouts FlexNode.Child with the calculated constraints.

The output ComputedLayout is the child's layout - FlexNode doesn't appear in the layout tree. This ensures output indices match input indices, which is critical for widget-to-layout mapping during rendering.

Example:

row := &RowNode{
    Children: []LayoutNode{
        &BoxNode{Width: 100},              // Fixed 100 cells
        &FlexNode{Flex: 1, Child: a},      // Gets 1/3 of remaining
        &FlexNode{Flex: 2, Child: b},      // Gets 2/3 of remaining
    },
}

FlexNode + MainAxisAlignment Composition: Flex children absorb remaining space first, then MainAxisAlignment distributes any leftover (e.g., if flex children hit MaxWidth). This matches CSS flexbox behavior where flex-grow and justify-content interact.

func IsFlexNode

func IsFlexNode(node LayoutNode) (*FlexNode, bool)

IsFlexNode returns true if the node is a FlexNode. Used by LinearNode to identify flex children during layout.

func (*FlexNode) ComputeLayout

func (f *FlexNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout delegates to the child. This is only called if FlexNode is used outside a LinearNode context, in which case the Flex value is ignored and the child is measured normally.

func (*FlexNode) FlexValue

func (f *FlexNode) FlexValue() float64

FlexValue returns the flex value, defaulting to 1 if not set or invalid. This avoids mutating the struct during layout computation.

type HorizontalAlignment

type HorizontalAlignment int

HorizontalAlignment specifies horizontal positioning within available space.

const (
	// HAlignStart aligns content at the start (left).
	HAlignStart HorizontalAlignment = iota
	// HAlignCenter centers content horizontally.
	HAlignCenter
	// HAlignEnd aligns content at the end (right).
	HAlignEnd
)

type LayoutNode

type LayoutNode interface {
	// ComputeLayout computes this node's size and positions all children.
	// Constraints specify the min/max bounds the node must fit within.
	// Returns a ComputedLayout containing the resulting BoxModel and positioned children.
	ComputeLayout(constraints Constraints) ComputedLayout
}

LayoutNode represents a node in the layout tree. It can be a leaf (BoxNode) or a container (RowNode, ColumnNode, etc.).

type LinearNode

type LinearNode struct {
	// Axis determines the layout direction.
	// Horizontal: children flow left-to-right (like Row)
	// Vertical: children flow top-to-bottom (like Column)
	Axis Axis

	// Spacing is the gap between children along the main axis.
	Spacing int

	// MainAlign controls distribution of children along the main axis.
	MainAlign MainAxisAlignment

	// CrossAlign controls positioning of children along the cross axis.
	CrossAlign CrossAxisAlignment

	// Children to lay out.
	Children []LayoutNode

	// Container's own insets (optional).
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Container's own size constraints (optional, 0 means unconstrained).
	// These are border-box constraints, applied after content-based sizing
	// but before parent constraints. Allows containers to enforce minimum
	// sizes independent of their children.
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int

	// Expand flags force the container to fill available space on that axis.
	// When true, MinWidth/MinHeight is set to MaxWidth/MaxHeight from constraints,
	// creating tight constraints that force expansion instead of shrink-wrapping.
	// This is used when a widget's dimension is Flex() to mean "fill available space".
	ExpandWidth  bool
	ExpandHeight bool

	// Preserve flags indicate the container should resist stretching on that axis.
	// When true, the container keeps its natural (content-fitted) size even when
	// a parent tries to stretch it via CrossAxisStretch. This is used when a
	// widget's dimension is explicitly Auto to mean "fit content, don't stretch".
	PreserveWidth  bool
	PreserveHeight bool
}

LinearNode lays out children along a single axis (horizontal or vertical). This is the shared implementation for Row (Horizontal) and Column (Vertical). The algorithm operates on "main axis" and "cross axis" concepts, making the code axis-agnostic.

func (*LinearNode) ComputeLayout

func (l *LinearNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the layout for this linear container.

func (*LinearNode) PreservesHeight

func (l *LinearNode) PreservesHeight() bool

PreservesHeight implements SizePreserver. Returns true if this node should resist vertical stretching.

func (*LinearNode) PreservesWidth

func (l *LinearNode) PreservesWidth() bool

PreservesWidth implements SizePreserver. Returns true if this node should resist horizontal stretching.

type MainAxisAlignment

type MainAxisAlignment int

MainAxisAlignment controls how children are distributed along the main axis when there is extra space available.

const (
	// MainAxisStart packs children at the start of the main axis.
	MainAxisStart MainAxisAlignment = iota

	// MainAxisCenter centers children along the main axis.
	MainAxisCenter

	// MainAxisEnd packs children at the end of the main axis.
	MainAxisEnd

	// MainAxisSpaceBetween distributes extra space evenly between children.
	// First child at start, last child at end, equal gaps between.
	MainAxisSpaceBetween

	// MainAxisSpaceAround distributes extra space evenly around children.
	// Each child gets equal space on both sides (gaps between children are 2x edge gaps).
	MainAxisSpaceAround

	// MainAxisSpaceEvenly distributes extra space so all gaps (including edges) are equal.
	MainAxisSpaceEvenly
)

type PercentNode

type PercentNode struct {
	// Child is the wrapped layout node.
	Child LayoutNode

	// Percent is the percentage of parent space (0-100+).
	// Values over 100 are allowed and will cause overflow.
	Percent float64

	// Axis determines which constraint dimension to use for percentage calculation.
	// Horizontal uses MaxWidth, Vertical uses MaxHeight.
	Axis Axis
}

PercentNode wraps a child with percentage-based sizing for use in LinearNode. Unlike FlexNode which distributes remaining space, PercentNode calculates a fixed size as a percentage of the parent's available space.

PercentNode is only meaningful as a direct child of LinearNode (Row/Column). The parent uses PercentNode during the first pass (non-flex measurement), where the percentage is resolved to a fixed size based on constraints.

Example:

row := &RowNode{
    Children: []LayoutNode{
        &PercentNode{Percent: 30, Axis: Horizontal, Child: a},  // 30% of parent width
        &PercentNode{Percent: 70, Axis: Horizontal, Child: b},  // 70% of parent width
    },
}

func IsPercentNode

func IsPercentNode(node LayoutNode) (*PercentNode, bool)

IsPercentNode returns true if the node is a PercentNode. Used by LinearNode to identify percentage children during layout.

func (*PercentNode) ComputeLayout

func (p *PercentNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout calculates the fixed size from the percentage and applies tight constraints on the main axis while preserving cross-axis constraints.

type PositionedChild

type PositionedChild struct {
	// X, Y is the child's border-box position relative to parent's content-area.
	X, Y int

	// Layout is the child's computed layout (recursive).
	Layout ComputedLayout
}

PositionedChild is a child with its computed position.

Coordinate system: X and Y specify the child's border-box position relative to the parent's content-area origin (after padding and border).

The renderer must add the parent's padding and border offsets when translating to screen coordinates. For example:

screenX := parentScreenX + parent.Box.Padding.Left + parent.Box.Border.Left + child.X
screenY := parentScreenY + parent.Box.Padding.Top + parent.Box.Border.Top + child.Y

Child margins are already accounted for in X,Y: if a child has Margin.Left=5, its X is offset by 5 from where its margin-box starts. This means X,Y point directly to where the child's visible border-box begins.

type Rect

type Rect struct {
	X, Y          int
	Width, Height int
}

Rect represents a rectangle with position and size.

func (Rect) Contains

func (r Rect) Contains(x, y int) bool

Contains returns true if the point (x, y) is within this rectangle.

type RowNode

type RowNode struct {
	// Spacing is the gap between children.
	Spacing int

	// MainAlign controls horizontal distribution of children.
	MainAlign MainAxisAlignment

	// CrossAlign controls vertical positioning of children.
	CrossAlign CrossAxisAlignment

	// Children to lay out.
	Children []LayoutNode

	// Container's own insets (optional).
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Container's own size constraints (optional, 0 means unconstrained).
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int

	// Expand flags force the container to fill available space on that axis.
	// Used when the widget's dimension is Flex() to mean "fill available space".
	ExpandWidth  bool
	ExpandHeight bool

	// Preserve flags indicate the container should resist stretching on that axis.
	// Used when the widget's dimension is explicitly Auto to mean "fit content, don't stretch".
	PreserveWidth  bool
	PreserveHeight bool
}

RowNode lays out children horizontally (left to right). This is a thin wrapper around LinearNode with Axis=Horizontal.

func (*RowNode) ComputeLayout

func (r *RowNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the row layout by delegating to LinearNode.

func (*RowNode) PreservesHeight

func (r *RowNode) PreservesHeight() bool

PreservesHeight implements SizePreserver.

func (*RowNode) PreservesWidth

func (r *RowNode) PreservesWidth() bool

PreservesWidth implements SizePreserver.

type ScrollableNode

type ScrollableNode struct {
	// Child is the content to scroll.
	Child LayoutNode

	// ScrollOffsetX is the horizontal scroll offset in cells.
	ScrollOffsetX int

	// ScrollOffsetY is the vertical scroll offset in cells.
	ScrollOffsetY int

	// ScrollbarWidth is the space reserved for a vertical scrollbar (default 1).
	// Set to 0 to disable vertical scrollbar space reservation.
	ScrollbarWidth int

	// ScrollbarHeight is the space reserved for a horizontal scrollbar (default 1).
	// Set to 0 to disable horizontal scrollbar space reservation.
	ScrollbarHeight int

	// AlwaysReserveScrollbarSpace forces scrollbar space reservation even when
	// content doesn't require scrolling. This prevents layout "jumping" when
	// content crosses the scrollable threshold.
	AlwaysReserveScrollbarSpace bool

	// Container's own insets (optional).
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Container's own size constraints (optional, 0 means unconstrained).
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int
}

ScrollableNode wraps a child and applies viewport/scrolling semantics. Unlike DockNode (which partitions space), ScrollableNode clips content that exceeds the viewport and tracks virtual dimensions.

The layout algorithm: 1. Reserve scrollbar space if needed (based on AlwaysReserveScrollbarSpace or content size) 2. Measure child with both bounded and unbounded heights, then choose virtual content size 3. If virtual size exceeds viewport, scrollbar space is reserved 4. Build BoxModel with VirtualHeight/ScrollOffsetY populated

Example:

scrollable := &ScrollableNode{
    Child:         longContent,
    ScrollOffsetY: 10,
    ScrollbarWidth: 1,
}
result := scrollable.ComputeLayout(Tight(80, 24))
// result.Box.VirtualHeight may exceed 24
// result.Box.IsScrollableY() returns true if content overflows

func (*ScrollableNode) ComputeLayout

func (s *ScrollableNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the scrollable layout. The child is measured with both viewport-bounded and unbounded height probes to derive a stable virtual size. The viewport is then constrained to the parent constraints, and scroll offsets are applied.

type SizePreserver

type SizePreserver interface {
	// PreservesWidth returns true if the node should resist horizontal stretching.
	PreservesWidth() bool
	// PreservesHeight returns true if the node should resist vertical stretching.
	PreservesHeight() bool
}

SizePreserver is implemented by nodes that can resist stretching on specific axes. This is used when a widget has an explicit Auto dimension, meaning "fit content, don't stretch beyond that" rather than the default "no preference, parent decides".

type SplitPaneNode

type SplitPaneNode struct {
	First  LayoutNode
	Second LayoutNode
	Axis   Axis

	Position    float64 // 0.0-1.0 along the main axis
	DividerSize int
	MinPaneSize int

	// Container insets and constraints.
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int

	// Preserve flags resist cross-axis stretching when Auto is explicitly set.
	PreserveWidth  bool
	PreserveHeight bool
}

SplitPaneNode lays out two children separated by a divider. The container always fills the available space in its constraints.

func (*SplitPaneNode) ComputeLayout

func (n *SplitPaneNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the layout for a split pane container.

func (*SplitPaneNode) PreservesHeight

func (n *SplitPaneNode) PreservesHeight() bool

PreservesHeight indicates whether this node resists vertical stretching.

func (*SplitPaneNode) PreservesWidth

func (n *SplitPaneNode) PreservesWidth() bool

PreservesWidth indicates whether this node resists horizontal stretching.

type StackChild

type StackChild struct {
	Node         LayoutNode // The child's layout node
	IsPositioned bool       // True if this child uses edge-based positioning

	// Edge offsets for positioned children (nil = not constrained).
	// If both Top and Bottom are set, child height = stack height - top - bottom.
	// If both Left and Right are set, child width = stack width - left - right.
	Top    *int
	Right  *int
	Bottom *int
	Left   *int
}

StackChild represents a child within a Stack, with optional positioning info.

type StackNode

type StackNode struct {
	Children []StackChild // Children to overlay

	// Default alignment for non-positioned children.
	DefaultHAlign HorizontalAlignment
	DefaultVAlign VerticalAlignment

	// Insets
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Node's own min/max constraints.
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int

	// Expand flags for flex sizing.
	ExpandWidth  bool
	ExpandHeight bool
}

StackNode overlays children on top of each other. First child is at the bottom, last child is on top.

Stack sizes itself based on the largest non-positioned child. Positioned children do not affect Stack's size (they can overflow).

func (*StackNode) ComputeLayout

func (s *StackNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the StackNode's layout given parent constraints. Unlike other containers, Stack positions children relative to its border-box, not content-box. This allows Positioned children to overlap borders.

type TextNode

type TextNode struct {
	// Content is the text to measure and display.
	Content string

	// Wrap controls how text wraps when it exceeds available width.
	// Default is WrapNone (no wrapping).
	Wrap WrapMode

	// Insets (passed through to BoxNode)
	Padding EdgeInsets
	Border  EdgeInsets
	Margin  EdgeInsets

	// Optional constraints (passed through to BoxNode).
	// NOTE: 0 means "no constraint" (unconstrained), not "zero size".
	MinWidth  int
	MaxWidth  int
	MinHeight int
	MaxHeight int
}

TextNode is a leaf node for text content that may wrap. It's a thin wrapper around BoxNode that provides a declarative API for text.

Rather than duplicating BoxNode's constraint/inset handling logic, TextNode creates an internal BoxNode with a MeasureFunc that calls MeasureText. This ensures bug fixes to BoxNode automatically apply to TextNode.

Example:

node := &TextNode{
    Content: "Hello, World!",
    Wrap:    WrapWord,
    Padding: EdgeInsets{Top: 1, Right: 2, Bottom: 1, Left: 2},
}
result := node.ComputeLayout(Loose(80, 24))

func (*TextNode) ComputeLayout

func (t *TextNode) ComputeLayout(constraints Constraints) ComputedLayout

ComputeLayout computes the layout for this text node. It delegates to a BoxNode with a MeasureFunc that calls MeasureText.

type VerticalAlignment

type VerticalAlignment int

VerticalAlignment specifies vertical positioning within available space.

const (
	// VAlignTop aligns content at the top.
	VAlignTop VerticalAlignment = iota
	// VAlignCenter centers content vertically.
	VAlignCenter
	// VAlignBottom aligns content at the bottom.
	VAlignBottom
)

type WrapMode

type WrapMode int

WrapMode controls text wrapping behavior.

const (
	// WrapNone disables wrapping - text may overflow available width.
	WrapNone WrapMode = iota
	// WrapWord wraps at word boundaries (spaces).
	// Falls back to character breaks for words longer than available width.
	WrapWord
	// WrapChar wraps at exact character boundaries.
	// Useful for CJK text or when precise character-level wrapping is needed.
	WrapChar
)

Jump to

Keyboard shortcuts

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