aerc/widgets/selector.go
Moritz Poldrack 978d35d356 lint: homogenize operations and minor fixes (gocritic)
Apply GoDoc comment policy (comments for humans should have a space
after the //; machine-readable comments shouldn't)

Use strings.ReplaceAll instead of strings.Replace when appropriate

Remove if/else chains by replacing them with switches

Use short assignment/increment notation

Replace single case switches with if statements

Combine else and if when appropriate

Signed-off-by: Moritz Poldrack <moritz@poldrack.dev>
Acked-by: Robin Jarry <robin@jarry.cc>
2022-08-04 21:58:01 +02:00

230 lines
4.9 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 widgets
import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib/ui"
)
type Selector struct {
ui.Invalidatable
chooser bool
focused bool
focus int
options []string
uiConfig *config.UIConfig
onChoose func(option string)
onSelect func(option string)
}
func NewSelector(options []string, focus int, uiConfig *config.UIConfig) *Selector {
return &Selector{
focus: focus,
options: options,
uiConfig: uiConfig,
}
}
func (sel *Selector) Chooser(chooser bool) *Selector {
sel.chooser = chooser
return sel
}
func (sel *Selector) Invalidate() {
sel.DoInvalidate(sel)
}
func (sel *Selector) Draw(ctx *ui.Context) {
defaultSelectorStyle := sel.uiConfig.GetStyle(config.STYLE_SELECTOR_DEFAULT)
w, h := ctx.Width(), ctx.Height()
ctx.Fill(0, 0, w, h, ' ', defaultSelectorStyle)
if w < 5 || h < 1 {
// if width and height are that small, don't even try to draw
// something
return
}
y := 1
if h == 1 {
y = 0
}
format := "[%s]"
calculateWidth := func(space int) int {
neededWidth := 2
for i, option := range sel.options {
neededWidth += runewidth.StringWidth(fmt.Sprintf(format, option))
if i < len(sel.options)-1 {
neededWidth += space
}
}
return neededWidth - space
}
space := 5
for ; space > 0; space-- {
if w > calculateWidth(space) {
break
}
}
x := 2
for i, option := range sel.options {
style := defaultSelectorStyle
if sel.focus == i {
if sel.focused {
style = sel.uiConfig.GetStyle(config.STYLE_SELECTOR_FOCUSED)
} else if sel.chooser {
style = sel.uiConfig.GetStyle(config.STYLE_SELECTOR_CHOOSER)
}
}
if space == 0 {
if sel.focus == i {
leftArrow, rightArrow := ' ', ' '
if i > 0 {
leftArrow = ''
}
if i < len(sel.options)-1 {
rightArrow = ''
}
s := runewidth.Truncate(option,
w-runewidth.RuneWidth(leftArrow)-runewidth.RuneWidth(rightArrow)-runewidth.StringWidth(fmt.Sprintf(format, "")),
"…")
nextPos := 0
nextPos += ctx.Printf(nextPos, y, defaultSelectorStyle, "%c", leftArrow)
nextPos += ctx.Printf(nextPos, y, style, format, s)
ctx.Printf(nextPos, y, defaultSelectorStyle, "%c", rightArrow)
}
} else {
x += ctx.Printf(x, y, style, format, option)
x += space
}
}
}
func (sel *Selector) OnChoose(fn func(option string)) *Selector {
sel.onChoose = fn
return sel
}
func (sel *Selector) OnSelect(fn func(option string)) *Selector {
sel.onSelect = fn
return sel
}
func (sel *Selector) Selected() string {
return sel.options[sel.focus]
}
func (sel *Selector) Focus(focus bool) {
sel.focused = focus
sel.Invalidate()
}
func (sel *Selector) Event(event tcell.Event) bool {
if event, ok := event.(*tcell.EventKey); ok {
switch event.Key() {
case tcell.KeyCtrlH:
fallthrough
case tcell.KeyLeft:
if sel.focus > 0 {
sel.focus--
sel.Invalidate()
}
if sel.onSelect != nil {
sel.onSelect(sel.Selected())
}
case tcell.KeyCtrlL:
fallthrough
case tcell.KeyRight:
if sel.focus < len(sel.options)-1 {
sel.focus++
sel.Invalidate()
}
if sel.onSelect != nil {
sel.onSelect(sel.Selected())
}
case tcell.KeyEnter:
if sel.onChoose != nil {
sel.onChoose(sel.Selected())
}
}
}
return false
}
var ErrNoOptionSelected = fmt.Errorf("no option selected")
type SelectorDialog struct {
ui.Invalidatable
callback func(string, error)
title string
prompt string
uiConfig *config.UIConfig
selector *Selector
}
func NewSelectorDialog(title string, prompt string, options []string, focus int,
uiConfig *config.UIConfig, cb func(string, error),
) *SelectorDialog {
sd := &SelectorDialog{
callback: cb,
title: title,
prompt: prompt,
uiConfig: uiConfig,
selector: NewSelector(options, focus, uiConfig).Chooser(true),
}
sd.selector.OnInvalidate(func(_ ui.Drawable) {
sd.Invalidate()
})
sd.selector.Focus(true)
return sd
}
func (gp *SelectorDialog) Draw(ctx *ui.Context) {
defaultStyle := gp.uiConfig.GetStyle(config.STYLE_DEFAULT)
titleStyle := gp.uiConfig.GetStyle(config.STYLE_TITLE)
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', defaultStyle)
ctx.Fill(0, 0, ctx.Width(), 1, ' ', titleStyle)
ctx.Printf(1, 0, titleStyle, "%s", gp.title)
ctx.Printf(1, 1, defaultStyle, gp.prompt)
gp.selector.Draw(ctx.Subcontext(1, 3, ctx.Width()-2, 1))
}
func (gp *SelectorDialog) Invalidate() {
gp.DoInvalidate(gp)
}
func (gp *SelectorDialog) Event(event tcell.Event) bool {
switch event := event.(type) {
case *tcell.EventKey:
switch event.Key() {
case tcell.KeyEnter:
gp.selector.Focus(false)
gp.callback(gp.selector.Selected(), nil)
case tcell.KeyEsc:
gp.selector.Focus(false)
gp.callback("", ErrNoOptionSelected)
default:
gp.selector.Event(event)
}
default:
gp.selector.Event(event)
}
return true
}
func (gp *SelectorDialog) Focus(f bool) {
gp.selector.Focus(f)
}