Skip to content

Latest commit

 

History

364 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Docs Linux CI Windows CI

Twin is a low-level library for drawing to the terminal screen: you own the widgets and layout, twin owns the terminal. Originally built for the moor pager, it's been battle-tested across Linux, macOS and Windows ever since.

Features

Demo

The moor pager was built using twin.

So is ftop:

ftop screenshot

Installation

go get github.com/walles/twin

Usage

Here's examples/hello, a complete, runnable program. Run it yourself, after cloning this repo:

go run ./examples/hello

hello example screenshot

// Command hello is a minimal, runnable twin demo. It draws some wide-character
// text over a diagonal color gradient, redrawing on resize, then waits for a
// keypress before exiting cleanly.
package main

import (
	"fmt"
	"os"

	"github.com/walles/twin"
)

func main() {
	screen, err := twin.NewScreen(twin.Options{})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	defer screen.Close()

	screen.SetProgress(twin.ProgressStateIndeterminate, 0)

	draw(screen)
	screen.Show()

	for event := range screen.Events() {
		switch event.(type) {
		case twin.EventExit, twin.EventKeyCode, twin.EventRune:
			return

		case twin.EventResize:
			draw(screen)
			screen.Show()
		}
	}
}

// draw renders the whole demo screen: the gradient background plus the
// greeting text on top of it. Called both up front and on every resize.
func draw(screen twin.Screen) {
	titleStyle := twin.StyleDefault.WithForeground(twin.NewColor24Bit(255, 230, 120)).WithAttr(twin.AttrBold)
	bodyStyle := twin.StyleDefault.WithForeground(twin.NewColor24Bit(230, 230, 230))

	url := "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
	linkStyle := bodyStyle.WithHyperlink(&url)

	drawText(screen, 2, 1, "Hello, 世界!", titleStyle)
	column := drawText(screen, 2, 2, "Drawn with ", bodyStyle)
	drawText(screen, column, 2, "github.com/walles/twin", linkStyle)
	drawText(screen, 2, 3, "Press any key to exit", bodyStyle)

	drawGradientBackground(screen)
}

// drawText writes text into screen starting at (column, row), advancing by each
// rune's actual on-screen width so wide characters don't overlap what follows
// them. Returns the column right after the text, for chaining
// differently-styled text on the same line.
func drawText(screen twin.Screen, column int, row int, text string, style twin.Style) int {
	for _, r := range text {
		width := screen.SetCell(column, row, twin.StyledRune{Rune: r, Style: style})
		column += width
	}
	return column
}

// drawGradientBackground paints every cell's background in a diagonal gradient
// from top-left to bottom-right. It runs after the text has already been drawn,
// and reads each cell back with GetCell() so it only changes the background,
// leaving that cell's rune and foreground color untouched.
func drawGradientBackground(screen twin.Screen) {
	topLeft := twin.NewColor24Bit(20, 20, 60)
	bottomRight := twin.NewColor24Bit(200, 70, 160)

	width, height := screen.Size()
	for row := range height {
		for column := range width {
			t := float64(column+row) / float64(width+height-2)

			cell := screen.GetCell(column, row)
			cell.Style = cell.Style.WithBackground(topLeft.Mix(bottomRight, t))
			screen.SetCell(column, row, cell)
		}
	}
}

Twin opens an alternate screen buffer that it draws into.

See the full API docs at pkg.go.dev/github.com/walles/twin.

Why not tcell?

Twin's API is similar to tcell's because twin started out as a from-scratch reimplementation of tcell for moor.

But the real case for twin isn't a longer feature list than tcell's, it's years of continuous, real-world use.

The trigger in 2021 was tcell's PollEvent(): it hands you one event at a time and blocks until the next one arrives, so moor had to redraw after every single event. On a trackpad fling-scroll that meant redrawing once per queued scroll tick, long after the user's finger had left the trackpad. Twin's Events() is a plain channel instead, so moor could drain everything queued up and redraw once, and scrolling immediately felt right.

Tcell later shipped its own answer to this: ChannelEvents(), so today either library can drain events non-blockingly before redrawing. But by then twin was already built, and it's been running moor — and later ftop — in production ever since.

Making a new release

  1. git tag --annotate vX.Y.Z, note the leading v in the version number. Write something descriptive in the annotation message.
  2. git push --tags

About

Terminal UI library

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages