tuigo

package module
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 6 Imported by: 0

README

tuigo

Go Reference Go Report Card Go Version

A declarative, component-based terminal UI (TUI) framework for Go — modeled after Ink (React for CLIs), but built from plain Go function calls, flexbox layout, and hooks. No JSX, no code generation, no virtual-DOM reconciliation you don't need. Just Box, Text, UseState, and events — a terminal UI library that feels like Go, not a port of a browser idea into a place that never needed it.

demo

Why tuigo

Most terminal UI is either imperative cursor math, or a port of a browser idea (virtual DOM diffing) into a place that never needed it — a terminal screen is a few thousand cells, not a million-node DOM tree. tuigo skips that step on purpose: every frame is a full rebuild of a small element tree, and only the cells that actually changed get written to the terminal. Simpler pipeline, faster in practice, at the scale a TUI actually runs at.

The design goal, stated plainly: best-in-class for 2026 — event-driven and property-based like Borland's VCL/TObject era, not a React clone in Go. See ARCHITECTURE.md for the reasoning behind every architectural choice below.

  • TUIML — components are just Go functions returning an Element tree built from Box/Text/Fragment and functional options. If you can write Go, you already know the syntax.
  • Hooks, not callbacks-on-callbacksUseState/UseReducer, one hook slot per component instance (keyed by tree position or an explicit key), the same mental model as React/Ink without needing a JS runtime.
  • Real events — keyboard (focus-scoped, with a Global() escape hatch for app-wide shortcuts like Ctrl+C) and mouse (click/scroll, hit-tested against the actual rendered layout — including floating overlays).
  • Fast by construction — flexbox layout → cell buffer → diff → ANSI. SGR state and cursor position are tracked across frames so unchanged style/position never gets re-emitted.
  • Batteries included — a widget layer (Panel, Dialog, Menu, ProgressBar, Spinner, Clock, Badge), a Picture-in-Picture overlay system, and standalone audio and asciiart libraries.

Install

go get github.com/wildneuro/tuigo

Requires Go 1.22+.

Quick start

package main

import tuigo "github.com/wildneuro/tuigo"

func Counter(ctx *tuigo.Ctx) tuigo.Element {
	n, setN := tuigo.UseState(ctx, 0)

	return tuigo.Box(
		tuigo.Padding(1), tuigo.FlexColumn(), tuigo.Border(tuigo.BorderSingle),
		tuigo.OnKey('+', func() { setN(n + 1) }),
		tuigo.Global()(tuigo.OnSpecialKey(tuigo.KeyCtrlC, ctx.Exit)),
		tuigo.Children(
			tuigo.Text("count: %d", n),
			tuigo.With(tuigo.Text("press + to increment, ctrl+c to quit"), tuigo.ColorFg(tuigo.ColorGray)),
		),
	)
}

func main() {
	if err := tuigo.Render(Counter); err != nil {
		panic(err)
	}
}

Render puts the terminal into raw/alt-screen mode, runs the loop, and always restores it on exit — including on panic. See _examples/static-counter, _examples/style-showcase, and the full _examples/chat app (bubbles, a / command menu, mouse, focus/Tab cycling, and 3+ simultaneous PiP windows) for more.

Core concepts

TUIML: composition, not markup

Every node is a plain types.Element built by a constructor plus functional options. Options apply left-to-right; later options win when they touch the same field.

tuigo.Box(
    tuigo.Padding(1), tuigo.FlexColumn(), tuigo.Gap(1), tuigo.Border(tuigo.BorderSingle),
    tuigo.Children(
        tuigo.Text("hello"),
        tuigo.With(tuigo.Text("dim caption"), tuigo.ColorFg(tuigo.ColorGray), tuigo.Italic()),
    ),
)

Box is a flex container (row or column, your call via FlexRow() / FlexColumn(), the latter being the common one). Text renders a formatted string. Fragment groups children without its own box, exactly like React's <>...</>. With applies extra options to an already-constructed element — mainly for styling Text, whose own variadic slot is reserved for fmt.Sprintf args.

Layout: pure Go flexbox

layout/ computes every node's position and size with no terminal involved — testable in isolation, deterministic, side-effect free. Padding, margin, gap, explicit Width/Height, and intrinsic sizing (a Text node's height is its word-wrapped line count) all compose the way you'd expect from CSS flexbox, minus the parts a terminal doesn't need.

Style: cascade where it makes sense, not everywhere

Text-styling fields (ColorFg, ColorBg, Bold, Italic, Underline) cascade from an ancestor Box down to Text children that don't set their own value — set a theme color once on a container, every child inherits it. Layout fields (Padding, Margin, Gap, Border, size, direction) never inherit; they only ever apply to the node they're set on. Colors support both the 256-color xterm palette (ColorRed, ColorBlue, …) and 24-bit truecolor (RGB(r, g, b)) for terminals that support it.

State: hooks, per component instance
count, setCount := tuigo.UseState(ctx, 0)
state, dispatch := tuigo.UseReducer(ctx, reducer, initialState)

Call hooks unconditionally at the top of your component, same rule as React. Each component instance (identified by its position in the tree, or an explicit key via WithKey for lists whose order/count can change) owns its own hook storage — not one flat sequence for the whole app, so independent components don't stomp on each other's state.

Events: focus-scoped by default, global when it matters
tuigo.OnKey('q', handler)              // matches a specific rune
tuigo.OnSpecialKey(tuigo.KeyEnter, h)  // matches a named key
tuigo.OnAnyKey(func(k tuigo.Key) {})   // every key — build text inputs with this
tuigo.OnClick(func(m tuigo.MouseEvent) {})
tuigo.OnScroll(func(delta int) {})     // -1 wheel up, +1 wheel down

tuigo.Focusable()          // mark an element as a Tab target
ctx.Focus("key")           // move focus programmatically
ctx.FocusNext(), ctx.FocusPrev()  // Tab / Shift+Tab

tuigo.Global()(tuigo.OnSpecialKey(tuigo.KeyCtrlC, ctx.Exit)) // fires regardless of focus

Keyboard events go to whichever element currently has focus — the VCL model, not "every handler in the tree fires for every keystroke." Global() wraps a handler so it fires app-wide instead (use it sparingly: quit shortcuts, not a way to avoid thinking about focus). Mouse events are hit-tested against the actual last-rendered layout, including floating overlays, bubbling from the innermost matching element outward.

Widgets

Panel, Divider, Badge, ProgressBar, Spinner, Clock, Dialog, Menu — all pure composition over Box/Text, no new primitives. Read their doc comments for the honest caveats (e.g. Dialog is a full-viewport takeover, not a floating window — see Overlays below for that).

Picture-in-Picture overlays

tuigo's layout is flow-only — no z-order, no absolute positioning. For floating content (PiP panels, popups, toasts) there's a small, explicit escape hatch instead of bolting z-order onto the whole layout model:

ctx.Overlay(myPanel, x, y) // call every render you want it visible

Each overlay gets its own independent layout+draw pass, sized by its own explicit Width/Height, then is composited onto the main frame before the cell diff — so it participates in the same minimal-redraw diffing as everything else. _examples/chat runs three-plus of these simultaneously: a live telemetry log, a CPU/mem meter, and (via the /music command) a music player + an ASCII-art image gallery, all on top of the same chat view at once.

Timers
cancel := ctx.After(2*time.Second, func() { /* runs on the render loop, safely */ })

Routed through a channel into the single render-loop goroutine — nothing outside that goroutine ever touches component state directly, so there's no locking and no data races to reason about.

Standalone libraries

Two more packages, usable with or without tuigo:

  • audioPlay/PlayAsync/Record by shelling out to platform-native tools (afplay/paplay/aplay/ffplay/PowerShell), plus a pure-Go WAV chirp synthesizer (Beep, GenerateChirpWAV) for UI feedback sounds with zero external dependencies.
  • asciiart — decode an image and render it as a grayscale ASCII ramp or a truecolor RGB grid (for solid-block "pixel art" in a 24-bit terminal). Pure stdlib, no CGo, no third-party deps.

Project layout

tuigo/
├── tuigo.go, node.go, style.go, event.go, state.go, overlay.go, widgets.go
├── types/       Element, Style, Key/MouseEvent — shared, avoids import cycles
├── layout/      pure flexbox geometry, zero terminal dependency
├── renderer/    Cell/Buffer/Diff + the terminal shell (raw mode, ANSI, input decode)
├── reconciler/  reserved — deliberately unimplemented, see ARCHITECTURE.md
├── audio/       standalone play/record library
├── asciiart/    standalone image → terminal-art library
└── _examples/   static-counter, style-showcase, chat

Development

go build ./...
go test ./...
go test -race ./...
go vet ./...
gofmt -l .
./example-chat.sh     # run the full chat demo in a real terminal
./make-demo.sh        # regenerate demo.gif via vhs
./release.sh          # bump VERSION, run checks, regenerate the demo, commit

example-chat.sh needs a real TTY on stdin (raw mode) — run it in an actual terminal, not piped through another tool.

Read more

  • PLAN.md — original design doc and v1 scope
  • STYLEGUIDE.md — the TUIML ruleset: what's enforced and why
  • ARCHITECTURE.md — the case for skipping React-style reconciliation, the focus/instance/timer design
  • TODO.md — what's left, with acceptance criteria per item

Status

Under active development. The core pipeline (layout → render → diff → flush), hooks, focus-scoped events, mouse, timers, truecolor, and the PiP overlay system all work today and are covered by tests — see TODO.md for the remaining gaps (row-direction text wrapping, Windows resize, more tuigo.go test coverage). Not yet at a tagged v1.

Documentation

Overview

Package tuigo is a declarative, component-based terminal UI framework.

Index

Constants

View Source
const (
	KeyNone      = types.KeyNone
	KeyEnter     = types.KeyEnter
	KeyEsc       = types.KeyEsc
	KeyTab       = types.KeyTab
	KeyBackspace = types.KeyBackspace
	KeyDelete    = types.KeyDelete
	KeyUp        = types.KeyUp
	KeyDown      = types.KeyDown
	KeyLeft      = types.KeyLeft
	KeyRight     = types.KeyRight
	KeyHome      = types.KeyHome
	KeyEnd       = types.KeyEnd
	KeyCtrlC     = types.KeyCtrlC
)
View Source
const (
	MouseLeft      = types.MouseLeft
	MouseMiddle    = types.MouseMiddle
	MouseRight     = types.MouseRight
	MouseWheelUp   = types.MouseWheelUp
	MouseWheelDown = types.MouseWheelDown
)
View Source
const (
	BorderNone    = types.BorderNone
	BorderSingle  = types.BorderSingle
	BorderDouble  = types.BorderDouble
	BorderRounded = types.BorderRounded
)

Variables

View Source
var DarkTheme = ThemeColors{
	Background:  RGB(16, 17, 22),
	Surface:     RGB(28, 29, 36),
	SurfaceAlt:  RGB(42, 44, 54),
	TextPrimary: ColorBrightWhite,
	TextMuted:   ColorGray,
	Accent:      RGB(0, 180, 216),
	AccentAlt:   RGB(0, 216, 180),
	Success:     ColorGreen,
	Warning:     ColorYellow,
	Error:       ColorRed,
}
View Source
var LightTheme = ThemeColors{
	Background:  RGB(245, 245, 245),
	Surface:     RGB(255, 255, 255),
	SurfaceAlt:  RGB(225, 225, 225),
	TextPrimary: RGB(20, 20, 20),
	TextMuted:   RGB(90, 90, 90),
	Accent:      RGB(0, 110, 170),
	AccentAlt:   RGB(0, 140, 120),
	Success:     RGB(20, 130, 60),
	Warning:     RGB(160, 110, 0),
	Error:       RGB(180, 30, 30),
}
View Source
var NamedThemes = map[string]ThemeColors{
	"dark":  DarkTheme,
	"light": LightTheme,
}

NamedThemes indexes the built-in themes for cycling/lookup (e.g. a "/theme" command).

View Source
var RGB = types.RGB
View Source
var Theme = DarkTheme

Theme is the active palette. It starts as DarkTheme; call SetTheme to switch it — every subsequent render that reads Theme.* picks up the change immediately, since it's read fresh each render, not cached.

Functions

func Clamp01 added in v0.1.8

func Clamp01(t float64) float64

Clamp01 clamps t to [0, 1] — animation progress is almost always a t := elapsed/duration computation that can overshoot past 1 once the animation has finished but before the caller notices and stops ticking.

func EaseInCubic added in v0.1.8

func EaseInCubic(t float64) float64

EaseInCubic is the mirror of EaseOutCubic: slow start, fast finish.

func EaseOutCubic added in v0.1.8

func EaseOutCubic(t float64) float64

EaseOutCubic is a common UI motion curve: fast start, slow finish. Pass a Clamp01'd t in [0, 1].

func Global

func Global() func(Option) Option

func Lerp added in v0.1.8

func Lerp(a, b, t float64) float64

Lerp linearly interpolates between a and b by t. t is not clamped — pass Clamp01(t) yourself if you need that, since some callers deliberately extrapolate (e.g. an overshoot bounce).

func Render

func Render(component Component) (err error)

func SetTheme

func SetTheme(t ThemeColors)

SetTheme replaces the active theme wholesale.

func UseReducer

func UseReducer[S, A any](ctx *Ctx, reducer func(S, A) S, initial S) (S, func(A))

func UseState

func UseState[T any](ctx *Ctx, initial T) (T, func(T))

Types

type BorderStyle

type BorderStyle = types.BorderStyle

Re-exported so callers only ever import the tuigo package for TUIML.

type Color

type Color = types.Color

Re-exported so callers only ever import the tuigo package for TUIML.

const (
	ColorBlack   Color = 16
	ColorRed     Color = 1
	ColorGreen   Color = 2
	ColorYellow  Color = 3
	ColorBlue    Color = 4
	ColorMagenta Color = 5
	ColorCyan    Color = 6
	ColorWhite   Color = 7
	ColorGray    Color = 8

	ColorBrightRed     Color = 9
	ColorBrightGreen   Color = 10
	ColorBrightYellow  Color = 11
	ColorBrightBlue    Color = 12
	ColorBrightMagenta Color = 13
	ColorBrightCyan    Color = 14
	ColorBrightWhite   Color = 15
)

Named xterm-256 colors. Color's zero value means "unset/inherit" (see types.Color), so plain black lives at the extended-palette index 16 rather than the ambiguous 0.

type Component

type Component func(ctx *Ctx) Element

type Ctx

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

func (*Ctx) After

func (c *Ctx) After(d time.Duration, fn func()) func()

func (*Ctx) Exit

func (c *Ctx) Exit()

func (*Ctx) Focus

func (c *Ctx) Focus(key string)

func (*Ctx) FocusNext

func (c *Ctx) FocusNext()

func (*Ctx) FocusPrev

func (c *Ctx) FocusPrev()

func (*Ctx) Overlay

func (c *Ctx) Overlay(el Element, x, y int)

Overlay registers ov to be drawn on top of the main frame for this render. Call it from within Component — like UseState, call it unconditionally on every render you want the overlay visible; omit the call on renders where it should disappear.

func (*Ctx) Size

func (c *Ctx) Size() (width, height int)

type Element

type Element = types.Element

Element is the tree node produced by Box, Text, and Fragment.

func Badge

func Badge(text string, bg Color) Element

Badge is a small colored pill label, e.g. for status tags or counts.

func Box

func Box(opts ...Option) Element

Box is a flex container: it lays out its children and never renders text of its own.

func Clock

func Clock(layout string) Element

Clock renders the current wall-clock time formatted per the time package layout convention (e.g. "15:04:05"). Render's main loop ticks once a second specifically so a Clock element updates on its own without any user input driving a re-render.

func Dialog

func Dialog(viewportW, viewportH, dialogW, dialogH int, title string, bodyOpts ...Option) Element

Dialog centers a titled Panel of exactly dialogW x dialogH within a viewportW x viewportH screen, using flex spacers on every side. It's a full-viewport takeover, not a true floating overlay — tuigo has no z-order/absolute-positioning compositing yet (see TODO.md), so a Dialog is meant to fully replace the tree it's shown in while open, not draw on top of other content.

func Divider

func Divider(width int) Element

Divider draws a horizontal rule width cells wide, e.g. to separate sections inside a Panel without a full nested border.

func Fragment

func Fragment(children ...Element) Element

Fragment groups children without introducing a layout box of its own.

func Menu(items []MenuItem, selected int, onSelect func(index int)) Element

Menu renders a selectable list — a dropdown/popup style widget for things like a "/" slash-command palette in a chat input. It occupies its own row(s) in normal flow layout (tuigo has no floating overlays yet), so the idiomatic use is to conditionally insert it as a sibling just above/below the input it's triggered from, pushing other content rather than floating over it. Menu renders items with items[selected] highlighted. onSelect, if non-nil, fires with a row's index when it's clicked — pass nil for a keyboard-only menu.

func Panel

func Panel(title string, opts ...Option) Element

Panel is a titled, bordered container in the spirit of a Turbo Vision / Borland-era window frame: a full-width colored title bar rather than a plain text line, so the title reads as a window's chrome instead of just another content row. The bar uses Theme.Accent — a Box, not a bare Text, since a standalone Text's ColorBg only paints behind its own characters (see renderer/draw.go's drawText), not the full row; a Box's fillBackground paints its entire rect. Pass Children(...) and any other Box options (Width, Height, Padding, ...) as opts; Border defaults to BorderRounded and FlexColumn if not overridden by a later option.

func ProgressBar

func ProgressBar(fraction float64, width int) Element

ProgressBar renders a fraction in [0,1] as a two-tone bar of the given width plus a percentage label. Zero-width segments are omitted rather than passed as Width(0), since Style's zero value means "unset" (see types.Style) and would otherwise be treated as a flex child instead of literally zero-width.

func Spinner

func Spinner(frame int) Element

Spinner returns one frame of a braille spinner glyph. It's pure — the caller drives animation by incrementing frame (e.g. once per received key, or once per tick once TODO.md item 3's timers land) and re-rendering.

func Text

func Text(format string, args ...any) Element

Text renders a formatted string. Unset style fields cascade from the nearest ancestor Box.

func With

func With(e Element, opts ...Option) Element

With applies style/key options to an already-constructed Element (most commonly a Text node, whose constructor reserves its variadic args for fmt.Sprintf). It never mutates the Element passed in.

type FlexDirection

type FlexDirection = types.FlexDirection

Re-exported so callers only ever import the tuigo package for TUIML.

type Key

type Key = types.Key
type MenuItem struct {
	Label string
	Hint  string // shown dim/right-aligned-ish after Label, e.g. a shortcut or description
}

MenuItem is one row in a Menu.

type MouseButton

type MouseButton = types.MouseButton

MouseButton identifies which button (or wheel direction) a MouseEvent reports.

type MouseEvent

type MouseEvent = types.MouseEvent

MouseEvent is a single mouse report: a button/wheel action at a 0-indexed cell coordinate, delivered by OnClick/OnMouse/OnScroll.

type Option

type Option func(*Element)

Option mutates an Element during construction. Options apply in the order they're passed and never mutate a shared Element.

func Bold

func Bold() Option

func Border

func Border(b BorderStyle) Option

Border sets the border style drawn around a Box.

func Children

func Children(children ...Element) Option

Children attaches child elements to a Box.

func ColorBg

func ColorBg(c Color) Option

ColorBg sets the background color.

func ColorFg

func ColorFg(c Color) Option

ColorFg sets the cascading foreground text color.

func FlexColumn

func FlexColumn() Option

FlexColumn lays out children top-to-bottom.

func FlexRow

func FlexRow() Option

FlexRow lays out children left-to-right. It's the zero-value direction, so this option exists mainly for explicitness.

func Focusable

func Focusable() Option

func Gap

func Gap(n int) Option

Gap sets the spacing between a Box's children.

func Height

func Height(n int) Option

func Italic

func Italic() Option

func Margin

func Margin(n int) Option

Margin sets all four margin edges to n.

func MarginBottom

func MarginBottom(n int) Option

func MarginLeft

func MarginLeft(n int) Option

func MarginRight

func MarginRight(n int) Option

func MarginTop

func MarginTop(n int) Option

func MinHeight

func MinHeight(n int) Option

func MinWidth

func MinWidth(n int) Option

func OnAnyKey

func OnAnyKey(handler func(Key)) Option

func OnClick

func OnClick(handler func(MouseEvent)) Option

OnClick calls handler when the left mouse button is pressed anywhere within this Element's rendered rect. The dispatcher hit-tests using the last computed layout, so it works for Box and Text alike.

func OnKey

func OnKey(r rune, handler func()) Option

func OnMouse

func OnMouse(handler func(MouseEvent)) Option

OnMouse calls handler for every mouse event that hits this Element's rendered rect, regardless of button.

func OnScroll

func OnScroll(handler func(delta int)) Option

OnScroll calls handler with -1 for wheel-up or +1 for wheel-down when the scroll wheel is used over this Element's rendered rect.

func OnSpecialKey

func OnSpecialKey(k SpecialKey, handler func()) Option

func Padding

func Padding(n int) Option

Padding sets all four padding edges to n.

func PaddingBottom

func PaddingBottom(n int) Option

func PaddingLeft

func PaddingLeft(n int) Option

func PaddingRight

func PaddingRight(n int) Option

func PaddingTop

func PaddingTop(n int) Option

func Truncate

func Truncate() Option

Truncate disables word-wrapping for a Text element and instead renders a single line, replacing the last visible character with '…' if the text is wider than the available width.

func Underline

func Underline() Option

func Width

func Width(n int) Option

func WithKey

func WithKey(key string) Option

WithKey assigns a stable identity used by the reconciler to match nodes across renders.

type Overlay

type Overlay struct {
	Element Element
	X, Y    int
}

Overlay is one floating panel — a Picture-in-Picture widget, a toast, a popup — composited on top of the main frame at a fixed screen position. Element must carry explicit Width/Height (e.g. via the Width/Height options) since it's laid out and drawn independently of the main tree, with no parent to size it.

tuigo has no z-order/absolute-positioning support in the layout tree itself (see ARCHITECTURE.md) — Overlay is the escape hatch: it renders its own small Element tree into its own Buffer, then stamps that buffer onto the main frame after the main Draw call and before the cell diff, so only the overlay's actually-changed cells get re-flushed like anything else. It does not participate in flow layout, hit-testing, or focus; call OnClick/OnScroll on its own Element if it needs input.

type SpecialKey

type SpecialKey = types.SpecialKey

type ThemeColors

type ThemeColors struct {
	// Surfaces
	Background Color
	Surface    Color
	SurfaceAlt Color

	// Text
	TextPrimary Color
	TextMuted   Color

	// Accents
	Accent    Color
	AccentAlt Color

	// Semantic
	Success Color
	Warning Color
	Error   Color
}

ThemeColors is a small, intentional color palette for tuigo applications. Prefer theme colors over raw palette indices; use RGB only for one-off visual effects that don't belong in the theme.

Directories

Path Synopsis
_examples
chat command
Command chat is a mock two-sided chat UI demonstrating TUIML's state management (UseState), event handling (OnAnyKey/OnSpecialKey/OnScroll), and widget layer (Panel/Badge/Clock/Spinner/ProgressBar/Menu/Dialog) on top of the style/flex system.
Command chat is a mock two-sided chat UI demonstrating TUIML's state management (UseState), event handling (OnAnyKey/OnSpecialKey/OnScroll), and widget layer (Panel/Badge/Clock/Spinner/ProgressBar/Menu/Dialog) on top of the style/flex system.
static-counter command
Command static-counter builds a small TUIML element tree and prints its structure.
Command static-counter builds a small TUIML element tree and prints its structure.
style-showcase command
Command style-showcase exercises every style option (colors, text attributes, border, flex row vs column) as a reference for TUIML's style system.
Command style-showcase exercises every style option (colors, text attributes, border, flex row vs column) as a reference for TUIML's style system.
Package asciiart decodes images (anything image.Decode supports — png/jpeg/gif via blank imports below) into terminal-friendly grids, either a grayscale character ramp or per-cell averaged RGB for truecolor block rendering.
Package asciiart decodes images (anything image.Decode supports — png/jpeg/gif via blank imports below) into terminal-friendly grids, either a grayscale character ramp or per-cell averaged RGB for truecolor block rendering.
Package audio plays and records audio by shelling out to platform-native tools rather than linking a CGo audio backend — no C toolchain, no cross-compile headaches, consistent with tuigo's "boring, testable layers" philosophy (see the root STYLEGUIDE.md).
Package audio plays and records audio by shelling out to platform-native tools rather than linking a CGo audio backend — no C toolchain, no cross-compile headaches, consistent with tuigo's "boring, testable layers" philosophy (see the root STYLEGUIDE.md).
Package layout computes pure Go flexbox geometry for an Element tree.
Package layout computes pure Go flexbox geometry for an Element tree.
Package renderer turns a laid-out Element tree into a cell buffer, diffs it against the previous frame, and flushes only the changed cells to a terminal.
Package renderer turns a laid-out Element tree into a cell buffer, diffs it against the previous frame, and flushes only the changed cells to a terminal.
Package types defines the declarative element tree shared by tuigo's layout, renderer, and reconciler packages.
Package types defines the declarative element tree shared by tuigo's layout, renderer, and reconciler packages.

Jump to

Keyboard shortcuts

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