--- title: "tview and you - Creating Rich Terminal User Interfaces" date: 2019-11-08T01:42:18-07:00 categories: [tutorial] --- [![Recording of presentation demo](https://raw.githubusercontent.com/rivo/tview/master/tview.gif)](https://github.com/rivo/tview/tree/master/demos/presentation) [![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space) This is an introduction to using [tview](https://github.com/rivo/tview) (or [cview](https://code.rocketnine.space/tslocum/cview)) to create rich terminal-based user interfaces with [Go](https://golang.org). ## Primitives The [Primitive](https://godoc.org/github.com/rivo/tview#Primitive) interface is as follows: {{< highlight go >}} type Primitive interface { Draw(screen tcell.Screen) GetRect() (int, int, int, int) SetRect(x, y, width, height int) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) Focus(delegate func(p Primitive)) Blur() GetFocusable() Focusable } {{< / highlight >}} [Box](https://godoc.org/github.com/rivo/tview#Box) is the only primitive implemented. It has a size, padding amount, optional border, optional title and background color. ## Widgets Widgets are structs which embed a [Box](https://godoc.org/github.com/rivo/tview#Box) and build upon it. From the [TextView](https://godoc.org/github.com/rivo/tview#TextView) declaration: {{< highlight go >}} type TextView struct { *Box // The text buffer. buffer []string // The text alignment, one of AlignLeft, AlignCenter, or AlignRight. align int // ... } {{< / highlight >}} Some widgets allow nesting other widgets within them, such as [Grid](https://godoc.org/github.com/rivo/tview#Grid). Most widget commands may be chained together: {{< highlight go >}} nameLabel := tview.NewTextView(). SetTextAlign(tview.AlignRight). SetDynamicColors(true). SetWrap(true). SetWordWrap(true). SetText("Please enter your name:") {{< / highlight >}} New widgets may be defined, as documented in the [Primitive demo](https://github.com/rivo/tview/tree/master/demos/primitive). ### Widget Elements --- [**Button**](https://godoc.org/github.com/rivo/tview#Button) is a labeled box that triggers an action when selected. {{< highlight go >}} button := tview.NewButton("OK").SetSelectedFunc(func() { pressedOK() }) {{< / highlight >}} --- [**Checkbox**](https://godoc.org/github.com/rivo/tview#Checkbox) holds a label and boolean value which may be checked and unchecked. {{< highlight go >}} checkbox := tview.NewCheckbox().SetLabel("Toggle value with Enter: ") {{< / highlight >}} --- [**DropDown**](https://godoc.org/github.com/rivo/tview#DropDown) holds one or more options which may be selected as a dropdown list. {{< highlight go >}} dropdown := tview.NewDropDown(). SetLabel("Select an option with Enter: "). SetOptions([]string{"Foo", "Bar", "Baz"}, nil) {{< / highlight >}} --- [**InputField**](https://godoc.org/github.com/rivo/tview#InputField) is a box where text may be entered. {{< highlight go >}} inputField := tview.NewInputField(). SetLabel("Name: "). SetPlaceholder("John Smith"). SetFieldWidth(14). SetDoneFunc(func(key tcell.Key) { processName() }) {{< / highlight >}} --- [**Modal**](https://godoc.org/github.com/rivo/tview#Modal) is a centered message window which may have one or more buttons. {{< highlight go >}} modal := tview.NewModal(). SetText("Are you sure you want to exit?"). AddButtons([]string{"Cancel", "Quit"}). SetDoneFunc(func(buttonIndex int, buttonLabel string) { if buttonIndex == 1 { app.Stop() } }) {{< / highlight >}} --- [**TextView**](https://godoc.org/github.com/rivo/tview#TextView) is a box containing text. [Colored text](https://godoc.org/github.com/rivo/tview#hdr-Colors) is supported when enabled. {{< highlight go >}} textView := tview.NewTextView(). SetWrap(true). SetWordWrap(true). SetText("Hello, World!") {{< / highlight >}} --- ### Widget Containers --- [**Flex**](https://godoc.org/github.com/rivo/tview#Flex) is a [flexbox layout](https://en.wikipedia.org/wiki/CSS_Flexible_Box_Layout) container. See the [Flex demo](https://github.com/rivo/tview/tree/master/demos/flex) for example usage. --- [**Grid**](https://godoc.org/github.com/rivo/tview#Grid) is a [grid layout](https://en.wikipedia.org/wiki/CSS_grid_layout) container. See the [Grid demo](https://github.com/rivo/tview/tree/master/demos/grid) for example usage. --- [**Form**](https://godoc.org/github.com/rivo/tview#Form) displays one or more form elements in a vertical or horizontal layout. See the [Form demo](https://github.com/rivo/tview/tree/master/demos/form) for example usage. --- [**List**](https://godoc.org/github.com/rivo/tview#List) displays one or more widgets as a selectable list. See the [List demo](https://github.com/rivo/tview/tree/master/demos/list) for example usage. --- [**Pages**](https://godoc.org/github.com/rivo/tview#Pages) displays one or more widgets at a time. See the [Pages demo](https://github.com/rivo/tview/tree/master/demos/pages) for example usage. --- [**Table**](https://godoc.org/github.com/rivo/tview#Table) displays one or more widgets in rows and columns. See the [Table demo](https://github.com/rivo/tview/tree/master/demos/table) for example usage. --- [**TreeView**](https://godoc.org/github.com/rivo/tview#TreeView) displays one or more widgets in a tree. See the [TreeView demo](https://github.com/rivo/tview/tree/master/demos/treeview) for example usage. --- ## Thread Safety **Most tview functions cannot safely be called from any thread except the main one**. Using either of the following functions, we can queue a function to be executed in the main thread. * [Application.QueueUpdate](https://godoc.org/github.com/rivo/tview#Application.QueueUpdate) * [Application.QueueUpdateDraw](https://godoc.org/github.com/rivo/tview#Application.QueueUpdateDraw) The only difference between the two is that QueueUpdateDraw calls [Application.Draw](https://godoc.org/github.com/rivo/tview#Application.Draw) after the queued function returns. One exception is [TextView.Write](https://godoc.org/github.com/rivo/tview#TextView.Write), which may safely be called from multiple goroutines. Below is an example of setting a new root primitive from another goroutine. {{< highlight go >}} app.QueueUpdateDraw(func() { app.SetRoot(appGrid, true) }) {{< / highlight >}} ## Example Application A tview application is constructed of a running [Application](https://godoc.org/github.com/rivo/tview#Application) with at least one root widget. To display a primitive (and its contents), we call [Application.SetRoot](https://godoc.org/github.com/rivo/tview#Application.SetRoot). This function has two arguments, a primitive which will become the root of the screen, and a boolean which controls whether the primitive will be resized to fit the screen. In this example, the root is a Grid containing a label, an input and a submit button. For a more complex example, see [netris](https://code.rocketnine.space/tslocum/netris). Install tview if you haven't already: {{< highlight command >}} go get -u github.com/rivo/tview {{< / highlight >}} Then create a file named greet.go: {{< highlight go >}} package main import ( "fmt" "log" "strings" "github.com/gdamore/tcell" "github.com/rivo/tview" ) func main() { // Initialize application app := tview.NewApplication() // Create label label := tview.NewTextView().SetText("Please enter your name:") // Create input field input := tview.NewInputField() // Create submit button btn := tview.NewButton("Submit") // Create empty Box to pad each side of appGrid bx := tview.NewBox() // Create Grid containing the application's widgets appGrid := tview.NewGrid(). SetColumns(-1, 24, 16, -1). SetRows(-1, 2, 3, -1). AddItem(bx, 0, 0, 3, 1, 0, 0, false). // Left - 3 rows AddItem(bx, 0, 1, 1, 1, 0, 0, false). // Top - 1 row AddItem(bx, 0, 3, 3, 1, 0, 0, false). // Right - 3 rows AddItem(bx, 3, 1, 1, 1, 0, 0, false). // Bottom - 1 row AddItem(label, 1, 1, 1, 1, 0, 0, false). AddItem(input, 1, 2, 1, 1, 0, 0, false). AddItem(btn, 2, 1, 1, 2, 0, 0, false) // submittedName is toggled each time Enter is pressed var submittedName bool // Capture user input app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { // Anything handled here will be executed on the main thread switch event.Key() { case tcell.KeyEnter: submittedName = !submittedName if submittedName { name := input.GetText() if strings.TrimSpace(name) == "" { name = "Anonymous" } // Create a modal dialog m := tview.NewModal(). SetText(fmt.Sprintf("Greetings, %s!", name)). AddButtons([]string{"Hello"}) // Display and focus the dialog app.SetRoot(m, true).SetFocus(m) } else { // Clear the input field input.SetText("") // Display appGrid and focus the input field app.SetRoot(appGrid, true).SetFocus(input) } return nil case tcell.KeyEsc: // Exit the application app.Stop() return nil } return event }) // Set the grid as the application root and focus the input field app.SetRoot(appGrid, true).SetFocus(input) // Run the application err := app.Run() if err != nil { log.Fatal(err) } } {{< / highlight >}}