-
Notifications
You must be signed in to change notification settings - Fork 787
Home
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
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 uses a 12 columns grid system inspired by Bootstrap. The Grid
is used by building a widget tree consisting of Row
s and Col
s.
// 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)
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()
}
}
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)
}
}
}
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 Cell
s. 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
}
}
}