131 lines
2.8 KiB
Go
131 lines
2.8 KiB
Go
package tui
|
|
|
|
import "github.com/lixenwraith/terminal"
|
|
|
|
// TabBounds stores position and size of a rendered tab
|
|
type TabBounds struct {
|
|
X, W int
|
|
}
|
|
|
|
// TabBarOpts configures tab bar rendering
|
|
type TabBarOpts struct {
|
|
ActiveStyle Style
|
|
InactiveStyle Style
|
|
Separator string // Between tabs, default " │ "
|
|
Padding int // Horizontal padding inside each tab, default 1
|
|
}
|
|
|
|
// DefaultTabBarOpts returns sensible defaults
|
|
func DefaultTabBarOpts() TabBarOpts {
|
|
return TabBarOpts{
|
|
ActiveStyle: Style{Attr: terminal.AttrBold | terminal.AttrReverse},
|
|
InactiveStyle: Style{Attr: terminal.AttrNone},
|
|
Separator: " │ ",
|
|
Padding: 1,
|
|
}
|
|
}
|
|
|
|
// TabBar renders horizontal tab strip at row y
|
|
// Returns bounds of each tab for hit testing / navigation
|
|
func (r Region) TabBar(y int, titles []string, active int, opts TabBarOpts) []TabBounds {
|
|
if y < 0 || y >= r.H || len(titles) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if opts.Separator == "" {
|
|
opts.Separator = " │ "
|
|
}
|
|
|
|
bounds := make([]TabBounds, len(titles))
|
|
x := 0
|
|
sepLen := RuneLen(opts.Separator)
|
|
|
|
for i, title := range titles {
|
|
if x >= r.W {
|
|
break
|
|
}
|
|
|
|
// Calculate tab width
|
|
tabW := RuneLen(title) + opts.Padding*2
|
|
if x+tabW > r.W {
|
|
tabW = r.W - x
|
|
}
|
|
|
|
bounds[i] = TabBounds{X: x, W: tabW}
|
|
|
|
// Select style
|
|
style := opts.InactiveStyle
|
|
if i == active {
|
|
style = opts.ActiveStyle
|
|
}
|
|
|
|
// Render padding + title + padding
|
|
for j := 0; j < opts.Padding && x+j < r.W; j++ {
|
|
r.Cell(x+j, y, ' ', style.Fg, style.Bg, style.Attr)
|
|
}
|
|
|
|
titleStart := x + opts.Padding
|
|
for j, ch := range title {
|
|
if titleStart+j >= r.W {
|
|
break
|
|
}
|
|
r.Cell(titleStart+j, y, ch, style.Fg, style.Bg, style.Attr)
|
|
}
|
|
|
|
for j := 0; j < opts.Padding; j++ {
|
|
pos := x + opts.Padding + RuneLen(title) + j
|
|
if pos < r.W {
|
|
r.Cell(pos, y, ' ', style.Fg, style.Bg, style.Attr)
|
|
}
|
|
}
|
|
|
|
x += tabW
|
|
|
|
// Separator between tabs
|
|
if i < len(titles)-1 && x+sepLen <= r.W {
|
|
for j, ch := range opts.Separator {
|
|
r.Cell(x+j, y, ch, opts.InactiveStyle.Fg, opts.InactiveStyle.Bg, terminal.AttrDim)
|
|
}
|
|
x += sepLen
|
|
}
|
|
}
|
|
|
|
return bounds
|
|
}
|
|
|
|
// TabBarCentered renders tab bar centered horizontally
|
|
func (r Region) TabBarCentered(y int, titles []string, active int, opts TabBarOpts) []TabBounds {
|
|
if len(titles) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if opts.Separator == "" {
|
|
opts.Separator = " │ "
|
|
}
|
|
|
|
// Calculate total width
|
|
totalW := 0
|
|
sepLen := RuneLen(opts.Separator)
|
|
for i, title := range titles {
|
|
totalW += RuneLen(title) + opts.Padding*2
|
|
if i < len(titles)-1 {
|
|
totalW += sepLen
|
|
}
|
|
}
|
|
|
|
// Create sub-region for centering
|
|
startX := (r.W - totalW) / 2
|
|
if startX < 0 {
|
|
startX = 0
|
|
}
|
|
|
|
subR := r.Sub(startX, y, totalW, 1)
|
|
bounds := subR.TabBar(0, titles, active, opts)
|
|
|
|
// Adjust bounds to parent coordinates
|
|
for i := range bounds {
|
|
bounds[i].X += startX
|
|
}
|
|
|
|
return bounds
|
|
} |