Documentation
¶
Overview ¶
Package tui is the terminal-I/O layer around github.com/go-widgets/painter's CellPainter. It renders a widget tree into an ANSI cell stream sized to the current terminal, so the same widget code that produces pixels for a WUI or GUI back-end also produces text for a terminal — no widget changes required.
The package is deliberately minimal: no raw mode, no keyboard event loop, no alt-screen management. It ships a "snapshot" model — render one frame to a writer and return — that composes cleanly with either a caller-managed event loop or a CLI that prints once and exits. Higher-level facilities (an App runner with input, resize, and cleanup handling) live in a follow-up cycle.
Sizing follows the caller's preference:
- RenderOnceSized takes explicit (cols, rows) — the reliable form used by tests, size-aware callers, and headless renderers.
- RenderOnce queries the size from environment variables (EnvSize) and falls back to [DefaultCols]x[DefaultRows] when the environment does not report a size. This keeps the package stdlib-only — no ioctl, no cgo, no dependency on golang.org/x/term.
Both variants write a self-contained ANSI stream to the caller's writer via painter.CellPainter.WriteANSI; the caller is responsible for the surrounding terminal state (raw mode, alt screen, cursor visibility) if any.
Index ¶
- Constants
- func EnvSize() (cols, rows int, ok bool)
- func GutterWidth(lineCount int) int
- func LineNumberInk(theme *toolkit.Theme) toolkit.RGBA
- func RenderOnce(w io.Writer, widgets []painter.Widget, theme *painter.Theme) error
- func RenderOnceSized(w io.Writer, cols, rows int, widgets []painter.Widget, theme *painter.Theme) error
- func RenderToolkit(w io.Writer, widgets []toolkit.Widget, theme *toolkit.Theme) error
- func RenderToolkitSized(w io.Writer, cols, rows int, widgets []toolkit.Widget, theme *toolkit.Theme) error
- func SizeOrDefault() (cols, rows int)
- func SyntaxInk(k syntax.Kind, theme *toolkit.Theme) toolkit.RGBA
- type App
- type Button
- type ButtonStyle
- type Cell
- type CheckButton
- type Color
- type Dialog
- type Dropdown
- type Entry
- type FocusRing
- type Focusable
- type HSplit
- type InputParser
- type LevelBar
- type ListBox
- type MenuBar
- type MenuDropdown
- type MenuItem
- type Notebook
- type NotebookTab
- type Popover
- type ProgressBar
- type RadioButton
- type RadioGroup
- type Scale
- type SpinButton
- type Spinner
- type Statusbar
- type TTY
- type Table
- type TableColumn
- type TermGrid
- type TextEditor
- func (t *TextEditor) Copy() string
- func (t *TextEditor) Cut() string
- func (t *TextEditor) DeleteSelection() bool
- func (t *TextEditor) Draw(pnt painter.Painter, theme *toolkit.Theme)
- func (t *TextEditor) Find(query string) bool
- func (t *TextEditor) FindNext() bool
- func (t *TextEditor) OnEvent(ev toolkit.Event)
- func (t *TextEditor) Paste()
- func (t *TextEditor) Replace(query, repl string) bool
- func (t *TextEditor) ReplaceAll(query, repl string) int
- func (t *TextEditor) SelectedText() string
- func (t *TextEditor) SetText(s string)
- func (t *TextEditor) Text() string
- type Toolbar
- type ToolbarItem
- type TreeNode
- type TreeView
- type VBox
- type VSplit
Constants ¶
const ( // HSplitMinFrac / HSplitMaxFrac bound LeftFrac so neither pane vanishes. HSplitMinFrac = 10 HSplitMaxFrac = 90 )
const ( DefaultCols = 80 DefaultRows = 24 )
DefaultCols and DefaultRows are the terminal dimensions used when neither the environment nor the caller supplies a size — the classic VT100 defaults every terminal emulator honours as a floor.
const ( // VSplitMinFrac / VSplitMaxFrac bound TopFrac so neither pane vanishes. VSplitMinFrac = 10 VSplitMaxFrac = 90 )
const EventTick toolkit.EventKind = 100
EventTick is the toolkit.EventKind the App emits at a caller-configured tick rate. Widgets that animate (e.g. Spinner) match on this kind to advance a frame counter and repaint. The value sits above the toolkit's own iota range so it can never collide with a widget-produced Click / KeyDown / Char event. Defined here (not in the unix-only app.go) so cross-platform widgets can reference it on every backend, including js/wasm.
const StatusbarSegmentMinW = 10
StatusbarSegmentMinW is the default minimum width of a non-last segment.
Variables ¶
This section is empty.
Functions ¶
func EnvSize ¶
EnvSize reads the terminal size from the COLUMNS and LINES environment variables. It returns (cols, rows, true) when both parse cleanly and hold positive integers; otherwise it returns (0, 0, false) and the caller should substitute defaults.
The env-vars-only strategy is a deliberate trade-off: it keeps the package stdlib-only (no TIOCGWINSZ ioctl per platform, no cgo, no golang.org/x/term dependency). Interactive shells that need dynamic sizes should export COLUMNS / LINES from their prompt hook, or pass an explicit size to RenderOnceSized.
func GutterWidth ¶ added in v0.8.0
GutterWidth returns the cell width of a line-number gutter for a code pane of lineCount lines: the number of digits in the largest line number (a minimum of 2) plus one cell of separation before the code. Shared by tui-explorer + tui-editor so their gutters line up.
func LineNumberInk ¶ added in v0.8.0
LineNumberInk is the muted ink for a line-number gutter: the theme's Border colour, which reads as a dim separator tone against any pane background.
func RenderOnce ¶
RenderOnce paints the widget tree into a fresh CellPainter sized to SizeOrDefault, fills the background with theme.Background, calls each widget's Draw, and writes the resulting ANSI stream to w. It is the snapshot form: no raw mode, no event loop; one frame then return.
The theme argument may be nil, in which case painter.LightTheme is used. This keeps CLI callers ergonomic (`tui.RenderOnce(os.Stdout, widgets, nil)`) without forcing them to import painter just to name the default theme.
func RenderOnceSized ¶
func RenderOnceSized(w io.Writer, cols, rows int, widgets []painter.Widget, theme *painter.Theme) error
RenderOnceSized is like RenderOnce but uses the explicit (cols, rows) dimensions instead of querying the environment. Cols and rows must both be positive; a non-positive value falls back to the corresponding default so callers cannot accidentally produce an empty render.
func RenderToolkit ¶ added in v0.2.0
RenderToolkit paints a toolkit.Widget tree into a fresh CellPainter sized to SizeOrDefault, fills the background with theme.Background, calls each widget's Draw, and writes the resulting ANSI stream to w.
It is the toolkit-widget counterpart of RenderOnce (which renders painter.Widget). The two exist side-by-side because toolkit.Theme carries fields (SurfaceAlt, OnBackground, Extra) that painter.Theme does not, so a toolkit widget cannot be rendered through RenderOnce without losing theme surface.
The theme argument may be nil, in which case toolkit.DefaultLight is used.
func RenderToolkitSized ¶ added in v0.2.0
func RenderToolkitSized(w io.Writer, cols, rows int, widgets []toolkit.Widget, theme *toolkit.Theme) error
RenderToolkitSized is like RenderToolkit but uses the explicit (cols, rows) dimensions instead of querying the environment. Cols and rows must both be positive; a non-positive value falls back to the corresponding default so callers cannot accidentally produce an empty render.
func SizeOrDefault ¶
func SizeOrDefault() (cols, rows int)
SizeOrDefault returns EnvSize when the environment reports a size, otherwise [DefaultCols]x[DefaultRows]. This is what RenderOnce consumes.
func SyntaxInk ¶ added in v0.5.0
SyntaxInk maps a syntax.Kind to a terminal ink for the given theme, choosing a dark- or light-appropriate One Dark / One Light-style palette from the theme's background luminance. Plain + Punct fall back to the theme foreground. Shared by tui-explorer + tui-editor so their code panes colour identically.
Types ¶
type App ¶ added in v0.3.0
type App struct {
// Root is the widget tree the App draws and dispatches events
// to. Callers wire the composition once and mutate its fields
// during OnKey callbacks; the next repaint reflects the state.
Root toolkit.Widget
// Theme cascades through the widget tree on every draw. Defaults
// to [toolkit.DefaultLight] if left nil.
Theme *toolkit.Theme
// Keys is a map from a key Code (matching InputParser output —
// "q", "Ctrl+C", "Up", "Enter", …) to a handler. Handlers can
// mutate App state (e.g. call [App.Quit]) or the widget tree.
// Global handlers run BEFORE the event reaches Root.OnEvent — the
// App always dispatches to Root afterwards.
Keys map[string]func(*App)
// InputTarget, when non-nil, receives every event that a Keys handler
// does not Consume — INSTEAD of Root. It is a modal input capture: a
// command palette or search box sets it to swallow typing while open,
// then clears it (sets nil) to hand input back to Root. Root is still
// drawn every frame; only event routing changes.
InputTarget toolkit.Widget
// TickHz sets the auto-tick frequency in Hz. 0 disables ticks. A
// widget subscribing to EventTick needs TickHz > 0 to animate.
TickHz int
// contains filtered or unexported fields
}
App is an interactive TUI runner. Instantiate one, set App.Root to your widget tree, optionally register keybindings + a tick rate, and call App.Run. The App handles alt-screen + raw mode, parses stdin into toolkit.Event values, dispatches them to the widget tree, repaints on demand, and cleans up on exit — including on panic, via a deferred TTY.Leave.
func NewApp ¶ added in v0.3.0
func NewApp() *App
NewApp returns an App wired to the real terminal: OpenTTY, os.Stdin, os.Stdout, real Unix signal handling, and time.NewTicker for the tick channel. Tests build an App by hand and swap the seams instead.
func (*App) Consume ¶ added in v0.3.1
func (a *App) Consume()
Consume tells the event loop that the current Keys handler fully handled the event and it must NOT propagate to Root.OnEvent. Without Consume, every key that matches a Keys handler also reaches Root — which is fine for global shortcuts (Ctrl+C to quit still lets the Root repaint from its own state), but wrong for mode-switching editors where pressing 'i' to enter edit mode would otherwise ALSO insert 'i' into the underlying TextView.
Idempotent within a single event dispatch. The event loop clears the flag at the top of every event so a Consume from a previous event never affects the next one.
func (*App) IsQuitting ¶ added in v0.3.0
IsQuitting reports whether Quit has been called (whether the loop is on its way to exit). Non-blocking. Useful for consumer tests that verify a key handler triggered a quit without spinning the event loop.
func (*App) Quit ¶ added in v0.3.0
func (a *App) Quit()
Quit signals the event loop to exit on the next iteration. Safe to call from a key handler or a goroutine, and idempotent: a second call is a no-op (the underlying quit channel is closed exactly once via sync.Once).
func (*App) Refresh ¶ added in v0.3.0
func (a *App) Refresh()
Refresh marks the frame dirty so the next iteration repaints. It also nudges the wake channel so a loop currently blocked in select unblocks — enabling async I/O goroutines to schedule a redraw without racing an unrelated event.
func (*App) Run ¶ added in v0.3.0
Run blocks, driving the event loop until Quit is called or an interrupt signal arrives. Returns the exit code (0 by default; 1 if TTY setup fails; 2 if a key handler or widget panics). Terminal state is guaranteed to be restored on exit — even on panic — via a deferred TTY.Leave.
The return is named (exitCode) so a panic recovered by the deferred recover func can overwrite it; without the named return the panic path would still yield the pre-panic value.
func (*App) SetOpenTTYFn ¶ added in v0.3.0
SetOpenTTYFn overrides the TTY factory the event loop uses at Run time. Consumer tests inject a fake TTY (or an error) so they can drive Run() without needing a real controlling terminal. Passing a factory that returns an error is a valid way to force Run() to exit with a non-zero code before any raw-mode side effect.
type Button ¶ added in v0.26.0
type Button struct {
toolkit.Base
Label string
OnClick func()
Style ButtonStyle // resting appearance; default ButtonDefault
// contains filtered or unexported fields
}
Button is a cell-native clickable action: a filled block with a centred label. The resting face comes from its Style (Default / Prominent / Secondary); hover and press states override the fill so the user sees feedback before OnClick fires. A parent container drives SetHovered / SetPressed (enter/leave logic stays in one place, not every leaf).
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewButton ¶ added in v0.26.0
NewButton constructs a Button with the given label and click handler (which may be nil -- a no-op button still renders).
func (*Button) Draw ¶ added in v0.26.0
Draw paints the face (cycling Surface / SurfaceAlt / Accent by style and interaction state) and the centred label, with an on-accent ink that stays legible over an Accent fill.
func (*Button) OnEvent ¶ added in v0.26.0
OnEvent fires OnClick on a click or an Enter key (so a focused button is keyboard-activatable); other events are ignored.
func (*Button) SetFocused ¶ added in v0.41.0
SetFocused implements Focusable — a focused button highlights (like hover) and activates on Enter.
func (*Button) SetHovered ¶ added in v0.26.0
SetHovered / SetPressed are wired by the parent's mouse dispatcher so the button can render its hover / press visual states.
func (*Button) SetPressed ¶ added in v0.26.0
type ButtonStyle ¶ added in v0.26.0
type ButtonStyle int
ButtonStyle selects a button's resting fill, giving a layout a visual hierarchy. Hover + press still override the fill on top of the style.
const ( // ButtonDefault is a Surface-faced button (the plain look). ButtonDefault ButtonStyle = iota // ButtonProminent is filled with Accent -- the primary action ("OK"). ButtonProminent // ButtonSecondary is filled with SurfaceAlt -- a muted key between the two. ButtonSecondary )
type Cell ¶ added in v0.3.6
Cell is one grid position after ANSI-decoding a terminal frame: the rune actually written, plus the foreground and background colors that were active when it was written. All zero-value colors mean "default" (nothing set). Cell exists so consumer tests can assert on the visual output at cell-precision — the previous text-strip protocol dropped color information and let bugs like "highlight rendered as `█` characters" ship undetected.
type CheckButton ¶ added in v0.29.0
type CheckButton struct {
toolkit.Base
Label string
Checked bool
OnToggle func(checked bool)
// contains filtered or unexported fields
}
CheckButton is a cell-native boolean toggle: a "[✓]" / "[ ]" box followed by a Label. A click anywhere on the row flips Checked and fires OnToggle. The box paints in Accent when checked so the state reads at a glance.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewCheckButton ¶ added in v0.29.0
func NewCheckButton(label string, checked bool) *CheckButton
NewCheckButton constructs a CheckButton with the given label and initial checked state.
func (*CheckButton) Draw ¶ added in v0.29.0
func (c *CheckButton) Draw(pnt painter.Painter, theme *toolkit.Theme)
Draw paints the box (Accent when checked, Border otherwise) and the label.
func (*CheckButton) OnEvent ¶ added in v0.29.0
func (c *CheckButton) OnEvent(ev toolkit.Event)
OnEvent flips Checked and fires OnToggle on a click or an Enter key (so a focused checkbox is keyboard-toggleable); other events are ignored.
func (*CheckButton) SetFocused ¶ added in v0.41.0
func (c *CheckButton) SetFocused(v bool)
SetFocused implements Focusable — a focused checkbox draws its box in Accent and toggles on Enter.
type Color ¶ added in v0.3.6
Color carries the 24-bit RGB values decoded from a CSI SGR "38;2" (foreground) or "48;2" (background) sequence. Zero value means "default" — SGR 0 or no color set.
type Dialog ¶ added in v0.37.0
type Dialog struct {
toolkit.Base
Title string
Message []string
Buttons []string
Active int // focused button index
Visible bool
OnAction func(idx int, label string)
}
Dialog is a cell-native modal: a content-sized box centred within its bounds, with a Title, Message lines, and a row of action Buttons. It manages focus across its own buttons — Left/Right and Tab/Shift+Tab move the focused button, Enter activates it, Escape cancels, and a click activates the clicked button. OnAction fires with the chosen button's index and label (index -1 on Escape / cancel).
Being a modal, the host both adds it to a container's overlays (so it is drawn and bounded) AND sets it as App.InputTarget while Visible (so it captures the keyboard) — the same pattern the command palette uses.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewDialog ¶ added in v0.37.0
NewDialog builds a Dialog with the given title, message lines, and button labels, focusing the first button.
func (*Dialog) Draw ¶ added in v0.37.0
Draw paints the box, title, message and button strip (the Active button in Accent), when Visible.
type Dropdown ¶ added in v0.39.0
type Dropdown struct {
toolkit.Base
Options []string
Selected int
Open bool
OnChange func(idx int, value string)
// contains filtered or unexported fields
}
Dropdown is a cell-native value picker (combobox): a one-row control showing the currently-selected Option and a ▼ indicator; when Open it lists the options below (within the widget's bounds) with the active one highlighted. Enter / Down / a click opens it; Up/Down move the highlight; Enter or a click selects (firing OnChange only on a real change) and closes; Escape closes without changing. Unlike MenuDropdown (a menu of one-shot actions), a Dropdown persists a chosen value — the core form select control.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewDropdown ¶ added in v0.39.0
NewDropdown builds a Dropdown over options with the given initial selection (clamped into range).
type Entry ¶ added in v0.25.0
type Entry struct {
toolkit.Base
Text string
Placeholder string
Cursor int // rune index in [0, len(runes)]
Focused bool
OnChange func(text string)
OnSubmit func(text string)
// contains filtered or unexported fields
}
Entry is a cell-native single-line text input: a search box, a command prompt, a form field. It edits Text via EventKeyDown (Backspace, Delete, ArrowLeft/Right, Home, End, Enter) and EventChar (printable runes), shows a muted Placeholder while empty and unfocused, and scrolls horizontally so the cursor stays visible in a narrow field. Cursor is a rune index, so multi-byte UTF-8 characters move it by one visible column.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewEntry ¶ added in v0.25.0
NewEntry builds an Entry with initial text and the cursor parked at the end.
func (*Entry) Draw ¶ added in v0.25.0
Draw paints the field background, the text (or Placeholder), and — when Focused — a reverse-video block cursor.
func (*Entry) OnEvent ¶ added in v0.25.0
OnEvent handles focus + caret placement on click, keyboard navigation and editing, and character insertion.
func (*Entry) SetFocused ¶ added in v0.41.0
SetFocused implements Focusable — it drives whether the caret is drawn.
type FocusRing ¶ added in v0.41.0
FocusRing gives a set of Focusables a shared keyboard focus: Tab advances and Shift+Tab retreats (both wrapping); a click focuses the widget it lands on; every other event is forwarded to the focused member. Arrow keys are NOT consumed by the ring — they reach the focused widget, so a focused Dropdown or Scale keeps its own arrow behaviour. Layout is the caller's job (position each member's bounds); the ring manages focus + routing and draws its members.
A form wires its inputs into a FocusRing and hands it to the App as Root (or as an InputTarget); Tab then walks the fields exactly as a GUI form does.
func NewFocusRing ¶ added in v0.41.0
NewFocusRing builds a ring over items, focusing the first.
func (*FocusRing) Focus ¶ added in v0.41.0
Focus moves focus to member i (clamped), updating SetFocused on the old and new members.
func (*FocusRing) Focused ¶ added in v0.41.0
Focused returns the focused member's index, or -1 when the ring is empty.
func (*FocusRing) Next ¶ added in v0.41.0
func (r *FocusRing) Next()
Next / Prev move focus to the following / previous member, wrapping.
type Focusable ¶ added in v0.41.0
Focusable is a widget that can hold keyboard focus within a FocusRing. The ring calls SetFocused as focus moves; the widget renders a focus cue while focused and acts on the keys the ring forwards to it (a Button/CheckButton/ RadioButton fires on Enter, an Entry edits its text).
type HSplit ¶ added in v0.23.0
type HSplit struct {
toolkit.Base
Left, Right toolkit.Widget
LeftFrac int
// contains filtered or unexported fields
}
HSplit is a cell-native resizable horizontal split: Left takes LeftFrac percent of the width, a 1-cell grip column separates it from Right, and Right takes the remainder. The grip glyph doubles as a drag handle -- a click on it starts a drag session; subsequent EventMouseDrag events (routed here by a parent VBox's drag-capture, or any container that keeps forwarding a captured drag) update LeftFrac; EventMouseUp ends the session. LeftFrac is clamped to [HSplitMinFrac, HSplitMaxFrac] so neither pane can collapse to zero cells.
Events are local (0-based within the split); bounds are absolute for Draw. Renders through painter.Painter, so the same split drives the cell (TUI) and RGBA (WUI/GUI) backends.
func (*HSplit) Draw ¶ added in v0.23.0
Draw paints both panes, then the grip column on top (Accent while dragging, Border otherwise) so the separator stays visible over either pane's bg.
type InputParser ¶ added in v0.3.0
type InputParser struct {
// contains filtered or unexported fields
}
InputParser reads bytes from a terminal stdin stream and emits toolkit.Event values. A single call to InputParser.Feed may produce zero, one, or many events: typing "abc" yields three toolkit.EventChar events, pasting a control sequence like "\x1b[A" yields one arrow-up event but consumes three bytes.
The toolkit exposes toolkit.EventKeyDown for named-key presses (Enter, Tab, arrows, Escape, …) and toolkit.EventChar for printable characters — the parser routes accordingly.
The pending buffer holds the incomplete tail of the last Feed call: a lone ESC (0x1B) whose next byte has not arrived, an ESC [ … partial CSI sequence still waiting for its final byte (0x40..0x7E), or a partial UTF-8 rune (1–3 bytes of a multi-byte codepoint split across two Reads). It is consumed automatically on the next Feed call, or drained by InputParser.Flush on an input-idle deadline.
func NewInputParser ¶ added in v0.3.0
func NewInputParser() *InputParser
NewInputParser returns a fresh parser with an empty buffer.
func (*InputParser) Feed ¶ added in v0.3.0
func (p *InputParser) Feed(b []byte) []toolkit.Event
Feed hands b bytes to the parser and returns any events that completed as a result. Bytes belonging to an incomplete sequence (a lone ESC, a partial CSI, a partial UTF-8 rune) are buffered internally and consumed on the next Feed call.
The classic Escape-vs-CSI ambiguity is resolved as follows: a lone ESC at the end of the buffer is held pending; on the next Feed a leading '[' starts a CSI sequence, any other byte causes the held ESC to be emitted as "Escape" and the following byte is then processed as a fresh input. Callers waiting on an idle deadline flush the held ESC via InputParser.Flush.
func (*InputParser) Flush ¶ added in v0.3.0
func (p *InputParser) Flush() []toolkit.Event
Flush emits any pending ESC (or partial CSI) as an "Escape" event and clears the buffer. Callers invoke it on an input-idle deadline to resolve the Escape-vs-CSI ambiguity in favour of a plain Escape. A partial UTF-8 rune is discarded silently — its tail bytes would be indistinguishable from an unrelated new keystroke by the time an idle timeout fires.
type LevelBar ¶ added in v0.32.0
LevelBar is the discrete cousin of ProgressBar: Max equal segments separated by a 1-cell gap, the first Value in Accent and the rest in SurfaceAlt. Useful for battery / signal-strength / step indicators at terminal scale.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewLevelBar ¶ added in v0.32.0
NewLevelBar builds a LevelBar with the given Max (floored at 1) and Value 0.
type ListBox ¶ added in v0.18.0
type ListBox struct {
toolkit.Base
Items []string
Selected int
// OnSelect fires after Selected changes (keyboard or click). Optional; a
// nil callback is a no-op. It receives the new index.
OnSelect func(int)
// contains filtered or unexported fields
}
ListBox is a cell-native single-selection list: one item per row, an accent-highlighted selection, arrow / Home / End / PageUp / PageDown navigation, click-to-select, and a viewport that scrolls to keep the selection visible. It is a toolkit.Widget, so like every widget here it renders through painter.Painter -- a terminal cell grid (TUI) or an RGBA pixel buffer (WUI/GUI) with the same code.
func NewListBox ¶ added in v0.18.0
NewListBox returns a ListBox over items with the first row selected.
type MenuBar ¶ added in v0.19.0
MenuBar is a cell-native horizontal menu: item labels laid out left to right on a single row, separated by menuBarSep cells, with a click on a label's cell range firing its OnClick. It is a toolkit.Widget, rendering through painter.Painter (cell grid for TUI, RGBA buffer for WUI/GUI).
func NewMenuBar ¶ added in v0.19.0
NewMenuBar returns a MenuBar over items.
func (*MenuBar) ItemXRange ¶ added in v0.19.0
ItemXRange returns the [start, end) local-X cell range of the i-th item, or (-1, -1) if i is out of range. Callers use it both to hit-test and to anchor a dropdown directly under an item.
type MenuDropdown ¶ added in v0.21.0
type MenuDropdown struct {
toolkit.Base
Title string
Body []string
ItemActions []func() // parallel to Body; nil / short = informational row
Visible bool
AnchorX int
AnchorY int
}
MenuDropdown is an anchored, self-sizing dropdown menu -- the panel that opens under a MenuBar item. It positions itself at (AnchorX, AnchorY) and sizes to its content, ignoring any bounds a container hands it. A click on a Body row runs the matching ItemActions entry (nil / short slice = an informational row: the click still dismisses the menu but runs nothing) and hides the menu.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func (*MenuDropdown) Draw ¶ added in v0.21.0
func (d *MenuDropdown) Draw(pnt painter.Painter, theme *toolkit.Theme)
Draw paints the anchored box (when Visible): Title on the top row, Body rows below.
func (*MenuDropdown) HitTest ¶ added in v0.21.0
func (d *MenuDropdown) HitTest(px, py int) bool
HitTest — hidden dropdowns claim no clicks.
func (*MenuDropdown) OnEvent ¶ added in v0.21.0
func (d *MenuDropdown) OnEvent(ev toolkit.Event)
OnEvent runs the clicked Body row's action (if any) and dismisses the menu.
func (*MenuDropdown) SetBounds ¶ added in v0.21.0
func (d *MenuDropdown) SetBounds(_ toolkit.Rect)
SetBounds ignores the requested rect and self-positions at the anchor with the natural size (a container's layout pass calls this; the dropdown opts out of the normal flow).
type MenuItem ¶ added in v0.19.0
type MenuItem struct {
Label string
OnClick func()
}
MenuItem is one clickable entry in a MenuBar. OnClick is optional -- a nil callback is a no-op (useful for a decorative or coming-soon slot).
type Notebook ¶ added in v0.31.0
type Notebook struct {
toolkit.Base
Tabs []NotebookTab
Active int
OnTabChanged func(idx int)
}
Notebook is a cell-native tabbed container: a 1-row tab strip on top, the active page's body below. Tabs size to their labels. A click on a tab swaps Active and fires OnTabChanged; every other event routes to the active page (translated below the strip).
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewNotebook ¶ added in v0.31.0
func NewNotebook() *Notebook
NewNotebook returns an empty Notebook (no tabs, Active = 0).
func (*Notebook) AddTab ¶ added in v0.31.0
AddTab appends a tab with the given label and page widget.
type NotebookTab ¶ added in v0.31.0
NotebookTab is one entry in a Notebook: a Label painted on the tab and the Page widget shown in the body when that tab is active.
type Popover ¶ added in v0.20.0
Popover is a cell-native modal overlay: a bordered box with a Title on the top row and Body lines below, shown only while Visible. It sizes its drawn height to the content (3 border/title rows + one per Body line, capped to its bounds). HitTest returns false while hidden, so an invisible Popover never claims a click from the widgets beneath it.
A toolkit.Widget: renders through painter.Painter (cell grid for TUI, RGBA buffer for WUI/GUI).
type ProgressBar ¶ added in v0.27.0
ProgressBar is a cell-native horizontal fill: a SurfaceAlt track with the first Fraction of its cells filled in Accent, and an optional Label centred over the bar. Fraction is clamped to [0,1]. Use it for download / load / completion indicators at terminal scale.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewProgressBar ¶ added in v0.27.0
func NewProgressBar() *ProgressBar
NewProgressBar builds an empty (Fraction=0) ProgressBar.
func (*ProgressBar) Draw ¶ added in v0.27.0
func (p *ProgressBar) Draw(pnt painter.Painter, theme *toolkit.Theme)
Draw paints the track, the Accent fill proportional to Fraction, and the centred Label.
func (*ProgressBar) SetFraction ¶ added in v0.27.0
func (p *ProgressBar) SetFraction(f float64)
SetFraction clamps f to [0,1] and assigns it (0 = empty, 1 = full).
type RadioButton ¶ added in v0.33.0
type RadioButton struct {
toolkit.Base
Label string
Checked bool
OnToggle func(checked bool)
// contains filtered or unexported fields
}
RadioButton is a cell-native circular toggle paired with a label: "(•)" checked / "( )" unchecked, then the Label. Grouped via RadioGroup for mutual exclusion; a standalone RadioButton toggles like a CheckButton.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewRadioButton ¶ added in v0.33.0
func NewRadioButton(label string) *RadioButton
NewRadioButton constructs a standalone RadioButton. Add it to a RadioGroup for mutual-exclusion behaviour.
func (*RadioButton) Draw ¶ added in v0.33.0
func (r *RadioButton) Draw(pnt painter.Painter, theme *toolkit.Theme)
Draw paints the mark (Accent when checked, Border otherwise) and the label.
func (*RadioButton) OnEvent ¶ added in v0.33.0
func (r *RadioButton) OnEvent(ev toolkit.Event)
OnEvent, on a click or Enter (so a focused radio is keyboard-activatable), routes through the group (siblings clear) if grouped, else toggles locally.
func (*RadioButton) SetFocused ¶ added in v0.41.0
func (r *RadioButton) SetFocused(v bool)
SetFocused implements Focusable — a focused radio draws its mark in Accent and activates on Enter.
type RadioGroup ¶ added in v0.33.0
type RadioGroup struct {
Members []*RadioButton
Active int
}
RadioGroup makes a set of RadioButtons mutually exclusive. Active is the index of the checked member, or -1 when none has been clicked yet.
func NewRadioGroup ¶ added in v0.33.0
func NewRadioGroup() *RadioGroup
NewRadioGroup builds an empty group with Active = -1.
func (*RadioGroup) Add ¶ added in v0.33.0
func (g *RadioGroup) Add(r *RadioButton)
Add appends r to the group and records its membership so a click on any member can clear the others.
type Scale ¶ added in v0.28.0
type Scale struct {
toolkit.Base
Min, Max float64
Value float64
Step float64 // arrow-key increment; <= 0 means a tenth of the range
OnChange func(v float64)
}
Scale is a cell-native horizontal slider over a continuous Min..Max range: a SurfaceAlt track, an Accent fill up to the thumb, and a thumb cell at the value's position. A click or drag jumps the thumb to that column; ArrowLeft/Right step by Step (or a tenth of the range when Step <= 0). Because it treats a click and a captured drag identically, a Scale nested in a drag-capturing container (VBox / HSplit) is smoothly draggable.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewScale ¶ added in v0.28.0
NewScale builds a Scale spanning [min, max] with the given initial value. Min == Max is allowed but renders a non-interactive track.
func (*Scale) Draw ¶ added in v0.28.0
Draw paints the track, the Accent fill up to the thumb, and the thumb cell.
type SpinButton ¶ added in v0.43.0
type SpinButton struct {
toolkit.Base
Min, Max int
Value int
Step int
OnChange func(v int)
// contains filtered or unexported fields
}
SpinButton is a cell-native integer field with steppers: a ◂ decrement cap on the left, the value, and a ▸ increment cap on the right, all within [Min, Max]. Clicking a cap (or, while focused, Up/Right/'+' and Down/Left/'-') steps the value by Step and fires OnChange on a real change. It is Focusable, so it drops into a FocusRing as a form control (the caps render in Accent when focused).
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewSpinButton ¶ added in v0.43.0
func NewSpinButton(min, max, initial, step int) *SpinButton
NewSpinButton builds a SpinButton over [min, max] with the given initial value (clamped) and step (floored at 1 so a stepper always moves).
func (*SpinButton) Draw ¶ added in v0.43.0
func (s *SpinButton) Draw(pnt painter.Painter, theme *toolkit.Theme)
Draw paints the ◂ / ▸ caps and the centred value.
func (*SpinButton) OnEvent ¶ added in v0.43.0
func (s *SpinButton) OnEvent(ev toolkit.Event)
OnEvent steps on a cap click or (when focused) an arrow / +/- key.
func (*SpinButton) SetFocused ¶ added in v0.43.0
func (s *SpinButton) SetFocused(v bool)
SetFocused implements Focusable — a focused spinner renders its caps in Accent and responds to arrow / +/- keys.
func (*SpinButton) SetValue ¶ added in v0.43.0
func (s *SpinButton) SetValue(v int)
SetValue clamps v to [Min, Max] and assigns it (no OnChange — direct setter).
type Spinner ¶ added in v0.38.0
type Spinner struct {
toolkit.Base
Frames []string // animation frames (one glyph each); defaults to braille dots
Label string
Active bool
// contains filtered or unexported fields
}
Spinner is a cell-native busy indicator: a single animated glyph (Accent), optionally followed by a Label. It advances one Frame per EventTick while Active, so a host that wants animation runs the App with TickHz > 0. Set Active = false to freeze it (e.g. when the work completes).
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewSpinner ¶ added in v0.38.0
NewSpinner builds an active Spinner with the default frames and the given label (which may be empty).
type Statusbar ¶ added in v0.24.0
type Statusbar struct {
toolkit.Base
Segments []string
// SegmentMinW is the minimum width (in cells) any non-last segment takes;
// the last segment always fills the rest of the bar. Defaults to
// StatusbarSegmentMinW when <= 0.
SegmentMinW int
}
Statusbar is a cell-native footer strip: N text segments laid out left-to-right (e.g. "Line 12, Col 4" + "UTF-8" + "Go" in an editor), each with a 1-cell pad, a thin vertical divider between them, and the LAST segment stretched to fill the remaining width so an empty bar still looks deliberate. It is the natural pairing for a MenuBar above and a document area between — together they assemble the stock window frame at terminal scale.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewStatusbar ¶ added in v0.24.0
NewStatusbar builds a Statusbar with the given segments and the default minimum segment width.
func (*Statusbar) Draw ¶ added in v0.24.0
Draw paints the strip background plus every segment, with a divider column between adjacent segments.
func (*Statusbar) SetSegment ¶ added in v0.24.0
SetSegment replaces the i-th segment in place. An index past the end grows the bar (filling intermediate slots with "") so callers can add segments lazily; a negative index is ignored.
type TTY ¶ added in v0.3.0
type TTY interface {
// Enter puts the terminal in raw mode, saves the current
// termios state, switches to the alt-screen, and hides the
// cursor. Idempotent — a second call while entered is a no-op.
Enter() error
// Leave reverses Enter: restores the alt-screen, shows the
// cursor, and restores the saved termios state. Always safe to
// call, including from a defer or a panic recovery.
Leave() error
// Size returns the current terminal dimensions in cells.
Size() (cols, rows int, err error)
}
TTY is the abstraction the App runner uses to talk to a real terminal. Implementations set raw mode, hide the cursor, switch to the alt-screen, and reverse everything on Close. The mock implementation used in tests satisfies the same interface without touching a real terminal.
type Table ¶ added in v0.35.0
type Table struct {
toolkit.Base
Columns []TableColumn
Rows [][]string
Selected int // -1 = no selection
OnSelect func(row int)
// contains filtered or unexported fields
}
Table is a cell-native data grid: a header row on top, then one body row per Rows entry with zebra striping and an accent-highlighted selection. Auto columns reflow to the widget width. Arrow / Home / End / PageUp / PageDown navigate and a click selects a body row; the viewport scrolls to keep the selection visible.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewTable ¶ added in v0.35.0
func NewTable(cols []TableColumn, rows [][]string) *Table
NewTable builds a Table with the given columns and rows, no row selected.
type TableColumn ¶ added in v0.35.0
type TableColumn struct {
Title string
Width int // cells; 0 = auto (equal share of the remainder)
}
TableColumn is one column: a header Title and an optional fixed Width in cells. A Width of 0 marks the column "auto" — it claims an equal share of the cells left after the fixed columns.
type TermGrid ¶ added in v0.3.6
TermGrid is a 2D cell buffer produced by DecodeANSI. Rows and Cols are the grid dimensions; Cells is row-major (Cells[y*Cols+x]).
func DecodeANSI ¶ added in v0.3.6
DecodeANSI parses stream as a sequence of CSI SGR + printable runes + cursor-positioning + line breaks, and lays every printable rune into a Cell at the current cursor position with the current foreground / background color. Only the subset of CSI a painter.CellPainter emits is honored:
- CSI H → cursor to (1, 1)
- CSI r ; c H → cursor to (c, r), 1-indexed
- CSI 0 m → reset colors
- CSI 38 ; 2 ; R ; G ; B m → foreground truecolor
- CSI 48 ; 2 ; R ; G ; B m → background truecolor
- CSI ?1049 h / l → alt-screen enter/leave (recognised, no-op)
- CSI ?25 h / l → cursor show/hide (recognised, no-op)
Unknown CSI sequences consume their bytes and are ignored so a caller can freely pass a frame that also contains cursor styling or bracketed paste. \n and \r reset the cursor to column 0 and advance the row (LF) or stay on the same row (CR).
func (*TermGrid) At ¶ added in v0.3.6
At returns the cell at (x, y). Out-of-bounds coordinates return the zero Cell — callers that care about bounds should check dims.
func (*TermGrid) Row ¶ added in v0.3.6
Row returns the y-th row as a Cell slice. Panics on out-of-bounds y — mirrors slice indexing conventions.
type TextEditor ¶ added in v0.9.0
type TextEditor struct {
toolkit.Base
Lines []string
CursorLine int
CursorCol int
Focused bool
Filename string
ReadOnly bool
ShowGutter bool
// contains filtered or unexported fields
}
TextEditor is a cell-native, read-write multi-line text editor widget with syntax highlighting and an optional line-number gutter. It is a toolkit.Widget: give it a bounds, set Focused, and forward events to OnEvent.
- One cell per glyph, one row per line (no soft wrap).
- Filename's extension drives the syntax language (see the syntax package); "" leaves the text plain. Highlighting is recomputed on every change so it tracks live edits.
- Set ReadOnly to use it as a code viewer: insert / delete / newline events are ignored, but the caret still navigates (arrows, click).
- ShowGutter draws a right-aligned line-number gutter (NewTextEditor enables it).
func NewTextEditor ¶ added in v0.9.0
func NewTextEditor() *TextEditor
NewTextEditor returns an empty editor with the line-number gutter enabled.
func (*TextEditor) Copy ¶ added in v0.13.0
func (t *TextEditor) Copy() string
Copy stores the selection in the widget clipboard and returns it.
func (*TextEditor) Cut ¶ added in v0.13.0
func (t *TextEditor) Cut() string
Cut copies the selection to the clipboard and removes it, returning the text.
func (*TextEditor) DeleteSelection ¶ added in v0.13.0
func (t *TextEditor) DeleteSelection() bool
DeleteSelection removes the selected text (a single undo step). Returns whether anything was deleted.
func (*TextEditor) Draw ¶ added in v0.9.0
func (t *TextEditor) Draw(pnt painter.Painter, theme *toolkit.Theme)
Draw paints the pane background, the (optional) line-number gutter, the syntax-highlighted text, and the caret.
func (*TextEditor) Find ¶ added in v0.11.0
func (t *TextEditor) Find(query string) bool
Find moves the caret to the next occurrence of query at or after the current caret, searching forward and wrapping to the top; it returns whether a match was found. An empty query is a no-op. The query is remembered for FindNext.
func (*TextEditor) FindNext ¶ added in v0.11.0
func (t *TextEditor) FindNext() bool
FindNext repeats the last Find starting just past the caret, so successive calls walk consecutive matches (wrapping). No-op with no prior Find.
func (*TextEditor) OnEvent ¶ added in v0.9.0
func (t *TextEditor) OnEvent(ev toolkit.Event)
OnEvent applies one input event: character insert, Backspace, Delete, Enter, Ctrl+Z/Y (undo/redo), Ctrl+X/V (cut/paste) -- all no-ops when ReadOnly -- the arrow / Home / End / PageUp / PageDown navigation keys, or a click / drag (caret + selection). Re-highlights after any change.
func (*TextEditor) Paste ¶ added in v0.13.0
func (t *TextEditor) Paste()
Paste inserts the clipboard at the caret, replacing an active selection. A single undo step. No-op when ReadOnly or the clipboard is empty.
func (*TextEditor) Replace ¶ added in v0.12.0
func (t *TextEditor) Replace(query, repl string) bool
Replace finds the next occurrence of query (forward from the caret, wrapping) and replaces it with repl, leaving the caret just after the replacement so a repeated call walks to the next one. Returns whether a replacement happened. No-op (false) when ReadOnly or query is empty. Undoable as one step.
func (*TextEditor) ReplaceAll ¶ added in v0.12.0
func (t *TextEditor) ReplaceAll(query, repl string) int
ReplaceAll replaces every occurrence of query with repl across the whole buffer and returns the number replaced. The caret column is clamped to its (possibly shorter) line. No-op (0) when ReadOnly or query is empty; the whole operation is a single undo step.
func (*TextEditor) SelectedText ¶ added in v0.13.0
func (t *TextEditor) SelectedText() string
SelectedText returns the text covered by the active selection, or "" if none.
func (*TextEditor) SetText ¶ added in v0.9.0
func (t *TextEditor) SetText(s string)
SetText replaces the buffer (split on "\n"), resets the caret to the top, and re-highlights.
func (*TextEditor) Text ¶ added in v0.9.0
func (t *TextEditor) Text() string
Text returns the buffer joined by newlines (no trailing newline).
type Toolbar ¶ added in v0.34.0
type Toolbar struct {
toolkit.Base
Items []ToolbarItem
}
Toolbar is a cell-native horizontal strip of labelled buttons and optional separators — the action strip that sits below a MenuBar and composes with Notebook + Statusbar into a stock window frame. Buttons size to their labels; a click runs an enabled item's OnClick.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewToolbar ¶ added in v0.34.0
func NewToolbar(items []ToolbarItem) *Toolbar
NewToolbar builds a Toolbar with the given items.
type ToolbarItem ¶ added in v0.34.0
ToolbarItem is one cell in a Toolbar: a labelled button with an OnClick and a Disabled flag, or — when Separator is true — a 1-cell vertical divider (the other fields are ignored).
type TreeNode ¶ added in v0.30.0
TreeNode is one entry in a TreeView. Children nest arbitrarily deep; Expanded controls whether they render. Data is anything the host wants to hang off the node (a path, an id, the model object) — the widget never reads it.
type TreeView ¶ added in v0.30.0
type TreeView struct {
toolkit.Base
Root *TreeNode
Selected *TreeNode
OnActivate func(node *TreeNode)
// contains filtered or unexported fields
}
TreeView renders a hierarchical TreeNode set as indented cell rows: a ▼/▶ chevron on nodes with children, then the label. A click on the chevron toggles Expanded; a click elsewhere selects the row and fires OnActivate. The arrow keys navigate (Up/Down move the selection, Left collapses, Right expands) and Enter activates the selection. Long trees scroll to keep the selection visible.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func NewTreeView ¶ added in v0.30.0
NewTreeView builds a TreeView rooted at root (nil for an empty view).
type VBox ¶ added in v0.22.0
type VBox struct {
toolkit.Base
Header toolkit.Widget
Body toolkit.Widget
HeaderH int
Overlays []toolkit.Widget
// contains filtered or unexported fields
}
VBox is a header / body / footer vertical layout: Header keeps a fixed HeaderH rows at the top, Footer a fixed FooterH rows at the bottom, and Body fills the space between. Overlays float on top, inset from the body area, and are hit-tested (so an invisible Popover / anchored MenuDropdown claims clicks only when it wants them). It captures a drag once a click lands on a child so subsequent EventMouseDrag / EventMouseUp keep flowing to that child with the same coordinate translation.
A toolkit.Widget rendering through painter.Painter (cell grid / RGBA buffer).
func (*VBox) Draw ¶ added in v0.22.0
Draw paints body first, then header + footer chrome, then overlays on top.
type VSplit ¶ added in v0.40.0
type VSplit struct {
toolkit.Base
Top, Bottom toolkit.Widget
TopFrac int
// contains filtered or unexported fields
}
VSplit is the vertical counterpart of HSplit: Top takes TopFrac percent of the height, a 1-row grip separates it from Bottom, and Bottom takes the rest. The grip row doubles as a drag handle -- a click on it starts a drag session; subsequent EventMouseDrag events (forwarded by a parent's capture) update TopFrac; EventMouseUp ends it. TopFrac is clamped to [VSplitMinFrac, VSplitMaxFrac] so neither pane collapses. Useful for editor-over-output or list-over-detail layouts.
Events are local (0-based within the split); bounds are absolute for Draw. Renders through painter.Painter (cell grid / RGBA buffer).
func (*VSplit) Draw ¶ added in v0.40.0
Draw paints both panes, then the grip row on top (Accent while dragging, Border otherwise) so the separator stays visible over either pane's bg.
Source Files
¶
- app.go
- bridge.go
- button.go
- checkbutton.go
- csi.go
- dialog.go
- dropdown.go
- entry.go
- focus.go
- gutter.go
- hsplit.go
- levelbar.go
- listbox.go
- menubar.go
- menudropdown.go
- notebook.go
- popover.go
- progressbar.go
- radio.go
- render.go
- scale.go
- size.go
- spinbutton.go
- spinner.go
- statusbar.go
- syntaxink.go
- table.go
- termgrid.go
- termios_unix.go
- texteditor.go
- toolbar.go
- treeview.go
- tui.go
- vbox.go
- vsplit.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
tui-editor
command
tui-editor is the reference loom-style interactive demo for tui.App: a modal (vi-inspired) text editor built on top of the existing toolkit widgets — TextView for the buffer, Statusbar for the mode + cursor + filename indicator, MenuBar for the chrome, and a Popover-wrapped SearchEntry for the command palette.
|
tui-editor is the reference loom-style interactive demo for tui.App: a modal (vi-inspired) text editor built on top of the existing toolkit widgets — TextView for the buffer, Statusbar for the mode + cursor + filename indicator, MenuBar for the chrome, and a Popover-wrapped SearchEntry for the command palette. |
|
tui-explorer
command
tui-explorer is the reference interactive demo for tui.App: a small k9s-style file-browser mockup composed entirely from the exported cell-native widgets in the tui package.
|
tui-explorer is the reference interactive demo for tui.App: a small k9s-style file-browser mockup composed entirely from the exported cell-native widgets in the tui package. |
|
tui-snapshot
command
tui-snapshot renders a sample widget tree to stdout as an ANSI stream.
|
tui-snapshot renders a sample widget tree to stdout as an ANSI stream. |
|
tui-widget-explorer
command
tui-widget-explorer is an interactive gallery of the go-widgets/tui cell-native widget set: a scrollable list of widget names on the left, a live instance of the selected widget on the right.
|
tui-widget-explorer is an interactive gallery of the go-widgets/tui cell-native widget set: a scrollable list of widget names on the left, a live instance of the selected widget on the right. |
|
tui-widgets
command
tui-widgets renders the go-widgets/tui CELL-NATIVE widget set (every kind with a public constructor) into a scrollable cell-grid frame written to stdout as an ANSI stream.
|
tui-widgets renders the go-widgets/tui CELL-NATIVE widget set (every kind with a public constructor) into a scrollable cell-grid frame written to stdout as an ANSI stream. |
|
Package syntax is a tiny, dependency-free syntax highlighter for the TUI previews.
|
Package syntax is a tiny, dependency-free syntax highlighter for the TUI previews. |