asciigen/tui/model.go
2026-08-26 11:56:56 +03:00

97 lines
2.6 KiB
Go

// Package tui реализует интерактивный выбор варианта: список превью слева,
// полный текущий превью справа, стрелки для навигации, Enter — подтвердить.
package tui
import (
"fmt"
"asciigen"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var (
selectedStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("42"))
normalStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
previewBox = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).Padding(1, 2)
)
// Model — состояние TUI. Renderer абстрагирует, картинка перед нами или текст.
type Model struct {
renderer asciigen.Renderer
variants []asciigen.Variant
cursor int
chosen string // ID выбранного варианта после подтверждения, "" пока не выбрано
fullText string // отрендеренная полная версия, заполняется после Enter
err error
}
func New(r asciigen.Renderer) (Model, error) {
variants, err := r.Previews()
if err != nil {
return Model{}, err
}
return Model{renderer: r, variants: variants}, nil
}
func (m Model) Init() tea.Cmd {
return nil
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
case "down", "j":
if m.cursor < len(m.variants)-1 {
m.cursor++
}
case "enter":
id := m.variants[m.cursor].ID
full, err := m.renderer.Full(id)
if err != nil {
m.err = err
return m, nil
}
m.chosen = id
m.fullText = full
return m, tea.Quit
}
}
return m, nil
}
func (m Model) View() string {
if m.err != nil {
return fmt.Sprintf("Ошибка: %v\n", m.err)
}
var list string
for i, v := range m.variants {
line := v.Label
if i == m.cursor {
line = selectedStyle.Render("> " + line)
} else {
line = normalStyle.Render(" " + line)
}
list += line + "\n"
}
preview := previewBox.Render(m.variants[m.cursor].Preview)
return lipgloss.JoinHorizontal(lipgloss.Top, list, " ", preview) +
"\n\n↑/↓ выбор • Enter подтвердить • q выход\n"
}
// Chosen возвращает (ID, полный рендер, ok) — вызывать после того как
// tea.NewProgram(...).Run() завершился.
func (m Model) Chosen() (string, string, bool) {
return m.chosen, m.fullText, m.chosen != ""
}