Files
streamed-tui/internal/columns.go
2025-11-23 00:16:24 -05:00

184 lines
4.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
Error lipgloss.Style // NEW: for red bold error lines
Subtle 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),
Active: lipgloss.NewStyle().
Border(border).
BorderForeground(lipgloss.Color("#FA8072")). // Not pink, its Salmon obviously
Padding(0, 1),
Status: lipgloss.NewStyle().Foreground(lipgloss.Color("8")).MarginTop(1),
Error: lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Bold(true),
Subtle: lipgloss.NewStyle().Foreground(lipgloss.Color("243")),
}
}
// ────────────────────────────────
// 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 truncateToWidth(text string, width int) string {
if width <= 0 {
return ""
}
if lipgloss.Width(text) <= width {
return text
}
runes := []rune(text)
total := 0
for i, r := range runes {
rWidth := lipgloss.Width(string(r))
if total+rWidth > width {
return string(runes[:i])
}
total += rWidth
}
return text
}
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) {
// w is the total width the app wants to allocate to the box.
// Subtract 4 for border (2) + padding (2) to get interior content width.
if w < 4 {
c.width = 0
return
}
c.width = w - 4
}
func (c *ListColumn[T]) SetHeight(h int) {
if h > 6 {
c.height = h - 6
}
}
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
}
titleText := fmt.Sprintf("%s (%d)", c.title, len(c.items))
if focused {
titleText = fmt.Sprintf("▶ %s", titleText)
}
head := styles.Title.Render(titleText)
meta := styles.Subtle.Render("Waiting for data…")
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)
}
meta = styles.Subtle.Render(fmt.Sprintf("Showing %d%d of %d", start+1, end, len(c.items)))
for i := start; i < end; i++ {
cursor := " "
lineText := c.render(c.items[i])
contentWidth := c.width - lipgloss.Width(cursor)
if contentWidth > 1 && lipgloss.Width(lineText) > contentWidth {
lineText = fmt.Sprintf("%s…", truncateToWidth(lineText, contentWidth-1))
}
if i == c.selected {
cursor = "▸ "
lineText = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FA8072")). // Not pink, its Salmon obviously
Bold(true).
Render(lineText)
}
line := fmt.Sprintf("%s%s", cursor, lineText)
lines = append(lines, line)
}
}
// Fill remaining lines if fewer than height
for len(lines) < c.height {
lines = append(lines, "")
}
content := strings.Join(lines, "\n")
// IMPORTANT: width = interior content width + 4 (border+padding)
return box.Width(c.width + 4).Render(head + "\n" + meta + "\n" + content)
}