Move source files into v3 directory
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
type BarChart struct {
|
||||
Block
|
||||
BarColors []Color
|
||||
LabelStyles []Style
|
||||
NumStyles []Style // only Fg and Modifier are used
|
||||
NumFormatter 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,
|
||||
NumFormatter: 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.NumFormatter(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)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
204
widgets/image.go
204
widgets/image.go
@@ -1,204 +0,0 @@
|
||||
// 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"
|
||||
"image/color"
|
||||
|
||||
. "github.com/gizak/termui/v3"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
Block
|
||||
Image image.Image
|
||||
Monochrome bool
|
||||
MonochromeThreshold uint8
|
||||
MonochromeInvert bool
|
||||
}
|
||||
|
||||
func NewImage(img image.Image) *Image {
|
||||
return &Image{
|
||||
Block: *NewBlock(),
|
||||
MonochromeThreshold: 128,
|
||||
Image: img,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Image) Draw(buf *Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
if self.Image == nil {
|
||||
return
|
||||
}
|
||||
|
||||
bufWidth := self.Inner.Dx()
|
||||
bufHeight := self.Inner.Dy()
|
||||
imageWidth := self.Image.Bounds().Dx()
|
||||
imageHeight := self.Image.Bounds().Dy()
|
||||
|
||||
if self.Monochrome {
|
||||
if bufWidth > imageWidth/2 {
|
||||
bufWidth = imageWidth / 2
|
||||
}
|
||||
if bufHeight > imageHeight/2 {
|
||||
bufHeight = imageHeight / 2
|
||||
}
|
||||
for bx := 0; bx < bufWidth; bx++ {
|
||||
for by := 0; by < bufHeight; by++ {
|
||||
ul := self.colorAverage(
|
||||
2*bx*imageWidth/bufWidth/2,
|
||||
(2*bx+1)*imageWidth/bufWidth/2,
|
||||
2*by*imageHeight/bufHeight/2,
|
||||
(2*by+1)*imageHeight/bufHeight/2,
|
||||
)
|
||||
ur := self.colorAverage(
|
||||
(2*bx+1)*imageWidth/bufWidth/2,
|
||||
(2*bx+2)*imageWidth/bufWidth/2,
|
||||
2*by*imageHeight/bufHeight/2,
|
||||
(2*by+1)*imageHeight/bufHeight/2,
|
||||
)
|
||||
ll := self.colorAverage(
|
||||
2*bx*imageWidth/bufWidth/2,
|
||||
(2*bx+1)*imageWidth/bufWidth/2,
|
||||
(2*by+1)*imageHeight/bufHeight/2,
|
||||
(2*by+2)*imageHeight/bufHeight/2,
|
||||
)
|
||||
lr := self.colorAverage(
|
||||
(2*bx+1)*imageWidth/bufWidth/2,
|
||||
(2*bx+2)*imageWidth/bufWidth/2,
|
||||
(2*by+1)*imageHeight/bufHeight/2,
|
||||
(2*by+2)*imageHeight/bufHeight/2,
|
||||
)
|
||||
buf.SetCell(
|
||||
NewCell(blocksChar(ul, ur, ll, lr, self.MonochromeThreshold, self.MonochromeInvert)),
|
||||
image.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if bufWidth > imageWidth {
|
||||
bufWidth = imageWidth
|
||||
}
|
||||
if bufHeight > imageHeight {
|
||||
bufHeight = imageHeight
|
||||
}
|
||||
for bx := 0; bx < bufWidth; bx++ {
|
||||
for by := 0; by < bufHeight; by++ {
|
||||
c := self.colorAverage(
|
||||
bx*imageWidth/bufWidth,
|
||||
(bx+1)*imageWidth/bufWidth,
|
||||
by*imageHeight/bufHeight,
|
||||
(by+1)*imageHeight/bufHeight,
|
||||
)
|
||||
buf.SetCell(
|
||||
NewCell(c.ch(), NewStyle(c.fgColor(), ColorBlack)),
|
||||
image.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Image) colorAverage(x0, x1, y0, y1 int) colorAverager {
|
||||
var c colorAverager
|
||||
for x := x0; x < x1; x++ {
|
||||
for y := y0; y < y1; y++ {
|
||||
c = c.add(
|
||||
self.Image.At(
|
||||
x+self.Image.Bounds().Min.X,
|
||||
y+self.Image.Bounds().Min.Y,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type colorAverager struct {
|
||||
rsum, gsum, bsum, asum, count uint64
|
||||
}
|
||||
|
||||
func (self colorAverager) add(col color.Color) colorAverager {
|
||||
r, g, b, a := col.RGBA()
|
||||
return colorAverager{
|
||||
rsum: self.rsum + uint64(r),
|
||||
gsum: self.gsum + uint64(g),
|
||||
bsum: self.bsum + uint64(b),
|
||||
asum: self.asum + uint64(a),
|
||||
count: self.count + 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (self colorAverager) RGBA() (uint32, uint32, uint32, uint32) {
|
||||
if self.count == 0 {
|
||||
return 0, 0, 0, 0
|
||||
}
|
||||
return uint32(self.rsum/self.count) & 0xffff,
|
||||
uint32(self.gsum/self.count) & 0xffff,
|
||||
uint32(self.bsum/self.count) & 0xffff,
|
||||
uint32(self.asum/self.count) & 0xffff
|
||||
}
|
||||
|
||||
func (self colorAverager) fgColor() Color {
|
||||
return palette.Convert(self).(paletteColor).attribute
|
||||
}
|
||||
|
||||
func (self colorAverager) ch() rune {
|
||||
gray := color.GrayModel.Convert(self).(color.Gray).Y
|
||||
switch {
|
||||
case gray < 51:
|
||||
return SHADED_BLOCKS[0]
|
||||
case gray < 102:
|
||||
return SHADED_BLOCKS[1]
|
||||
case gray < 153:
|
||||
return SHADED_BLOCKS[2]
|
||||
case gray < 204:
|
||||
return SHADED_BLOCKS[3]
|
||||
default:
|
||||
return SHADED_BLOCKS[4]
|
||||
}
|
||||
}
|
||||
|
||||
func (self colorAverager) monochrome(threshold uint8, invert bool) bool {
|
||||
return self.count != 0 && (color.GrayModel.Convert(self).(color.Gray).Y < threshold != invert)
|
||||
}
|
||||
|
||||
type paletteColor struct {
|
||||
rgba color.RGBA
|
||||
attribute Color
|
||||
}
|
||||
|
||||
func (self paletteColor) RGBA() (uint32, uint32, uint32, uint32) {
|
||||
return self.rgba.RGBA()
|
||||
}
|
||||
|
||||
var palette = color.Palette([]color.Color{
|
||||
paletteColor{color.RGBA{0, 0, 0, 255}, ColorBlack},
|
||||
paletteColor{color.RGBA{255, 0, 0, 255}, ColorRed},
|
||||
paletteColor{color.RGBA{0, 255, 0, 255}, ColorGreen},
|
||||
paletteColor{color.RGBA{255, 255, 0, 255}, ColorYellow},
|
||||
paletteColor{color.RGBA{0, 0, 255, 255}, ColorBlue},
|
||||
paletteColor{color.RGBA{255, 0, 255, 255}, ColorMagenta},
|
||||
paletteColor{color.RGBA{0, 255, 255, 255}, ColorCyan},
|
||||
paletteColor{color.RGBA{255, 255, 255, 255}, ColorWhite},
|
||||
})
|
||||
|
||||
func blocksChar(ul, ur, ll, lr colorAverager, threshold uint8, invert bool) rune {
|
||||
index := 0
|
||||
if ul.monochrome(threshold, invert) {
|
||||
index |= 1
|
||||
}
|
||||
if ur.monochrome(threshold, invert) {
|
||||
index |= 2
|
||||
}
|
||||
if ll.monochrome(threshold, invert) {
|
||||
index |= 4
|
||||
}
|
||||
if lr.monochrome(threshold, invert) {
|
||||
index |= 8
|
||||
}
|
||||
return IRREGULAR_BLOCKS[index]
|
||||
}
|
||||
136
widgets/list.go
136
widgets/list.go
@@ -1,136 +0,0 @@
|
||||
// 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"
|
||||
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
|
||||
. "github.com/gizak/termui/v3"
|
||||
)
|
||||
|
||||
type List struct {
|
||||
Block
|
||||
Rows []string
|
||||
WrapText bool
|
||||
TextStyle Style
|
||||
SelectedRow int
|
||||
topRow int
|
||||
SelectedRowStyle Style
|
||||
}
|
||||
|
||||
func NewList() *List {
|
||||
return &List{
|
||||
Block: *NewBlock(),
|
||||
TextStyle: Theme.List.Text,
|
||||
SelectedRowStyle: Theme.List.Text,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *List) Draw(buf *Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
point := self.Inner.Min
|
||||
|
||||
// adjusts view into widget
|
||||
if self.SelectedRow >= self.Inner.Dy()+self.topRow {
|
||||
self.topRow = self.SelectedRow - self.Inner.Dy() + 1
|
||||
} else if self.SelectedRow < self.topRow {
|
||||
self.topRow = self.SelectedRow
|
||||
}
|
||||
|
||||
// draw rows
|
||||
for row := self.topRow; row < len(self.Rows) && point.Y < self.Inner.Max.Y; row++ {
|
||||
cells := ParseStyles(self.Rows[row], self.TextStyle)
|
||||
if self.WrapText {
|
||||
cells = WrapCells(cells, uint(self.Inner.Dx()))
|
||||
}
|
||||
for j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {
|
||||
style := cells[j].Style
|
||||
if row == self.SelectedRow {
|
||||
style = self.SelectedRowStyle
|
||||
}
|
||||
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(ELLIPSES, style), point.Add(image.Pt(-1, 0)))
|
||||
break
|
||||
} else {
|
||||
buf.SetCell(NewCell(cells[j].Rune, style), point)
|
||||
point = point.Add(image.Pt(rw.RuneWidth(cells[j].Rune), 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
point = image.Pt(self.Inner.Min.X, point.Y+1)
|
||||
}
|
||||
|
||||
// draw UP_ARROW if needed
|
||||
if self.topRow > 0 {
|
||||
buf.SetCell(
|
||||
NewCell(UP_ARROW, NewStyle(ColorWhite)),
|
||||
image.Pt(self.Inner.Max.X-1, self.Inner.Min.Y),
|
||||
)
|
||||
}
|
||||
|
||||
// draw DOWN_ARROW if needed
|
||||
if len(self.Rows) > int(self.topRow)+self.Inner.Dy() {
|
||||
buf.SetCell(
|
||||
NewCell(DOWN_ARROW, NewStyle(ColorWhite)),
|
||||
image.Pt(self.Inner.Max.X-1, self.Inner.Max.Y-1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollAmount scrolls by amount given. If amount is < 0, then scroll up.
|
||||
// There is no need to set self.topRow, as this will be set automatically when drawn,
|
||||
// since if the selected item is off screen then the topRow variable will change accordingly.
|
||||
func (self *List) ScrollAmount(amount int) {
|
||||
if len(self.Rows)-int(self.SelectedRow) <= amount {
|
||||
self.SelectedRow = len(self.Rows) - 1
|
||||
} else if int(self.SelectedRow)+amount < 0 {
|
||||
self.SelectedRow = 0
|
||||
} else {
|
||||
self.SelectedRow += amount
|
||||
}
|
||||
}
|
||||
|
||||
func (self *List) ScrollUp() {
|
||||
self.ScrollAmount(-1)
|
||||
}
|
||||
|
||||
func (self *List) ScrollDown() {
|
||||
self.ScrollAmount(1)
|
||||
}
|
||||
|
||||
func (self *List) ScrollPageUp() {
|
||||
// If an item is selected below top row, then go to the top row.
|
||||
if self.SelectedRow > self.topRow {
|
||||
self.SelectedRow = self.topRow
|
||||
} else {
|
||||
self.ScrollAmount(-self.Inner.Dy())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *List) ScrollPageDown() {
|
||||
self.ScrollAmount(self.Inner.Dy())
|
||||
}
|
||||
|
||||
func (self *List) ScrollHalfPageUp() {
|
||||
self.ScrollAmount(-int(FloorFloat64(float64(self.Inner.Dy()) / 2)))
|
||||
}
|
||||
|
||||
func (self *List) ScrollHalfPageDown() {
|
||||
self.ScrollAmount(int(FloorFloat64(float64(self.Inner.Dy()) / 2)))
|
||||
}
|
||||
|
||||
func (self *List) ScrollTop() {
|
||||
self.SelectedRow = 0
|
||||
}
|
||||
|
||||
func (self *List) ScrollBottom() {
|
||||
self.SelectedRow = len(self.Rows) - 1
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
type Paragraph struct {
|
||||
Block
|
||||
Text string
|
||||
TextStyle Style
|
||||
WrapText bool
|
||||
}
|
||||
|
||||
func NewParagraph() *Paragraph {
|
||||
return &Paragraph{
|
||||
Block: *NewBlock(),
|
||||
TextStyle: Theme.Paragraph.Text,
|
||||
WrapText: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Paragraph) Draw(buf *Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
cells := ParseStyles(self.Text, self.TextStyle)
|
||||
if self.WrapText {
|
||||
cells = WrapCells(cells, uint(self.Inner.Dx()))
|
||||
}
|
||||
|
||||
rows := SplitCells(cells, '\n')
|
||||
|
||||
for y, row := range rows {
|
||||
if y+self.Inner.Min.Y >= self.Inner.Max.Y {
|
||||
break
|
||||
}
|
||||
row = TrimCells(row, self.Inner.Dx())
|
||||
for _, cx := range BuildCellWithXArray(row) {
|
||||
x, cell := cx.X, cx.Cell
|
||||
buf.SetCell(cell, image.Pt(x, y).Add(self.Inner.Min))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
. "github.com/gizak/termui/v3"
|
||||
)
|
||||
|
||||
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
|
||||
LabelFormatter PieChartLabel // callback function for labels
|
||||
AngleOffset 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,
|
||||
AngleOffset: 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.AngleOffset
|
||||
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_BLOCKS[1], NewStyle(SelectColor(self.Colors, i))), buf)
|
||||
}
|
||||
phi += size
|
||||
}
|
||||
|
||||
// draw labels
|
||||
if self.LabelFormatter != nil {
|
||||
phi = self.AngleOffset
|
||||
for i, size := range sliceSizes {
|
||||
labelPoint := middleCircle.at(phi + size/2.0)
|
||||
if len(self.Data) == 1 {
|
||||
labelPoint = center
|
||||
}
|
||||
buf.SetString(
|
||||
self.LabelFormatter(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)
|
||||
}
|
||||
227
widgets/plot.go
227
widgets/plot.go
@@ -1,227 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
// Plot has two modes: line(default) and scatter.
|
||||
// Plot also has two marker types: 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 Plot struct {
|
||||
Block
|
||||
|
||||
Data [][]float64
|
||||
DataLabels []string
|
||||
MaxVal float64
|
||||
|
||||
LineColors []Color
|
||||
AxesColor Color // TODO
|
||||
ShowAxes bool
|
||||
|
||||
Marker PlotMarker
|
||||
DotMarkerRune rune
|
||||
PlotType PlotType
|
||||
HorizontalScale int
|
||||
DrawDirection DrawDirection // TODO
|
||||
}
|
||||
|
||||
const (
|
||||
xAxisLabelsHeight = 1
|
||||
yAxisLabelsWidth = 4
|
||||
xAxisLabelsGap = 2
|
||||
yAxisLabelsGap = 1
|
||||
)
|
||||
|
||||
type PlotType uint
|
||||
|
||||
const (
|
||||
LineChart PlotType = iota
|
||||
ScatterPlot
|
||||
)
|
||||
|
||||
type PlotMarker uint
|
||||
|
||||
const (
|
||||
MarkerBraille PlotMarker = iota
|
||||
MarkerDot
|
||||
)
|
||||
|
||||
type DrawDirection uint
|
||||
|
||||
const (
|
||||
DrawLeft DrawDirection = iota
|
||||
DrawRight
|
||||
)
|
||||
|
||||
func NewPlot() *Plot {
|
||||
return &Plot{
|
||||
Block: *NewBlock(),
|
||||
LineColors: Theme.Plot.Lines,
|
||||
AxesColor: Theme.Plot.Axes,
|
||||
Marker: MarkerBraille,
|
||||
DotMarkerRune: DOT,
|
||||
Data: [][]float64{},
|
||||
HorizontalScale: 1,
|
||||
DrawDirection: DrawRight,
|
||||
ShowAxes: true,
|
||||
PlotType: LineChart,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
|
||||
canvas := NewCanvas()
|
||||
canvas.Rectangle = drawArea
|
||||
|
||||
switch self.PlotType {
|
||||
case ScatterPlot:
|
||||
for i, line := range self.Data {
|
||||
for j, val := range line {
|
||||
height := int((val / maxVal) * float64(drawArea.Dy()-1))
|
||||
canvas.SetPoint(
|
||||
image.Pt(
|
||||
(drawArea.Min.X+(j*self.HorizontalScale))*2,
|
||||
(drawArea.Max.Y-height-1)*4,
|
||||
),
|
||||
SelectColor(self.LineColors, i),
|
||||
)
|
||||
}
|
||||
}
|
||||
case LineChart:
|
||||
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.SetLine(
|
||||
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 *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
|
||||
switch self.PlotType {
|
||||
case ScatterPlot:
|
||||
for i, line := range self.Data {
|
||||
for j, val := range line {
|
||||
height := int((val / maxVal) * float64(drawArea.Dy()-1))
|
||||
point := image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height)
|
||||
if point.In(drawArea) {
|
||||
buf.SetCell(
|
||||
NewCell(self.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))),
|
||||
point,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
case LineChart:
|
||||
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.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))),
|
||||
image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Plot) 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 *Plot) 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,
|
||||
)
|
||||
}
|
||||
|
||||
switch self.Marker {
|
||||
case MarkerBraille:
|
||||
self.renderBraille(buf, drawArea, maxVal)
|
||||
case MarkerDot:
|
||||
self.renderDot(buf, drawArea, maxVal)
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
// 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[1]
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
type StackedBarChart struct {
|
||||
Block
|
||||
BarColors []Color
|
||||
LabelStyles []Style
|
||||
NumStyles []Style // only Fg and Modifier are used
|
||||
NumFormatter 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,
|
||||
NumFormatter: 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.NumFormatter(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
|
||||
if i < len(self.Labels) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
136
widgets/table.go
136
widgets/table.go
@@ -1,136 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
/*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
|
||||
TextAlignment Alignment
|
||||
RowStyles map[int]Style
|
||||
FillRow bool
|
||||
|
||||
// ColumnResizer is called on each Draw. Can be used for custom column sizing.
|
||||
ColumnResizer func()
|
||||
}
|
||||
|
||||
func NewTable() *Table {
|
||||
return &Table{
|
||||
Block: *NewBlock(),
|
||||
TextStyle: Theme.Table.Text,
|
||||
RowSeparator: true,
|
||||
RowStyles: make(map[int]Style),
|
||||
ColumnResizer: func() {},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Table) Draw(buf *Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
self.ColumnResizer()
|
||||
|
||||
columnWidths := self.ColumnWidths
|
||||
if len(columnWidths) == 0 {
|
||||
columnCount := len(self.Rows[0])
|
||||
columnWidth := self.Inner.Dx() / columnCount
|
||||
for i := 0; i < columnCount; i++ {
|
||||
columnWidths = append(columnWidths, columnWidth)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
rowStyle := self.TextStyle
|
||||
// get the row style if one exists
|
||||
if style, ok := self.RowStyles[i]; ok {
|
||||
rowStyle = style
|
||||
}
|
||||
|
||||
if self.FillRow {
|
||||
blankCell := NewCell(' ', rowStyle)
|
||||
buf.Fill(blankCell, image.Rect(self.Inner.Min.X, yCoordinate, self.Inner.Max.X, yCoordinate+1))
|
||||
}
|
||||
|
||||
// draw row cells
|
||||
for j := 0; j < len(row); j++ {
|
||||
col := ParseStyles(row[j], rowStyle)
|
||||
// draw row cell
|
||||
if len(col) > columnWidths[j] || self.TextAlignment == AlignLeft {
|
||||
for _, cx := range BuildCellWithXArray(col) {
|
||||
k, cell := cx.X, cx.Cell
|
||||
if k == columnWidths[j] || colXCoordinate+k == self.Inner.Max.X {
|
||||
cell.Rune = ELLIPSES
|
||||
buf.SetCell(cell, image.Pt(colXCoordinate+k-1, yCoordinate))
|
||||
break
|
||||
} else {
|
||||
buf.SetCell(cell, image.Pt(colXCoordinate+k, yCoordinate))
|
||||
}
|
||||
}
|
||||
} else if self.TextAlignment == AlignCenter {
|
||||
xCoordinateOffset := (columnWidths[j] - len(col)) / 2
|
||||
stringXCoordinate := xCoordinateOffset + colXCoordinate
|
||||
for _, cx := range BuildCellWithXArray(col) {
|
||||
k, cell := cx.X, cx.Cell
|
||||
buf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))
|
||||
}
|
||||
} else if self.TextAlignment == AlignRight {
|
||||
stringXCoordinate := MinInt(colXCoordinate+columnWidths[j], self.Inner.Max.X) - len(col)
|
||||
for _, cx := range BuildCellWithXArray(col) {
|
||||
k, cell := cx.X, cx.Cell
|
||||
buf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))
|
||||
}
|
||||
}
|
||||
colXCoordinate += columnWidths[j] + 1
|
||||
}
|
||||
|
||||
// draw vertical separators
|
||||
separatorStyle := self.Block.BorderStyle
|
||||
|
||||
separatorXCoordinate := self.Inner.Min.X
|
||||
verticalCell := NewCell(VERTICAL_LINE, separatorStyle)
|
||||
for i, width := range columnWidths {
|
||||
if self.FillRow && i < len(columnWidths)-1 {
|
||||
verticalCell.Style.Bg = rowStyle.Bg
|
||||
} else {
|
||||
verticalCell.Style.Bg = self.Block.BorderStyle.Bg
|
||||
}
|
||||
|
||||
separatorXCoordinate += width
|
||||
buf.SetCell(verticalCell, image.Pt(separatorXCoordinate, yCoordinate))
|
||||
separatorXCoordinate++
|
||||
}
|
||||
|
||||
yCoordinate++
|
||||
|
||||
// draw horizontal separator
|
||||
horizontalCell := NewCell(HORIZONTAL_LINE, separatorStyle)
|
||||
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++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// 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/v3"
|
||||
)
|
||||
|
||||
// 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 int
|
||||
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 < 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 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user