mblaze-ui

A minimal TUI for the mblaze email client

git clone https://git.8pit.net/mblaze-ui.git

  1package main
  2
  3import (
  4	"github.com/gdamore/tcell/v2"
  5)
  6
  7type UserInterface struct {
  8	Mails  []Mail
  9	Screen tcell.Screen
 10	index  int
 11}
 12
 13const (
 14	// Rune used to indicate that the row has been abbreviated.
 15	abbreviated = '…'
 16)
 17
 18var (
 19	// Style used for non-selected rows.
 20	defStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
 21
 22	// Style used for the currently selected row.
 23	selStyle = tcell.StyleDefault.Background(tcell.ColorWhite).Foreground(tcell.ColorBlack)
 24)
 25
 26func NewUI(mails []Mail, screen tcell.Screen) *UserInterface {
 27	return &UserInterface{
 28		Mails:  mails,
 29		Screen: screen,
 30		index:  0,
 31	}
 32}
 33
 34func (ui *UserInterface) Refresh() error {
 35	var err error
 36	ui.Mails, err = mscan()
 37	if err != nil {
 38		return err
 39	}
 40
 41	// If the last mail was modified, the length of the sequence could
 42	// have been reduced by 1; therefore, we need to update the index.
 43	ui.index = min(ui.index, len(ui.Mails)-1)
 44	return nil
 45}
 46
 47func (ui *UserInterface) visible() int {
 48	_, ymax := ui.Screen.Size()
 49	return min(ymax, len(ui.Mails))
 50}
 51
 52func (ui *UserInterface) SelectedMail() Mail {
 53	return ui.Mails[ui.index]
 54}
 55
 56func (ui *UserInterface) IsSelected(m Mail) bool {
 57	selected := ui.SelectedMail()
 58	return selected.Path == m.Path
 59}
 60
 61func (ui *UserInterface) NextMail() {
 62	if ui.index < len(ui.Mails)-1 {
 63		ui.index++
 64		ui.Draw()
 65	}
 66}
 67
 68func (ui *UserInterface) PrevMail() {
 69	if ui.index > 0 {
 70		ui.index--
 71		ui.Draw()
 72	}
 73}
 74
 75func (ui *UserInterface) NextPage() {
 76	ui.index = min(len(ui.Mails)-1, ui.index+ui.visible())
 77	ui.Draw()
 78}
 79
 80func (ui *UserInterface) PrevPage() {
 81	visible := ui.visible()
 82	if ui.index > visible {
 83		ui.index = ui.index - visible
 84	} else {
 85		ui.index = 0
 86	}
 87	ui.Draw()
 88}
 89
 90func (ui *UserInterface) Draw() {
 91	xmax, _ := ui.Screen.Size()
 92	if xmax <= 1 {
 93		panic("terminal is too small")
 94	}
 95
 96	mails := ui.Mails
 97	visible := ui.visible()
 98	if ui.index >= visible {
 99		start := ui.index - visible
100		mails = ui.Mails[start+1:]
101	}
102
103	y := 0
104	for _, mail := range mails {
105		text := mail.String()
106
107		var style tcell.Style
108		if ui.IsSelected(mail) {
109			style = selStyle
110		} else {
111			style = defStyle
112		}
113
114		truncated := false
115		if len(text) >= xmax {
116			text = text[0 : len(text)-1]
117			truncated = true
118		}
119		lastCol := drawText(ui.Screen, y, 0, style, text)
120		if truncated {
121			// TODO: Determine cells needed for abbreviated.
122			ui.Screen.SetContent(xmax-1, y, abbreviated, nil, style)
123		} else {
124			for x := lastCol; x < xmax; x++ {
125				ui.Screen.SetContent(x, y, ' ', nil, style)
126			}
127		}
128
129		y++
130		if y >= visible {
131			break
132		}
133	}
134}
135
136func (ui *UserInterface) withoutScreen(fn func() error) error {
137	ui.Screen.Fini()
138	err := fn()
139	if err != nil {
140		return err
141	}
142
143	ui.Screen, err = initScreen()
144	if err != nil {
145		return err
146	}
147	ui.Draw()
148
149	return nil
150}