Add UI options to save/pipe messages with unsupported mimetypes
Adds a message indicating the user's ability to :save or :pipe a message with an unsupported mimetype and also adds a selector widget (similar to the tutorial). The selector widget was previously defined in the account wizard module, so this commit breaks it out into its own module to allow for re-use. Further, modify the BeginExLine() function to take an argument that pre-populates the command line, allowing functions to initiate an ex command without executing it. Closes #95.
This commit is contained in:
parent
809083f843
commit
4bdef7d860
7 changed files with 163 additions and 123 deletions
|
@ -63,9 +63,10 @@ func (ti *TextInput) StringRight() string {
|
||||||
return string(ti.text[ti.index:])
|
return string(ti.text[ti.index:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ti *TextInput) Set(value string) {
|
func (ti *TextInput) Set(value string) *TextInput {
|
||||||
ti.text = []rune(value)
|
ti.text = []rune(value)
|
||||||
ti.index = len(ti.text)
|
ti.index = len(ti.text)
|
||||||
|
return ti
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ti *TextInput) Invalidate() {
|
func (ti *TextInput) Invalidate() {
|
||||||
|
|
|
@ -177,7 +177,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
At(7, 0)
|
At(7, 0)
|
||||||
basics.AddChild(wizard.email).
|
basics.AddChild(wizard.email).
|
||||||
At(8, 0)
|
At(8, 0)
|
||||||
selecter := newSelecter([]string{"Next"}, 0).
|
selecter := NewSelecter([]string{"Next"}, 0).
|
||||||
OnChoose(func(option string) {
|
OnChoose(func(option string) {
|
||||||
email := wizard.email.String()
|
email := wizard.email.String()
|
||||||
if strings.ContainsRune(email, '@') {
|
if strings.ContainsRune(email, '@') {
|
||||||
|
@ -254,7 +254,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
incoming.AddChild(
|
incoming.AddChild(
|
||||||
ui.NewText("Connection mode").Bold(true)).
|
ui.NewText("Connection mode").Bold(true)).
|
||||||
At(10, 0)
|
At(10, 0)
|
||||||
imapMode := newSelecter([]string{
|
imapMode := NewSelecter([]string{
|
||||||
"IMAP over SSL/TLS",
|
"IMAP over SSL/TLS",
|
||||||
"IMAP with STARTTLS",
|
"IMAP with STARTTLS",
|
||||||
"Insecure IMAP",
|
"Insecure IMAP",
|
||||||
|
@ -270,7 +270,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
wizard.imapUri()
|
wizard.imapUri()
|
||||||
})
|
})
|
||||||
incoming.AddChild(imapMode).At(11, 0)
|
incoming.AddChild(imapMode).At(11, 0)
|
||||||
selecter = newSelecter([]string{"Previous", "Next"}, 1).
|
selecter = NewSelecter([]string{"Previous", "Next"}, 1).
|
||||||
OnChoose(wizard.advance)
|
OnChoose(wizard.advance)
|
||||||
incoming.AddChild(ui.NewFill(' ')).At(12, 0)
|
incoming.AddChild(ui.NewFill(' ')).At(12, 0)
|
||||||
incoming.AddChild(wizard.imapStr).At(13, 0)
|
incoming.AddChild(wizard.imapStr).At(13, 0)
|
||||||
|
@ -331,7 +331,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
outgoing.AddChild(
|
outgoing.AddChild(
|
||||||
ui.NewText("Connection mode").Bold(true)).
|
ui.NewText("Connection mode").Bold(true)).
|
||||||
At(10, 0)
|
At(10, 0)
|
||||||
smtpMode := newSelecter([]string{
|
smtpMode := NewSelecter([]string{
|
||||||
"SMTP over SSL/TLS",
|
"SMTP over SSL/TLS",
|
||||||
"SMTP with STARTTLS",
|
"SMTP with STARTTLS",
|
||||||
"Insecure SMTP",
|
"Insecure SMTP",
|
||||||
|
@ -347,7 +347,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
wizard.smtpUri()
|
wizard.smtpUri()
|
||||||
})
|
})
|
||||||
outgoing.AddChild(smtpMode).At(11, 0)
|
outgoing.AddChild(smtpMode).At(11, 0)
|
||||||
selecter = newSelecter([]string{"Previous", "Next"}, 1).
|
selecter = NewSelecter([]string{"Previous", "Next"}, 1).
|
||||||
OnChoose(wizard.advance)
|
OnChoose(wizard.advance)
|
||||||
outgoing.AddChild(ui.NewFill(' ')).At(12, 0)
|
outgoing.AddChild(ui.NewFill(' ')).At(12, 0)
|
||||||
outgoing.AddChild(wizard.smtpStr).At(13, 0)
|
outgoing.AddChild(wizard.smtpStr).At(13, 0)
|
||||||
|
@ -355,7 +355,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
outgoing.AddChild(
|
outgoing.AddChild(
|
||||||
ui.NewText("Copy sent messages to 'Sent' folder?").Bold(true)).
|
ui.NewText("Copy sent messages to 'Sent' folder?").Bold(true)).
|
||||||
At(15, 0)
|
At(15, 0)
|
||||||
copySent := newSelecter([]string{"Yes", "No"}, 0).
|
copySent := NewSelecter([]string{"Yes", "No"}, 0).
|
||||||
Chooser(true).OnChoose(func(option string) {
|
Chooser(true).OnChoose(func(option string) {
|
||||||
switch option {
|
switch option {
|
||||||
case "Yes":
|
case "Yes":
|
||||||
|
@ -385,7 +385,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
||||||
"You can go back and double check your settings, or choose 'Finish' to\n" +
|
"You can go back and double check your settings, or choose 'Finish' to\n" +
|
||||||
"save your settings to accounts.conf.\n\n" +
|
"save your settings to accounts.conf.\n\n" +
|
||||||
"To add another account in the future, run ':new-account'."))
|
"To add another account in the future, run ':new-account'."))
|
||||||
selecter = newSelecter([]string{
|
selecter = NewSelecter([]string{
|
||||||
"Previous",
|
"Previous",
|
||||||
"Finish & open tutorial",
|
"Finish & open tutorial",
|
||||||
"Finish",
|
"Finish",
|
||||||
|
@ -716,102 +716,6 @@ func (wizard *AccountWizard) Event(event tcell.Event) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type selecter struct {
|
|
||||||
ui.Invalidatable
|
|
||||||
chooser bool
|
|
||||||
focused bool
|
|
||||||
focus int
|
|
||||||
options []string
|
|
||||||
|
|
||||||
onChoose func(option string)
|
|
||||||
onSelect func(option string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSelecter(options []string, focus int) *selecter {
|
|
||||||
return &selecter{
|
|
||||||
focus: focus,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) Chooser(chooser bool) *selecter {
|
|
||||||
sel.chooser = chooser
|
|
||||||
return sel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) Invalidate() {
|
|
||||||
sel.DoInvalidate(sel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) Draw(ctx *ui.Context) {
|
|
||||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
|
||||||
x := 2
|
|
||||||
for i, option := range sel.options {
|
|
||||||
style := tcell.StyleDefault
|
|
||||||
if sel.focus == i {
|
|
||||||
if sel.focused {
|
|
||||||
style = style.Reverse(true)
|
|
||||||
} else if sel.chooser {
|
|
||||||
style = style.Bold(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x += ctx.Printf(x, 1, style, "[%s]", option)
|
|
||||||
x += 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) OnChoose(fn func(option string)) *selecter {
|
|
||||||
sel.onChoose = fn
|
|
||||||
return sel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) OnSelect(fn func(option string)) *selecter {
|
|
||||||
sel.onSelect = fn
|
|
||||||
return sel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) Selected() string {
|
|
||||||
return sel.options[sel.focus]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) Focus(focus bool) {
|
|
||||||
sel.focused = focus
|
|
||||||
sel.Invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sel *selecter) Event(event tcell.Event) bool {
|
|
||||||
switch event := event.(type) {
|
|
||||||
case *tcell.EventKey:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSRV(host string, services []string) (string, string) {
|
func getSRV(host string, services []string) (string, string) {
|
||||||
var hostport, srv string
|
var hostport, srv string
|
||||||
for _, srv = range services {
|
for _, srv = range services {
|
||||||
|
|
|
@ -240,7 +240,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
|
||||||
exKey = aerc.conf.Bindings.Global.ExKey
|
exKey = aerc.conf.Bindings.Global.ExKey
|
||||||
}
|
}
|
||||||
if event.Key() == exKey.Key && event.Rune() == exKey.Rune {
|
if event.Key() == exKey.Key && event.Rune() == exKey.Rune {
|
||||||
aerc.BeginExCommand()
|
aerc.BeginExCommand("")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive)
|
interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive)
|
||||||
|
@ -370,9 +370,9 @@ func (aerc *Aerc) focus(item ui.Interactive) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aerc *Aerc) BeginExCommand() {
|
func (aerc *Aerc) BeginExCommand(cmd string) {
|
||||||
previous := aerc.focused
|
previous := aerc.focused
|
||||||
exline := NewExLine(func(cmd string) {
|
exline := NewExLine(cmd, func(cmd string) {
|
||||||
parts, err := shlex.Split(cmd)
|
parts, err := shlex.Split(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aerc.PushStatus(" "+err.Error(), 10*time.Second).
|
aerc.PushStatus(" "+err.Error(), 10*time.Second).
|
||||||
|
|
|
@ -16,11 +16,11 @@ type ExLine struct {
|
||||||
input *ui.TextInput
|
input *ui.TextInput
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExLine(commit func(cmd string), finish func(),
|
func NewExLine(cmd string, commit func(cmd string), finish func(),
|
||||||
tabcomplete func(cmd string) []string,
|
tabcomplete func(cmd string) []string,
|
||||||
cmdHistory lib.History) *ExLine {
|
cmdHistory lib.History) *ExLine {
|
||||||
|
|
||||||
input := ui.NewTextInput("").Prompt(":").TabComplete(tabcomplete)
|
input := ui.NewTextInput("").Prompt(":").TabComplete(tabcomplete).Set(cmd)
|
||||||
exline := &ExLine{
|
exline := &ExLine{
|
||||||
commit: commit,
|
commit: commit,
|
||||||
finish: finish,
|
finish: finish,
|
||||||
|
|
|
@ -68,7 +68,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
switcher := &PartSwitcher{}
|
switcher := &PartSwitcher{}
|
||||||
err := createSwitcher(switcher, conf, store, msg)
|
err := createSwitcher(acct, switcher, conf, store, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &MessageViewer{
|
return &MessageViewer{
|
||||||
err: err,
|
err: err,
|
||||||
|
@ -112,7 +112,7 @@ func fmtHeader(msg *models.MessageInfo, header string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
func enumerateParts(acct *AccountView, conf *config.AercConfig, store *lib.MessageStore,
|
||||||
msg *models.MessageInfo, body *models.BodyStructure,
|
msg *models.MessageInfo, body *models.BodyStructure,
|
||||||
index []int) ([]*PartViewer, error) {
|
index []int) ([]*PartViewer, error) {
|
||||||
|
|
||||||
|
@ -124,14 +124,14 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
||||||
pv := &PartViewer{part: part}
|
pv := &PartViewer{part: part}
|
||||||
parts = append(parts, pv)
|
parts = append(parts, pv)
|
||||||
subParts, err := enumerateParts(
|
subParts, err := enumerateParts(
|
||||||
conf, store, msg, part, curindex)
|
acct, conf, store, msg, part, curindex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
parts = append(parts, subParts...)
|
parts = append(parts, subParts...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pv, err := NewPartViewer(conf, store, msg, part, curindex)
|
pv, err := NewPartViewer(acct, conf, store, msg, part, curindex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
||||||
return parts, nil
|
return parts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
|
func createSwitcher(acct *AccountView, switcher *PartSwitcher, conf *config.AercConfig,
|
||||||
store *lib.MessageStore, msg *models.MessageInfo) error {
|
store *lib.MessageStore, msg *models.MessageInfo) error {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -150,7 +150,7 @@ func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
|
||||||
|
|
||||||
if len(msg.BodyStructure.Parts) == 0 {
|
if len(msg.BodyStructure.Parts) == 0 {
|
||||||
switcher.selected = 0
|
switcher.selected = 0
|
||||||
pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure, []int{1})
|
pv, err := NewPartViewer(acct, conf, store, msg, msg.BodyStructure, []int{1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
|
||||||
switcher.Invalidate()
|
switcher.Invalidate()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
switcher.parts, err = enumerateParts(conf, store,
|
switcher.parts, err = enumerateParts(acct, conf, store,
|
||||||
msg, msg.BodyStructure, []int{})
|
msg, msg.BodyStructure, []int{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -236,7 +236,7 @@ func (mv *MessageViewer) ToggleHeaders() {
|
||||||
switcher := mv.switcher
|
switcher := mv.switcher
|
||||||
mv.conf.Viewer.ShowHeaders = !mv.conf.Viewer.ShowHeaders
|
mv.conf.Viewer.ShowHeaders = !mv.conf.Viewer.ShowHeaders
|
||||||
err := createSwitcher(
|
err := createSwitcher(
|
||||||
switcher, mv.conf, mv.store, mv.msg)
|
mv.acct, switcher, mv.conf, mv.store, mv.msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mv.acct.Logger().Printf(
|
mv.acct.Logger().Printf(
|
||||||
"warning: error during create switcher - %v", err)
|
"warning: error during create switcher - %v", err)
|
||||||
|
@ -299,10 +299,7 @@ func (ps *PartSwitcher) Focus(focus bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PartSwitcher) Event(event tcell.Event) bool {
|
func (ps *PartSwitcher) Event(event tcell.Event) bool {
|
||||||
if ps.parts[ps.selected].term != nil {
|
return ps.parts[ps.selected].Event(event)
|
||||||
return ps.parts[ps.selected].term.Event(event)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PartSwitcher) Draw(ctx *ui.Context) {
|
func (ps *PartSwitcher) Draw(ctx *ui.Context) {
|
||||||
|
@ -414,9 +411,11 @@ type PartViewer struct {
|
||||||
source io.Reader
|
source io.Reader
|
||||||
store *lib.MessageStore
|
store *lib.MessageStore
|
||||||
term *Terminal
|
term *Terminal
|
||||||
|
selecter *Selecter
|
||||||
|
grid *ui.Grid
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartViewer(conf *config.AercConfig,
|
func NewPartViewer(acct *AccountView, conf *config.AercConfig,
|
||||||
store *lib.MessageStore, msg *models.MessageInfo,
|
store *lib.MessageStore, msg *models.MessageInfo,
|
||||||
part *models.BodyStructure,
|
part *models.BodyStructure,
|
||||||
index []int) (*PartViewer, error) {
|
index []int) (*PartViewer, error) {
|
||||||
|
@ -475,6 +474,26 @@ func NewPartViewer(conf *config.AercConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
||||||
|
{ui.SIZE_EXACT, 3}, // Message
|
||||||
|
{ui.SIZE_EXACT, 1}, // Selector
|
||||||
|
{ui.SIZE_WEIGHT, 1},
|
||||||
|
}).Columns([]ui.GridSpec{
|
||||||
|
{ui.SIZE_WEIGHT, 1},
|
||||||
|
})
|
||||||
|
|
||||||
|
selecter := NewSelecter([]string{"Save message", "Pipe to command"}, 0).
|
||||||
|
OnChoose(func(option string) {
|
||||||
|
switch option {
|
||||||
|
case "Save message":
|
||||||
|
acct.aerc.BeginExCommand("save ")
|
||||||
|
case "Pipe to command":
|
||||||
|
acct.aerc.BeginExCommand("pipe ")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.AddChild(selecter).At(2, 0)
|
||||||
|
|
||||||
pv := &PartViewer{
|
pv := &PartViewer{
|
||||||
filter: filter,
|
filter: filter,
|
||||||
index: index,
|
index: index,
|
||||||
|
@ -486,6 +505,8 @@ func NewPartViewer(conf *config.AercConfig,
|
||||||
sink: pipe,
|
sink: pipe,
|
||||||
store: store,
|
store: store,
|
||||||
term: term,
|
term: term,
|
||||||
|
selecter: selecter,
|
||||||
|
grid: grid,
|
||||||
}
|
}
|
||||||
|
|
||||||
if term != nil {
|
if term != nil {
|
||||||
|
@ -590,6 +611,10 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
|
||||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||||
ctx.Printf(0, 0, tcell.StyleDefault.Foreground(tcell.ColorRed),
|
ctx.Printf(0, 0, tcell.StyleDefault.Foreground(tcell.ColorRed),
|
||||||
"No filter configured for this mimetype")
|
"No filter configured for this mimetype")
|
||||||
|
ctx.Printf(0, 2, tcell.StyleDefault,
|
||||||
|
"You can still :save the message or :pipe it to an external command")
|
||||||
|
pv.selecter.Focus(true)
|
||||||
|
pv.grid.Draw(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !pv.fetched {
|
if !pv.fetched {
|
||||||
|
@ -611,6 +636,13 @@ func (pv *PartViewer) Cleanup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pv *PartViewer) Event(event tcell.Event) bool {
|
||||||
|
if pv.term != nil {
|
||||||
|
return pv.term.Event(event)
|
||||||
|
}
|
||||||
|
return pv.selecter.Event(event)
|
||||||
|
}
|
||||||
|
|
||||||
type HeaderView struct {
|
type HeaderView struct {
|
||||||
ui.Invalidatable
|
ui.Invalidatable
|
||||||
Name string
|
Name string
|
||||||
|
|
103
widgets/selecter.go
Normal file
103
widgets/selecter.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/lib/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Selecter struct {
|
||||||
|
ui.Invalidatable
|
||||||
|
chooser bool
|
||||||
|
focused bool
|
||||||
|
focus int
|
||||||
|
options []string
|
||||||
|
|
||||||
|
onChoose func(option string)
|
||||||
|
onSelect func(option string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSelecter(options []string, focus int) *Selecter {
|
||||||
|
return &Selecter{
|
||||||
|
focus: focus,
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) Chooser(chooser bool) *Selecter {
|
||||||
|
sel.chooser = chooser
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) Invalidate() {
|
||||||
|
sel.DoInvalidate(sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) Draw(ctx *ui.Context) {
|
||||||
|
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||||
|
x := 2
|
||||||
|
for i, option := range sel.options {
|
||||||
|
style := tcell.StyleDefault
|
||||||
|
if sel.focus == i {
|
||||||
|
if sel.focused {
|
||||||
|
style = style.Reverse(true)
|
||||||
|
} else if sel.chooser {
|
||||||
|
style = style.Bold(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x += ctx.Printf(x, 1, style, "[%s]", option)
|
||||||
|
x += 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) OnChoose(fn func(option string)) *Selecter {
|
||||||
|
sel.onChoose = fn
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) OnSelect(fn func(option string)) *Selecter {
|
||||||
|
sel.onSelect = fn
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) Selected() string {
|
||||||
|
return sel.options[sel.focus]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) Focus(focus bool) {
|
||||||
|
sel.focused = focus
|
||||||
|
sel.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sel *Selecter) Event(event tcell.Event) bool {
|
||||||
|
switch event := event.(type) {
|
||||||
|
case *tcell.EventKey:
|
||||||
|
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
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TabHost interface {
|
type TabHost interface {
|
||||||
BeginExCommand()
|
BeginExCommand(cmd string)
|
||||||
SetStatus(status string) *StatusMessage
|
SetStatus(status string) *StatusMessage
|
||||||
PushStatus(text string, expiry time.Duration) *StatusMessage
|
PushStatus(text string, expiry time.Duration) *StatusMessage
|
||||||
Beep()
|
Beep()
|
||||||
|
|
Loading…
Reference in a new issue