This commit is contained in:
gizak 2015-03-24 17:16:43 -04:00
parent b689689056
commit b8d37842ee
12 changed files with 197 additions and 24 deletions

16
bar.go
View File

@ -6,6 +6,20 @@ package termui
import "fmt" import "fmt"
// BarChart creates multiple bars in a widget:
/*
bc := termui.NewBarChart()
data := []int{3, 2, 5, 3, 9, 5}
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
bc.Border.Label = "Bar Chart"
bc.Data = data
bc.Width = 26
bc.Height = 10
bc.DataLabels = bclabels
bc.TextColor = termui.ColorGreen
bc.BarColor = termui.ColorRed
bc.NumColor = termui.ColorYellow
*/
type BarChart struct { type BarChart struct {
Block Block
BarColor Attribute BarColor Attribute
@ -22,6 +36,7 @@ type BarChart struct {
max int max int
} }
// NewBarChart returns a new *BarChart with current theme.
func NewBarChart() *BarChart { func NewBarChart() *BarChart {
bc := &BarChart{Block: *NewBlock()} bc := &BarChart{Block: *NewBlock()}
bc.BarColor = theme.BarChartBar bc.BarColor = theme.BarChartBar
@ -53,6 +68,7 @@ func (bc *BarChart) layout() {
bc.scale = float64(bc.max) / float64(bc.innerHeight-1) bc.scale = float64(bc.max) / float64(bc.innerHeight-1)
} }
// Buffer implements Bufferer interface.
func (bc *BarChart) Buffer() []Point { func (bc *BarChart) Buffer() []Point {
ps := bc.Block.Buffer() ps := bc.Block.Buffer()
bc.layout() bc.layout()

View File

@ -4,7 +4,9 @@
package termui package termui
// basic struct, consider it as css: display:block // Block is a base struct for all other upper level widgets,
// consider it as css: display:block.
// Normally you do not need to create it manually.
type Block struct { type Block struct {
X int X int
Y int Y int
@ -24,6 +26,7 @@ type Block struct {
PaddingRight int PaddingRight int
} }
// NewBlock returns a *Block which inherits styles from current theme.
func NewBlock() *Block { func NewBlock() *Block {
d := Block{} d := Block{}
d.IsDisplay = true d.IsDisplay = true
@ -52,11 +55,13 @@ func (d *Block) align() {
d.Border.Y = d.Y d.Border.Y = d.Y
d.Border.Width = d.Width d.Border.Width = d.Width
d.Border.Height = d.Height d.Border.Height = d.Height
d.innerX += 1 d.innerX++
d.innerY += 1 d.innerY++
} }
} }
// Buffer implements Bufferer interface.
// Draw background and border (if any).
func (d *Block) Buffer() []Point { func (d *Block) Buffer() []Point {
d.align() d.align()
@ -82,22 +87,23 @@ func (d *Block) Buffer() []Point {
return ps return ps
} }
// GetHeight implements GridBufferer.
// It returns current height of the block.
func (d Block) GetHeight() int { func (d Block) GetHeight() int {
return d.Height return d.Height
} }
func (d Block) GetWidth() int { // SetX implements GridBufferer interface, which sets block's x position.
return d.Width
}
func (d *Block) SetX(x int) { func (d *Block) SetX(x int) {
d.X = x d.X = x
} }
// SetY implements GridBufferer interface, it sets y position for block.
func (d *Block) SetY(y int) { func (d *Block) SetY(y int) {
d.Y = y d.Y = y
} }
// SetWidth implements GridBuffer interface, it sets block's width.
func (d *Block) SetWidth(w int) { func (d *Block) SetWidth(w int) {
d.Width = w d.Width = w
} }

4
box.go
View File

@ -29,6 +29,7 @@ type vline struct {
BgColor Attribute BgColor Attribute
} }
// Draw a horizontal line.
func (l hline) Buffer() []Point { func (l hline) Buffer() []Point {
pts := make([]Point, l.Length) pts := make([]Point, l.Length)
for i := 0; i < l.Length; i++ { for i := 0; i < l.Length; i++ {
@ -41,6 +42,7 @@ func (l hline) Buffer() []Point {
return pts return pts
} }
// Draw a vertical line.
func (l vline) Buffer() []Point { func (l vline) Buffer() []Point {
pts := make([]Point, l.Length) pts := make([]Point, l.Length)
for i := 0; i < l.Length; i++ { for i := 0; i < l.Length; i++ {
@ -53,6 +55,7 @@ func (l vline) Buffer() []Point {
return pts return pts
} }
// Draw a box border.
func (b border) Buffer() []Point { func (b border) Buffer() []Point {
if b.Width < 2 || b.Height < 2 { if b.Width < 2 || b.Height < 2 {
return nil return nil
@ -98,6 +101,7 @@ type labeledBorder struct {
LabelBgColor Attribute LabelBgColor Attribute
} }
// Draw a box border with label.
func (lb labeledBorder) Buffer() []Point { func (lb labeledBorder) Buffer() []Point {
ps := lb.border.Buffer() ps := lb.border.Buffer()
maxTxtW := lb.Width - 2 maxTxtW := lb.Width - 2

View File

@ -32,8 +32,18 @@ var braillePatterns = map[[2]int]rune{
var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'} var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'}
var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'} var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
//var singleBraille = [4]rune{'⣀', '⠤', '⠒', '⠉'} // LineChart has two modes: braille(default) and dot. Using braille gives 2x capicity as dot mode,
// because one braille char can represent two data points.
/*
lc := termui.NewLineChart()
lc.Border.Label = "braille-mode Line Chart"
lc.Data = [1.2, 1.3, 1.5, 1.7, 1.5, 1.6, 1.8, 2.0]
lc.Width = 50
lc.Height = 12
lc.AxesColor = termui.ColorWhite
lc.LineColor = termui.ColorGreen | termui.AttrBold
// termui.Render(lc)...
*/
type LineChart struct { type LineChart struct {
Block Block
Data []float64 Data []float64
@ -58,6 +68,7 @@ type LineChart struct {
minY float64 minY float64
} }
// NewLineChart returns a new LineChart with current theme.
func NewLineChart() *LineChart { func NewLineChart() *LineChart {
lc := &LineChart{Block: *NewBlock()} lc := &LineChart{Block: *NewBlock()}
lc.AxesColor = theme.LineChartAxes lc.AxesColor = theme.LineChartAxes
@ -298,6 +309,7 @@ func (lc *LineChart) plotAxes() []Point {
return ps return ps
} }
// Buffer implements Bufferer interface.
func (lc *LineChart) Buffer() []Point { func (lc *LineChart) Buffer() []Point {
ps := lc.Block.Buffer() ps := lc.Block.Buffer()
if lc.Data == nil || len(lc.Data) == 0 { if lc.Data == nil || len(lc.Data) == 0 {

27
doc.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
/*
Package termui is a library designed for creating command line UI. For more info, goto http://github.com/gizak/termui
A simplest example:
package main
import ui "github.com/gizak/termui"
func main() {
if err:=ui.Init(); err != nil {
panic(err)
}
defer ui.Close()
g := ui.NewGauge()
g.Percent = 50
g.Width = 50
g.Border.Label = "Gauge"
ui.Render(g)
}
*/
package termui

View File

@ -6,6 +6,17 @@ package termui
import "strconv" import "strconv"
// Gauge is a progress bar like widget.
// A simple example:
/*
g := termui.NewGauge()
g.Percent = 40
g.Width = 50
g.Height = 3
g.Border.Label = "Slim Gauge"
g.BarColor = termui.ColorRed
g.PercentColor = termui.ColorBlue
*/
type Gauge struct { type Gauge struct {
Block Block
Percent int Percent int
@ -13,6 +24,7 @@ type Gauge struct {
PercentColor Attribute PercentColor Attribute
} }
// NewGauge return a new gauge with current theme.
func NewGauge() *Gauge { func NewGauge() *Gauge {
g := &Gauge{ g := &Gauge{
Block: *NewBlock(), Block: *NewBlock(),
@ -23,6 +35,7 @@ func NewGauge() *Gauge {
return g return g
} }
// Buffer implements Bufferer interface.
func (g *Gauge) Buffer() []Point { func (g *Gauge) Buffer() []Point {
ps := g.Block.Buffer() ps := g.Block.Buffer()

60
grid.go
View File

@ -4,8 +4,8 @@
package termui package termui
// Bufferers that can be manipulated by Grid // GridBufferer introduces a Bufferer that can be manipulated by Grid.
type LayoutBufferer interface { type GridBufferer interface {
Bufferer Bufferer
GetHeight() int GetHeight() int
SetWidth(int) SetWidth(int)
@ -13,10 +13,10 @@ type LayoutBufferer interface {
SetY(int) SetY(int)
} }
// build a layout tree // row builds a layout tree
type row struct { type row struct {
Cols []*row //children Cols []*row //children
Widget LayoutBufferer // root Widget GridBufferer // root
X int X int
Y int Y int
Width int Width int
@ -25,6 +25,7 @@ type row struct {
Offset int Offset int
} }
// calculate and set the underlying layout tree's x, y, height and width.
func (r *row) calcLayout() { func (r *row) calcLayout() {
r.assignWidth(r.Width) r.assignWidth(r.Width)
r.Height = r.solveHeight() r.Height = r.solveHeight()
@ -32,6 +33,7 @@ func (r *row) calcLayout() {
r.assignY(r.Y) r.assignY(r.Y)
} }
// tell if the node is leaf in the tree.
func (r *row) isLeaf() bool { func (r *row) isLeaf() bool {
return r.Cols == nil || len(r.Cols) == 0 return r.Cols == nil || len(r.Cols) == 0
} }
@ -40,16 +42,18 @@ func (r *row) isRenderableLeaf() bool {
return r.isLeaf() && r.Widget != nil return r.isLeaf() && r.Widget != nil
} }
// assign widgets' (and their parent rows') width recursively.
func (r *row) assignWidth(w int) { func (r *row) assignWidth(w int) {
cw := int(float64(w*r.Span) / 12) cw := int(float64(w*r.Span) / 12)
r.SetWidth(cw) r.SetWidth(cw)
for i, _ := range r.Cols { for i := range r.Cols {
r.Cols[i].assignWidth(cw) r.Cols[i].assignWidth(cw)
} }
} }
// bottom up, return r's total height // bottom up calc and set rows' (and their widgets') height,
// return r's total height.
func (r *row) solveHeight() int { func (r *row) solveHeight() int {
if r.isRenderableLeaf() { if r.isRenderableLeaf() {
r.Height = r.Widget.GetHeight() r.Height = r.Widget.GetHeight()
@ -74,6 +78,7 @@ func (r *row) solveHeight() int {
return maxh return maxh
} }
// recursively assign x position for r tree.
func (r *row) assignX(x int) { func (r *row) assignX(x int) {
r.SetX(x) r.SetX(x)
@ -89,6 +94,7 @@ func (r *row) assignX(x int) {
} }
} }
// recursively assign y position to r.
func (r *row) assignY(y int) { func (r *row) assignY(y int) {
r.SetY(y) r.SetY(y)
@ -106,10 +112,12 @@ func (r *row) assignY(y int) {
} }
// GetHeight implements GridBufferer interface.
func (r row) GetHeight() int { func (r row) GetHeight() int {
return r.Height return r.Height
} }
// SetX implements GridBufferer interface.
func (r *row) SetX(x int) { func (r *row) SetX(x int) {
r.X = x r.X = x
if r.Widget != nil { if r.Widget != nil {
@ -117,6 +125,7 @@ func (r *row) SetX(x int) {
} }
} }
// SetY implements GridBufferer interface.
func (r *row) SetY(y int) { func (r *row) SetY(y int) {
r.Y = y r.Y = y
if r.Widget != nil { if r.Widget != nil {
@ -124,6 +133,7 @@ func (r *row) SetY(y int) {
} }
} }
// SetWidth implements GridBufferer interface.
func (r *row) SetWidth(w int) { func (r *row) SetWidth(w int) {
r.Width = w r.Width = w
if r.Widget != nil { if r.Widget != nil {
@ -131,6 +141,7 @@ func (r *row) SetWidth(w int) {
} }
} }
// Buffer implements Bufferer interface,
// recursively merge all widgets buffer // recursively merge all widgets buffer
func (r *row) Buffer() []Point { func (r *row) Buffer() []Point {
merged := []Point{} merged := []Point{}
@ -154,6 +165,27 @@ func (r *row) Buffer() []Point {
return merged return merged
} }
// Grid implements 12 columns system.
// A simple example:
/*
import ui "github.com/gizak/termui"
// init and create widgets...
// build
ui.Body.AddRows(
ui.NewRow(
ui.NewCol(6, 0, widget0),
ui.NewCol(6, 0, widget1)),
ui.NewRow(
ui.NewCol(3, 0, widget2),
ui.NewCol(3, 0, widget30, widget31, widget32),
ui.NewCol(6, 0, widget4)))
// calculate layout
ui.Body.Align()
ui.Render(ui.Body)
*/
type Grid struct { type Grid struct {
Rows []*row Rows []*row
Width int Width int
@ -162,23 +194,25 @@ type Grid struct {
BgColor Attribute BgColor Attribute
} }
// NewGrid returns *Grid with given rows.
func NewGrid(rows ...*row) *Grid { func NewGrid(rows ...*row) *Grid {
return &Grid{Rows: rows} return &Grid{Rows: rows}
} }
// AddRows appends given rows to Grid.
func (g *Grid) AddRows(rs ...*row) { func (g *Grid) AddRows(rs ...*row) {
g.Rows = append(g.Rows, rs...) g.Rows = append(g.Rows, rs...)
} }
// NewRow creates a new row out of given columns.
func NewRow(cols ...*row) *row { func NewRow(cols ...*row) *row {
rs := &row{Span: 12, Cols: cols} rs := &row{Span: 12, Cols: cols}
return rs return rs
} }
// NewCol accepts: widgets are LayoutBufferer or // NewCol accepts: widgets are LayoutBufferer or widgets is A NewRow.
// widgets is A NewRow // Note that if multiple widgets are provided, they will stack up in the col.
// Note that if multiple widgets are provided, they will stack up in the col func NewCol(span, offset int, widgets ...GridBufferer) *row {
func NewCol(span, offset int, widgets ...LayoutBufferer) *row {
r := &row{Span: span, Offset: offset} r := &row{Span: span, Offset: offset}
if widgets != nil && len(widgets) == 1 { if widgets != nil && len(widgets) == 1 {
@ -203,7 +237,7 @@ func NewCol(span, offset int, widgets ...LayoutBufferer) *row {
return r return r
} }
// Calculate each rows' layout // Align calculate each rows' layout.
func (g *Grid) Align() { func (g *Grid) Align() {
h := 0 h := 0
for _, r := range g.Rows { for _, r := range g.Rows {
@ -215,6 +249,7 @@ func (g *Grid) Align() {
} }
} }
// Buffer implments Bufferer interface.
func (g Grid) Buffer() []Point { func (g Grid) Buffer() []Point {
ps := []Point{} ps := []Point{}
for _, r := range g.Rows { for _, r := range g.Rows {
@ -223,4 +258,5 @@ func (g Grid) Buffer() []Point {
return ps return ps
} }
// Body corresponds to the entire terminal display region.
var Body *Grid var Body *Grid

25
list.go
View File

@ -6,6 +6,29 @@ package termui
import "strings" import "strings"
// List displays []string as its items,
// it has a Overflow option (default is "hidden"), when set to "hidden",
// the item exceeding List's width is truncated, but when set to "wrap",
// the overflowed text breaks into next line.
/*
strs := []string{
"[0] github.com/gizak/termui",
"[1] editbox.go",
"[2] iterrupt.go",
"[3] keyboard.go",
"[4] output.go",
"[5] random_out.go",
"[6] dashboard.go",
"[7] nsf/termbox-go"}
ls := termui.NewList()
ls.Items = strs
ls.ItemFgColor = termui.ColorYellow
ls.Border.Label = "List"
ls.Height = 7
ls.Width = 25
ls.Y = 0
*/
type List struct { type List struct {
Block Block
Items []string Items []string
@ -14,6 +37,7 @@ type List struct {
ItemBgColor Attribute ItemBgColor Attribute
} }
// NewList returns a new *List with current theme.
func NewList() *List { func NewList() *List {
l := &List{Block: *NewBlock()} l := &List{Block: *NewBlock()}
l.Overflow = "hidden" l.Overflow = "hidden"
@ -22,6 +46,7 @@ func NewList() *List {
return l return l
} }
// Buffer implements Bufferer interface.
func (l *List) Buffer() []Point { func (l *List) Buffer() []Point {
ps := l.Block.Buffer() ps := l.Block.Buffer()
switch l.Overflow { switch l.Overflow {

9
p.go
View File

@ -4,6 +4,13 @@
package termui package termui
// Par displays a paragraph.
/*
par := termui.NewPar("Simple Text")
par.Height = 3
par.Width = 17
par.Border.Label = "Label"
*/
type Par struct { type Par struct {
Block Block
Text string Text string
@ -11,6 +18,7 @@ type Par struct {
TextBgColor Attribute TextBgColor Attribute
} }
// NewPar returns a new *Par with given text as its content.
func NewPar(s string) *Par { func NewPar(s string) *Par {
return &Par{ return &Par{
Block: *NewBlock(), Block: *NewBlock(),
@ -19,6 +27,7 @@ func NewPar(s string) *Par {
TextBgColor: theme.ParTextBg} TextBgColor: theme.ParTextBg}
} }
// Buffer implements Bufferer interface.
func (p *Par) Buffer() []Point { func (p *Par) Buffer() []Point {
ps := p.Block.Buffer() ps := p.Block.Buffer()

View File

@ -4,6 +4,7 @@
package termui package termui
// Point stands for a single cell in terminal.
type Point struct { type Point struct {
Ch rune Ch rune
Bg Attribute Bg Attribute

View File

@ -6,11 +6,13 @@ package termui
import tm "github.com/nsf/termbox-go" import tm "github.com/nsf/termbox-go"
// all renderable components should implement this // Bufferer should be implemented by all renderable components.
type Bufferer interface { type Bufferer interface {
Buffer() []Point Buffer() []Point
} }
// Init initializes termui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function.
func Init() error { func Init() error {
Body = NewGrid() Body = NewGrid()
Body.X = 0 Body.X = 0
@ -23,21 +25,26 @@ func Init() error {
return tm.Init() return tm.Init()
} }
// Close finalizes termui library,
// should be called after successful initialization when termui's functionality isn't required anymore.
func Close() { func Close() {
tm.Close() tm.Close()
} }
// TermWidth returns the current terminal's width.
func TermWidth() int { func TermWidth() int {
w, _ := tm.Size() w, _ := tm.Size()
return w return w
} }
// TermHeight returns the current terminal's height.
func TermHeight() int { func TermHeight() int {
_, h := tm.Size() _, h := tm.Size()
return h return h
} }
// render all from left to right, right could overlap on left ones // Render renders all Bufferer in the given order from left to right,
// right could overlap on left ones.
func Render(rs ...Bufferer) { func Render(rs ...Bufferer) {
tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg)) tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg))
for _, r := range rs { for _, r := range rs {

View File

@ -6,6 +6,14 @@ package termui
import "math" import "math"
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃
/*
data := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1}
spl := termui.NewSparkline()
spl.Data = data
spl.Title = "Sparkline 0"
spl.LineColor = termui.ColorGreen
*/
type Sparkline struct { type Sparkline struct {
Data []int Data []int
Height int Height int
@ -17,6 +25,12 @@ type Sparkline struct {
max int max int
} }
// Sparklines is a renderable widget which groups together the given sparklines.
/*
spls := termui.NewSparklines(spl0,spl1,spl2) //...
spls.Height = 2
spls.Width = 20
*/
type Sparklines struct { type Sparklines struct {
Block Block
Lines []Sparkline Lines []Sparkline
@ -26,11 +40,12 @@ type Sparklines struct {
var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'} var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
// Add appends a given Sparkline to s *Sparklines.
func (s *Sparklines) Add(sl Sparkline) { func (s *Sparklines) Add(sl Sparkline) {
s.Lines = append(s.Lines, sl) s.Lines = append(s.Lines, sl)
} }
// return unrenderable single sparkline, need to add it into Sparklines // NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines.
func NewSparkline() Sparkline { func NewSparkline() Sparkline {
return Sparkline{ return Sparkline{
Height: 1, Height: 1,
@ -38,6 +53,7 @@ func NewSparkline() Sparkline {
LineColor: theme.SparklineLine} LineColor: theme.SparklineLine}
} }
// NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later.
func NewSparklines(ss ...Sparkline) *Sparklines { func NewSparklines(ss ...Sparkline) *Sparklines {
s := &Sparklines{Block: *NewBlock(), Lines: ss} s := &Sparklines{Block: *NewBlock(), Lines: ss}
return s return s
@ -79,6 +95,7 @@ func (sl *Sparklines) update() {
} }
} }
// Buffer implements Bufferer interface.
func (sl *Sparklines) Buffer() []Point { func (sl *Sparklines) Buffer() []Point {
ps := sl.Block.Buffer() ps := sl.Block.Buffer()
sl.update() sl.update()