---
title: "tview and you - Creating Rich Terminal User Interfaces"
date: 2019-11-08T01:42:18-07:00
categories: [tutorial]
---
[](https://github.com/rivo/tview/tree/master/demos/presentation)
This is an introduction to using [tview ](https://github.com/rivo/tview ) (or [cview ](https://git.sr.ht/~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://git.sr.ht/~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 > }}