Skip to content
Jonathan González Benavides edited this page Aug 5, 2021 · 15 revisions

Initialization

termui is initialized with:

	if err := ui.Init(); err != nil {
		log.Fatalf("failed to initialize termui: %v", err)
	}
	defer ui.Close()

which basically wraps the termbox-go Init() function and sets some sane defaults like 256 color support and mouse support. Close also wraps the termbox-go Close function. Initializing termui sets up the terminal to display widgets that you create and Close resets terminal settings to their normal state.

Widgets

There are several built in widgets, and custom widgets can also be created. To use a built-in widget, you simply give it data to render, optionally customize its rendering options, then pass it to ui.Render. Each widget has unique rendering options, but they all inherit from Block which provides addition common options for managing border, padding, and a title.

A common approach for using widgets is to create a struct that inherits from a renderable widget, and also implements an update method that updates the data to render.

Custom Widgets

To implement a custom widget, you can inherit from Block, and create a custom Draw(Buffer) method. The Draw(Buffer) method allows a widget to draw its contents onto a provided Buffer object. Buffers represent a rectangular region of a terminal, and contain Cells which represent individual terminal cells. Cells hold a character and a Style, and a Style contains foreground and background colors and any modifications like underline, etc. There are various constructor methods to use for each of these objects, e.g. NewCell().

Note: Block.Inner should be used to calculate the position of where to put Cells in the Buffer. e.g.: to draw an 'a' in the top left corner of the Buffer, you should do:

buf.SetCell(NewCell('a'), image.Pt(self.Inner.Min.X, self.Inner.Min.Y))

Layout

Widgets inherit from Block which provides methods for positioning and resizing widgets.

Absolute layout

To position a widget absolutely, call the SetRect(x0, y0, x1, y1) method which defines the area that the widget draws to.

Grid layout

To position widgets in a relative way, you can create a Grid which stores widgets in rows and columns that take up a percentage of the screen. The Grid sizes the widgets initially, and also resizes them when the terminal resizes.

Events

termui provides event handling for keypresses, mouse actions, and screen resizing. termui.PollEvents() returns a channel that converts and propogates events originating from termbox. Event types are detailed in events.go.

To update widget data on an interval, use Go's built in tickers.

Event loop example:

	uiEvents := ui.PollEvents()
	ticker := time.NewTicker(time.Second).C
	for {
		select {
		case e := <-uiEvents:
			switch e.ID { // event string/identifier
			case "q", "<C-c>": // press 'q' or 'C-c' to quit
				return
			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 {
			case ui.KeyboardEvent: // handle all key presses
				eventID = e.ID // keypress string
			}
		// use Go's built-in tickers for updating and drawing data
		case <-ticker:
			drawFunction()
		}
	}

Theming

Each widget has fields that determine what its colors should be. Widgets inhereit their colors from termui.Theme which holds the default colors for every widget. You can either set the colors of a widget after instantiating it, or change the default widget colors in termui.Theme for all new widgets to inherit from.

The 2 ways of representing color in termui are either with Color or Style. A Color is a regular terminal color from -1 to 255, with -1 being clear. A Style holds a foreground color, background color, and also an optional modifier, such as reverse, underline, or bold.

Rendering

Rendering widgets or a Grid works by calling the termui.Render(Drawable) function. Any struct that implements Drawable can be rendered. Drawable requires the GetRect and SetRect methods for widget sizing and the Draw(Buffer) method which renders the widget. GetRect and SetRect are implemented by Block so you don't have to worry about that if you inherit from it. Render works by creating a Buffer, filling it with empty Cells, passing the Buffer to the widget through its Draw method, and then iterating over the drawn Cells which are then passed to termbox-go. Grid's Draw method works by having each of its widgets Draw to the Buffer that it is given.