Files
streamed-tui/internal/columns.go

125 lines
3.0 KiB
Go

package internal
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
)
// ────────────────────────────────
// STYLES
// ────────────────────────────────
type Styles struct {
Title lipgloss.Style
Box lipgloss.Style
Active lipgloss.Style
Status lipgloss.Style
}
func NewStyles() Styles {
border := lipgloss.RoundedBorder()
return Styles{
Title: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12")),
Box: lipgloss.NewStyle().Border(border).Padding(0, 1).MarginRight(1),
Active: lipgloss.NewStyle().Border(border).BorderForeground(lipgloss.Color("10")).Padding(0, 1).MarginRight(1),
Status: lipgloss.NewStyle().Foreground(lipgloss.Color("8")).MarginTop(1),
}
}
// ────────────────────────────────
// GENERIC LIST COLUMN (SCROLLABLE)
// ────────────────────────────────
type renderer[T any] func(T) string
type ListColumn[T any] struct {
title string
items []T
selected int
scroll int
width int
height int
render renderer[T]
}
func NewListColumn[T any](title string, r renderer[T]) *ListColumn[T] {
return &ListColumn[T]{title: title, render: r, width: 30, height: 20}
}
func (c *ListColumn[T]) SetItems(items []T) {
c.items = items
c.selected = 0
c.scroll = 0
}
func (c *ListColumn[T]) SetTitle(title string) { c.title = title }
func (c *ListColumn[T]) SetWidth(w int) { if w > 20 { c.width = w - 2 } }
func (c *ListColumn[T]) SetHeight(h int) { if h > 5 { c.height = h - 5 } }
func (c *ListColumn[T]) CursorUp() {
if c.selected > 0 {
c.selected--
}
if c.selected < c.scroll {
c.scroll = c.selected
}
}
func (c *ListColumn[T]) CursorDown() {
if c.selected < len(c.items)-1 {
c.selected++
}
if c.selected >= c.scroll+c.height {
c.scroll = c.selected - c.height + 1
}
}
func (c *ListColumn[T]) Selected() (T, bool) {
var zero T
if len(c.items) == 0 {
return zero, false
}
return c.items[c.selected], true
}
func (c *ListColumn[T]) View(styles Styles, focused bool) string {
box := styles.Box
if focused {
box = styles.Active
}
head := styles.Title.Render(c.title)
lines := []string{}
if len(c.items) == 0 {
lines = append(lines, "(no items)")
} else {
start := c.scroll
end := start + c.height
if end > len(c.items) {
end = len(c.items)
}
for i := start; i < end; i++ {
cursor := " "
if i == c.selected {
cursor = "▸ "
}
line := fmt.Sprintf("%s%s", cursor, c.render(c.items[i]))
if len(line) > c.width && c.width > 3 {
line = line[:c.width-3] + "…"
}
lines = append(lines, line)
}
}
// Fill remaining lines if fewer than height
for len(lines) < c.height {
lines = append(lines, "")
}
content := strings.Join(lines, "\n")
return box.Width(c.width + 2).Render(head + "\n" + content)
}