2019-11-20 12:48:44 +00:00
---
title: "tview and you - Creating Rich Terminal User Interfaces"
date: 2019-11-08T01:42:18-07:00
categories: [tutorial]
---
2020-01-18 02:14:28 +00:00
[![Recording of presentation demo ](https://raw.githubusercontent.com/rivo/tview/master/tview.gif )](https://github.com/rivo/tview/tree/master/demos/presentation)
2019-11-20 12:48:44 +00:00
2020-08-29 16:48:44 +00:00
[![Donate ](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay )](https://liberapay.com/rocketnine.space)
2021-04-20 05:18:34 +00:00
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 ).
2019-11-20 12:48:44 +00:00
2020-01-18 02:14:28 +00:00
## Primitives
2019-11-20 12:48:44 +00:00
The [Primitive ](https://godoc.org/github.com/rivo/tview#Primitive ) interface is as follows:
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
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
}
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
[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.
2020-01-18 02:14:28 +00:00
## Widgets
2019-11-20 12:48:44 +00:00
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:
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
type TextView struct {
*Box
// The text buffer.
buffer []string
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.
align int
// ...
}
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
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:
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
nameLabel := tview.NewTextView().
SetTextAlign(tview.AlignRight).
SetDynamicColors(true).
SetWrap(true).
SetWordWrap(true).
SetText("Please enter your name:")
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
New widgets may be defined, as documented in the [Primitive demo ](https://github.com/rivo/tview/tree/master/demos/primitive ).
2020-01-18 02:14:28 +00:00
### Widget Elements
2019-11-20 12:48:44 +00:00
---
[**Button** ](https://godoc.org/github.com/rivo/tview#Button ) is a labeled box that triggers an action when selected.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
button := tview.NewButton("OK").SetSelectedFunc(func() {
pressedOK()
})
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
---
[**Checkbox** ](https://godoc.org/github.com/rivo/tview#Checkbox ) holds a label and boolean value which may be checked and unchecked.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
checkbox := tview.NewCheckbox().SetLabel("Toggle value with Enter: ")
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
---
[**DropDown** ](https://godoc.org/github.com/rivo/tview#DropDown ) holds one or more options which may be selected as a dropdown list.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
dropdown := tview.NewDropDown().
SetLabel("Select an option with Enter: ").
SetOptions([]string{"Foo", "Bar", "Baz"}, nil)
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
---
[**InputField** ](https://godoc.org/github.com/rivo/tview#InputField ) is a box where text may be entered.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
inputField := tview.NewInputField().
SetLabel("Name: ").
SetPlaceholder("John Smith").
SetFieldWidth(14).
SetDoneFunc(func(key tcell.Key) {
processName()
})
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
---
[**Modal** ](https://godoc.org/github.com/rivo/tview#Modal ) is a centered message window which may have one or more buttons.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
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()
}
})
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
---
[**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.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
textView := tview.NewTextView().
SetWrap(true).
SetWordWrap(true).
SetText("Hello, World!")
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
---
2020-01-18 02:14:28 +00:00
### Widget Containers
2019-11-20 12:48:44 +00:00
---
[**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.
---
2020-01-18 02:14:28 +00:00
## Thread Safety
2019-11-20 12:48:44 +00:00
**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.
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
app.QueueUpdateDraw(func() {
app.SetRoot(appGrid, true)
})
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
2020-01-18 02:14:28 +00:00
## Example Application
2019-11-20 12:48:44 +00:00
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.
2021-04-20 05:18:34 +00:00
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 ).
2019-11-20 12:48:44 +00:00
Install tview if you haven't already:
2020-01-18 02:14:28 +00:00
{{< highlight command > }}
2019-11-20 12:48:44 +00:00
go get -u github.com/rivo/tview
2020-01-18 02:14:28 +00:00
{{< / highlight > }}
2019-11-20 12:48:44 +00:00
Then create a file named greet.go:
2020-01-18 02:14:28 +00:00
{{< highlight go > }}
2019-11-20 12:48:44 +00:00
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)
}
}
2020-01-18 02:14:28 +00:00
{{< / highlight > }}