Skip to content

Instantly share code, notes, and snippets.

@christophberger
Last active April 4, 2017 09:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christophberger/08c26260a10525c90168beb8c9cb0f49 to your computer and use it in GitHub Desktop.
Save christophberger/08c26260a10525c90168beb8c9cb0f49 to your computer and use it in GitHub Desktop.
Code from github.com/appliedgo/tui
package main
// Under normal circumstances, importing two UI libraries at the
// same time is probably not a good idea, as each has its own event
// loop. This demo code takes care of using only one of these
// libraries at a time.
import (
"fmt"
"log"
"os"
// Both TUI packages are abbreviated to avoid making the code
// overly verbose.
t "github.com/gizak/termui"
c "github.com/jroimartin/gocui"
"github.com/pkg/errors"
)
const (
// List box width.
lw = 20
// Input box height.
ih = 3
)
// Items to fill the list with.
var listItems = []string{
"Line 1",
"Line 2",
"Line 3",
"Line 4",
"Line 5",
}
func runTermui() {
// Initialize termui.
err := t.Init()
if err != nil {
log.Fatalln("Cannot initialize termui")
}
// `termui` needs some cleanup when terminating.
defer t.Close()
// Get the height of the terminal.
th := t.TermHeight()
// The list block
lb := t.NewList()
lb.Height = th
lb.BorderLabel = "List"
lb.BorderLabelFg = t.ColorGreen
lb.BorderFg = t.ColorGreen
lb.ItemFgColor = t.ColorWhite
lb.Items = listItems
// The input block. termui has no edit box yet, but at the time of
// this writing, there is an open [pull request](https://github.com/gizak/termui/pull/129) for adding
// a text input widget.
ib := t.NewPar("")
ib.Height = ih
ib.BorderLabel = "Input"
ib.BorderLabelFg = t.ColorYellow
ib.BorderFg = t.ColorYellow
ib.TextFgColor = t.ColorWhite
// The Output block.
ob := t.NewPar("\nPress Ctrl-C to quit")
ob.Height = th - ih
ob.BorderLabel = "Output"
ob.BorderLabelFg = t.ColorCyan
ob.BorderFg = t.ColorCyan
ob.TextFgColor = t.ColorWhite
// Now we need to create the layout. The blocks have gotten a size
// but no position. A grid layout puts everything into place.
// t.Body is a pre-defined grid. We add one row that contains
// two columns.
//
// The grid uses a 12-column system, so we have to give a "span"
// parameter to each column that specifies how many grid column
// each column occupies.
t.Body.AddRows(
t.NewRow(
t.NewCol(3, 0, lb),
t.NewCol(9, 0, ob, ib)))
// Render the grid.
t.Body.Align()
t.Render(t.Body)
// When the window resizes, the grid must adopt to the new size.
// We use a hander func for this.
t.Handle("/sys/wnd/resize", func(t.Event) {
// Update the heights of list box and output box.
lb.Height = t.TermHeight()
ob.Height = t.TermHeight() - ih
t.Body.Width = t.TermWidth()
t.Body.Align()
t.Render(t.Body)
})
//
// We need a way out. Ctrl-C shall stop the event loop.
t.Handle("/sys/kbd/C-c", func(t.Event) {
t.StopLoop()
})
// start the event loop.
t.Loop()
}
func runGocui() {
// Create a new GUI.
g, err := c.NewGui(c.OutputNormal)
if err != nil {
log.Println("Failed to create a GUI:", err)
return
}
defer g.Close()
// Activate the cursor for the current view.
g.Cursor = true
// The GUI object wants to know how to manage the layout.
// Unlike `termui`, `gocui` does not use
// a grid layout. Instead, it relies on a custom layout handler function
// to manage the layout.
// Here we set the layout manager to a function named `layout`
// that is defined further down.
g.SetManagerFunc(layout)
// Bind the `quit` handler function (also defined further down) to Ctrl-C,
// so that we can leave the application at any time.
err = g.SetKeybinding("", c.KeyCtrlC, c.ModNone, quit)
if err != nil {
log.Println("Could not set key binding:", err)
return
}
// Now let's define the views.
// The terminal's width and height are needed for layout calculations.
tw, th := g.Size()
// First, create the list view.
lv, err := g.SetView("list", 0, 0, lw, th-1)
// ErrUnknownView is not a real error condition.
// It just says that the view did not exist before and needs initialization.
if err != nil && err != c.ErrUnknownView {
log.Println("Failed to create main view:", err)
return
}
lv.Title = "List"
lv.FgColor = c.ColorCyan
// Then the output view.
ov, err := g.SetView("output", lw+1, 0, tw-1, th-ih-1)
if err != nil && err != c.ErrUnknownView {
log.Println("Failed to create output view:", err)
return
}
ov.Title = "Output"
ov.FgColor = c.ColorGreen
// Let the view scroll if the output exceeds the visible area.
ov.Autoscroll = true
_, err = fmt.Fprintln(ov, "Press Ctrl-c to quit")
if err != nil {
log.Println("Failed to print into output view:", err)
}
// And finally the input view.
iv, err := g.SetView("input", lw+1, th-ih, tw-1, th-1)
if err != nil && err != c.ErrUnknownView {
log.Println("Failed to create input view:", err)
return
}
iv.Title = "Input"
iv.FgColor = c.ColorYellow
// The input view shall be editable.
iv.Editable = true
err = iv.SetCursor(0, 0)
if err != nil {
log.Println("Failed to set cursor:", err)
return
}
// Make the enter key copy the input to the output.
err = g.SetKeybinding("input", c.KeyEnter, c.ModNone, func(g *c.Gui, iv *c.View) error {
// We want to read the view's buffer from the beginning.
iv.Rewind()
// Get the output view via its name.
ov, e := g.View("output")
if e != nil {
log.Println("Cannot get output view:", e)
return e
}
// Thanks to views being an io.Writer, we can simply Fprint to a view.
_, e = fmt.Fprint(ov, iv.Buffer())
if e != nil {
log.Println("Cannot print to output view:", e)
}
// Clear the input view
iv.Clear()
// Put the cursor back to the start.
e = iv.SetCursor(0, 0)
if e != nil {
log.Println("Failed to set cursor:", e)
}
return e
})
if err != nil {
log.Println("Cannot bind the enter key:", err)
}
// Fill the list view.
for _, s := range listItems {
// Again, we can simply Fprint to a view.
_, err = fmt.Fprintln(lv, s)
if err != nil {
log.Println("Error writing to the list view:", err)
return
}
}
// Set the focus to the input view.
_, err = g.SetCurrentView("input")
if err != nil {
log.Println("Cannot set focus to input view:", err)
}
// Start the main loop.
err = g.MainLoop()
log.Println("Main loop has finished:", err)
}
// The layout handler calculates all sizes depending
// on the current terminal size.
func layout(g *c.Gui) error {
// Get the current terminal size.
tw, th := g.Size()
// Update the views according to the new terminal size.
_, err := g.SetView("list", 0, 0, lw, th-1)
if err != nil {
return errors.Wrap(err, "Cannot update list view")
}
_, err = g.SetView("output", lw+1, 0, tw-1, th-ih-1)
if err != nil {
return errors.Wrap(err, "Cannot update output view")
}
_, err = g.SetView("input", lw+1, th-ih, tw-1, th-1)
if err != nil {
return errors.Wrap(err, "Cannot update input view.")
}
return nil
}
// `quit` is a handler that gets bound to Ctrl-C.
// It signals the main loop to exit.
func quit(g *c.Gui, v *c.View) error {
return c.ErrQuit
}
func main() {
if len(os.Args) <= 1 {
log.Println("Usage: go run tui.go [termui|gocui]")
return
}
if os.Args[1] == "termui" {
runTermui()
return
}
if os.Args[1] == "gocui" {
runGocui()
return
}
log.Println("No such option:", os.Args[0])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment