v0.1.0 initial commit
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user