From 5a0bce64faa187fe2103ceafddae5ddb53cb0f51 Mon Sep 17 00:00:00 2001 From: marigs Date: Mon, 25 May 2015 20:55:34 +0200 Subject: [PATCH 1/3] Add Tabpane --- example/tabs.go | 79 ++++++++++++++++ tabpane.go | 235 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 example/tabs.go create mode 100644 tabpane.go diff --git a/example/tabs.go b/example/tabs.go new file mode 100644 index 0000000..6bc13b6 --- /dev/null +++ b/example/tabs.go @@ -0,0 +1,79 @@ +package main + +import ( + "github.com/gizak/termui" + //"fmt" + //"os" +) + +func main() { + err := termui.Init() + if err != nil { + panic(err) + } + defer termui.Close() + + termui.UseTheme("helloworld") + + header := termui.NewPar("Press q to quit, Press j or k to switch tabs") + header.Height = 1 + header.Width = 50 + header.HasBorder = false + header.TextBgColor = termui.ColorBlue + + tab1 := termui.NewTab("pierwszy") + par2 := termui.NewPar("Press q to quit\nPress j or k to switch tabs\n") + par2.Height = 5 + par2.Width = 37 + par2.Y = 0 + par2.Border.Label = "Keys" + par2.Border.FgColor = termui.ColorYellow + tab1.AddBlocks(par2) + + tab2 := termui.NewTab("drugi") + bc := termui.NewBarChart() + data := []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.Data = data + bc.Width = 26 + bc.Height = 10 + bc.DataLabels = bclabels + bc.TextColor = termui.ColorGreen + bc.BarColor = termui.ColorRed + bc.NumColor = termui.ColorYellow + tab2.AddBlocks(bc) + + tab3 := termui.NewTab("trzeci") + tab4 := termui.NewTab("żółw") + tab5 := termui.NewTab("four") + tab6 := termui.NewTab("five") + + tabpane := termui.NewTabpane() + tabpane.Y = 1 + tabpane.Width = 30 + tabpane.HasBorder = true + + tabpane.SetTabs(*tab1, *tab2, *tab3, *tab4, *tab5, *tab6) + + termui.Render(header, tabpane) + + evt := termui.EventCh() + for { + select { + case e := <- evt: + if e.Type == termui.EventKey { + switch e.Ch { + case 'q': + return + case 'j': + tabpane.SetActiveLeft() + termui.Render(header, tabpane) + case 'k': + tabpane.SetActiveRight() + termui.Render(header, tabpane) + } + } + } + } +} diff --git a/tabpane.go b/tabpane.go new file mode 100644 index 0000000..7561bfe --- /dev/null +++ b/tabpane.go @@ -0,0 +1,235 @@ + +package termui + +import ( + "unicode/utf8" +) + +type Tab struct { + Label string + RuneLen int + Blocks []Bufferer +} + +func NewTab(label string) *Tab { + return &Tab { + Label: label, + RuneLen: utf8.RuneCount([]byte(label)) } +} + +func (tab *Tab) AddBlocks(rs ...Bufferer) { + for _, r := range rs { + tab.Blocks = append(tab.Blocks, r) + } +} + +func (tab *Tab) Buffer() []Point { + points := []Point{} + for blockNum := 0; blockNum < len(tab.Blocks); blockNum++ { + b := &tab.Blocks[blockNum] + blockPoints := (*b).Buffer() + points = append(points, blockPoints...) + } + return points +} + +type Tabpane struct { + Block + Tabs []Tab + activeTabIndex int + posTabText []int + offTabText int +} + +func NewTabpane() *Tabpane { + tp := Tabpane { + Block: *NewBlock(), + activeTabIndex: 0, + offTabText: 0} + return &tp +} + +func (tp *Tabpane) SetTabs(tabs ...Tab) { + tp.Tabs = make([]Tab, len(tabs)) + tp.posTabText = make([]int, len(tabs)+1) + off := 0 + for i :=0; i < len(tp.Tabs); i++ { + tp.Tabs[i] = tabs[i] + tp.posTabText[i] = off + off += tp.Tabs[i].RuneLen+1 //+1 for space between tabs + } + tp.posTabText[len(tabs)] = off-1 //total length of Tab's text +} + +func (tp *Tabpane) SetActiveLeft() { + if tp.activeTabIndex == 0 { + return + } + tp.activeTabIndex -= 1 + if tp.posTabText[tp.activeTabIndex] < tp.offTabText { + tp.offTabText = tp.posTabText[tp.activeTabIndex] + } +} + +func (tp *Tabpane) SetActiveRight() { + if tp.activeTabIndex == len(tp.Tabs)-1 { + return + } + tp.activeTabIndex += 1 + endOffset := tp.posTabText[tp.activeTabIndex] + tp.Tabs[tp.activeTabIndex].RuneLen + if endOffset + tp.offTabText > tp.innerWidth { + tp.offTabText = endOffset - tp.innerWidth + } +} + +// Checks if left and right tabs are fully visible +// if only left tabs are not visible return -1 +// if only right tabs are not visible return 1 +// if both return 0 +// use only if fitsWidth() returns false +func (tp *Tabpane) checkAlignment() int { + ret := 0 + if tp.offTabText > 0 { + ret = -1 + } + if tp.offTabText + tp.innerWidth < tp.posTabText[len(tp.Tabs)] { + ret += 1 + } + return ret +} + +// Checks if all tabs fits innerWidth of Tabpane +func (tp *Tabpane) fitsWidth() bool { + return tp.innerWidth >= tp.posTabText[len(tp.Tabs)] +} + +func (tp *Tabpane) align() { + if !tp.fitsWidth() && !tp.HasBorder { + tp.innerWidth -= 2 + tp.innerX += 1 + } +} + +// Adds the point only if it is visible in Tabpane. +// Point can be invisible if concatenation of Tab's texts is widther then +// innerWidth of Tabpane +func (tp *Tabpane) addPoint(ptab []Point, charOffset *int, oftX *int, points ...Point) []Point { + if *charOffset < tp.offTabText || tp.offTabText + tp.innerWidth < *charOffset { + *charOffset++ + return ptab + } + for _, p := range(points) { + p.X = *oftX + ptab = append(ptab, p) + } + *oftX++ + *charOffset++ + return ptab +} + +// Draws the point and redraws upper and lower border points (if it has one) +func (tp *Tabpane) drawPointWithBorder(p Point, ch rune, chbord rune, chdown rune, chup rune) []Point { + var addp []Point + p.Ch = ch + if tp.HasBorder { + p.Ch = chdown + p.Y = tp.innerY-1 + addp = append(addp, p) + p.Ch = chup + p.Y = tp.innerY+1 + addp = append(addp, p) + p.Ch = chbord + } + p.Y = tp.innerY + return append(addp, p) +} + +func (tp *Tabpane) Buffer() []Point { + if tp.HasBorder { + tp.Height = 3 + } else { + tp.Height = 1 + } + if tp.Width > tp.posTabText[len(tp.Tabs)]+2 { + tp.Width = tp.posTabText[len(tp.Tabs)]+2 + } + ps := tp.Block.Buffer() + tp.align() + if tp.innerHeight <= 0 || tp.innerWidth <= 0 { + return nil + } + oftX := tp.innerX + charOffset := 0 + pt := Point{Bg: tp.Border.BgColor, Fg: tp.Border.FgColor} + for i, tab := range tp.Tabs { + + if i != 0 { + pt.X = oftX + pt.Y = tp.innerY + addp := tp.drawPointWithBorder(pt, ' ', VERTICAL_LINE, HORIZONTAL_DOWN, HORIZONTAL_UP) + ps = tp.addPoint(ps, &charOffset, &oftX, addp...) + } + + if i == tp.activeTabIndex { + pt.Bg = theme.TabActiveBg + } + rs := str2runes(tab.Label) + for k := 0; k < len(rs); k++ { + + addp := make([]Point, 0, 2) + if i == tp.activeTabIndex && tp.HasBorder { + pt.Ch = ' ' + pt.Y = tp.innerY + 1 + pt.Bg = tp.Border.BgColor + addp = append(addp, pt) + pt.Bg = theme.TabActiveBg + } + + pt.Y = tp.innerY + pt.Ch = rs[k] + + addp = append(addp, pt) + ps = tp.addPoint(ps, &charOffset, &oftX, addp...) + } + pt.Bg = tp.Border.BgColor + + if !tp.fitsWidth() { + all := tp.checkAlignment() + pt.X = tp.innerX-1 + + pt.Ch = '*' + if tp.HasBorder { + pt.Ch = VERTICAL_LINE + } + ps = append(ps, pt) + + if all <= 0 { + addp := tp.drawPointWithBorder(pt, '<', '«', HORIZONTAL_LINE, HORIZONTAL_LINE) + ps = append(ps, addp...) + } + + pt.X = tp.innerX + tp.innerWidth + pt.Ch = '*' + if tp.HasBorder { + pt.Ch = VERTICAL_LINE + } + ps = append(ps, pt) + if all >= 0 { + addp := tp.drawPointWithBorder(pt, '>', '»', HORIZONTAL_LINE, HORIZONTAL_LINE) + ps = append(ps, addp...) + } + } + + //draw tab content below the Tabpane + if i == tp.activeTabIndex { + blockPoints := tab.Buffer() + for i := 0; i < len(blockPoints); i++ { + blockPoints[i].Y += tp.Height + tp.Y + } + ps = append(ps, blockPoints...) + } + } + + return ps +} + From 52789d8258aa03889768820b73413877ee63b39b Mon Sep 17 00:00:00 2001 From: marigs Date: Tue, 26 May 2015 21:37:04 +0200 Subject: [PATCH 2/3] bugfix: added missing changes i.e. some constans needed in Tabpane --- box_others.go | 6 ++++++ example/tabs.go | 4 +--- theme.go | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/box_others.go b/box_others.go index bcc3d7d..ed0f6a6 100644 --- a/box_others.go +++ b/box_others.go @@ -12,3 +12,9 @@ const HORIZONTAL_LINE = '─' const TOP_LEFT = '┌' const BOTTOM_RIGHT = '┘' const BOTTOM_LEFT = '└' +const VERTICAL_LEFT = '┤' +const VERTICAL_RIGHT = '├' +const HORIZONTAL_DOWN = '┬' +const HORIZONTAL_UP = '┴' +const QUOTA_LEFT = '«' +const QUOTA_RIGHT = '»' diff --git a/example/tabs.go b/example/tabs.go index 6bc13b6..14d57b7 100644 --- a/example/tabs.go +++ b/example/tabs.go @@ -1,9 +1,7 @@ package main import ( - "github.com/gizak/termui" - //"fmt" - //"os" + "github.com/marigs/termui" ) func main() { diff --git a/theme.go b/theme.go index c8ad947..0196231 100644 --- a/theme.go +++ b/theme.go @@ -29,6 +29,7 @@ type ColorScheme struct { MBarChartBar Attribute MBarChartText Attribute MBarChartNum Attribute + TabActiveBg Attribute } // default color scheme depends on the user's terminal setting. @@ -58,6 +59,7 @@ var themeHelloWorld = ColorScheme{ MBarChartBar: ColorRed, MBarChartNum: ColorWhite, MBarChartText: ColorCyan, + TabActiveBg: ColorMagenta, } var theme = themeDefault // global dep From c30bf1612a70b1859efd17929e834f8baf1de264 Mon Sep 17 00:00:00 2001 From: gizak Date: Tue, 20 Oct 2015 21:45:28 -0400 Subject: [PATCH 3/3] Move Tab to extra --- block.go | 20 ++++++ example/tabs.go | 40 +++++------ tabpane.go => extra/tabpane.go | 117 +++++++++++++++++---------------- 3 files changed, 101 insertions(+), 76 deletions(-) rename tabpane.go => extra/tabpane.go (66%) diff --git a/block.go b/block.go index 9531365..d7d78dd 100644 --- a/block.go +++ b/block.go @@ -140,3 +140,23 @@ func (d *Block) chopOverflow(ps []Point) []Point { } return nps } + +func (b Block) InnerWidth() int { + return b.innerWidth +} + +func (b Block) InnerHeight() int { + return b.innerHeight +} + +func (b Block) InnerX() int { + return b.innerX +} + +func (b Block) InnerY() int { + return b.innerY +} + +func (b *Block) Align() { + b.align() +} diff --git a/example/tabs.go b/example/tabs.go index 14d57b7..5df2a77 100644 --- a/example/tabs.go +++ b/example/tabs.go @@ -1,15 +1,16 @@ package main import ( - "github.com/marigs/termui" + "github.com/gizak/termui" + "github.com/gizak/termui/extra" ) func main() { - err := termui.Init() - if err != nil { - panic(err) - } - defer termui.Close() + err := termui.Init() + if err != nil { + panic(err) + } + defer termui.Close() termui.UseTheme("helloworld") @@ -19,7 +20,7 @@ func main() { header.HasBorder = false header.TextBgColor = termui.ColorBlue - tab1 := termui.NewTab("pierwszy") + tab1 := extra.NewTab("pierwszy") par2 := termui.NewPar("Press q to quit\nPress j or k to switch tabs\n") par2.Height = 5 par2.Width = 37 @@ -28,7 +29,7 @@ func main() { par2.Border.FgColor = termui.ColorYellow tab1.AddBlocks(par2) - tab2 := termui.NewTab("drugi") + tab2 := extra.NewTab("drugi") bc := termui.NewBarChart() data := []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"} @@ -42,24 +43,25 @@ func main() { bc.NumColor = termui.ColorYellow tab2.AddBlocks(bc) - tab3 := termui.NewTab("trzeci") - tab4 := termui.NewTab("żółw") - tab5 := termui.NewTab("four") - tab6 := termui.NewTab("five") + tab3 := extra.NewTab("trzeci") + tab4 := extra.NewTab("żółw") + tab5 := extra.NewTab("four") + tab6 := extra.NewTab("five") - tabpane := termui.NewTabpane() + tabpane := extra.NewTabpane() tabpane.Y = 1 - tabpane.Width = 30 - tabpane.HasBorder = true + tabpane.Width = 30 + tabpane.HasBorder = true - tabpane.SetTabs(*tab1, *tab2, *tab3, *tab4, *tab5, *tab6) - - termui.Render(header, tabpane) + tabpane.SetTabs(*tab1, *tab2, *tab3, *tab4, *tab5, *tab6) evt := termui.EventCh() + + termui.Render(header, tabpane) + for { select { - case e := <- evt: + case e := <-evt: if e.Type == termui.EventKey { switch e.Ch { case 'q': diff --git a/tabpane.go b/extra/tabpane.go similarity index 66% rename from tabpane.go rename to extra/tabpane.go index 7561bfe..dbfefe6 100644 --- a/tabpane.go +++ b/extra/tabpane.go @@ -1,51 +1,54 @@ - -package termui +package extra import ( "unicode/utf8" + + . "github.com/gizak/termui" ) type Tab struct { - Label string + Label string RuneLen int - Blocks []Bufferer + Blocks []Bufferer } func NewTab(label string) *Tab { - return &Tab { - Label: label, - RuneLen: utf8.RuneCount([]byte(label)) } + return &Tab{ + Label: label, + RuneLen: utf8.RuneCount([]byte(label))} } func (tab *Tab) AddBlocks(rs ...Bufferer) { - for _, r := range rs { - tab.Blocks = append(tab.Blocks, r) - } + for _, r := range rs { + tab.Blocks = append(tab.Blocks, r) + } } func (tab *Tab) Buffer() []Point { - points := []Point{} - for blockNum := 0; blockNum < len(tab.Blocks); blockNum++ { - b := &tab.Blocks[blockNum] - blockPoints := (*b).Buffer() - points = append(points, blockPoints...) - } - return points + points := []Point{} + for blockNum := 0; blockNum < len(tab.Blocks); blockNum++ { + b := &tab.Blocks[blockNum] + blockPoints := (*b).Buffer() + points = append(points, blockPoints...) + } + return points } type Tabpane struct { - Block - Tabs []Tab - activeTabIndex int - posTabText []int - offTabText int + Block + Tabs []Tab + activeTabIndex int + ActiveTabBg Attribute + posTabText []int + offTabText int } func NewTabpane() *Tabpane { - tp := Tabpane { - Block: *NewBlock(), + tp := Tabpane{ + Block: *NewBlock(), activeTabIndex: 0, - offTabText: 0} + offTabText: 0, + ActiveTabBg: Theme().TabActiveBg} return &tp } @@ -53,19 +56,19 @@ func (tp *Tabpane) SetTabs(tabs ...Tab) { tp.Tabs = make([]Tab, len(tabs)) tp.posTabText = make([]int, len(tabs)+1) off := 0 - for i :=0; i < len(tp.Tabs); i++ { + for i := 0; i < len(tp.Tabs); i++ { tp.Tabs[i] = tabs[i] tp.posTabText[i] = off - off += tp.Tabs[i].RuneLen+1 //+1 for space between tabs + off += tp.Tabs[i].RuneLen + 1 //+1 for space between tabs } - tp.posTabText[len(tabs)] = off-1 //total length of Tab's text + tp.posTabText[len(tabs)] = off - 1 //total length of Tab's text } func (tp *Tabpane) SetActiveLeft() { if tp.activeTabIndex == 0 { return } - tp.activeTabIndex -= 1 + tp.activeTabIndex -= 1 if tp.posTabText[tp.activeTabIndex] < tp.offTabText { tp.offTabText = tp.posTabText[tp.activeTabIndex] } @@ -75,10 +78,10 @@ func (tp *Tabpane) SetActiveRight() { if tp.activeTabIndex == len(tp.Tabs)-1 { return } - tp.activeTabIndex += 1 + tp.activeTabIndex += 1 endOffset := tp.posTabText[tp.activeTabIndex] + tp.Tabs[tp.activeTabIndex].RuneLen - if endOffset + tp.offTabText > tp.innerWidth { - tp.offTabText = endOffset - tp.innerWidth + if endOffset+tp.offTabText > tp.InnerWidth() { + tp.offTabText = endOffset - tp.InnerWidth() } } @@ -92,7 +95,7 @@ func (tp *Tabpane) checkAlignment() int { if tp.offTabText > 0 { ret = -1 } - if tp.offTabText + tp.innerWidth < tp.posTabText[len(tp.Tabs)] { + if tp.offTabText+tp.InnerWidth() < tp.posTabText[len(tp.Tabs)] { ret += 1 } return ret @@ -100,13 +103,14 @@ func (tp *Tabpane) checkAlignment() int { // Checks if all tabs fits innerWidth of Tabpane func (tp *Tabpane) fitsWidth() bool { - return tp.innerWidth >= tp.posTabText[len(tp.Tabs)] + return tp.InnerWidth() >= tp.posTabText[len(tp.Tabs)] } func (tp *Tabpane) align() { if !tp.fitsWidth() && !tp.HasBorder { - tp.innerWidth -= 2 - tp.innerX += 1 + tp.PaddingLeft += 1 + tp.PaddingRight += 1 + tp.Block.Align() } } @@ -114,11 +118,11 @@ func (tp *Tabpane) align() { // Point can be invisible if concatenation of Tab's texts is widther then // innerWidth of Tabpane func (tp *Tabpane) addPoint(ptab []Point, charOffset *int, oftX *int, points ...Point) []Point { - if *charOffset < tp.offTabText || tp.offTabText + tp.innerWidth < *charOffset { + if *charOffset < tp.offTabText || tp.offTabText+tp.InnerWidth() < *charOffset { *charOffset++ return ptab } - for _, p := range(points) { + for _, p := range points { p.X = *oftX ptab = append(ptab, p) } @@ -133,14 +137,14 @@ func (tp *Tabpane) drawPointWithBorder(p Point, ch rune, chbord rune, chdown run p.Ch = ch if tp.HasBorder { p.Ch = chdown - p.Y = tp.innerY-1 + p.Y = tp.InnerY() - 1 addp = append(addp, p) p.Ch = chup - p.Y = tp.innerY+1 + p.Y = tp.InnerY() + 1 addp = append(addp, p) p.Ch = chbord } - p.Y = tp.innerY + p.Y = tp.InnerY() return append(addp, p) } @@ -151,41 +155,41 @@ func (tp *Tabpane) Buffer() []Point { tp.Height = 1 } if tp.Width > tp.posTabText[len(tp.Tabs)]+2 { - tp.Width = tp.posTabText[len(tp.Tabs)]+2 + tp.Width = tp.posTabText[len(tp.Tabs)] + 2 } - ps := tp.Block.Buffer() + ps := tp.Block.Buffer() tp.align() - if tp.innerHeight <= 0 || tp.innerWidth <= 0 { + if tp.InnerHeight() <= 0 || tp.InnerWidth() <= 0 { return nil } - oftX := tp.innerX + oftX := tp.InnerX() charOffset := 0 pt := Point{Bg: tp.Border.BgColor, Fg: tp.Border.FgColor} for i, tab := range tp.Tabs { if i != 0 { pt.X = oftX - pt.Y = tp.innerY + pt.Y = tp.InnerY() addp := tp.drawPointWithBorder(pt, ' ', VERTICAL_LINE, HORIZONTAL_DOWN, HORIZONTAL_UP) ps = tp.addPoint(ps, &charOffset, &oftX, addp...) } if i == tp.activeTabIndex { - pt.Bg = theme.TabActiveBg + pt.Bg = tp.ActiveTabBg } - rs := str2runes(tab.Label) + rs := []rune(tab.Label) for k := 0; k < len(rs); k++ { addp := make([]Point, 0, 2) - if i == tp.activeTabIndex && tp.HasBorder { + if i == tp.activeTabIndex && tp.HasBorder { pt.Ch = ' ' - pt.Y = tp.innerY + 1 + pt.Y = tp.InnerY() + 1 pt.Bg = tp.Border.BgColor addp = append(addp, pt) - pt.Bg = theme.TabActiveBg + pt.Bg = tp.ActiveTabBg } - pt.Y = tp.innerY + pt.Y = tp.InnerY() pt.Ch = rs[k] addp = append(addp, pt) @@ -195,7 +199,7 @@ func (tp *Tabpane) Buffer() []Point { if !tp.fitsWidth() { all := tp.checkAlignment() - pt.X = tp.innerX-1 + pt.X = tp.InnerX() - 1 pt.Ch = '*' if tp.HasBorder { @@ -208,7 +212,7 @@ func (tp *Tabpane) Buffer() []Point { ps = append(ps, addp...) } - pt.X = tp.innerX + tp.innerWidth + pt.X = tp.InnerX() + tp.InnerWidth() pt.Ch = '*' if tp.HasBorder { pt.Ch = VERTICAL_LINE @@ -221,8 +225,8 @@ func (tp *Tabpane) Buffer() []Point { } //draw tab content below the Tabpane - if i == tp.activeTabIndex { - blockPoints := tab.Buffer() + if i == tp.activeTabIndex { + blockPoints := tab.Buffer() for i := 0; i < len(blockPoints); i++ { blockPoints[i].Y += tp.Height + tp.Y } @@ -232,4 +236,3 @@ func (tp *Tabpane) Buffer() []Point { return ps } -