The Great Rewrite

This commit is contained in:
Caleb Bassi
2019-01-23 20:12:10 -08:00
parent b3075f7313
commit 958a28575d
95 changed files with 2626 additions and 4974 deletions

89
widgets/barchart.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"fmt"
"image"
rw "github.com/mattn/go-runewidth"
. "github.com/gizak/termui"
)
type BarChart struct {
Block
BarColors []Color
LabelStyles []Style
NumStyles []Style // only Fg and Modifier are used
NumFmt func(float64) string
Data []float64
Labels []string
BarWidth int
BarGap int
MaxVal float64
}
func NewBarChart() *BarChart {
return &BarChart{
Block: *NewBlock(),
BarColors: Theme.BarChart.Bars,
NumStyles: Theme.BarChart.Nums,
LabelStyles: Theme.BarChart.Labels,
NumFmt: func(n float64) string { return fmt.Sprint(n) },
BarGap: 1,
BarWidth: 3,
}
}
func (self *BarChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
if maxVal == 0 {
maxVal, _ = GetMaxFloat64FromSlice(self.Data)
}
barXCoordinate := self.Inner.Min.X
for i, data := range self.Data {
// draw bar
height := int((data / maxVal) * float64(self.Inner.Dy()-1))
for x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {
for y := self.Inner.Max.Y - 2; y > (self.Inner.Max.Y-2)-height; y-- {
c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, i)))
buf.SetCell(c, image.Pt(x, y))
}
}
// draw label
if i < len(self.Labels) {
labelXCoordinate := barXCoordinate +
int((float64(self.BarWidth) / 2)) -
int((float64(rw.StringWidth(self.Labels[i])) / 2))
buf.SetString(
self.Labels[i],
SelectStyle(self.LabelStyles, i),
image.Pt(labelXCoordinate, self.Inner.Max.Y-1),
)
}
// draw number
numberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2))
if numberXCoordinate <= self.Inner.Max.X {
buf.SetString(
self.NumFmt(data),
NewStyle(
SelectStyle(self.NumStyles, i+1).Fg,
SelectColor(self.BarColors, i),
SelectStyle(self.NumStyles, i+1).Modifier,
),
image.Pt(numberXCoordinate, self.Inner.Max.Y-2),
)
}
barXCoordinate += (self.BarWidth + self.BarGap)
}
}

57
widgets/gauge.go Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"fmt"
"image"
. "github.com/gizak/termui"
)
type Gauge struct {
Block
Percent int
BarColor Color
Label string
LabelStyle Style
}
func NewGauge() *Gauge {
return &Gauge{
Block: *NewBlock(),
BarColor: Theme.Gauge.Bar,
LabelStyle: Theme.Gauge.Label,
}
}
func (self *Gauge) Draw(buf *Buffer) {
self.Block.Draw(buf)
label := self.Label
if label == "" {
label = fmt.Sprintf("%d%%", self.Percent)
}
// plot bar
barWidth := int((float64(self.Percent) / 100) * float64(self.Inner.Dx()))
buf.Fill(
NewCell(' ', NewStyle(ColorClear, self.BarColor)),
image.Rect(self.Inner.Min.X, self.Inner.Min.Y, self.Inner.Min.X+barWidth, self.Inner.Max.Y),
)
// plot label
labelXCoordinate := self.Inner.Min.X + (self.Inner.Dx() / 2) - int(float64(len(label))/2)
labelYCoordinate := self.Inner.Min.Y + ((self.Inner.Dy() - 1) / 2)
if labelYCoordinate < self.Inner.Max.Y {
for i, char := range label {
style := self.LabelStyle
if labelXCoordinate+i+1 <= self.Inner.Min.X+barWidth {
style = NewStyle(self.BarColor, ColorClear, ModifierReverse)
}
buf.SetCell(NewCell(char, style), image.Pt(labelXCoordinate+i, labelYCoordinate))
}
}
}

181
widgets/linechart.go Normal file
View File

@@ -0,0 +1,181 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"fmt"
"image"
. "github.com/gizak/termui"
)
// LineChart has two modes: braille(default) and dot.
// A single braille character is a 2x4 grid of dots, so using braille
// gives 2x X resolution and 4x Y resolution over dot mode.
type LineChart struct {
Block
Data [][]float64
DataLabels []string
HorizontalScale int
LineType LineType
DotChar rune
LineColors []Color
AxesColor Color // TODO
MaxVal float64
ShowAxes bool
DrawDirection DrawDirection // TODO
}
const (
xAxisLabelsHeight = 1
yAxisLabelsWidth = 4
xAxisLabelsGap = 2
yAxisLabelsGap = 1
)
type LineType int
const (
BrailleLine LineType = iota
DotLine
)
type DrawDirection uint
const (
DrawLeft DrawDirection = iota
DrawRight
)
func NewLineChart() *LineChart {
return &LineChart{
Block: *NewBlock(),
LineColors: Theme.LineChart.Lines,
AxesColor: Theme.LineChart.Axes,
LineType: BrailleLine,
DotChar: DOT,
Data: [][]float64{},
HorizontalScale: 1,
DrawDirection: DrawRight,
ShowAxes: true,
}
}
func (self *LineChart) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
canvas := NewCanvas()
canvas.Rectangle = drawArea
for i, line := range self.Data {
previousHeight := int((line[1] / maxVal) * float64(drawArea.Dy()-1))
for j, val := range line[1:] {
height := int((val / maxVal) * float64(drawArea.Dy()-1))
canvas.Line(
image.Pt(
(drawArea.Min.X+(j*self.HorizontalScale))*2,
(drawArea.Max.Y-previousHeight-1)*4,
),
image.Pt(
(drawArea.Min.X+((j+1)*self.HorizontalScale))*2,
(drawArea.Max.Y-height-1)*4,
),
SelectColor(self.LineColors, i),
)
previousHeight = height
}
}
canvas.Draw(buf)
}
func (self *LineChart) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
for i, line := range self.Data {
for j := 0; j < len(line) && j*self.HorizontalScale < drawArea.Dx(); j++ {
val := line[j]
height := int((val / maxVal) * float64(drawArea.Dy()-1))
buf.SetCell(
NewCell(self.DotChar, NewStyle(SelectColor(self.LineColors, i))),
image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height),
)
}
}
}
func (self *LineChart) plotAxes(buf *Buffer, maxVal float64) {
// draw origin cell
buf.SetCell(
NewCell(BOTTOM_LEFT, NewStyle(ColorWhite)),
image.Pt(self.Inner.Min.X+yAxisLabelsWidth, self.Inner.Max.Y-xAxisLabelsHeight-1),
)
// draw x axis line
for i := yAxisLabelsWidth + 1; i < self.Inner.Dx(); i++ {
buf.SetCell(
NewCell(HORIZONTAL_DASH, NewStyle(ColorWhite)),
image.Pt(i+self.Inner.Min.X, self.Inner.Max.Y-xAxisLabelsHeight-1),
)
}
// draw y axis line
for i := 0; i < self.Inner.Dy()-xAxisLabelsHeight-1; i++ {
buf.SetCell(
NewCell(VERTICAL_DASH, NewStyle(ColorWhite)),
image.Pt(self.Inner.Min.X+yAxisLabelsWidth, i+self.Inner.Min.Y),
)
}
// draw x axis labels
// draw 0
buf.SetString(
"0",
NewStyle(ColorWhite),
image.Pt(self.Inner.Min.X+yAxisLabelsWidth, self.Inner.Max.Y-1),
)
// draw rest
for x := self.Inner.Min.X + yAxisLabelsWidth + (xAxisLabelsGap)*self.HorizontalScale + 1; x < self.Inner.Max.X-1; {
label := fmt.Sprintf(
"%d",
(x-(self.Inner.Min.X+yAxisLabelsWidth)-1)/(self.HorizontalScale)+1,
)
buf.SetString(
label,
NewStyle(ColorWhite),
image.Pt(x, self.Inner.Max.Y-1),
)
x += (len(label) + xAxisLabelsGap) * self.HorizontalScale
}
// draw y axis labels
verticalScale := maxVal / float64(self.Inner.Dy()-xAxisLabelsHeight-1)
for i := 0; i*(yAxisLabelsGap+1) < self.Inner.Dy()-1; i++ {
buf.SetString(
fmt.Sprintf("%.2f", float64(i)*verticalScale*(yAxisLabelsGap+1)),
NewStyle(ColorWhite),
image.Pt(self.Inner.Min.X, self.Inner.Max.Y-(i*(yAxisLabelsGap+1))-2),
)
}
}
func (self *LineChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
if maxVal == 0 {
maxVal, _ = GetMaxFloat64From2dSlice(self.Data)
}
if self.ShowAxes {
self.plotAxes(buf, maxVal)
}
drawArea := self.Inner
if self.ShowAxes {
drawArea = image.Rect(
self.Inner.Min.X+yAxisLabelsWidth+1, self.Inner.Min.Y,
self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,
)
}
if self.LineType == BrailleLine {
self.renderBraille(buf, drawArea, maxVal)
} else {
self.renderDot(buf, drawArea, maxVal)
}
}

52
widgets/list.go Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"image"
. "github.com/gizak/termui"
)
type List struct {
Block
Rows []string
Wrap bool
TextStyle Style
}
func NewList() *List {
return &List{
Block: *NewBlock(),
TextStyle: Theme.List.Text,
}
}
func (self *List) Draw(buf *Buffer) {
self.Block.Draw(buf)
point := self.Inner.Min
for row := 0; row < len(self.Rows) && point.Y < self.Inner.Max.Y; row++ {
cells := ParseText(self.Rows[row], self.TextStyle)
if self.Wrap {
cells = WrapCells(cells, uint(self.Inner.Dx()))
}
for j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {
if cells[j].Rune == '\n' {
point = image.Pt(self.Inner.Min.X, point.Y+1)
} else {
if point.X+1 == self.Inner.Max.X+1 && len(cells) > self.Inner.Dx() {
buf.SetCell(NewCell(DOTS, cells[j].Style), point.Add(image.Pt(-1, 0)))
break
} else {
buf.SetCell(cells[j], point)
point = point.Add(image.Pt(1, 0))
}
}
}
point = image.Pt(self.Inner.Min.X, point.Y+1)
}
}

40
widgets/paragraph.go Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"image"
. "github.com/gizak/termui"
)
type Paragraph struct {
Block
Text string
TextStyle Style
}
func NewParagraph() *Paragraph {
return &Paragraph{
Block: *NewBlock(),
TextStyle: Theme.Paragraph.Text,
}
}
func (self *Paragraph) Draw(buf *Buffer) {
self.Block.Draw(buf)
point := self.Inner.Min
cells := WrapCells(ParseText(self.Text, self.TextStyle), uint(self.Inner.Dx()))
for i := 0; i < len(cells) && point.Y < self.Inner.Max.Y; i++ {
if cells[i].Rune == '\n' {
point = image.Pt(self.Inner.Min.X, point.Y+1)
} else {
buf.SetCell(cells[i], point)
point = point.Add(image.Pt(1, 0))
}
}
}

149
widgets/piechart.go Normal file
View File

@@ -0,0 +1,149 @@
package widgets
import (
"image"
"math"
. "github.com/gizak/termui"
)
const (
piechartOffsetUp = -.5 * math.Pi // the northward angle
resolutionFactor = .0001 // circle resolution: precision vs. performance
fullCircle = 2.0 * math.Pi // the full circle angle
xStretch = 2.0 // horizontal adjustment
)
// PieChartLabel callback
type PieChartLabel func(dataIndex int, currentValue float64) string
type PieChart struct {
Block
Data []float64 // list of data items
Colors []Color // colors to by cycled through
Label PieChartLabel // callback function for labels
Offset float64 // which angle to start drawing at? (see piechartOffsetUp)
}
// NewPieChart Creates a new pie chart with reasonable defaults and no labels.
func NewPieChart() *PieChart {
return &PieChart{
Block: *NewBlock(),
Colors: Theme.PieChart.Slices,
Offset: piechartOffsetUp,
}
}
func (self *PieChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
center := self.Inner.Min.Add(self.Inner.Size().Div(2))
radius := MinFloat64(float64(self.Inner.Dx()/2/xStretch), float64(self.Inner.Dy()/2))
// compute slice sizes
sum := SumFloat64Slice(self.Data)
sliceSizes := make([]float64, len(self.Data))
for i, v := range self.Data {
sliceSizes[i] = v / sum * fullCircle
}
borderCircle := &circle{center, radius}
middleCircle := circle{Point: center, radius: radius / 2.0}
// draw sectors
phi := self.Offset
for i, size := range sliceSizes {
for j := 0.0; j < size; j += resolutionFactor {
borderPoint := borderCircle.at(phi + j)
line := line{P1: center, P2: borderPoint}
line.draw(NewCell(SHADED_BLOCK, NewStyle(SelectColor(self.Colors, i))), buf)
}
phi += size
}
// draw labels
if self.Label != nil {
phi = self.Offset
for i, size := range sliceSizes {
labelPoint := middleCircle.at(phi + size/2.0)
if len(self.Data) == 1 {
labelPoint = center
}
buf.SetString(
self.Label(i, self.Data[i]),
NewStyle(SelectColor(self.Colors, i)),
image.Pt(labelPoint.X, labelPoint.Y),
)
phi += size
}
}
}
type circle struct {
image.Point
radius float64
}
// computes the point at a given angle phi
func (self circle) at(phi float64) image.Point {
x := self.X + int(RoundFloat64(xStretch*self.radius*math.Cos(phi)))
y := self.Y + int(RoundFloat64(self.radius*math.Sin(phi)))
return image.Point{X: x, Y: y}
}
// computes the perimeter of a circle
func (self circle) perimeter() float64 {
return 2.0 * math.Pi * self.radius
}
// a line between two points
type line struct {
P1, P2 image.Point
}
// draws the line
func (self line) draw(cell Cell, buf *Buffer) {
isLeftOf := func(p1, p2 image.Point) bool {
return p1.X <= p2.X
}
isTopOf := func(p1, p2 image.Point) bool {
return p1.Y <= p2.Y
}
p1, p2 := self.P1, self.P2
buf.SetCell(NewCell('*', cell.Style), self.P2)
width, height := self.size()
if width > height { // paint left to right
if !isLeftOf(p1, p2) {
p1, p2 = p2, p1
}
flip := 1.0
if !isTopOf(p1, p2) {
flip = -1.0
}
for x := p1.X; x <= p2.X; x++ {
ratio := float64(height) / float64(width)
factor := float64(x - p1.X)
y := ratio * factor * flip
buf.SetCell(cell, image.Pt(x, int(RoundFloat64(y))+p1.Y))
}
} else { // paint top to bottom
if !isTopOf(p1, p2) {
p1, p2 = p2, p1
}
flip := 1.0
if !isLeftOf(p1, p2) {
flip = -1.0
}
for y := p1.Y; y <= p2.Y; y++ {
ratio := float64(width) / float64(height)
factor := float64(y - p1.Y)
x := ratio * factor * flip
buf.SetCell(cell, image.Pt(int(RoundFloat64(x))+p1.X, y))
}
}
}
// width and height of a line
func (self line) size() (w, h int) {
return AbsInt(self.P2.X - self.P1.X), AbsInt(self.P2.Y - self.P1.Y)
}

94
widgets/sparkline.go Normal file
View File

@@ -0,0 +1,94 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"image"
. "github.com/gizak/termui"
)
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
type Sparkline struct {
Data []float64
Title string
TitleStyle Style
LineColor Color
MaxVal float64
MaxHeight int // TODO
}
// SparklineGroup is a renderable widget which groups together the given sparklines.
type SparklineGroup struct {
Block
Sparklines []*Sparkline
}
// NewSparkline returns a unrenderable single sparkline that needs to be added to a SparklineGroup
func NewSparkline() *Sparkline {
return &Sparkline{
TitleStyle: Theme.Sparkline.Title,
LineColor: Theme.Sparkline.Line,
}
}
func NewSparklineGroup(sls ...*Sparkline) *SparklineGroup {
return &SparklineGroup{
Block: *NewBlock(),
Sparklines: sls,
}
}
func (self *SparklineGroup) Draw(buf *Buffer) {
self.Block.Draw(buf)
sparklineHeight := self.Inner.Dy() / len(self.Sparklines)
for i, sl := range self.Sparklines {
heightOffset := (sparklineHeight * (i + 1))
barHeight := sparklineHeight
if i == len(self.Sparklines)-1 {
heightOffset = self.Inner.Dy()
barHeight = self.Inner.Dy() - (sparklineHeight * i)
}
if sl.Title != "" {
barHeight--
}
maxVal := sl.MaxVal
if maxVal == 0 {
maxVal, _ = GetMaxFloat64FromSlice(sl.Data)
}
// draw line
for j := 0; j < len(sl.Data) && j < self.Inner.Dx(); j++ {
data := sl.Data[j]
height := int((data / maxVal) * float64(barHeight))
sparkChar := BARS[len(BARS)-1]
for k := 0; k < height; k++ {
buf.SetCell(
NewCell(sparkChar, NewStyle(sl.LineColor)),
image.Pt(j+self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset-k),
)
}
if height == 0 {
sparkChar = BARS[0]
buf.SetCell(
NewCell(sparkChar, NewStyle(sl.LineColor)),
image.Pt(j+self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset),
)
}
}
if sl.Title != "" {
// draw title
buf.SetString(
TrimString(sl.Title, self.Inner.Dx()),
sl.TitleStyle,
image.Pt(self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset-barHeight),
)
}
}
}

View File

@@ -0,0 +1,94 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"fmt"
"image"
rw "github.com/mattn/go-runewidth"
. "github.com/gizak/termui"
)
type StackedBarChart struct {
Block
BarColors []Color
LabelStyles []Style
NumStyles []Style // only Fg and Modifier are used
NumFmt func(float64) string
Data [][]float64
Labels []string
BarWidth int
BarGap int
MaxVal float64
}
func NewStackedBarChart() *StackedBarChart {
return &StackedBarChart{
Block: *NewBlock(),
BarColors: Theme.StackedBarChart.Bars,
LabelStyles: Theme.StackedBarChart.Labels,
NumStyles: Theme.StackedBarChart.Nums,
NumFmt: func(n float64) string { return fmt.Sprint(n) },
BarGap: 1,
BarWidth: 3,
}
}
func (self *StackedBarChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
if maxVal == 0 {
for _, data := range self.Data {
maxVal = MaxFloat64(maxVal, SumFloat64Slice(data))
}
}
barXCoordinate := self.Inner.Min.X
for i, bar := range self.Data {
// draw stacked bars
stackedBarYCoordinate := 0
for j, data := range bar {
// draw each stacked bar
height := int((data / maxVal) * float64(self.Inner.Dy()-1))
for x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {
for y := (self.Inner.Max.Y - 2) - stackedBarYCoordinate; y > (self.Inner.Max.Y-2)-stackedBarYCoordinate-height; y-- {
c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, j)))
buf.SetCell(c, image.Pt(x, y))
}
}
// draw number
numberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2)) - 1
buf.SetString(
self.NumFmt(data),
NewStyle(
SelectStyle(self.NumStyles, j+1).Fg,
SelectColor(self.BarColors, j),
SelectStyle(self.NumStyles, j+1).Modifier,
),
image.Pt(numberXCoordinate, (self.Inner.Max.Y-2)-stackedBarYCoordinate),
)
stackedBarYCoordinate += height
}
// draw label
labelXCoordinate := barXCoordinate + MaxInt(
int((float64(self.BarWidth)/2))-int((float64(rw.StringWidth(self.Labels[i]))/2)),
0,
)
buf.SetString(
TrimString(self.Labels[i], self.BarWidth),
SelectStyle(self.LabelStyles, i),
image.Pt(labelXCoordinate, self.Inner.Max.Y-1),
)
barXCoordinate += (self.BarWidth + self.BarGap)
}
}

106
widgets/table.go Normal file
View File

@@ -0,0 +1,106 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"image"
. "github.com/gizak/termui"
)
/* Table is like:
┌ Awesome Table ───────────────────────────────────────────────┐
│ Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
│──────────────────────────────────────────────────────────────│
│ Some Item #1 | AAA | 123 | CCCCC | EEEEE | GGGGG | IIIII |
│──────────────────────────────────────────────────────────────│
│ Some Item #2 | BBB | 456 | DDDDD | FFFFF | HHHHH | JJJJJ |
└──────────────────────────────────────────────────────────────┘
*/
type Table struct {
Block
Rows [][]string
ColumnWidths []int
TextStyle Style
RowSeparator bool
TextAlign Alignment
}
func NewTable() *Table {
return &Table{
Block: *NewBlock(),
TextStyle: Theme.Table.Text,
RowSeparator: true,
}
}
func (self *Table) Draw(buf *Buffer) {
self.Block.Draw(buf)
columnWidths := self.ColumnWidths
if len(columnWidths) == 0 {
columnCount := len(self.Rows[0])
colWidth := self.Inner.Dx() / columnCount
for i := 0; i < columnCount; i++ {
columnWidths = append(columnWidths, colWidth)
}
}
yCoordinate := self.Inner.Min.Y
// draw rows
for i := 0; i < len(self.Rows) && yCoordinate < self.Inner.Max.Y; i++ {
row := self.Rows[i]
colXCoordinate := self.Inner.Min.X
// draw row cells
for j := 0; j < len(row); j++ {
col := ParseText(row[j], self.TextStyle)
// draw row cell
if len(col) > columnWidths[j] || self.TextAlign == AlignLeft {
for k, cell := range col {
if k == columnWidths[j] || colXCoordinate+k == self.Inner.Max.X {
cell.Rune = DOTS
buf.SetCell(cell, image.Pt(colXCoordinate+k-1, yCoordinate))
break
} else {
buf.SetCell(cell, image.Pt(colXCoordinate+k, yCoordinate))
}
}
} else if self.TextAlign == AlignCenter {
xCoordinateOffset := (columnWidths[j] - len(col)) / 2
stringXCoordinate := xCoordinateOffset + colXCoordinate
for k, cell := range col {
buf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))
}
} else if self.TextAlign == AlignRight {
stringXCoordinate := MinInt(colXCoordinate+columnWidths[j], self.Inner.Max.X) - len(col)
for k, cell := range col {
buf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))
}
}
colXCoordinate += columnWidths[j] + 1
}
// draw vertical separators
separatorXCoordinate := self.Inner.Min.X
verticalCell := NewCell(VERTICAL_LINE, NewStyle(ColorWhite))
for _, width := range columnWidths {
separatorXCoordinate += width
buf.SetCell(verticalCell, image.Pt(separatorXCoordinate, yCoordinate))
separatorXCoordinate++
}
yCoordinate++
// draw horizontal separator
horizontalCell := NewCell(HORIZONTAL_LINE, NewStyle(ColorWhite))
if self.RowSeparator && yCoordinate < self.Inner.Max.Y && i != len(self.Rows)-1 {
buf.Fill(horizontalCell, image.Rect(self.Inner.Min.X, yCoordinate, self.Inner.Max.X, yCoordinate+1))
yCoordinate++
}
}
}

71
widgets/tabs.go Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package widgets
import (
"image"
. "github.com/gizak/termui"
)
// TabPane is a renderable widget which can be used to conditionally render certain tabs/views.
// TabPane shows a list of Tab names.
// The currently selected tab can be found through the `ActiveTabIndex` field.
type TabPane struct {
Block
TabNames []string
ActiveTabIndex uint
ActiveTabStyle Style
InactiveTabStyle Style
}
func NewTabPane(names ...string) *TabPane {
return &TabPane{
Block: *NewBlock(),
TabNames: names,
ActiveTabStyle: Theme.Tab.Active,
InactiveTabStyle: Theme.Tab.Inactive,
}
}
func (self *TabPane) FocusLeft() {
if self.ActiveTabIndex > 0 {
self.ActiveTabIndex--
}
}
func (self *TabPane) FocusRight() {
if self.ActiveTabIndex < uint(len(self.TabNames)-1) {
self.ActiveTabIndex++
}
}
func (self *TabPane) Draw(buf *Buffer) {
self.Block.Draw(buf)
xCoordinate := self.Inner.Min.X
for i, name := range self.TabNames {
ColorPair := self.InactiveTabStyle
if uint(i) == self.ActiveTabIndex {
ColorPair = self.ActiveTabStyle
}
buf.SetString(
TrimString(name, self.Inner.Max.X-xCoordinate),
ColorPair,
image.Pt(xCoordinate, self.Inner.Min.Y),
)
xCoordinate += 1 + len(name)
if i < len(self.TabNames)-1 && xCoordinate < self.Inner.Max.X {
buf.SetCell(
NewCell(VERTICAL_LINE, NewStyle(ColorWhite)),
image.Pt(xCoordinate, self.Inner.Min.Y),
)
}
xCoordinate += 2
}
}