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:
- Overwrite the text layer for all non-empty text cells inside the rectangle. Fill the text with space " " to overdraw all cells.
- Draw the foreground of the source over the foreground of the destination image. Typically, the foreground is transparent for all cells empty of text. Otherwise, this operation can have interesting results.
- Draw the background of the source over the foreground of the destination image. This allows for translucent background colors on the source image partially obscuring the text of the destination image.
- Draw the background of the source over the background of the destination image.
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.- The first 8 are the 3-bit color palette.
- The second 8 (8-15) are their bright versions from the 4-bit color palette.
- The remaining colors form the 6x6x6 color cube and 24 grays scale.
Palette3
,Palette4
, andPalette8
are Go"image".Palette
instances for colors expressible in those ranges.Model0
,Model3
,Model4
,Model8
, andModel24
are virtual terminal color depth models with methods for rendering background and foreground colors to ANSI escape sequences, as used by"display".Render
.Model0
is monochrome and does not render color.Model24
uses paletted colors only for exact matches.
Displays 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:
- overrides the foreground color for all cells with text, except empty and space cells.
- draws the panel's background color over the foreground and background color of the underlying cell. This makes it possible to render translucent panels. The underlying text shows through, but faded by the overlying background color.
display.Draw(front, panel.Bounds(), panel, image.ZP, draw.Over)
Copyright 2017 Kristopher Michael Kowal and contributors. Apache 2.0 license.