composer.go

  1package tui
  2
  3import (
  4	"github.com/charmbracelet/bubbles/textarea"
  5	"github.com/charmbracelet/bubbles/textinput"
  6	tea "github.com/charmbracelet/bubbletea"
  7	"github.com/charmbracelet/lipgloss"
  8)
  9
 10// Styles for the UI
 11var (
 12	focusedStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
 13	blurredStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
 14	cursorStyle         = focusedStyle.Copy()
 15	noStyle             = lipgloss.NewStyle()
 16	focusedButton       = focusedStyle.Copy().Render("[ Send ]")
 17	blurredButton       = blurredStyle.Copy().Render("[ Send ]")
 18	emailRecipientStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
 19)
 20
 21// Composer model holds the state of the email composition UI.
 22type Composer struct {
 23	focusIndex int
 24	toInput    textinput.Model
 25	subjectInput textinput.Model
 26	bodyInput  textarea.Model
 27	fromAddr   string
 28}
 29
 30// NewComposer initializes a new composer model.
 31func NewComposer(from string) Composer {
 32	m := Composer{fromAddr: from}
 33
 34	m.toInput = textinput.New()
 35	m.toInput.Cursor.Style = cursorStyle
 36	m.toInput.Placeholder = "To"
 37	m.toInput.Focus()
 38	m.toInput.Prompt = "> "
 39	m.toInput.CharLimit = 256
 40
 41	m.subjectInput = textinput.New()
 42	m.subjectInput.Cursor.Style = cursorStyle
 43	m.subjectInput.Placeholder = "Subject"
 44	m.subjectInput.Prompt = "> "
 45	m.subjectInput.CharLimit = 256
 46
 47	m.bodyInput = textarea.New()
 48	m.bodyInput.Cursor.Style = cursorStyle
 49	m.bodyInput.Placeholder = "Body..."
 50	m.bodyInput.Prompt = "> "
 51	m.bodyInput.SetHeight(10)
 52	m.bodyInput.SetWidth(60)
 53
 54	return m
 55}
 56
 57func (m Composer) Init() tea.Cmd {
 58	return textinput.Blink
 59}
 60
 61func (m Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 62	var cmds []tea.Cmd
 63	var cmd tea.Cmd
 64
 65	switch msg := msg.(type) {
 66	case tea.KeyMsg:
 67		switch msg.Type {
 68		case tea.KeyCtrlC, tea.KeyEsc:
 69			return m, tea.Quit
 70
 71		// Handle Tab and Shift+Tab to cycle focus between inputs.
 72		case tea.KeyTab, tea.KeyShiftTab:
 73			if msg.Type == tea.KeyShiftTab {
 74				m.focusIndex--
 75			} else {
 76				m.focusIndex++
 77			}
 78
 79			// Wrap around
 80			if m.focusIndex > 3 { // 3 is the Send button
 81				m.focusIndex = 0
 82			} else if m.focusIndex < 0 {
 83				m.focusIndex = 3
 84			}
 85			
 86			// Blur all inputs
 87			m.toInput.Blur()
 88			m.subjectInput.Blur()
 89			m.bodyInput.Blur()
 90
 91			// Focus the correct input
 92			switch m.focusIndex {
 93			case 0:
 94				m.toInput.Focus()
 95			case 1:
 96				m.subjectInput.Focus()
 97			case 2:
 98				m.bodyInput.Focus()
 99			}
100			return m, tea.Batch(cmds...)
101
102		// Handle Enter key.
103		case tea.KeyEnter:
104			// If on the Send button, send the email.
105			if m.focusIndex == 3 {
106				return m, func() tea.Msg {
107					return SendEmailMsg{
108						To:      m.toInput.Value(),
109						Subject: m.subjectInput.Value(),
110						Body:    m.bodyInput.Value(),
111					}
112				}
113			}
114		}
115	}
116
117	// Update the focused input.
118	switch m.focusIndex {
119	case 0:
120		m.toInput, cmd = m.toInput.Update(msg)
121		cmds = append(cmds, cmd)
122	case 1:
123		m.subjectInput, cmd = m.subjectInput.Update(msg)
124		cmds = append(cmds, cmd)
125	case 2:
126		m.bodyInput, cmd = m.bodyInput.Update(msg)
127		cmds = append(cmds, cmd)
128	}
129
130	return m, tea.Batch(cmds...)
131}
132
133// View renders the UI.
134func (m Composer) View() string {
135	button := &blurredButton
136	if m.focusIndex == 3 {
137		button = &focusedButton
138	}
139
140	return lipgloss.JoinVertical(lipgloss.Left,
141		"Compose New Email",
142		"From: "+emailRecipientStyle.Render(m.fromAddr),
143		m.toInput.View(),
144		m.subjectInput.View(),
145		m.bodyInput.View(),
146		*button,
147		helpStyle.Render("tab: next field • esc: quit"),
148	)
149}