Skip to content
Caleb Bassi edited this page Nov 30, 2018 · 15 revisions

Widgets

termui ships with quite a few widgets that can be used to present data. Each widgets has unique fields, and all widgets inherit from termui.Block which allows updating fields such as BorderFg and BorderLabel.

	p := ui.NewParagraph("I'm a Paragraph")
	p.TextFgColor = ui.ColorWhite
	p.BorderLabel = "Text Box"
	p.BorderFg = ui.ColorCyan

	g := ui.NewGauge()
	g.Percent = 50
	g.BarColor = ui.ColorRed
	g.BorderLabel = "Gauge"
	g.BorderFg = ui.ColorWhite
	g.BorderLabelFg = ui.ColorCyan

Layout

Absolute layout

To position widgets absolutely, modify the fields X, Y, Height, and Width inherited from termui.Block.

	p.Height = 3
	p.Width = 50

	g.Width = 50
	g.Height = 3
	g.Y = 11

	ui.Render(p, g) // feel free to call Render, it's async and non-block

Note that components can be overlapped (I'd rather call this a feature...) with components being rendered from left to right.

Grid layout

grid

Grid layout uses a 12 columns grid system inspired by Bootstrap. The Grid is used by building a widget tree consisting of Rows and Cols.

	// build
	ui.Body.AddRows(
		ui.NewRow(
			ui.NewCol(6, 0, widget0),
			ui.NewCol(6, 0, widget1)),
		ui.NewRow(
			ui.NewCol(3, 0, widget2),
			ui.NewCol(3, 0, widget30, widget31, widget32),
			ui.NewCol(6, 0, widget4)))

	// calculate layout
	ui.Body.Align()

	ui.Render(ui.Body)

Events

termui provides event handling for keypresses, mouse actions, and screen resizing. termui.PollEvents() returns a channel that propogates Events originating from termbox. Events are detailed in events.go.

	draw := func() {
		...
	}

    uiEvents := ui.PollEvents()
    ticker := time.NewTicker(time.Second).C
	for {
		select {
		case e := <-uiEvents:
			switch e.ID {
			// press 'q' or 'C-c' to quit
			case "q", "<C-c>":
				ui.StopLoop()
			case "<MouseLeft>":
				payload := e.Payload.(ui.Mouse)
				x, y := payload.X, payload.Y
			case "<Resize>":
				payload := e.Payload.(ui.Resize)
				width, height := payload.Width, payload.Height
			}
			switch e.Type {
			// handle all key presses
			case ui.KeyboardEvent:
				eventID = e.ID // keypress string
			}
		// use Go's built-in tickers for updating and drawing data
		case <-ticker:
			draw()
		}
	}

Recommendations

Especially for larger projects, it can be a good idea to implement your widgets as separate structs that inherit from the termui widget they use and handle their own data. Example:

package main

import (
	"time"

	ui "github.com/gizak/termui"
)

type Dataset1 struct {
	ui.LineChart
	Count uint
}

func NewDataset1() *Dataset1 {
	d1 := &Dataset1{
		LineChart: *ui.NewLineChart(),
		Count:     0,
	}
	d1.Data["Default"] = []float64{0}
	d1.BorderLabel = " Dataset1 "
	return d1
}

func (d1 *Dataset1) Update() {
	d1.Count += 1
	d1.Data["Default"] = append(d1.Data["Default"], float64(d1.Count))
}

func main() {
	err := ui.Init()
	if err != nil {
		panic(err)
	}
	defer ui.Close()

	d1 := NewDataset1()
	d1.Width = 50
	d1.Height = 25
	ui.Render(d1)

	for {
		select {
		case e := <-ui.PollEvent():
			if e.Type == ui.KeyboardEvent {
				return
			}
		case <-time.NewTicker(time.Second).C:
			d1.Update()
			ui.Render(d1)
		}
	}
}

Custom Widgets

If one of the premade widgets doesn't suite your needs, you can create a custom widget with its own properties and rendering method and pass it to Render() or add it to the Grid like any other widget. The only requirement is that the widget implementes the Bufferer interface, which means that it has a Buffer() method that return a Buffer. A Buffer represents a rectangular section of a terminal and holds Cells. A Cell represent a terminal cell and holds a rune (a single character) and 2 Attributes representing the foreground and background colors.

In practice, your widget should inherit from a Block since it handles a lot of common widget features like position, size, title, etc. and because it returns a premade Buffer for your widget to drawn on. Here's an example that implements a paragraph with the text centered:

package main

import (
	ui "github.com/gizak/termui"
)

type CustomWidget struct {
	ui.Block
	Data string
}

func NewCustomWidget(data string) *CustomWidget {
	cw := &CustomWidget{
		Block: *ui.NewBlock(),
		Data:  data,
	}
	cw.BorderLabel = " My Custom Widget "
	return cw
}

func (cw *CustomWidget) Buffer() ui.Buffer {
	buf := cw.Block.Buffer()
	for i, c := range cw.Data {
        // NewCell takes a rune and 2 Attributes
        cell := ui.NewCell(c, ui.ColorBlue, ui.ColorDefault)
        // Set takes x, y, and a Cell
		buf.Set(i+5, 2, cell)
	}
	return buf
}

func main() {
	err := ui.Init()
	if err != nil {
		panic(err)
	}
	defer ui.Close()

	cw := NewCustomWidget("Hello World!")
	cw.Width = 25
	cw.Height = 5
	ui.Render(cw)

	for {
		e := <-ui.PollEvent()
		if e.Type == ui.KeyboardEvent {
			return
		}
	}
}
Clone this wiki locally