termui/extra/tabpane.go

266 lines
5.7 KiB
Go
Raw Normal View History

2016-01-26 18:45:18 -07:00
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
2015-10-20 19:45:28 -06:00
package extra
2015-05-25 12:55:34 -06:00
import (
"unicode/utf8"
2015-10-20 19:45:28 -06:00
. "github.com/gizak/termui"
2015-05-25 12:55:34 -06:00
)
type Tab struct {
2015-10-20 19:45:28 -06:00
Label string
2015-05-25 12:55:34 -06:00
RuneLen int
2015-10-20 19:45:28 -06:00
Blocks []Bufferer
2015-05-25 12:55:34 -06:00
}
func NewTab(label string) *Tab {
2015-10-20 19:45:28 -06:00
return &Tab{
Label: label,
RuneLen: utf8.RuneCount([]byte(label))}
2015-05-25 12:55:34 -06:00
}
func (tab *Tab) AddBlocks(rs ...Bufferer) {
2015-10-20 19:45:28 -06:00
for _, r := range rs {
tab.Blocks = append(tab.Blocks, r)
}
2015-05-25 12:55:34 -06:00
}
2016-11-02 23:41:47 -06:00
func (tab *Tab) Buffer() Buffer {
buf := NewBuffer()
2015-10-20 19:45:28 -06:00
for blockNum := 0; blockNum < len(tab.Blocks); blockNum++ {
2016-11-02 23:41:47 -06:00
b := tab.Blocks[blockNum]
buf.Merge(b.Buffer())
2015-10-20 19:45:28 -06:00
}
2016-11-02 23:41:47 -06:00
return buf
2015-05-25 12:55:34 -06:00
}
type Tabpane struct {
2015-10-20 19:45:28 -06:00
Block
Tabs []Tab
activeTabIndex int
ActiveTabBg Attribute
posTabText []int
offTabText int
2015-05-25 12:55:34 -06:00
}
func NewTabpane() *Tabpane {
2015-10-20 19:45:28 -06:00
tp := Tabpane{
Block: *NewBlock(),
2015-05-25 12:55:34 -06:00
activeTabIndex: 0,
2015-10-20 19:45:28 -06:00
offTabText: 0,
2016-11-02 23:41:47 -06:00
ActiveTabBg: ThemeAttr("bg.tab.active")}
2015-05-25 12:55:34 -06:00
return &tp
}
func (tp *Tabpane) SetTabs(tabs ...Tab) {
tp.Tabs = make([]Tab, len(tabs))
tp.posTabText = make([]int, len(tabs)+1)
off := 0
2015-10-20 19:45:28 -06:00
for i := 0; i < len(tp.Tabs); i++ {
2015-05-25 12:55:34 -06:00
tp.Tabs[i] = tabs[i]
tp.posTabText[i] = off
2015-10-20 19:45:28 -06:00
off += tp.Tabs[i].RuneLen + 1 //+1 for space between tabs
2015-05-25 12:55:34 -06:00
}
2015-10-20 19:45:28 -06:00
tp.posTabText[len(tabs)] = off - 1 //total length of Tab's text
2015-05-25 12:55:34 -06:00
}
func (tp *Tabpane) SetActiveLeft() {
if tp.activeTabIndex == 0 {
return
}
2015-10-20 19:45:28 -06:00
tp.activeTabIndex -= 1
2015-05-25 12:55:34 -06:00
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
}
2015-10-20 19:45:28 -06:00
tp.activeTabIndex += 1
2015-05-25 12:55:34 -06:00
endOffset := tp.posTabText[tp.activeTabIndex] + tp.Tabs[tp.activeTabIndex].RuneLen
2015-10-20 19:45:28 -06:00
if endOffset+tp.offTabText > tp.InnerWidth() {
tp.offTabText = endOffset - tp.InnerWidth()
2015-05-25 12:55:34 -06:00
}
}
// 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
}
2015-10-20 19:45:28 -06:00
if tp.offTabText+tp.InnerWidth() < tp.posTabText[len(tp.Tabs)] {
2015-05-25 12:55:34 -06:00
ret += 1
}
return ret
}
// Checks if all tabs fits innerWidth of Tabpane
func (tp *Tabpane) fitsWidth() bool {
2015-10-20 19:45:28 -06:00
return tp.InnerWidth() >= tp.posTabText[len(tp.Tabs)]
2015-05-25 12:55:34 -06:00
}
func (tp *Tabpane) align() {
2016-11-02 23:41:47 -06:00
if !tp.fitsWidth() && !tp.Border {
2015-10-20 19:45:28 -06:00
tp.PaddingLeft += 1
tp.PaddingRight += 1
tp.Block.Align()
2015-05-25 12:55:34 -06:00
}
}
2016-11-02 23:41:47 -06:00
// bridge the old Point stuct
type point struct {
X int
Y int
Ch rune
Bg Attribute
Fg Attribute
}
func buf2pt(b Buffer) []point {
ps := make([]point, 0, len(b.CellMap))
for k, c := range b.CellMap {
ps = append(ps, point{X: k.X, Y: k.Y, Ch: c.Ch, Fg: c.Fg, Bg: c.Bg})
}
return ps
}
2015-05-25 12:55:34 -06:00
// 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
2016-11-02 23:41:47 -06:00
func (tp *Tabpane) addPoint(ptab []point, charOffset *int, oftX *int, points ...point) []point {
2015-10-20 19:45:28 -06:00
if *charOffset < tp.offTabText || tp.offTabText+tp.InnerWidth() < *charOffset {
2015-05-25 12:55:34 -06:00
*charOffset++
return ptab
}
2015-10-20 19:45:28 -06:00
for _, p := range points {
2015-05-25 12:55:34 -06:00
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)
2016-11-02 23:41:47 -06:00
func (tp *Tabpane) drawPointWithBorder(p point, ch rune, chbord rune, chdown rune, chup rune) []point {
var addp []point
2015-05-25 12:55:34 -06:00
p.Ch = ch
2016-11-02 23:41:47 -06:00
if tp.Border {
2015-05-25 12:55:34 -06:00
p.Ch = chdown
2015-10-20 19:45:28 -06:00
p.Y = tp.InnerY() - 1
2015-05-25 12:55:34 -06:00
addp = append(addp, p)
p.Ch = chup
2015-10-20 19:45:28 -06:00
p.Y = tp.InnerY() + 1
2015-05-25 12:55:34 -06:00
addp = append(addp, p)
p.Ch = chbord
}
2015-10-20 19:45:28 -06:00
p.Y = tp.InnerY()
2015-05-25 12:55:34 -06:00
return append(addp, p)
}
2016-11-02 23:41:47 -06:00
func (tp *Tabpane) Buffer() Buffer {
if tp.Border {
2015-05-25 12:55:34 -06:00
tp.Height = 3
} else {
tp.Height = 1
}
if tp.Width > tp.posTabText[len(tp.Tabs)]+2 {
2015-10-20 19:45:28 -06:00
tp.Width = tp.posTabText[len(tp.Tabs)] + 2
2015-05-25 12:55:34 -06:00
}
2016-11-02 23:41:47 -06:00
buf := tp.Block.Buffer()
ps := buf2pt(buf)
2015-05-25 12:55:34 -06:00
tp.align()
2015-10-20 19:45:28 -06:00
if tp.InnerHeight() <= 0 || tp.InnerWidth() <= 0 {
2016-11-02 23:41:47 -06:00
return NewBuffer()
2015-05-25 12:55:34 -06:00
}
2015-10-20 19:45:28 -06:00
oftX := tp.InnerX()
2015-05-25 12:55:34 -06:00
charOffset := 0
2016-11-02 23:41:47 -06:00
pt := point{Bg: tp.BorderBg, Fg: tp.BorderFg}
2015-05-25 12:55:34 -06:00
for i, tab := range tp.Tabs {
if i != 0 {
pt.X = oftX
2015-10-20 19:45:28 -06:00
pt.Y = tp.InnerY()
2015-05-25 12:55:34 -06:00
addp := tp.drawPointWithBorder(pt, ' ', VERTICAL_LINE, HORIZONTAL_DOWN, HORIZONTAL_UP)
ps = tp.addPoint(ps, &charOffset, &oftX, addp...)
}
if i == tp.activeTabIndex {
2015-10-20 19:45:28 -06:00
pt.Bg = tp.ActiveTabBg
2015-05-25 12:55:34 -06:00
}
2015-10-20 19:45:28 -06:00
rs := []rune(tab.Label)
2015-05-25 12:55:34 -06:00
for k := 0; k < len(rs); k++ {
2016-11-02 23:41:47 -06:00
addp := make([]point, 0, 2)
if i == tp.activeTabIndex && tp.Border {
2015-05-25 12:55:34 -06:00
pt.Ch = ' '
2015-10-20 19:45:28 -06:00
pt.Y = tp.InnerY() + 1
2016-11-02 23:41:47 -06:00
pt.Bg = tp.BorderBg
2015-05-25 12:55:34 -06:00
addp = append(addp, pt)
2015-10-20 19:45:28 -06:00
pt.Bg = tp.ActiveTabBg
2015-05-25 12:55:34 -06:00
}
2015-10-20 19:45:28 -06:00
pt.Y = tp.InnerY()
2015-05-25 12:55:34 -06:00
pt.Ch = rs[k]
addp = append(addp, pt)
ps = tp.addPoint(ps, &charOffset, &oftX, addp...)
}
2016-11-02 23:41:47 -06:00
pt.Bg = tp.BorderBg
2015-05-25 12:55:34 -06:00
if !tp.fitsWidth() {
all := tp.checkAlignment()
2015-10-20 19:45:28 -06:00
pt.X = tp.InnerX() - 1
2015-05-25 12:55:34 -06:00
pt.Ch = '*'
2016-11-02 23:41:47 -06:00
if tp.Border {
2015-05-25 12:55:34 -06:00
pt.Ch = VERTICAL_LINE
}
ps = append(ps, pt)
if all <= 0 {
addp := tp.drawPointWithBorder(pt, '<', '«', HORIZONTAL_LINE, HORIZONTAL_LINE)
ps = append(ps, addp...)
}
2015-10-20 19:45:28 -06:00
pt.X = tp.InnerX() + tp.InnerWidth()
2015-05-25 12:55:34 -06:00
pt.Ch = '*'
2016-11-02 23:41:47 -06:00
if tp.Border {
2015-05-25 12:55:34 -06:00
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
2015-10-20 19:45:28 -06:00
if i == tp.activeTabIndex {
2016-11-02 23:41:47 -06:00
blockPoints := buf2pt(tab.Buffer())
panic(len(blockPoints))
2015-05-25 12:55:34 -06:00
for i := 0; i < len(blockPoints); i++ {
blockPoints[i].Y += tp.Height + tp.Y
}
ps = append(ps, blockPoints...)
}
}
2016-11-02 23:41:47 -06:00
for _, v := range ps {
buf.Set(v.X, v.Y, NewCell(v.Ch, v.Fg, v.Bg))
}
return buf
2015-05-25 12:55:34 -06:00
}