tuiml

package module
v0.0.0-...-3a75a7a Latest Latest
Warning

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

Go to latest
Published: Nov 24, 2025 License: Apache-2.0 Imports: 15 Imported by: 0

README

TUIML - Terminal UI Markup Language

⚠️ EXPERIMENTAL: This is an experimental project, primarily AI-generated as an exploration of declarative TUI frameworks. Use at your own risk.

Go Reference Build Status Lint Status Go Report Card codecov

A declarative, type-safe markup language for building terminal user interfaces with Ultraviolet as the rendering engine.

type ViewData struct {
    Title string
    Count int
}

const tmpl = `
<vstack gap="1">
    <box border="rounded" border-style="fg:cyan; bold">
        <text style="bold; fg:yellow">{{ .Title }}</text>
    </box>
    <text>Count: {{ .Count }}</text>
</vstack>
`

tmpl := tuiml.MustParse[ViewData](tmpl)
output := tmpl.Render(ViewData{Title: "My App", Count: 42}, 80, 24)

Features

  • Type-safe templates with Go generics
  • Full styling system (colors, attributes, borders)
  • Responsive layouts (%, auto, fixed sizes)
  • Advanced layout (flex-grow, absolute positioning, layering)
  • Custom components with clean API
  • Stateful components via slots
  • Scrolling with scrollbars
  • Bubble Tea integration

Quick Start

Installation
go get github.com/kujtimiihoxha/tuiml
Basic Example
package main

import (
    "fmt"
    "github.com/kujtimiihoxha/tuiml"
)

func main() {
    const tmpl = `
    <vstack gap="1">
        <box border="rounded">
            <text>Hello, World!</text>
        </box>
        <text>Welcome to TUIML!</text>
    </vstack>
    `

    t := tuiml.MustParse[interface{}](tmpl)
    output := t.Render(nil, 80, 24)
    fmt.Print(output)
}

Elements

Containers

VStack - Vertical stack

<vstack gap="1" align="center" width="50%" height="20">
    <!-- children -->
</vstack>

Attributes: gap, align (left|center|right), width, height

HStack - Horizontal stack

<hstack gap="2" valign="middle" width="100%">
    <!-- children -->
</hstack>

Attributes: gap, valign (top|middle|bottom), width, height

ZStack - Layered stack (overlays)

<zstack align="center" valign="middle">
    <box border="rounded">Background</box>
    <text style="bold">Overlay</text>
</zstack>

Attributes: align (left|center|right), valign (top|middle|bottom), width, height

Children are drawn on top of each other (later children on top).

Content

Text

<text style="fg:cyan; bold" align="center" wrap="true">
    Content
</text>

Attributes: style, align (left|center|right), wrap

Box - Container (like a div)

<box padding="2" margin="1" width="50%">
    <!-- child -->
</box>

<!-- With border -->
<box border="rounded" border-style="fg:cyan" padding="2">
    <!-- child -->
</box>

<!-- Individual margins -->
<box margin-top="2" margin-left="3" margin-right="1" margin-bottom="2">
    <!-- child -->
</box>

Attributes: border, border-style, padding, margin, margin-top, margin-right, margin-bottom, margin-left, width, height

Border styles: normal, rounded, thick, double, hidden, none (default)

Divider - Separator line

<divider style="fg:gray" />
<divider vertical="true" char="|" />

Spacer - Empty space

<!-- Fixed spacer -->
<spacer size="2" />

<!-- Flexible spacer (grows to fill available space) -->
<spacer />

ScrollView - Scrollable viewport

<scrollview height="10" scrollbar="true">
    <!-- large content -->
</scrollview>

Slot - Dynamic content placeholder

<slot name="content" />
Advanced Layout

Flex - Flexible sizing wrapper

<hstack>
    <box width="20">Fixed</box>
    <flex grow="1">
        <box>Grows 1x</box>
    </flex>
    <flex grow="2">
        <box>Grows 2x</box>
    </flex>
</hstack>

Attributes: grow (flex-grow factor), shrink (flex-shrink factor), basis (initial size)

Positioned - Absolute positioning

<zstack>
    <box>Background</box>
    
    <!-- Position from top-left -->
    <positioned x="10" y="5">
        <text>At (10,5)</text>
    </positioned>
    
    <!-- Position from edges -->
    <positioned right="2" bottom="1">
        <text>Bottom-right corner</text>
    </positioned>
</zstack>

Attributes: x, y (position from top-left), right, bottom (position from edges), width, height

Positioned elements don't affect parent layout (out of flow).

Built-in Components

Badge - Status indicator

<badge text="NEW" style="fg:green; bold" />

Progress - Progress bar

<progress value="75" max="100" width="40" style="fg:green" />

Header - Section header

<header text="Title" style="fg:cyan; bold" border="true" />

Button - Clickable button

<button id="submit-btn" text="Submit" border="rounded" padding="1" style="fg:green" />

Attributes: id, text, border, padding, style, width, height

Styling

In Markup
<text style="fg:red; bg:black; bold; italic">Styled text</text>

Colors:

  • Named: fg:red, bg:blue
  • Hex: fg:#FF5555, bg:#282a36
  • RGB: fg:rgb(255,85,85)
  • ANSI: fg:196

Attributes: bold, italic, underline, strikethrough, faint, blink, reverse

Underline styles: underline:single|double|curly|dotted|dashed

In Code (Helpers)
style := tuiml.NewStyle().
    Fg(tuiml.Hex("#FF5555")).
    Bg(tuiml.RGB(40, 42, 54)).
    Bold().
    Italic().
    Build()

text := &tuiml.Text{
    Content: "Hello",
    Style:   style,
}

Layout

Sizing
<box width="50%">...</box>    <!-- Percentage -->
<box width="20">...</box>      <!-- Fixed cells -->
<box width="auto">...</box>    <!-- Content size (default) -->
<box width="min">...</box>     <!-- Minimum content size -->
<box width="max">...</box>     <!-- Maximum available -->
Alignment

Text:

<text align="left|center|right">...</text>

VStack children:

<vstack align="left|center|right">...</vstack>

HStack children:

<hstack valign="top|middle|bottom">...</hstack>

ZStack children:

<zstack align="left|center|right" valign="top|middle|bottom">...</zstack>
Flexible Sizing

Use <spacer /> or <flex> for flexible layouts:

<vstack>
    <text>Header</text>
    <spacer />  <!-- Grows to fill space -->
    <text>Footer</text>
</vstack>

<hstack>
    <box width="20">Fixed sidebar</box>
    <flex grow="1">
        <box>Main content (grows)</box>
    </flex>
</hstack>

Go Templates

Variables
<text>Hello, {{ .Username }}!</text>
Conditionals
{{ if .IsOnline }}
<text style="fg:green">● Online</text>
{{ else }}
<text style="fg:red">○ Offline</text>
{{ end }}
Loops
{{ range .Items }}
<text>• {{ . }}</text>
{{ end }}
Functions

Built-in: upper, lower, title, trim, join, printf, add, sub, mul, div, repeat

<text>{{ upper .Title }}</text>
<text>{{ printf "Count: %d" .Count }}</text>

Custom Components

Register a Component
// Simple functional component
tuiml.Register("card", func(props tuiml.Props, children []tuiml.Element) tuiml.Element {
    titleStyle := tuiml.NewStyle().Bold().Build()
    
    return tuiml.NewBox(
        tuiml.NewVStack(
            tuiml.NewText(props.Get("title")).WithStyle(titleStyle),
            tuiml.NewDivider(),
            tuiml.NewVStack(children...),
        ),
    ).WithBorder("rounded").WithPadding(1)
})

// Or create a custom type for more control
type Card struct {
    tuiml.BaseElement  // Required for ID and bounds tracking
    Title   string
    Color   string
    Content []tuiml.Element
}

func NewCard(props tuiml.Props, children []tuiml.Element) tuiml.Element {
    return &Card{
        Title:   props.Get("title"),
        Color:   props.GetOr("color", "blue"),
        Content: children,
    }
}

func (c *Card) Draw(scr uv.Screen, area uv.Rectangle) {
    c.SetBounds(area) // Track bounds for mouse interaction
    
    // Build composed structure
    style := tuiml.NewStyle().Fg(tuiml.Hex("#00FFFF")).Bold().Build()
    card := tuiml.NewBox(
        tuiml.NewVStack(
            tuiml.NewText(c.Title).WithStyle(style),
            tuiml.NewDivider(),
            tuiml.NewVStack(c.Content...),
        ),
    ).WithBorder("rounded").WithPadding(1)
    
    card.Draw(scr, area)
}

func (c *Card) Layout(constraints tuiml.Constraints) tuiml.Size {
    // Delegate to composed structure
    style := tuiml.NewStyle().Fg(tuiml.Hex("#00FFFF")).Bold().Build()
    card := tuiml.NewBox(
        tuiml.NewVStack(
            tuiml.NewText(c.Title).WithStyle(style),
            tuiml.NewDivider(),
            tuiml.NewVStack(c.Content...),
        ),
    ).WithBorder("rounded").WithPadding(1)
    
    return card.Layout(constraints)
}

func (c *Card) Children() []tuiml.Element {
    return c.Content
}

// Register it
tuiml.Register("card", NewCard)
Use in Markup
<card title="Profile">
    <text>Name: Alice</text>
    <text>Role: Developer</text>
</card>

Stateful Components

Components with state use the slot system:

type Input struct {
    value  string
    cursor int
}

func (i *Input) Update(msg tea.Msg) {
    // Handle input
}

func (i *Input) Render() tuiml.Element {
    return tuiml.NewBox(
        tuiml.NewText(i.value),
    ).WithBorder("rounded")
}

Template with slots:

<vstack>
    <text>Username:</text>
    <slot name="input" />
</vstack>

Render with slots:

func (m model) View() tea.View {
    slots := map[string]tuiml.Element{
        "input": m.inputComp.Render(),
    }
    
    output := m.template.RenderWithSlots(data, slots, m.width, m.height)
    return tea.NewView(output)
}

Bubble Tea Integration

import (
    tea "charm.land/bubbletea/v2"
    "github.com/kujtimiihoxha/tuiml"
)

type ViewData struct {
    Count int
}

type model struct {
    template *tuiml.Template[ViewData]
    count    int
    width    int
    height   int
}

func (m model) Init() tea.Cmd {
    return tea.RequestWindowSize
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.WindowSizeMsg:
        m.width = msg.Width
        m.height = msg.Height
    case tea.KeyPressMsg:
        if msg.String() == "space" {
            m.count++
        }
    }
    return m, nil
}

func (m model) View() tea.View {
    data := ViewData{Count: m.count}
    output := m.template.Render(data, m.width, m.height)
    return tea.NewView(output)
}

Mouse Click Handling

TUIML provides stateless mouse click handling through bounds tracking and hit testing. All elements are interactive by default with no state mutation in View.

Quick Example
type buttonClickMsg string

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case buttonClickMsg:
        switch msg {
        case "submit-btn":
            return m, m.submitForm()
        case "cancel-btn":
            return m, tea.Quit
        }
    }
    return m, nil
}

func (m model) View() tea.View {
    data := ViewData{...}
    
    // Render and get bounds map
    scr, boundsMap := m.template.RenderWithBounds(data, nil, m.width, m.height)
    
    view := tea.NewView(scr.Render())
    view.MouseMode = tea.MouseModeAllMotion  // Enable mouse events
    
    // Callback captures boundsMap - no model mutation!
    view.Callback = func(msg tea.Msg) tea.Cmd {
        if click, ok := msg.(tea.MouseClickMsg); ok {
            mouse := click.Mouse()
            
            // Find which element was clicked
            if elem := boundsMap.HitTest(mouse.X, mouse.Y); elem != nil {
                return func() tea.Msg {
                    return buttonClickMsg(elem.ID())
                }
            }
        }
        return nil
    }
    
    return view
}
Set Element IDs

In markup:

<button id="submit-btn" text="Submit" />
<button id="cancel-btn" text="Cancel" />

Programmatically:

btn := tuiml.NewButton("Submit")
btn.SetID("submit-btn")

For custom components that render other elements:

// IMPORTANT: Pass through your component's ID to the rendered element
// so clicks anywhere in the component return your component's ID

func (i *Input) Render() tuiml.Element {
    vstack := tuiml.NewVStack(
        tuiml.NewText(i.label),
        tuiml.NewBox(tuiml.NewText(i.value)).WithBorder("rounded"),
    )
    
    // Set the input's ID on the VStack so clicks return "my-input", not child IDs
    vstack.SetID(i.ID())
    
    return vstack
}

// Usage
input := NewInput("Name:")
input.SetID("name-input")  // When clicked, returns "name-input"
Button Component
<!-- Basic button -->
<button id="my-btn" text="Click Me" />

<!-- Styled button -->
<button id="submit" text="Submit" 
        border="rounded" 
        padding="1" 
        style="fg:green; bold" />
BoundsMap API
// Hit test - find element at coordinates
// Prefers elements with explicit IDs over auto-generated ones
elem := boundsMap.HitTest(x, y) // Returns Element or nil

// Get element by ID
elem, ok := boundsMap.GetByID("button-id")

// Get bounds for element ID
bounds, ok := boundsMap.GetBounds("button-id")

// Get all elements with bounds
elements := boundsMap.AllElements() // []ElementWithBounds

Important: HitTest() prefers elements with explicit IDs when multiple elements overlap. This ensures that clicking inside a component returns the component's ID, not its children's IDs. Always use SetID() on the root element your component returns (see example above).

Hover Detection
type hoverMsg string

view.MouseMode = tea.MouseModeAllMotion  // Enable all mouse motion

view.Callback = func(msg tea.Msg) tea.Cmd {
    switch msg := msg.(type) {
    case tea.MouseClickMsg:
        // Handle clicks
        
    case tea.MouseMotionMsg:
        // Handle hover
        mouse := msg.Mouse()
        if elem := boundsMap.HitTest(mouse.X, mouse.Y); elem != nil {
            return func() tea.Msg {
                return hoverMsg(elem.ID())
            }
        }
    }
    return nil
}
Requirements

Mouse handling requires Bubble Tea PR #1549 (View callback support). Pin to this commit:

require (
    charm.land/bubbletea/v2 v2.0.0-20250120210912-18cfb8c3ccb3
)
How It Works
  1. RenderWithBounds() returns screen + BoundsMap (every element's position)
  2. View.Callback captures BoundsMap via closure (no model mutation!)
  3. HitTest() finds which element is at mouse coordinates
    • Prefers elements with explicit IDs over auto-generated IDs
    • This means clicking inside a component returns the component's ID, not child IDs
  4. Callback returns Cmd with element ID
  5. Update() handles custom messages based on element ID

Benefits:

  • ✅ Pure View() - no state mutation
  • ✅ Stateless - BoundsMap is immutable
  • ✅ Universal - all elements interactive by default
  • ✅ Type-safe - element IDs are strings
  • ✅ Single render - no double-rendering needed
  • ✅ Smart hit testing - prefers meaningful IDs

Examples

See examples/ for complete working examples:

  • hello - Basic hello world
  • layout - Responsive layouts
  • styled - Styling showcase
  • dynamic - Type-safe templates
  • alignment - Alignment & padding
  • components - Built-in components
  • custom - Custom components
  • stateful - Stateful components
  • scrolling - Scrollable views
  • helpers - Style helpers
  • advanced - Advanced layout (ZStack, Flex, Positioned, Margin)
  • simple-bubbletea - Minimal Bubble Tea app
  • bubbletea - Full interactive app
  • buttons - Mouse click handling with interactive buttons
  • interactive-form - Complex form with slots, validation, and mouse interactions

API Reference

Template
// Parse with type safety
tmpl, err := tuiml.Parse[YourDataType](markup)
tmpl := tuiml.MustParse[YourDataType](markup)

// Render
output := tmpl.Render(data, width, height)
output := tmpl.RenderWithSlots(data, slots, width, height)

// Render with bounds for mouse handling
scr, boundsMap := tmpl.RenderWithBounds(data, slots, width, height)
Element Constructors
tuiml.NewText(content)
tuiml.NewBox(child)
tuiml.NewVStack(children...)
tuiml.NewHStack(children...)
tuiml.NewZStack(children...)
tuiml.NewButton(text)
tuiml.NewDivider()
tuiml.NewSpacer()
tuiml.NewFlex(child)
tuiml.NewPositioned(child, x, y)
tuiml.NewSlot(name)
tuiml.NewScrollView(child)
Fluent API
box := tuiml.NewBox(child).
    WithBorder("rounded").
    WithPadding(2).
    WithMargin(1).
    WithMarginTop(2).
    WithWidth(tuiml.NewFixedConstraint(50)).
    WithBorderStyle(style)

text := tuiml.NewText("Hello").
    WithStyle(style).
    WithAlign("center").
    WithWrap(true)

button := tuiml.NewButton("Click Me").
    WithBorder("rounded").
    WithPadding(1).
    WithStyle(style).
    WithWidth(tuiml.NewFixedConstraint(20))
button.SetID("my-button")

flex := tuiml.NewFlex(child).
    WithGrow(1).
    WithShrink(0).
    WithBasis(20)

positioned := tuiml.NewPositioned(child, 10, 5).
    WithRight(2).
    WithBottom(1)
Style Builder
style := tuiml.NewStyle().
    Fg(tuiml.Hex("#FF5555")).
    Bg(tuiml.RGB(40, 42, 54)).
    Bold().
    Italic().
    Underline().
    Build()
Component Registry
tuiml.Register(name, factory)
tuiml.Unregister(name)
tuiml.GetComponent(name)
tuiml.RegisteredComponents()
Layout Helpers
// Basic layouts
tuiml.Panel(child, border, padding)
tuiml.PanelWithMargin(child, border, padding, margin)
tuiml.Card(title, titleStyle, borderStyle, children...)
tuiml.Section(header, headerStyle, children...)
tuiml.Separated(children...)

// Advanced layouts
tuiml.Overlay(children...)  // ZStack with default alignment
tuiml.FlexGrow(child, grow)
tuiml.Position(child, x, y)
tuiml.PositionRight(child, right, y)
tuiml.PositionBottom(child, x, bottom)
tuiml.PositionCorner(child, right, bottom)

Architecture

Template[T] → Go Template → XML Parse → Element Tree → Fill Slots → Layout → UV Render

Documentation

Overview

Package tuiml provides a declarative, type-safe markup language for building terminal user interfaces using Ultraviolet as the rendering engine.

⚠️ EXPERIMENTAL: This is an experimental project, primarily AI-generated as an exploration of declarative TUI frameworks. Use at your own risk.

TUIML allows you to define TUI layouts using familiar XML-like markup syntax combined with Go templates for dynamic content. It integrates seamlessly with Bubble Tea for application lifecycle management while leveraging Ultraviolet's efficient cell-based rendering.

Basic Example

type ViewData struct {
    Title   string
    Content string
}

const tmpl = `
<vstack gap="1">
  <box border="rounded">
    <text style="bold; fg:cyan">{{ .Title }}</text>
  </box>
  <text>{{ .Content }}</text>
</vstack>
`

t := tuiml.MustParse[ViewData](tmpl)
data := ViewData{
    Title:   "Hello World",
    Content: "Welcome to TUIML!",
}
output := t.Render(data, 80, 24)

Elements

  • vstack: Vertical stack container with gap and alignment
  • hstack: Horizontal stack container with gap and alignment
  • text: Text content with styling and alignment
  • box: Container with borders and padding
  • scrollview: Scrollable viewport with scrollbars
  • divider: Horizontal or vertical separator line
  • spacer: Flexible or fixed empty space
  • slot: Placeholder for dynamic content

Styling

Elements support CSS-like inline styling:

<text style="fg:cyan; bg:#1a1b26; bold; italic">Styled text</text>

For programmatic styling, use the StyleBuilder:

style := tuiml.NewStyle().
    Fg(tuiml.Hex("#FF5555")).
    Bold().
    Build()

Custom Components

Register custom components with the component registry:

tuiml.Register("card", func(props Props, children []Element) Element {
    return tuiml.NewBox(
        tuiml.NewVStack(children...),
    ).WithBorder("rounded").WithPadding(1)
})

Use in markup:

<card><text>Content</text></card>

Stateful Components

Use slots for stateful components that manage their own state:

type Input struct {
    value string
}

func (i *Input) Update(msg tea.Msg) { /* handle events */ }

func (i *Input) Render() tuiml.Element {
    return tuiml.NewBox(tuiml.NewText(i.value)).WithBorder("rounded")
}

Template with slot:

<vstack>
  <text>Enter name:</text>
  <slot name="input" />
</vstack>

Render with slots:

slots := map[string]tuiml.Element{
    "input": m.inputComp.Render(),
}
output := tmpl.RenderWithSlots(data, slots, width, height)

Bubble Tea Integration

type model struct {
    template *tuiml.Template[ViewData]
    width    int
    height   int
}

func (m model) Init() tea.Cmd {
    return tea.RequestWindowSize
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.WindowSizeMsg:
        m.width = msg.Width
        m.height = msg.Height
    }
    return m, nil
}

func (m model) View() tea.View {
    data := ViewData{...}
    output := m.template.Render(data, m.width, m.height)
    return tea.NewView(output)
}

Package tuiml provides examples and patterns for building interactive TUI components.

Building Interactive Components

When creating custom components that render other elements, you must pass through your component's ID to the root element you return. This ensures that mouse clicks anywhere in your component return your component's ID, not child element IDs.

## Pattern: Pass Through Component ID

type MyComponent struct {
    tuiml.BaseElement  // Provides ID() and SetID()
    // ... your fields
}

func (c *MyComponent) Render() tuiml.Element {
    // Build your UI
    root := tuiml.NewVStack(
        tuiml.NewText(c.label),
        tuiml.NewBox(tuiml.NewText(c.value)),
    )

    // CRITICAL: Set your component's ID on the root element
    root.SetID(c.ID())

    return root
}

## Why This Matters

HitTest() prefers elements with explicit IDs over auto-generated ones. When multiple elements overlap at a click point:

  1. Without SetID: Returns child element with auto-generated ID like "elem_0x123..."
  2. With SetID: Returns your component with meaningful ID like "name-input"

This allows you to handle clicks on the component as a whole, not individual children.

## Example Usage

input := NewInput("Name:")
input.SetID("name-input")

// In template
slots := map[string]tuiml.Element{
    "input": input.Render(),  // VStack with ID "name-input"
}

// In callback
view.Callback = func(msg tea.Msg) tea.Cmd {
    if click, ok := msg.(tea.MouseClickMsg); ok {
        elem := boundsMap.HitTest(click.X, click.Y)
        // elem.ID() returns "name-input" - exactly what you want!
    }
}

See examples/interactive-form for a complete working example.

Index

Constants

View Source
const (
	BorderNone    = "none"
	BorderNormal  = "normal"
	BorderRounded = "rounded"
	BorderThick   = "thick"
	BorderDouble  = "double"
	BorderHidden  = "hidden"
)

Border styles.

View Source
const (
	AlignLeft   = "left"
	AlignCenter = "center"
	AlignRight  = "right"
	AlignTop    = "top"
	AlignMiddle = "middle"
	AlignBottom = "bottom"
)

Alignment constants.

View Source
const (
	UnitAuto    = "auto"
	UnitMin     = "min"
	UnitMax     = "max"
	UnitPercent = "%"
)

Size constraint units.

View Source
const (
	UnderlineNone   = "none"
	UnderlineSingle = "single"
	UnderlineDouble = "double"
	UnderlineCurly  = "curly"
	UnderlineDotted = "dotted"
	UnderlineDashed = "dashed"
	UnderlineSolid  = "solid"
)

Underline styles (matching UV).

Variables

This section is empty.

Functions

func ClearRegistry

func ClearRegistry()

ClearRegistry removes all registered components. Useful for testing.

func GetFlexBasis

func GetFlexBasis(elem Element) int

GetFlexBasis returns the flex-basis value for an element. Returns 0 (auto) if the element is not a Flex wrapper.

func GetFlexGrow

func GetFlexGrow(elem Element) int

GetFlexGrow returns the flex-grow value for an element. Returns 0 if the element is not a Flex wrapper.

func GetFlexShrink

func GetFlexShrink(elem Element) int

GetFlexShrink returns the flex-shrink value for an element. Returns 1 if the element is not a Flex wrapper.

func Hex

func Hex(s string) color.Color

Hex creates a color from a hex string. Panics if invalid - use HexSafe for error handling.

func HexSafe

func HexSafe(s string) (color.Color, error)

HexSafe creates a color from a hex string with error handling.

func IsFlexible

func IsFlexible(elem Element) bool

IsFlexible returns true if the element can grow.

func ParseStyle

func ParseStyle(s string) (uv.Style, error)

ParseStyle parses a style string into a UV Style. Format: "fg:red; bg:#1a1b26; bold; italic".

func RGB

func RGB(r, g, b uint8) color.Color

RGB creates a color from RGB values.

func Register

func Register(name string, factory ComponentFactory)

Register registers a custom component with the given name. The factory function will be called to create instances of the component.

Example:

tuiml.Register("badge", func(props Props, children []Element) Element {
    return &Badge{
        text:  props.Get("text"),
        color: props.Get("color"),
    }
})

func RegisteredComponents

func RegisteredComponents() []string

RegisteredComponents returns a list of all registered component names.

func Unregister

func Unregister(name string)

Unregister removes a registered component.

Types

type AutoConstraint

type AutoConstraint struct{}

AutoConstraint represents content-based sizing.

func (AutoConstraint) Apply

func (a AutoConstraint) Apply(available int) int

Apply returns the available size (will be calculated based on content).

type Badge

type Badge struct {
	BaseElement
	Text  string
	Style uv.Style
}

Badge represents a badge element (like "NEW", "BETA", status indicators).

func (*Badge) Children

func (b *Badge) Children() []Element

Children returns nil.

func (*Badge) Draw

func (b *Badge) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the badge.

func (*Badge) Layout

func (b *Badge) Layout(constraints Constraints) Size

Layout calculates badge size.

type BaseElement

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

BaseElement provides common functionality for all elements. Elements should embed this to get ID and bounds tracking.

func (*BaseElement) Bounds

func (b *BaseElement) Bounds() uv.Rectangle

Bounds returns the element's last rendered bounds.

func (*BaseElement) ID

func (b *BaseElement) ID() string

ID returns the element's identifier. If no ID was explicitly set, returns a pointer-based ID.

func (*BaseElement) SetBounds

func (b *BaseElement) SetBounds(bounds uv.Rectangle)

SetBounds records the element's rendered bounds. This should be called at the start of Draw().

func (*BaseElement) SetID

func (b *BaseElement) SetID(id string)

SetID sets the element's identifier.

type BoundsMap

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

BoundsMap tracks all rendered elements and their positions for hit testing. It is immutable after creation and safe for concurrent reads.

func NewBoundsMap

func NewBoundsMap() *BoundsMap

NewBoundsMap creates a new empty bounds map.

func (*BoundsMap) AllElements

func (bm *BoundsMap) AllElements() []ElementWithBounds

AllElements returns all registered elements with their bounds.

func (*BoundsMap) GetBounds

func (bm *BoundsMap) GetBounds(id string) (uv.Rectangle, bool)

GetBounds returns the rendered bounds for an element by ID.

func (*BoundsMap) GetByID

func (bm *BoundsMap) GetByID(id string) (Element, bool)

GetByID retrieves an element by its ID.

func (*BoundsMap) HitTest

func (bm *BoundsMap) HitTest(x, y int) Element

HitTest returns the top-most element at the given screen coordinates. When multiple elements overlap at a point, it prefers elements with explicitly set IDs over auto-generated IDs (elem_*).

This behavior is crucial for interactive components: when you click inside a component's rendered area, you want the component's ID, not its children's. For example, clicking anywhere in an Input component should return the Input's ID, not the Text or Box child inside it.

To achieve this, set your component's ID on the root element it returns:

func (i *Input) Render() tuiml.Element {
    vstack := tuiml.NewVStack(...)
    vstack.SetID(i.ID())  // Pass through component ID
    return vstack
}

Returns nil if no element is found at that position.

func (*BoundsMap) HitTestAll

func (bm *BoundsMap) HitTestAll(x, y int) []Element

HitTestAll returns all elements at the given screen coordinates, ordered from top to bottom (first element is visually on top).

This is useful for nested interactive components like scroll views with clickable children, where you need to know both the child that was clicked and the parent containers.

Example usage:

hits := boundsMap.HitTestAll(x, y)
for _, elem := range hits {
    switch elem.ID() {
    case "list-item-5":
        // Handle item click
    case "main-scroll-view":
        // Also track that we're in the scroll view
    }
}

Returns empty slice if no elements are found at that position.

func (*BoundsMap) HitTestWithContainer

func (bm *BoundsMap) HitTestWithContainer(x, y int) (top Element, container Element)

HitTestWithContainer returns the top element and the first parent container with an explicit ID. This is useful for scroll views with clickable items where you want to know both what was clicked and which container it's in.

The "top" element is the visually topmost element at the coordinates. The "container" is the first element in the hit stack (after top) that has an explicit ID (not auto-generated with "elem_" prefix).

Example usage:

top, container := boundsMap.HitTestWithContainer(x, y)
if top != nil {
    handleClick(top.ID())
}
if container != nil && container.ID() == "scroll-view" {
    // We know we clicked inside a scroll view
}

Returns (nil, nil) if no element is found at that position.

func (*BoundsMap) Register

func (bm *BoundsMap) Register(elem Element, bounds uv.Rectangle)

Register records an element and its rendered bounds. This should be called during the render pass.

type Box

type Box struct {
	BaseElement
	Child        Element
	Border       string // normal, rounded, thick, double, hidden, none
	BorderStyle  uv.Style
	Width        SizeConstraint
	Height       SizeConstraint
	Padding      int
	Margin       int // margin on all sides
	MarginTop    int
	MarginRight  int
	MarginBottom int
	MarginLeft   int
}

Box represents a container with an optional border.

func NewBox

func NewBox(child Element) *Box

NewBox creates a new box element.

func Panel

func Panel(child Element, border string, padding int) *Box

Panel creates a box with border and padding.

func PanelWithMargin

func PanelWithMargin(child Element, border string, padding, margin int) *Box

PanelWithMargin creates a box with border, padding, and margin.

func (*Box) Children

func (b *Box) Children() []Element

Children returns the child element.

func (*Box) Draw

func (b *Box) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the box to the screen.

func (*Box) Layout

func (b *Box) Layout(constraints Constraints) Size

Layout calculates the box size.

func (*Box) WithBorder

func (b *Box) WithBorder(border string) *Box

WithBorder sets the border style and returns the box for chaining.

func (*Box) WithBorderStyle

func (b *Box) WithBorderStyle(style uv.Style) *Box

WithBorderStyle sets the border style and returns the box for chaining.

func (*Box) WithHeight

func (b *Box) WithHeight(height SizeConstraint) *Box

WithHeight sets the height constraint and returns the box for chaining.

func (*Box) WithMargin

func (b *Box) WithMargin(margin int) *Box

WithMargin sets the margin on all sides and returns the box for chaining.

func (*Box) WithMarginBottom

func (b *Box) WithMarginBottom(margin int) *Box

WithMarginBottom sets the bottom margin and returns the box for chaining.

func (*Box) WithMarginLeft

func (b *Box) WithMarginLeft(margin int) *Box

WithMarginLeft sets the left margin and returns the box for chaining.

func (*Box) WithMarginRight

func (b *Box) WithMarginRight(margin int) *Box

WithMarginRight sets the right margin and returns the box for chaining.

func (*Box) WithMarginTop

func (b *Box) WithMarginTop(margin int) *Box

WithMarginTop sets the top margin and returns the box for chaining.

func (*Box) WithPadding

func (b *Box) WithPadding(padding int) *Box

WithPadding sets the padding and returns the box for chaining.

func (*Box) WithWidth

func (b *Box) WithWidth(width SizeConstraint) *Box

WithWidth sets the width constraint and returns the box for chaining.

type Button

type Button struct {
	BaseElement
	Text        string
	Style       uv.Style
	HoverStyle  uv.Style
	ActiveStyle uv.Style
	Border      string
	Padding     int
	Width       SizeConstraint
	Height      SizeConstraint
}

Button represents a clickable button element.

func NewButton

func NewButton(text string) *Button

NewButton creates a new button element.

func (*Button) Children

func (b *Button) Children() []Element

Children returns nil for buttons.

func (*Button) Draw

func (b *Button) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the button to the screen.

func (*Button) Layout

func (b *Button) Layout(constraints Constraints) Size

Layout calculates button size.

func (*Button) WithActiveStyle

func (b *Button) WithActiveStyle(style uv.Style) *Button

WithActiveStyle sets the active (pressed) style and returns the button for chaining.

func (*Button) WithBorder

func (b *Button) WithBorder(border string) *Button

WithBorder sets the border type and returns the button for chaining.

func (*Button) WithHeight

func (b *Button) WithHeight(height SizeConstraint) *Button

WithHeight sets the height constraint and returns the button for chaining.

func (*Button) WithHoverStyle

func (b *Button) WithHoverStyle(style uv.Style) *Button

WithHoverStyle sets the hover style and returns the button for chaining.

func (*Button) WithPadding

func (b *Button) WithPadding(padding int) *Button

WithPadding sets the padding and returns the button for chaining.

func (*Button) WithStyle

func (b *Button) WithStyle(style uv.Style) *Button

WithStyle sets the button style and returns the button for chaining.

func (*Button) WithWidth

func (b *Button) WithWidth(width SizeConstraint) *Button

WithWidth sets the width constraint and returns the button for chaining.

type ComponentFactory

type ComponentFactory func(props Props, children []Element) Element

ComponentFactory is a function that creates an Element from props and children. Custom components should implement this signature.

func GetComponent

func GetComponent(name string) (ComponentFactory, bool)

GetComponent retrieves a component factory by name. Returns nil if the component is not registered.

type Constraint

type Constraint interface {
	// Apply applies the constraint to the given available size.
	Apply(available int) int
}

Constraint represents a size constraint that can be applied.

type Constraints

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

Constraints define the size constraints for layout calculations.

func Fixed

func Fixed(width, height int) Constraints

Fixed returns constraints for a fixed size.

func Unbounded

func Unbounded() Constraints

Unbounded returns constraints with no limits.

func (Constraints) Constrain

func (c Constraints) Constrain(size Size) Size

Constrain returns a size that satisfies the constraints.

type Divider

type Divider struct {
	BaseElement
	Vertical bool
	Char     string
	Style    uv.Style
}

Divider represents a horizontal or vertical line.

func NewDivider

func NewDivider() *Divider

NewDivider creates a new divider element.

func NewVerticalDivider

func NewVerticalDivider() *Divider

NewVerticalDivider creates a new vertical divider.

func (*Divider) Children

func (d *Divider) Children() []Element

Children returns nil for dividers.

func (*Divider) Draw

func (d *Divider) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the divider to the screen.

func (*Divider) Layout

func (d *Divider) Layout(constraints Constraints) Size

Layout calculates the divider size.

func (*Divider) WithChar

func (d *Divider) WithChar(char string) *Divider

WithChar sets the character and returns the divider for chaining.

func (*Divider) WithStyle

func (d *Divider) WithStyle(style uv.Style) *Divider

WithStyle sets the style and returns the divider for chaining.

type Element

type Element interface {
	uv.Drawable

	// Layout calculates the element's desired size within the given constraints.
	// It returns the actual size the element will occupy.
	Layout(constraints Constraints) Size

	// Children returns the child elements for container types.
	// Returns nil for leaf elements.
	Children() []Element

	// ID returns a unique identifier for this element.
	// Used for hit testing and event handling.
	ID() string

	// SetID sets the element's identifier.
	SetID(id string)

	// Bounds returns the element's last rendered screen coordinates.
	// Updated during Draw() and used for mouse hit testing.
	Bounds() uv.Rectangle

	// SetBounds records the element's rendered bounds.
	// This should be called at the start of Draw().
	SetBounds(bounds uv.Rectangle)
}

Element represents a renderable component in the TUI. Elements implement the UV Drawable interface and can be composed into trees.

func Card

func Card(title string, titleStyle, borderStyle uv.Style, children ...Element) Element

Card creates a titled card with content.

func NewBadge

func NewBadge(props Props, children []Element) Element

NewBadge creates a badge component.

func NewButtonFromProps

func NewButtonFromProps(props Props, children []Element) Element

NewButtonFromProps creates a button from props (for parser).

func NewHeader

func NewHeader(props Props, children []Element) Element

NewHeader creates a header component.

func NewProgress

func NewProgress(props Props, _ []Element) Element

NewProgress creates a progress bar component.

func Overlay

func Overlay(children ...Element) Element

Overlay creates a ZStack with children layered on top of each other.

func Section

func Section(header string, headerStyle uv.Style, children ...Element) Element

Section creates a section with a header and content.

func Separated

func Separated(children ...Element) Element

Separated adds a divider between each child.

type ElementWithBounds

type ElementWithBounds struct {
	Element Element
	Bounds  uv.Rectangle
}

ElementWithBounds pairs an element with its rendered bounds.

type FixedConstraint

type FixedConstraint int

FixedConstraint represents a fixed size in cells.

func (FixedConstraint) Apply

func (f FixedConstraint) Apply(available int) int

Apply returns the fixed size, clamped to available space.

type Flex

type Flex struct {
	BaseElement
	Child  Element
	Grow   int // flex-grow: how much to grow relative to siblings (default 0 = no grow)
	Shrink int // flex-shrink: how much to shrink relative to siblings (default 1)
	Basis  int // flex-basis: initial size before flex calculation (default 0 = auto)
}

Flex represents an element wrapper that supports flex-grow and flex-shrink. Use this to make elements flexible within VStack or HStack.

func FlexGrow

func FlexGrow(child Element, grow int) *Flex

FlexGrow creates a flex wrapper with the specified grow value.

func NewFlex

func NewFlex(child Element) *Flex

NewFlex creates a new flex wrapper.

func (*Flex) Children

func (f *Flex) Children() []Element

Children returns the child element.

func (*Flex) Draw

func (f *Flex) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the flex child.

func (*Flex) Layout

func (f *Flex) Layout(constraints Constraints) Size

Layout calculates the flex child size.

func (*Flex) WithBasis

func (f *Flex) WithBasis(basis int) *Flex

WithBasis sets the flex-basis and returns the flex for chaining.

func (*Flex) WithGrow

func (f *Flex) WithGrow(grow int) *Flex

WithGrow sets the flex-grow and returns the flex for chaining.

func (*Flex) WithShrink

func (f *Flex) WithShrink(shrink int) *Flex

WithShrink sets the flex-shrink and returns the flex for chaining.

type HStack

type HStack struct {
	BaseElement
	Items  []Element
	Gap    int
	Width  SizeConstraint
	Height SizeConstraint
	Valign string // top, middle, bottom (vertical alignment of children)
}

HStack represents a horizontal stack container.

func NewHStack

func NewHStack(children ...Element) *HStack

NewHStack creates a new horizontal stack.

func (*HStack) Children

func (h *HStack) Children() []Element

Children returns the child elements.

func (*HStack) Draw

func (h *HStack) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the horizontal stack to the screen.

func (*HStack) Layout

func (h *HStack) Layout(constraints Constraints) Size

Layout calculates the total size of the horizontal stack.

func (*HStack) WithGap

func (h *HStack) WithGap(gap int) *HStack

WithGap sets the gap and returns the hstack for chaining.

func (*HStack) WithHeight

func (h *HStack) WithHeight(height SizeConstraint) *HStack

WithHeight sets the height constraint and returns the hstack for chaining.

func (*HStack) WithValign

func (h *HStack) WithValign(valign string) *HStack

WithValign sets the vertical alignment and returns the hstack for chaining.

func (*HStack) WithWidth

func (h *HStack) WithWidth(width SizeConstraint) *HStack

WithWidth sets the width constraint and returns the hstack for chaining.

type Header struct {
	BaseElement
	Text   string
	Level  int // 1-6, like HTML
	Style  uv.Style
	Border bool
}

Header is a styled header component.

func (*Header) Children

func (h *Header) Children() []Element

Children returns nil.

func (*Header) Draw

func (h *Header) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the header.

func (*Header) Layout

func (h *Header) Layout(constraints Constraints) Size

Layout calculates header size.

type PercentConstraint

type PercentConstraint int

PercentConstraint represents a percentage of available space (0-100).

func (PercentConstraint) Apply

func (p PercentConstraint) Apply(available int) int

Apply returns the percentage of available space.

type Positioned

type Positioned struct {
	BaseElement
	Child  Element
	X      int // X position (cells from left)
	Y      int // Y position (cells from top)
	Right  int // Distance from right edge (if >= 0, overrides X)
	Bottom int // Distance from bottom edge (if >= 0, overrides Y)
	Width  SizeConstraint
	Height SizeConstraint
}

Positioned represents an absolutely positioned element. The element is positioned at specific coordinates relative to its parent.

func NewPositioned

func NewPositioned(child Element, x, y int) *Positioned

NewPositioned creates a new absolutely positioned element.

func Position

func Position(child Element, x, y int) *Positioned

Position creates an absolutely positioned element.

func PositionBottom

func PositionBottom(child Element, x, bottom int) *Positioned

PositionBottom creates an element positioned relative to the bottom edge.

func PositionCorner

func PositionCorner(child Element, right, bottom int) *Positioned

PositionCorner creates an element positioned at a corner.

func PositionRight

func PositionRight(child Element, right, y int) *Positioned

PositionRight creates an element positioned relative to the right edge.

func (*Positioned) Children

func (p *Positioned) Children() []Element

Children returns the child element.

func (*Positioned) Draw

func (p *Positioned) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the positioned element.

func (*Positioned) Layout

func (p *Positioned) Layout(_ Constraints) Size

Layout calculates the positioned element size. Positioned elements don't affect parent layout - they return 0 size.

func (*Positioned) WithBottom

func (p *Positioned) WithBottom(bottom int) *Positioned

WithBottom sets the bottom edge distance and returns the positioned element for chaining. When set (>= 0), this overrides the Y position.

func (*Positioned) WithHeight

func (p *Positioned) WithHeight(height SizeConstraint) *Positioned

WithHeight sets the height constraint and returns the positioned element for chaining.

func (*Positioned) WithRight

func (p *Positioned) WithRight(right int) *Positioned

WithRight sets the right edge distance and returns the positioned element for chaining. When set (>= 0), this overrides the X position.

func (*Positioned) WithWidth

func (p *Positioned) WithWidth(width SizeConstraint) *Positioned

WithWidth sets the width constraint and returns the positioned element for chaining.

type Progress

type Progress struct {
	BaseElement
	Value int
	Max   int
	Width SizeConstraint
	Style uv.Style
	Char  string
}

Progress represents a progress bar element.

func (*Progress) Children

func (p *Progress) Children() []Element

Children returns nil.

func (*Progress) Draw

func (p *Progress) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the progress bar.

func (*Progress) Layout

func (p *Progress) Layout(constraints Constraints) Size

Layout calculates progress bar size.

type Props

type Props map[string]string

Props is a map of properties passed to elements.

func (Props) Get

func (p Props) Get(key string) string

Get returns a property value or empty string if not found.

func (Props) GetOr

func (p Props) GetOr(key, defaultValue string) string

GetOr returns a property value or the default if not found.

func (Props) Has

func (p Props) Has(key string) bool

Has checks if a property exists.

type ScrollView

type ScrollView struct {
	BaseElement
	Child Element

	// Scroll position
	OffsetX int
	OffsetY int

	// Viewport size constraints
	Width  SizeConstraint
	Height SizeConstraint

	// Scrollbar options
	ShowScrollbar  bool
	ScrollbarStyle uv.Style

	// Scroll direction
	Horizontal bool // If true, scrolls horizontally
	Vertical   bool // If true, scrolls vertically (default)
}

ScrollView represents a scrollable container. Content can be larger than the viewport and will be clipped.

func NewScrollView

func NewScrollView(child Element) *ScrollView

NewScrollView creates a new scrollable view.

func (*ScrollView) Children

func (s *ScrollView) Children() []Element

Children returns the child element.

func (*ScrollView) ContentSize

func (s *ScrollView) ContentSize() Size

ContentSize returns the full size of the content.

func (*ScrollView) Draw

func (s *ScrollView) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the scrollable view.

func (*ScrollView) Layout

func (s *ScrollView) Layout(constraints Constraints) Size

Layout calculates the scroll view size.

func (*ScrollView) ScrollDown

func (s *ScrollView) ScrollDown(amount int, contentHeight, viewportHeight int)

ScrollDown scrolls down by the given amount.

func (*ScrollView) ScrollLeft

func (s *ScrollView) ScrollLeft(amount int)

ScrollLeft scrolls left by the given amount.

func (*ScrollView) ScrollRight

func (s *ScrollView) ScrollRight(amount int, contentWidth, viewportWidth int)

ScrollRight scrolls right by the given amount.

func (*ScrollView) ScrollUp

func (s *ScrollView) ScrollUp(amount int)

ScrollUp scrolls up by the given amount.

func (*ScrollView) WithHeight

func (s *ScrollView) WithHeight(height SizeConstraint) *ScrollView

WithHeight sets the height constraint.

func (*ScrollView) WithHorizontal

func (s *ScrollView) WithHorizontal(enabled bool) *ScrollView

WithHorizontal enables/disables horizontal scrolling.

func (*ScrollView) WithOffset

func (s *ScrollView) WithOffset(x, y int) *ScrollView

WithOffset sets the scroll offset and returns the scroll view for chaining.

func (*ScrollView) WithScrollbar

func (s *ScrollView) WithScrollbar(show bool) *ScrollView

WithScrollbar enables/disables scrollbar.

func (*ScrollView) WithVertical

func (s *ScrollView) WithVertical(enabled bool) *ScrollView

WithVertical enables/disables vertical scrolling.

func (*ScrollView) WithWidth

func (s *ScrollView) WithWidth(width SizeConstraint) *ScrollView

WithWidth sets the width constraint.

type Size

type Size struct {
	Width  int
	Height int
}

Size represents dimensions in terminal cells.

type SizeConstraint

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

SizeConstraint represents a size constraint with units.

func NewFixedConstraint

func NewFixedConstraint(size int) SizeConstraint

NewFixedConstraint creates a fixed size constraint.

func NewPercentConstraint

func NewPercentConstraint(percent int) SizeConstraint

NewPercentConstraint creates a percentage constraint.

func (SizeConstraint) Apply

func (sc SizeConstraint) Apply(available, content int) int

Apply applies the size constraint to get an actual size.

func (SizeConstraint) IsAuto

func (sc SizeConstraint) IsAuto() bool

IsAuto returns true if this is an auto constraint.

func (SizeConstraint) IsFixed

func (sc SizeConstraint) IsFixed() bool

IsFixed returns true if this is a fixed size constraint.

func (SizeConstraint) IsPercent

func (sc SizeConstraint) IsPercent() bool

IsPercent returns true if this is a percentage constraint.

func (SizeConstraint) String

func (sc SizeConstraint) String() string

String returns a string representation.

type Slot

type Slot struct {
	BaseElement
	Name string
	// contains filtered or unexported fields
}

Slot represents a placeholder for dynamic content. Slots are filled with Elements passed via RenderWithSlots.

func NewSlot

func NewSlot(name string) *Slot

NewSlot creates a new slot element.

func (*Slot) Children

func (s *Slot) Children() []Element

Children returns the slot's element children if it exists.

func (*Slot) Draw

func (s *Slot) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the slot's element if it exists.

func (*Slot) Layout

func (s *Slot) Layout(constraints Constraints) Size

Layout calculates the slot's element size if it exists.

type Spacer

type Spacer struct {
	BaseElement
	Size int // fixed size, 0 means flexible
}

Spacer represents empty space that can grow to fill available space.

func NewFixedSpacer

func NewFixedSpacer(size int) *Spacer

NewFixedSpacer creates a new spacer with fixed size.

func NewSpacer

func NewSpacer() *Spacer

NewSpacer creates a new spacer element.

func (*Spacer) Children

func (s *Spacer) Children() []Element

Children returns nil for spacers.

func (*Spacer) Draw

func (s *Spacer) Draw(_ uv.Screen, area uv.Rectangle)

Draw renders the spacer (nothing to draw).

func (*Spacer) Layout

func (s *Spacer) Layout(constraints Constraints) Size

Layout calculates the spacer size.

func (*Spacer) WithSize

func (s *Spacer) WithSize(size int) *Spacer

WithSize sets the size and returns the spacer for chaining.

type StyleBuilder

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

StyleBuilder provides a fluent API for building styles.

func NewStyle

func NewStyle() *StyleBuilder

NewStyle creates a new style builder.

func (*StyleBuilder) Bg

func (sb *StyleBuilder) Bg(c color.Color) *StyleBuilder

Bg sets the background color.

func (sb *StyleBuilder) Blink() *StyleBuilder

Blink makes the text blink.

func (*StyleBuilder) Bold

func (sb *StyleBuilder) Bold() *StyleBuilder

Bold makes the text bold.

func (*StyleBuilder) Build

func (sb *StyleBuilder) Build() uv.Style

Build returns the built style.

func (*StyleBuilder) Faint

func (sb *StyleBuilder) Faint() *StyleBuilder

Faint makes the text faint/dim.

func (*StyleBuilder) Fg

func (sb *StyleBuilder) Fg(c color.Color) *StyleBuilder

Fg sets the foreground color.

func (*StyleBuilder) Italic

func (sb *StyleBuilder) Italic() *StyleBuilder

Italic makes the text italic.

func (*StyleBuilder) Reverse

func (sb *StyleBuilder) Reverse() *StyleBuilder

Reverse reverses foreground and background.

func (*StyleBuilder) Strikethrough

func (sb *StyleBuilder) Strikethrough() *StyleBuilder

Strikethrough adds strikethrough.

func (*StyleBuilder) Underline

func (sb *StyleBuilder) Underline() *StyleBuilder

Underline sets single underline.

func (*StyleBuilder) UnderlineColor

func (sb *StyleBuilder) UnderlineColor(c color.Color) *StyleBuilder

UnderlineColor sets the underline color.

func (*StyleBuilder) UnderlineStyle

func (sb *StyleBuilder) UnderlineStyle(style uv.Underline) *StyleBuilder

UnderlineStyle sets the underline style.

type Template

type Template[T any] struct {
	// contains filtered or unexported fields
}

Template is a type-safe TUIML template that can be rendered with data of type T.

func MustParse

func MustParse[T any](markup string) *Template[T]

MustParse parses TUIML markup and panics on error.

func MustParseWithFuncs

func MustParseWithFuncs[T any](markup string, funcs template.FuncMap) *Template[T]

MustParseWithFuncs parses TUIML markup with custom functions and panics on error.

func Parse

func Parse[T any](markup string) (*Template[T], error)

Parse parses TUIML markup into a type-safe template. The markup can contain Go template syntax like {{ .Variable }}.

func ParseWithFuncs

func ParseWithFuncs[T any](markup string, funcs template.FuncMap) (*Template[T], error)

ParseWithFuncs parses TUIML markup with custom template functions.

func (*Template[T]) Render

func (t *Template[T]) Render(data T, width, height int) string

Render renders the template with the given data to the specified viewport size.

func (*Template[T]) RenderWithBounds

func (t *Template[T]) RenderWithBounds(data T, slots map[string]Element, width, height int) (uv.ScreenBuffer, *BoundsMap)

RenderWithBounds renders the template and returns both the screen buffer and bounds map. The bounds map can be used for mouse hit testing in event handlers.

func (*Template[T]) RenderWithSlots

func (t *Template[T]) RenderWithSlots(data T, slots map[string]Element, width, height int) string

RenderWithSlots renders the template with data and slot elements. Slots allow injecting stateful components into the template.

type Text

type Text struct {
	BaseElement
	Content string
	Style   uv.Style
	Wrap    bool
	Align   string // left, center, right
}

Text represents a text element.

func NewText

func NewText(content string) *Text

NewText creates a new text element.

func (*Text) Children

func (t *Text) Children() []Element

Children returns nil for text elements.

func (*Text) Draw

func (t *Text) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the text to the screen.

func (*Text) Layout

func (t *Text) Layout(constraints Constraints) Size

Layout calculates the text size.

func (*Text) WithAlign

func (t *Text) WithAlign(align string) *Text

WithAlign sets the alignment and returns the text for chaining.

func (*Text) WithStyle

func (t *Text) WithStyle(style uv.Style) *Text

WithStyle sets the style and returns the text for chaining.

func (*Text) WithWrap

func (t *Text) WithWrap(wrap bool) *Text

WithWrap enables wrapping and returns the text for chaining.

type VStack

type VStack struct {
	BaseElement
	Items  []Element
	Gap    int
	Width  SizeConstraint
	Height SizeConstraint
	Align  string // left, center, right (horizontal alignment of children)
}

VStack represents a vertical stack container.

func NewVStack

func NewVStack(children ...Element) *VStack

NewVStack creates a new vertical stack.

func (*VStack) Children

func (v *VStack) Children() []Element

Children returns the child elements.

func (*VStack) Draw

func (v *VStack) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the vertical stack to the screen.

func (*VStack) Layout

func (v *VStack) Layout(constraints Constraints) Size

Layout calculates the total size of the vertical stack.

func (*VStack) WithAlign

func (v *VStack) WithAlign(align string) *VStack

WithAlign sets the alignment and returns the vstack for chaining.

func (*VStack) WithGap

func (v *VStack) WithGap(gap int) *VStack

WithGap sets the gap and returns the vstack for chaining.

func (*VStack) WithHeight

func (v *VStack) WithHeight(height SizeConstraint) *VStack

WithHeight sets the height constraint and returns the vstack for chaining.

func (*VStack) WithWidth

func (v *VStack) WithWidth(width SizeConstraint) *VStack

WithWidth sets the width constraint and returns the vstack for chaining.

type ZStack

type ZStack struct {
	BaseElement
	Items  []Element
	Width  SizeConstraint
	Height SizeConstraint
	Align  string // Horizontal alignment: left, center, right
	Valign string // Vertical alignment: top, middle, bottom
}

ZStack represents a layered stack container where children are drawn on top of each other. Later children in the stack are drawn on top of earlier children.

func NewZStack

func NewZStack(children ...Element) *ZStack

NewZStack creates a new layered stack.

func (*ZStack) Children

func (z *ZStack) Children() []Element

Children returns the child elements.

func (*ZStack) Draw

func (z *ZStack) Draw(scr uv.Screen, area uv.Rectangle)

Draw renders the layered stack to the screen.

func (*ZStack) Layout

func (z *ZStack) Layout(constraints Constraints) Size

Layout calculates the total size of the layered stack. ZStack takes the maximum width and height of all children.

func (*ZStack) WithAlign

func (z *ZStack) WithAlign(align string) *ZStack

WithAlign sets the horizontal alignment and returns the zstack for chaining.

func (*ZStack) WithHeight

func (z *ZStack) WithHeight(height SizeConstraint) *ZStack

WithHeight sets the height constraint and returns the zstack for chaining.

func (*ZStack) WithValign

func (z *ZStack) WithValign(valign string) *ZStack

WithValign sets the vertical alignment and returns the zstack for chaining.

func (*ZStack) WithWidth

func (z *ZStack) WithWidth(width SizeConstraint) *ZStack

WithWidth sets the width constraint and returns the zstack for chaining.

Jump to

Keyboard shortcuts

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