A terminal UI library for Go
APACHE-2.0 License
Cops is a Go library for rendering terminal user interfaces.
Cops supports 24 bit color and pairs well with Go's color
, image
, and
image/draw
packages.
Cops models a terminal Display as an image with three layers:
Text *"github.com/kriskowal/cops/textile".Textile
Foreground *"image".RGBA
Background *"image".RGBA
The display package provides a display type that models these three layers.
Since the foreground and background layers are standard Go images,
we can use Go's draw
package and third-party image processing packages to
composite color layers.
bounds := image.Rect(0, 0, 80, 23)
disp := display.New(bounds)
Cops can draw displays with alpha transparency channels over lower display
layers.
Use the Draw
method to compose displays in layers.
display.Draw(
dst *display.Display,
r image.Rectangle,
src *display.Display,
sp image.Point,
op draw.Op, // draw.Over or draw.Src
)
Drawing will:
Cops defers the decision to render to 3, 4, 8, or 24 bit terminal color model
to the very last phase of rendering, so application authors are free to use the
gammut of any color model supported by Go, including third-party color models
like HUSLuv, or pluck from the terminal 256 color palette in display.Colors
.
The display package provides Render
and RenderOver
methods.
The render method produces a sequence of bytes to write that will update a
terminal, skipping over cells that have not changed.
Render(
buf []byte,
cur display.Cursor,
dis *display.Display,
model display.Model,
) (
buf []byte,
cur display.Cursor,
)
RenderOver(
buf []byte,
cur display.Cursor,
over, under *display.Display,
model display.Model,
) (
buf []byte,
cur display.Cursor,
)
The default display is blank and rendering it will cause no changes. Rendering a non-blank display over a blank display will effect a full display rewrite.
Render, like append
, accepts and returns a slice of bytes, prefering to reuse
the prior allocation, growing the allocation only when necessary.
Typical terminal applications swap a front and back display, drawing each frame over the previous.
var buf []byte
cur := display.Start
front, back := display.New2(bounds)
buf, cur = display.Render(buf, cur, front, back, display.Model24)
front, back = back, front
buf = buf[0:0]
Although the display models all colors in 32 bit RGBA, the color model samples these colors down to the terminal's supported color model.
Render accepts the current cursor state and returns the cursor state after
applying the rendered bytes to the terminal.
The display
package provides the cursor type, which has methods for updating
the cursor's position, foreground color, and background color. Each of these
methods append to a buffer and return the resulting cursor state.
The initial cursor state is unknown, assuming nothing about the cursor position or coloring.
cur := display.Start
The differential update will attempt to use relative cursor position changes whenever possible, resorting to changes relative to the beginning of the same line if it loses track of its horizontal position, or relative to the home or display origin, only when the cursor position is wholely unknown.
Partial-display or log-leading renders are possible by postulating that the current cursor position at the origin and drawing around it.
cur := display.Reset
The cursor has methods to produce the commands that will show and hide the cursor, clear the display, reset its state, seek to the origin, or move to another cell's coordinates.
var buf []byte
cur := display.Start
buf, cur = cur.Hide(buf)
buf, cur = cur.Clear(buf)
buf, cur = cur.Home(buf)
os.Stdout.Write(buf)
buf = buf[0:0]
cur, buf = cur.Go(buf, image.Pt(10, 20))
buf, cur = cur.Home(buf)
buf, cur = cur.Clear(buf)
buf, cur = cur.Show(buf)
os.Stdout.Write(buf)
buf = buf[0:0]
The display
package provides the terminal colors, palettes, terminal
rendering color models.
Colors
is an array of 256 palette colors.
Palette3
, Palette4
, and Palette8
are Go "image".Palette
instances forModel0
, Model3
, Model4
, Model8
, and Model24
are virtual terminal"display".Render
.Model0
is monochrome and does not render color. Model24
usesDisplays have a text image or "textile" of strings.
The textile
package implements a text image, modeled after Go's "image"
package.
text := textile.New(image.Rect(0, 0, 80, 23))
Just as with Go's images, the bounding box for the textile does not need to start at the origin, and subtexts share the same memory.
text.Subtext(image.Rect(1, 1, 79, 22)).Fill(".")
The default subtext is a matrix of nil strings. Nil strings are "transparent". Drawing a blank text over another textile effects no change.
Each cell of the textile may be a string of arbitrary length, so it is possible to model cells as sequences of UTF-8 including multiple code points with joiners.
The display render function is sensitive to the uncertainty whether these characters will necessarily be merged on the terminal display, invalidating the cursor's horizontal position after rendering each cell that contains more than one byte of text, then seeking to the next cell before rendering another.
The text
package provides a convenience for rendering plain text
onto a display.
The Bounds(string)
method returns a bounding rectangle by measuring
how much space the string would need.
The Write(*Display, Rectangle, string, Color)
method can then
write the string into a display.
front, back := display.New2(bounds)
msg := "Hello, Cops!"
msgbox := text.Bounds(msg)
center := rectangle.MiddleCenter(msgbox, bounds)
text.Write(front, center, msg, display.Colors[7])
The text package executes only the smallest subset of the terminal language, respecting "\n", "\t", and " ". Newline advances to the first column of the next line. Tab and space advance the cursor without drawing, leaving a transparent gap in the display.
Fill the bounds with " " or add a translucent or opaque background color to the display to occlude holes left by transparent space.
The terminal
package is a thin wrapper around terminal control, to make
terminal capability changes and restoration easy and idiomatic to Go.
term := terminal.New(os.Stdin.Fd())
defer term.Restore()
term.SetRaw()
term.SetNoEcho()
The Bounds()
method returns an "image".Rectangle
from the terminal size,
suitable for constructing a virtual display of the same size.
bounds := term.Bounds()
front, back := display.New2(bounds)
The bitmap
package provides a memory compact image type for images with only
two colors, as well as an image transformation layer that interprets another
image as a bitmap of the closer match of two colors.
The braille
package draws bitmaps as matrices of braille dots.
See cmd/braille/
for a demonstration.
panel := text.Display("Press any key to continue...", display.Colors[7])
draw.Draw(panel.Background, panel.Bounds(), &image.Uniform{color.NRGBA{63, 63, 63, 128}}, image.ZP, draw.Over)
The background color can be translucent. Drawing a display over another display:
display.Draw(front, panel.Bounds(), panel, image.ZP, draw.Over)
Copyright 2017 Kristopher Michael Kowal and contributors. Apache 2.0 license.