diff --git a/README.md b/README.md index 6b63593..ec54eb7 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ __Demo:__ ## Usage -Each component's layout is a bit like HTML block, which has border and padding. +Each component's layout is a bit like HTML block (box model), which has border and padding. -The `Border` property can be chosen to hide or display (with its border label), when it comes to display, in this case the space it takes is counted as padding space (i.e. `PaddingTop=PaddingBottom=PaddingLeft=PaddingRight=1`). +The `Border` property can be chosen to hide or display (with its border label), when it comes to display, the label takes 1 padding space (i.e. in css: `padding: 1;`, innerHeight and innerWidth therefore shrunk by 1). `````go import ui "github.com/gizak/termui" // <- ui shortcut, optional @@ -50,11 +50,29 @@ The `Border` property can be chosen to hide or display (with its border label), } ````` -Note that components can be overlapped (I'd rather call this as a feature...), `Render(rs ...Renderer)` renders its args from left to right (i.e. each component's weight is arising from left to right). +Note that components can be overlapped (I'd rather call this a feature...), `Render(rs ...Renderer)` renders its args from left to right (i.e. each component's weight is arising from left to right). + +## Themes + +All colors in all components call be changed at any time, while there provides some predefined color scheme. + +``` +// for now there are only two themes: default and helloworld +termui.UseTheme("helloworld") + +// create components... +``` +The `default ` theme's settings depend on the user's terminal color scheme, which is saying if your terminal default font color is white and background is white, it will be like: + +default + +The `helloworld` color scheme drops in some colors! + +helloworld ## Widgets -_APIs are subject to change, docs will be added after 2 or 3 commits_ +_APIs are subject to change, docs will be added after 1 or 2 commits_ ## GoDoc diff --git a/bar.go b/bar.go index af6431d..18aa964 100644 --- a/bar.go +++ b/bar.go @@ -20,9 +20,9 @@ type BarChart struct { func NewBarChart() *BarChart { bc := &BarChart{Block: *NewBlock()} - bc.BarColor = ColorCyan - bc.NumColor = ColorWhite - bc.TextColor = ColorWhite + bc.BarColor = theme.BarChartBar + bc.NumColor = theme.BarChartNum + bc.TextColor = theme.BarChartText bc.BarGap = 1 bc.BarWidth = 3 return bc @@ -62,6 +62,9 @@ func (bc *BarChart) Buffer() []Point { p := Point{} p.Ch = ' ' p.Bg = bc.BarColor + if bc.BarColor == ColorDefault { // when color is default, space char treated as transparent! + p.Bg |= AttrReverse + } p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j p.Y = bc.innerY + bc.innerHeight - 2 - k ps = append(ps, p) @@ -83,6 +86,9 @@ func (bc *BarChart) Buffer() []Point { p.Ch = bc.dataNum[i][j] p.Fg = bc.NumColor p.Bg = bc.BarColor + if bc.BarColor == ColorDefault { // the same as above + p.Bg |= AttrReverse + } if h == 0 { p.Bg = bc.BgColor } diff --git a/block.go b/block.go index bdab2b9..bd69544 100644 --- a/block.go +++ b/block.go @@ -1,5 +1,6 @@ package termui +// basic struct, consider it as css: display:block type Block struct { X int Y int @@ -22,7 +23,12 @@ type Block struct { func NewBlock() *Block { d := Block{} d.IsDisplay = true - d.HasBorder = true + d.HasBorder = theme.HasBorder + d.Border.BgColor = theme.BorderBg + d.Border.FgColor = theme.BorderFg + d.Border.LabelBgColor = theme.BorderLabelTextBg + d.Border.LabelFgColor = theme.BorderLabelTextFg + d.BgColor = theme.BlockBg d.Width = 2 d.Height = 2 return &d diff --git a/chart.go b/chart.go index c93338f..c2831c8 100644 --- a/chart.go +++ b/chart.go @@ -55,6 +55,8 @@ type LineChart struct { func NewLineChart() *LineChart { lc := &LineChart{Block: *NewBlock()} + lc.AxesColor = theme.LineChartAxes + lc.LineColor = theme.LineChartLine lc.Mode = "braille" lc.DotStyle = '•' lc.axisXLebelGap = 2 diff --git a/example/dashboard.go b/example/dashboard.go index 73b54ca..c7a2326 100644 --- a/example/dashboard.go +++ b/example/dashboard.go @@ -13,7 +13,7 @@ func main() { } defer ui.Close() - p := ui.NewP(":PRESS q TO QUIT DEMO") + p := ui.NewPar(":PRESS q TO QUIT DEMO") p.Height = 3 p.Width = 50 p.TextFgColor = ui.ColorWhite @@ -96,7 +96,7 @@ func main() { lc1 := ui.NewLineChart() lc1.Border.Label = "Line Chart" rndwalk := (func() []float64 { - n := 100 + n := 150 d := make([]float64, n) for i := 1; i < n; i++ { if i < 20 { @@ -116,7 +116,7 @@ func main() { lc1.AxesColor = ui.ColorWhite lc1.LineColor = ui.ColorYellow | ui.AttrBold - p1 := ui.NewP("Hey!\nI am a borderless block!") + p1 := ui.NewPar("Hey!\nI am a borderless block!") p1.HasBorder = false p1.Width = 26 p1.Height = 2 diff --git a/example/theme.go b/example/theme.go new file mode 100644 index 0000000..5e0a332 --- /dev/null +++ b/example/theme.go @@ -0,0 +1,144 @@ +package main + +import ui "github.com/gizak/termui" +import tm "github.com/nsf/termbox-go" +import "math" + +import "time" + +func main() { + err := ui.Init() + if err != nil { + panic(err) + } + defer ui.Close() + + ui.UseTheme("helloworld") + + p := ui.NewPar(":PRESS q TO QUIT DEMO") + p.Height = 3 + p.Width = 50 + p.Border.Label = "Text Box" + + strs := []string{"[0] 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"} + list := ui.NewList() + list.Items = strs + list.Border.Label = "List" + list.Height = 7 + list.Width = 25 + list.Y = 4 + + g := ui.NewGauge() + g.Percent = 50 + g.Width = 50 + g.Height = 3 + g.Y = 11 + g.Border.Label = "Gauge" + + spark := ui.NewSparkline() + spark.Title = "srv 0:" + spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6} + spark.Data = spdata + + spark1 := ui.NewSparkline() + spark1.Title = "srv 1:" + spark1.Data = spdata + + sp := ui.NewSparklines(spark, spark1) + sp.Width = 25 + sp.Height = 7 + sp.Border.Label = "Sparkline" + sp.Y = 4 + sp.X = 25 + + lc := ui.NewLineChart() + sinps := (func() []float64 { + n := 100 + ps := make([]float64, n) + for i := range ps { + ps[i] = 1 + math.Sin(float64(i)/4) + } + return ps + })() + + lc.Border.Label = "Line Chart" + lc.Data = sinps + lc.Width = 50 + lc.Height = 11 + lc.X = 0 + lc.Y = 14 + lc.Mode = "dot" + + bc := ui.NewBarChart() + bcdata := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6} + bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"} + bc.Border.Label = "Bar Chart" + bc.Width = 26 + bc.Height = 10 + bc.X = 51 + bc.Y = 0 + bc.DataLabels = bclabels + + lc1 := ui.NewLineChart() + lc1.Border.Label = "Line Chart" + rndwalk := (func() []float64 { + n := 150 + d := make([]float64, n) + for i := 1; i < n; i++ { + if i < 20 { + d[i] = d[i-1] + 0.01 + } + if i > 20 { + d[i] = d[i-1] - 0.05 + } + } + return d + })() + lc1.Data = rndwalk + lc1.Width = 26 + lc1.Height = 11 + lc1.X = 51 + lc1.Y = 14 + + p1 := ui.NewPar("Hey!\nI am a borderless block!") + p1.HasBorder = false + p1.Width = 26 + p1.Height = 2 + p1.X = 52 + p1.Y = 11 + + draw := func(t int) { + g.Percent = t % 101 + list.Items = strs[t%9:] + sp.Lines[0].Data = spdata[t%10:] + sp.Lines[1].Data = spdata[t/2%10:] + lc.Data = sinps[t/2:] + lc1.Data = rndwalk[t:] + bc.Data = bcdata[t/2%10:] + ui.Render(p, list, g, sp, lc, bc, lc1, p1) + } + + evt := make(chan tm.Event) + go func() { + for { + evt <- tm.PollEvent() + } + }() + + i := 0 + for { + select { + case e := <-evt: + if e.Type == tm.EventKey && e.Ch == 'q' { + return + } + default: + draw(i) + i++ + if i == 102 { + return + } + time.Sleep(time.Second / 2) + } + } +} diff --git a/example/themedefault.tiff b/example/themedefault.tiff new file mode 100644 index 0000000..2f89b54 Binary files /dev/null and b/example/themedefault.tiff differ diff --git a/example/themehelloworld.tiff b/example/themehelloworld.tiff new file mode 100644 index 0000000..fbf0f24 Binary files /dev/null and b/example/themehelloworld.tiff differ diff --git a/gauge.go b/gauge.go index 0d8eff3..6df5c78 100644 --- a/gauge.go +++ b/gauge.go @@ -10,7 +10,10 @@ type Gauge struct { } func NewGauge() *Gauge { - g := &Gauge{Block: *NewBlock(), PercentColor: ColorWhite, BarColor: ColorGreen} + g := &Gauge{ + Block: *NewBlock(), + PercentColor: theme.GaugePercent, + BarColor: theme.GaugeBar} g.Width = 12 g.Height = 5 return g @@ -34,6 +37,9 @@ func (g *Gauge) Buffer() []Point { p.Y = g.innerY + i p.Ch = ' ' p.Bg = g.BarColor + if p.Bg == ColorDefault { + p.Bg |= AttrReverse + } ps = append(ps, p) } } @@ -47,6 +53,10 @@ func (g *Gauge) Buffer() []Point { p.Fg = g.PercentColor if w > g.innerWidth/2-1+i { p.Bg = g.BarColor + if p.Bg == ColorDefault { + p.Bg |= AttrReverse + } + } else { p.Bg = g.Block.BgColor } diff --git a/list.go b/list.go index b61682f..3ad7d18 100644 --- a/list.go +++ b/list.go @@ -13,6 +13,8 @@ type List struct { func NewList() *List { l := &List{Block: *NewBlock()} l.Overflow = "hidden" + l.ItemFgColor = theme.ListItemFg + l.ItemBgColor = theme.ListItemBg return l } diff --git a/p.go b/p.go index 3fd848f..c5dd936 100644 --- a/p.go +++ b/p.go @@ -1,17 +1,21 @@ package termui -type P struct { +type Par struct { Block Text string TextFgColor Attribute TextBgColor Attribute } -func NewP(s string) *P { - return &P{Block: *NewBlock(), Text: s} +func NewPar(s string) *Par { + return &Par{ + Block: *NewBlock(), + Text: s, + TextFgColor: theme.ParTextFg, + TextBgColor: theme.ParTextBg} } -func (p *P) Buffer() []Point { +func (p *Par) Buffer() []Point { ps := p.Block.Buffer() rs := str2runes(p.Text) diff --git a/render.go b/render.go index e138ea2..ef544ab 100644 --- a/render.go +++ b/render.go @@ -2,6 +2,7 @@ package termui import tm "github.com/nsf/termbox-go" +// all renderable components should implement this type Bufferer interface { Buffer() []Point } @@ -14,7 +15,9 @@ func Close() { tm.Close() } +// render all from left to right, right could overlap on left ones func Render(rs ...Bufferer) { + tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg)) for _, r := range rs { buf := r.Buffer() for _, v := range buf { diff --git a/sparkline.go b/sparkline.go index 44fcd08..e8e8a6b 100644 --- a/sparkline.go +++ b/sparkline.go @@ -26,6 +26,14 @@ func (s *Sparklines) Add(sl Sparkline) { s.Lines = append(s.Lines, sl) } +// return unrenderable single sparkline, need to add it into Sparklines +func NewSparkline() Sparkline { + return Sparkline{ + Height: 1, + TitleColor: theme.SparklineTitle, + LineColor: theme.SparklineLine} +} + func NewSparklines(ss ...Sparkline) *Sparklines { s := &Sparklines{Block: *NewBlock(), Lines: ss} return s diff --git a/theme.go b/theme.go new file mode 100644 index 0000000..a2294e2 --- /dev/null +++ b/theme.go @@ -0,0 +1,61 @@ +package termui + +type colorScheme struct { + BodyBg Attribute + BlockBg Attribute + HasBorder bool + BorderFg Attribute + BorderBg Attribute + BorderLabelTextFg Attribute + BorderLabelTextBg Attribute + ParTextFg Attribute + ParTextBg Attribute + SparklineLine Attribute + SparklineTitle Attribute + GaugeBar Attribute + GaugePercent Attribute + LineChartLine Attribute + LineChartAxes Attribute + ListItemFg Attribute + ListItemBg Attribute + BarChartBar Attribute + BarChartText Attribute + BarChartNum Attribute +} + +// default color scheme depends on the user's terminal setting. +var themeDefault = colorScheme{HasBorder: true} + +var themeHelloWorld = colorScheme{ + BodyBg: ColorBlack, + BlockBg: ColorBlack, + HasBorder: true, + BorderFg: ColorWhite, + BorderBg: ColorBlack, + BorderLabelTextBg: ColorBlack, + BorderLabelTextFg: ColorGreen, + ParTextBg: ColorBlack, + ParTextFg: ColorWhite, + SparklineLine: ColorMagenta, + SparklineTitle: ColorWhite, + GaugeBar: ColorRed, + GaugePercent: ColorWhite, + LineChartLine: ColorYellow | AttrBold, + LineChartAxes: ColorWhite, + ListItemBg: ColorBlack, + ListItemFg: ColorYellow, + BarChartBar: ColorRed, + BarChartNum: ColorWhite, + BarChartText: ColorCyan, +} + +var theme = themeDefault // global dep + +func UseTheme(th string) { + switch th { + case "helloworld": + theme = themeHelloWorld + default: + theme = themeDefault + } +}