v0.1.0 initial commit

This commit is contained in:
2026-07-12 17:01:37 -04:00
commit c3ca2ebb63
64 changed files with 10453 additions and 0 deletions
+210
View File
@@ -0,0 +1,210 @@
package tui
import "github.com/lixenwraith/terminal"
// BarSection represents one segment of a status bar
type BarSection struct {
Label string
Value string
LabelStyle Style
ValueStyle Style
Priority int // Higher = survives truncation
}
// BarAlign specifies status bar alignment mode
type BarAlign uint8
const (
BarAlignRight BarAlign = iota // Pack sections from right
BarAlignLeft // Pack sections from left
BarAlignDistribute // Evenly space sections
BarAlignCenter // Center all sections as a group
)
// BarOpts configures status bar rendering
type BarOpts struct {
Separator string // Between sections, default " │ "
SepStyle Style // Separator styling
Bg terminal.RGB
Align BarAlign
Padding int // Left/right padding, default 1
}
// DefaultBarOpts returns sensible defaults
func DefaultBarOpts() BarOpts {
return BarOpts{
Separator: " │ ",
SepStyle: Style{Fg: terminal.RGB{R: 80, G: 80, B: 100}},
Padding: 1,
Align: BarAlignRight,
}
}
// StatusBar renders horizontal status bar on row y
func (r Region) StatusBar(y int, sections []BarSection, opts BarOpts) {
if y < 0 || y >= r.H || len(sections) == 0 {
return
}
if opts.Separator == "" {
opts.Separator = " │ "
}
if opts.Padding == 0 {
opts.Padding = 1
}
// Fill background
for x := 0; x < r.W; x++ {
r.Cell(x, y, ' ', terminal.RGB{}, opts.Bg, terminal.AttrNone)
}
sepLen := RuneLen(opts.Separator)
// Calculate total width needed
totalW := 0
sectionWidths := make([]int, len(sections))
for i, sec := range sections {
w := RuneLen(sec.Label) + RuneLen(sec.Value)
sectionWidths[i] = w
totalW += w
if i < len(sections)-1 {
totalW += sepLen
}
}
availW := r.W - opts.Padding*2
// Truncate low-priority sections if needed
if totalW > availW {
sections, sectionWidths = truncateSections(sections, sectionWidths, sepLen, availW)
totalW = 0
for i, w := range sectionWidths {
totalW += w
if i < len(sectionWidths)-1 {
totalW += sepLen
}
}
}
// Calculate starting position based on alignment
var x int
switch opts.Align {
case BarAlignLeft:
x = opts.Padding
case BarAlignRight:
x = r.W - opts.Padding - totalW
if x < opts.Padding {
x = opts.Padding
}
case BarAlignDistribute:
x = opts.Padding
// Handled specially below
case BarAlignCenter:
x = (r.W - totalW) / 2
if x < opts.Padding {
x = opts.Padding
}
}
// Render sections
if opts.Align == BarAlignDistribute && len(sections) > 1 {
gap := (availW - totalW) / (len(sections) - 1)
if gap < 0 {
gap = 0
}
for i, sec := range sections {
x = r.renderBarSection(x, y, sec, opts)
if i < len(sections)-1 {
x += gap
}
}
} else {
for i, sec := range sections {
x = r.renderBarSection(x, y, sec, opts)
if i < len(sections)-1 {
// Separator
for j, ch := range opts.Separator {
if x+j < r.W-opts.Padding {
r.Cell(x+j, y, ch, opts.SepStyle.Fg, opts.Bg, opts.SepStyle.Attr)
}
}
x += sepLen
}
}
}
}
func (r Region) renderBarSection(x, y int, sec BarSection, opts BarOpts) int {
// Label
for _, ch := range sec.Label {
if x >= r.W-opts.Padding {
break
}
r.Cell(x, y, ch, sec.LabelStyle.Fg, opts.Bg, sec.LabelStyle.Attr)
x++
}
// Value
for _, ch := range sec.Value {
if x >= r.W-opts.Padding {
break
}
r.Cell(x, y, ch, sec.ValueStyle.Fg, opts.Bg, sec.ValueStyle.Attr)
x++
}
return x
}
// truncateSections removes lowest priority sections until fit
func truncateSections(sections []BarSection, widths []int, sepLen, availW int) ([]BarSection, []int) {
// Copy to avoid modifying original
secs := make([]BarSection, len(sections))
copy(secs, sections)
ws := make([]int, len(widths))
copy(ws, widths)
for {
total := 0
for i, w := range ws {
total += w
if i < len(ws)-1 {
total += sepLen
}
}
if total <= availW || len(secs) <= 1 {
break
}
// Find lowest priority
minIdx := 0
minPrio := secs[0].Priority
for i, sec := range secs {
if sec.Priority < minPrio {
minPrio = sec.Priority
minIdx = i
}
}
// RemoveEntityAt it
secs = append(secs[:minIdx], secs[minIdx+1:]...)
ws = append(ws[:minIdx], ws[minIdx+1:]...)
}
return secs, ws
}
// QuickStatusBar renders simple label:value pairs right-aligned
func (r Region) QuickStatusBar(y int, pairs [][2]string, labelFg, valueFg, bg terminal.RGB) {
sections := make([]BarSection, len(pairs))
for i, p := range pairs {
sections[i] = BarSection{
Label: p[0],
Value: p[1],
LabelStyle: Style{Fg: labelFg},
ValueStyle: Style{Fg: valueFg},
}
}
r.StatusBar(y, sections, BarOpts{
Bg: bg,
Align: BarAlignRight,
})
}