Help us shape the future of Go! Take the 2026 Go Developer Survey

timui

package module
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 8 Imported by: 1

README

TIMUI

Disclaimer: This is a personal pet project / experiment

is a terminal ui immediate mode user interface library. inspired by https://github.com/ocornut/imgui

Focus

  • simple
  • lightweight

Widgets

are rendered using function calls like:

if tui.Button("ClickMe +") {
  count++
}

if tui.Button("ClickMe -") {
  count--
}

tui.Checkbox("Alpha", &checkedA)

Input

Mouse position and buttons are polled, wheel notches and key presses are collected by the backend and drained once per frame. The wheel goes to the area under the cursor, keys go to every widget because there is no focus model yet. tui.Mods() reports the modifiers currently held, which makes ctrl-clicks and ctrl-hovers possible. The vaxis backend tracks them through the kitty keyboard protocol, so a lone Ctrl press is seen; the tcell backend only sees modifiers when another event is reported.

for _, k := range tui.Keys() {
  if k.Key == timui.KeyPageUp {
    state.PageUp()
  }
}

Backends

The manipulating of the terminal and event handling is done via backends.

Currently there is a github.com/gdamore/tcell backend and a go.rockorager.dev/vaxis backend.

Example

Demo

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Transparent = RGBA(0, 0, 0, 0)
	White       = RGBA(0xff, 0xff, 0xff, 0xff)
	Black       = RGBA(0x0, 0x0, 0x0, 0xff)
)
View Source
var BorderBasic = BorderStyle{
	Rect:       [6]rune{'-', '|', '/', '\\', '\\', '/'},
	Vertical:   [3]rune{'+', '|', '+'},
	Horizontal: [3]rune{'+', '-', '+'},
}
View Source
var BorderDouble = BorderStyle{
	Rect:       [6]rune{'═', '║', '╔', '╗', '╚', '╝'},
	Vertical:   [3]rune{'╦', '║', '╩'},
	Horizontal: [3]rune{'╠', '═', '╣'},
}
View Source
var BorderNone = BorderStyle{
	Rect:       [6]rune{' ', ' ', ' ', ' ', ' ', ' '},
	Vertical:   [3]rune{' ', ' ', ' '},
	Horizontal: [3]rune{' ', ' ', ' '},
}
View Source
var BorderRoundSingle = BorderStyle{
	Rect:       [6]rune{'─', '│', '╭', '╮', '╰', '╯'},
	Vertical:   [3]rune{'┬', '│', '┴'},
	Horizontal: [3]rune{'├', '─', '┤'},
}
View Source
var BorderSingle = BorderStyle{
	Rect:       [6]rune{'─', '│', '┌', '┐', '└', '┘'},
	Vertical:   [3]rune{'┬', '│', '┴'},
	Horizontal: [3]rune{'├', '─', '┤'},
}
View Source
var DefaultTheme = Theme{
	BG:         MustRGBS("#000"),
	Text:       MustRGBS("#f33"),
	BorderLine: MustRGBS("#3ff"),
	BorderBG:   MustRGBS("#000"),
	Widget: WidgetTheme{
		BG:         MustRGBS("#004"),
		Text:       MustRGBS("#bbb"),
		Line:       MustRGBS("#a0a"),
		HoverBG:    MustRGBS("#22a"),
		InteractBG: MustRGBS("#008"),
		FocusLine:  MustRGBS("#ffa"),
	},
	ScrollBar: ScrollBarTheme{
		Text:    MustRGBS("#bbb"),
		BG:      MustRGBS("#013"),
		ArrowBG: MustRGBS("#135"),
		Knob:    MustRGBS("#46f"),
		KnobBG:  MustRGBS("#00a"),
	},
	Selection: SelectionTheme{
		Text: MustRGBS("#000"),
		BG:   MustRGBS("#5af"),
	},
	BorderStyle: BorderDouble,
}

Functions

func OptionGroup

func OptionGroup[V comparable](t *Timui, name string, selected *V, f func(*OptionGroupElement[V]))

func WriteClipboardLocal added in v0.0.5

func WriteClipboardLocal(text string) error

WriteClipboardLocal tries local clipboard tools (wl-copy, then xclip/xsel) in turn. It exists because OSC 52 - the primary mechanism backends use, since it also works over SSH - is a silent no-op on VTE-based terminals (GNOME Terminal, Guake, Tilix, ...), which don't implement OSC 52 copy at all. This only reaches a local X11/Wayland session, so backends call it as a fallback alongside OSC 52, not instead of it.

It only runs at all when VTE_VERSION is set (every VTE-based terminal exports it), since xclip/xsel have to hold a hidden window open to keep owning the X11 CLIPBOARD selection, and creating that window steals focus for an instant - harmless on most terminals, but enough to trip a drop-down terminal's "hide on lost focus" setting (Guake, and likely others). Terminals where OSC 52 already works don't need this fallback, so they don't pay for that risk at all.

Types

type Backend

type Backend interface {
	Size() mathi.Vec2
	MousePosition() mathi.Vec2
	MousePressed(key Key) bool

	// TakeInput returns the wheel movement and key presses collected since the
	// previous call and clears them, so nothing is delivered twice. The modifier
	// state it carries is not cleared, it is the currently held state.
	TakeInput() Input

	Set(pos mathi.Vec2, char rune, fg, bg uint32)
	Render()

	// WriteClipboard pushes text to the system clipboard, typically via an OSC 52
	// escape sequence so it also works over SSH without a helper binary on the
	// remote end.
	WriteClipboard(text string)
}

type BorderStyle

type BorderStyle struct {
	Rect       [6]rune
	Horizontal [3]rune
	Vertical   [3]rune
}

type Draggable

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

type Grid

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

func (*Grid) Columns

func (g *Grid) Columns(opts *SplitOptions, cells ...func(cell *GridCell))

func (*Grid) Rows

func (g *Grid) Rows(opts *SplitOptions, cells ...func(cell *GridCell))

type GridCell

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

GridCell is the context of one grid cell. Its area is the complete cell including the border lines around it, so nested splits overlap the parent's lines and their divider glyphs merge into junctions.

func (*GridCell) Columns

func (c *GridCell) Columns(opts *SplitOptions, cells ...func(cell *GridCell))

func (*GridCell) Rows

func (c *GridCell) Rows(opts *SplitOptions, cells ...func(cell *GridCell))

type Input

type Input struct {
	// Wheel is the accumulated wheel movement, Y positive when scrolling down.
	Wheel mathi.Vec2

	// Keys are the presses in the order they arrived.
	Keys []KeyEvent

	// Mods are the modifiers currently held. It is a state and not an event, so
	// it survives being read.
	Mods ModMask
}

Input is everything that reached the backend since it was last read.

type Key

type Key int
const (
	KeyNone Key = iota
	KeyRune
	KeyUp
	KeyDown
	KeyLeft
	KeyRight
	KeyPageUp
	KeyPageDown
	KeyHome
	KeyEnd
	KeyEnter
	KeyEscape
	KeyTab
	KeyBackspace
	KeyDelete
)
var (
	MouseButtonLeft  Key = 1_000_000
	MouseButtonRight Key = 1_000_001
)

type KeyEvent

type KeyEvent struct {
	Key  Key
	Rune rune
}

KeyEvent is a single key press. For printable characters Key is KeyRune and Rune holds the character, for everything else Key names the key and Rune is zero.

type ModMask added in v0.0.3

type ModMask int

ModMask is a set of modifier keys. Backends track the currently held state: the vaxis backend sees a modifier pressed or let go on its own through the kitty keyboard protocol. On a backend that can't report those (tcell), the modifiers only change when the mouse moves or a key is typed.

const (
	ModShift ModMask = 1 << iota
	ModCtrl
	ModAlt
)

func (ModMask) Has added in v0.0.3

func (m ModMask) Has(o ModMask) bool

Has reports whether every modifier in o is set.

type MouseInput

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

func (*MouseInput) Hovered

func (m *MouseInput) Hovered() int

func (*MouseInput) LeftPressed

func (m *MouseInput) LeftPressed() int

func (*MouseInput) LeftReleased

func (m *MouseInput) LeftReleased() bool

func (*MouseInput) Wheel

func (m *MouseInput) Wheel() mathi.Vec2

Wheel is the wheel movement that happened over this area since the previous frame. Y is positive when scrolling down.

type OptionGroupElement

type OptionGroupElement[V comparable] struct {
	// contains filtered or unexported fields
}

func (OptionGroupElement[V]) Option

func (o OptionGroupElement[V]) Option(name string, value V) bool

type Panel

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

func (*Panel) HLine

func (p *Panel) HLine()

func (*Panel) Header

func (p *Panel) Header(body func())

Header runs body inside a one-row strip on the panel's top border line.

type RGBAColor

type RGBAColor uint32

func MustRGBAS

func MustRGBAS(s string) RGBAColor

func RGBA

func RGBA(r, g, b, a int) RGBAColor

func RGBAS

func RGBAS(s string) (RGBAColor, error)

RGBAS parses "#rgba" or "#rrggbbaa" into an RGBAColor.

func (RGBAColor) RGBA

func (rgba RGBAColor) RGBA() (int, int, int, int)

func (RGBAColor) String

func (rgb RGBAColor) String() string

type RGBColor

type RGBColor uint32

func MustRGBS

func MustRGBS(s string) RGBColor

func RGB

func RGB(r, g, b int) RGBColor

func RGBS

func RGBS(s string) (RGBColor, error)

RGBS parses "#rgb" or "#rrggbb" into an RGBColor.

func (RGBColor) Add

func (rgb RGBColor) Add(o RGBColor) RGBColor

func (RGBColor) Blend

func (rgb RGBColor) Blend(rgba RGBAColor) RGBColor

func (RGBColor) MulDiv

func (rgb RGBColor) MulDiv(factor, div int) RGBColor

func (RGBColor) RGB

func (rgb RGBColor) RGB() (int, int, int)

func (RGBColor) RGBA

func (rgb RGBColor) RGBA(a int) RGBAColor

func (RGBColor) String

func (rgb RGBColor) String() string

type ScrollBarTheme

type ScrollBarTheme struct {
	Text    RGBColor
	BG      RGBColor
	ArrowBG RGBColor
	Knob    RGBColor
	KnobBG  RGBColor
}

type ScrollState

type ScrollState struct {
	// Offset is the index of the first visible row.
	Offset int

	// Follow pins the view to the last row while rows are appended. It is cleared
	// when the user scrolls away from the bottom and set again when they return to
	// it, so a log tails by default but stays put once it is read.
	Follow bool

	// ViewHeight is how many rows fit, written on every render so the caller can
	// page by a screenful.
	ViewHeight int
}

ScrollState is the caller owned position of a scroll list.

func (*ScrollState) Bottom

func (s *ScrollState) Bottom()

Bottom jumps to the last row and follows from there.

func (*ScrollState) PageDown

func (s *ScrollState) PageDown()

PageDown moves the view down by a screenful.

func (*ScrollState) PageUp

func (s *ScrollState) PageUp()

PageUp moves the view up by a screenful and stops following.

func (*ScrollState) Scroll

func (s *ScrollState) Scroll(rows int)

Scroll moves the view by rows. The next render clamps the offset and decides whether the view ended up at the bottom again.

func (*ScrollState) Top

func (s *ScrollState) Top()

Top jumps to the first row.

type SelectionTheme added in v0.0.4

type SelectionTheme struct {
	Text RGBColor
	BG   RGBColor
}

type SplitOptions

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

func Split

func Split() *SplitOptions

func (*SplitOptions) Add

func (s *SplitOptions) Add(factor float32, fixed int) *SplitOptions

func (*SplitOptions) Factor

func (s *SplitOptions) Factor(factors ...float32) *SplitOptions

func (*SplitOptions) Fixed

func (s *SplitOptions) Fixed(fixed ...int) *SplitOptions

func (*SplitOptions) Pad

func (s *SplitOptions) Pad(padding int) *SplitOptions

type Theme

type Theme struct {
	BG          RGBColor
	Text        RGBColor
	BorderLine  RGBColor
	BorderBG    RGBColor
	Widget      WidgetTheme
	ScrollBar   ScrollBarTheme
	Selection   SelectionTheme
	BorderStyle BorderStyle
}

func (*Theme) WithBorder

func (t *Theme) WithBorder(b BorderStyle, content func())

type Timui

type Timui struct {
	Theme Theme
	// contains filtered or unexported fields
}

func New

func New(backend Backend) *Timui

func (*Timui) Border

func (t *Timui) Border(style [6]rune, fg, bg RGBColor)

func (*Timui) Button

func (g *Timui) Button(name string) bool

func (*Timui) Checkbox

func (g *Timui) Checkbox(name string, checked *bool) bool

func (*Timui) ClipContains

func (t *Timui) ClipContains(pos mathi.Vec2) bool

func (*Timui) Columns

func (g *Timui) Columns(opts *SplitOptions, cells ...func())

Columns splits the current area horizontally and runs one cell func per split entry, each inside its column area. Afterwards the parent cursor is advanced to the bottom of the tallest column. Panics if the cell count does not match the split count.

func (*Timui) CurrentArea

func (t *Timui) CurrentArea() *mathi.Box2

func (*Timui) Dialog

func (t *Timui) Dialog(title string, visible *bool, content func())

func (*Timui) Draggable

func (t *Timui) Draggable(id string, area mathi.Box2, size mathi.Vec2, pos *mathi.Vec2) (bool, bool)

func (*Timui) Dropdown

func (g *Timui) Dropdown(id string, elements int, selected *int, paint func(i int, s bool))

func (*Timui) Finish

func (t *Timui) Finish()

func (*Timui) GetMousePosition

func (t *Timui) GetMousePosition() mathi.Vec2

func (*Timui) Grid

func (t *Timui) Grid(body func(grid *Grid))

Grid draws a bordered container and runs body inside its padded area. Rows and Columns on the grid subdivide it with divider lines that join the surrounding border.

func (*Timui) HLine

func (t *Timui) HLine(style [3]rune, fg, bg RGBColor)

func (*Timui) KeyPressed

func (t *Timui) KeyPressed(key Key) bool

KeyPressed reports whether the given key was pressed this frame.

func (*Timui) Keys

func (t *Timui) Keys() []KeyEvent

Keys returns the keys pressed since the previous frame. There is no focus, so every widget sees all of them and decides for itself whether a key means anything to it.

func (*Timui) Label

func (t *Timui) Label(name string)

func (*Timui) Mods added in v0.0.3

func (t *Timui) Mods() ModMask

Mods returns the modifiers currently held.

func (*Timui) MouseInput

func (t *Timui) MouseInput(id string) *MouseInput

MouseInput creates a mouse area for the current cursor area

func (*Timui) MouseInputForArea

func (t *Timui) MouseInputForArea(id string, area mathi.Box2) *MouseInput

MouseInputForArea creates a mouse area from given area relative to the current cursor area

func (*Timui) MouseInputForSize

func (t *Timui) MouseInputForSize(id string, size mathi.Vec2) *MouseInput

MouseInputForSize creates a mouse area from given size relative to the current cursor area

func (*Timui) Pad

func (t *Timui) Pad(top, right, bottom, left int, body func())

func (*Timui) Panel

func (t *Timui) Panel(body func(p *Panel))

Panel draws a bordered container and runs body inside its padded area.

func (*Timui) PeekClip

func (g *Timui) PeekClip() mathi.Box2

func (*Timui) PeekCopyRect added in v0.0.4

func (t *Timui) PeekCopyRect() mathi.Box2

func (*Timui) PopArea

func (t *Timui) PopArea()

func (*Timui) PopClip

func (g *Timui) PopClip()

func (*Timui) PopCopyRect added in v0.0.4

func (t *Timui) PopCopyRect()

func (*Timui) PushArea

func (t *Timui) PushArea(area mathi.Box2)

func (*Timui) PushAreaTranslation

func (t *Timui) PushAreaTranslation(dir mathi.Vec2)

func (*Timui) PushClip

func (g *Timui) PushClip(area mathi.Box2)

func (*Timui) PushCopyRect added in v0.0.4

func (t *Timui) PushCopyRect(area mathi.Box2)

PushCopyRect scopes text selection to area: a drag started inside it can only select within it. Grid/Rows/Columns cells, ScrollAreaV, ScrollListV and Dialog each push one around their content, so a selection can't run into a neighboring cell or off-screen content clipped away.

func (*Timui) Rows

func (g *Timui) Rows(opts *SplitOptions, cells ...func())

Rows splits the current area vertically and runs one cell func per split entry, each inside its row area. Panics if the cell count does not match the split count.

func (*Timui) RunePressed

func (t *Timui) RunePressed(r rune) bool

RunePressed reports whether the given character was typed this frame.

func (*Timui) ScrollAreaV

func (g *Timui) ScrollAreaV(id string, body func())

func (*Timui) ScrollListV

func (g *Timui) ScrollListV(id string, state *ScrollState, count int, row func(i int))

ScrollListV renders count rows of height one and calls row only for the visible ones. Unlike ScrollAreaV, which draws all of its content and clips it away, the cost of a list does not grow with the number of rows.

func (*Timui) Set

func (t *Timui) Set(pos mathi.Vec2, char rune, fg, bg RGBColor)

func (*Timui) SetAlpha

func (t *Timui) SetAlpha(pos mathi.Vec2, char rune, fg, bg RGBAColor)

func (*Timui) SetArea

func (t *Timui) SetArea(char rune, fg, bg RGBColor)

func (*Timui) SetAreaAlpha

func (t *Timui) SetAreaAlpha(char rune, fg, bg RGBAColor)

func (*Timui) SetBorder

func (t *Timui) SetBorder(pos mathi.Vec2, char rune, fg, bg RGBColor)

func (*Timui) Size

func (t *Timui) Size() mathi.Vec2

func (*Timui) Text

func (t *Timui) Text(name string, pos mathi.Vec2, fg, bg RGBAColor)

func (*Timui) VLine

func (t *Timui) VLine(style [3]rune, fg, bg RGBColor)

func (*Timui) WithArea

func (t *Timui) WithArea(area mathi.Box2, body func())

WithArea runs body with area as the current area.

func (*Timui) WithAreaTranslation

func (t *Timui) WithAreaTranslation(dir mathi.Vec2, body func())

WithAreaTranslation runs body with the current area translated by dir.

func (*Timui) WithClip

func (g *Timui) WithClip(area mathi.Box2, body func())

WithClip runs body with drawing clipped to area.

func (*Timui) WithCopyRect added in v0.0.4

func (t *Timui) WithCopyRect(area mathi.Box2, body func())

WithCopyRect runs body with the selection scoped to area.

func (*Timui) WithID

func (t *Timui) WithID(id string, body func())

WithID runs body inside an id scope. Widgets created by body derive their id relative to this scope, so identically-labelled widgets (for example two "+" buttons in different rows) stay distinct.

type WidgetTheme

type WidgetTheme struct {
	BG         RGBColor
	Text       RGBColor
	Line       RGBColor
	HoverBG    RGBColor
	InteractBG RGBColor
	FocusLine  RGBColor
}

Directories

Path Synopsis
cmd
example command

Jump to

Keyboard shortcuts

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