diff options
author | Reto Brunner <reto@labrat.space> | 2020-05-27 07:37:02 +0200 |
---|---|---|
committer | Reto Brunner <reto@labrat.space> | 2020-05-27 07:57:10 +0200 |
commit | 0f78f06610c0e8887aba2ae50e99b86477a384b3 (patch) | |
tree | ff4cd6581d3af0911954a37550602366d2bb0e2f /widgets | |
parent | 6c4ed3cfe2fe66a1e5f26c404ea90e048142db72 (diff) | |
download | aerc-0f78f06610c0e8887aba2ae50e99b86477a384b3.tar.gz |
Add Style configuration
The following functionalities are added to configure aerc ui styles. - Read stylesets from file with very basic fnmatch wildcard matching - Add default styleset - Support different stylesets as part of UiConfig allowing contextual styles. - Move widgets/ui elements to use the stylesets. - Add configuration manual for the styleset
Diffstat (limited to 'widgets')
-rw-r--r-- | widgets/account-wizard.go | 109 | ||||
-rw-r--r-- | widgets/account.go | 11 | ||||
-rw-r--r-- | widgets/aerc.go | 29 | ||||
-rw-r--r-- | widgets/compose.go | 69 | ||||
-rw-r--r-- | widgets/dirlist.go | 12 | ||||
-rw-r--r-- | widgets/exline.go | 6 | ||||
-rw-r--r-- | widgets/getpasswd.go | 18 | ||||
-rw-r--r-- | widgets/msglist.go | 55 | ||||
-rw-r--r-- | widgets/msgviewer.go | 63 | ||||
-rw-r--r-- | widgets/pgpinfo.go | 34 | ||||
-rw-r--r-- | widgets/selecter.go | 26 | ||||
-rw-r--r-- | widgets/spinner.go | 6 | ||||
-rw-r--r-- | widgets/status.go | 49 | ||||
-rw-r--r-- | widgets/tabhost.go | 3 |
14 files changed, 295 insertions, 195 deletions
diff --git a/widgets/account-wizard.go b/widgets/account-wizard.go index 6f93367..e247dd2 100644 --- a/widgets/account-wizard.go +++ b/widgets/account-wizard.go @@ -10,6 +10,7 @@ import ( "path" "strconv" "strings" + "time" "github.com/gdamore/tcell" "github.com/go-ini/ini" @@ -75,21 +76,21 @@ type AccountWizard struct { func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { wizard := &AccountWizard{ - accountName: ui.NewTextInput("").Prompt("> "), + accountName: ui.NewTextInput("", conf.Ui).Prompt("> "), aerc: aerc, conf: conf, temporary: false, copySent: true, - email: ui.NewTextInput("").Prompt("> "), - fullName: ui.NewTextInput("").Prompt("> "), - imapPassword: ui.NewTextInput("").Prompt("] ").Password(true), - imapServer: ui.NewTextInput("").Prompt("> "), - imapStr: ui.NewText("imaps://"), - imapUsername: ui.NewTextInput("").Prompt("> "), - smtpPassword: ui.NewTextInput("").Prompt("] ").Password(true), - smtpServer: ui.NewTextInput("").Prompt("> "), - smtpStr: ui.NewText("smtps://"), - smtpUsername: ui.NewTextInput("").Prompt("> "), + email: ui.NewTextInput("", conf.Ui).Prompt("> "), + fullName: ui.NewTextInput("", conf.Ui).Prompt("> "), + imapPassword: ui.NewTextInput("", conf.Ui).Prompt("] ").Password(true), + imapServer: ui.NewTextInput("", conf.Ui).Prompt("> "), + imapStr: ui.NewText("imaps://", conf.Ui.GetStyle(config.STYLE_DEFAULT)), + imapUsername: ui.NewTextInput("", conf.Ui).Prompt("> "), + smtpPassword: ui.NewTextInput("", conf.Ui).Prompt("] ").Password(true), + smtpServer: ui.NewTextInput("", conf.Ui).Prompt("> "), + smtpStr: ui.NewText("smtps://", conf.Ui.GetStyle(config.STYLE_DEFAULT)), + smtpUsername: ui.NewTextInput("", conf.Ui).Prompt("> "), } // Autofill some stuff for the user @@ -150,33 +151,36 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { {ui.SIZE_WEIGHT, 1}, }) basics.AddChild( - ui.NewText("\nWelcome to aerc! Let's configure your account.\n\n" + - "This wizard supports basic IMAP & SMTP configuration.\n" + - "For other configurations, use <Ctrl+q> to exit and read the " + - "aerc-config(5) man page.\n" + - "Press <Tab> and <Shift+Tab> to cycle between each field in this form, or <Ctrl+j> and <Ctrl+k>.")) + ui.NewText("\nWelcome to aerc! Let's configure your account.\n\n"+ + "This wizard supports basic IMAP & SMTP configuration.\n"+ + "For other configurations, use <Ctrl+q> to exit and read the "+ + "aerc-config(5) man page.\n"+ + "Press <Tab> and <Shift+Tab> to cycle between each field in this form, "+ + "or <Ctrl+j> and <Ctrl+k>.", + conf.Ui.GetStyle(config.STYLE_DEFAULT))) basics.AddChild( - ui.NewText("Name for this account? (e.g. 'Personal' or 'Work')"). - Bold(true)). + ui.NewText("Name for this account? (e.g. 'Personal' or 'Work')", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(1, 0) basics.AddChild(wizard.accountName). At(2, 0) basics.AddChild(ui.NewFill(' ')). At(3, 0) basics.AddChild( - ui.NewText("Full name for outgoing emails? (e.g. 'John Doe')"). - Bold(true)). + ui.NewText("Full name for outgoing emails? (e.g. 'John Doe')", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(4, 0) basics.AddChild(wizard.fullName). At(5, 0) basics.AddChild(ui.NewFill(' ')). At(6, 0) basics.AddChild( - ui.NewText("Your email address? (e.g. 'john@example.org')").Bold(true)). + ui.NewText("Your email address? (e.g. 'john@example.org')", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(7, 0) basics.AddChild(wizard.email). At(8, 0) - selecter := NewSelecter([]string{"Next"}, 0). + selecter := NewSelecter([]string{"Next"}, 0, conf.Ui). OnChoose(func(option string) { email := wizard.email.String() if strings.ContainsRune(email, '@') { @@ -227,16 +231,19 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { }).Columns([]ui.GridSpec{ {ui.SIZE_WEIGHT, 1}, }) - incoming.AddChild(ui.NewText("\nConfigure incoming mail (IMAP)")) + incoming.AddChild(ui.NewText("\nConfigure incoming mail (IMAP)", + conf.Ui.GetStyle(config.STYLE_DEFAULT))) incoming.AddChild( - ui.NewText("Username").Bold(true)). + ui.NewText("Username", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(1, 0) incoming.AddChild(wizard.imapUsername). At(2, 0) incoming.AddChild(ui.NewFill(' ')). At(3, 0) incoming.AddChild( - ui.NewText("Password").Bold(true)). + ui.NewText("Password", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(4, 0) incoming.AddChild(wizard.imapPassword). At(5, 0) @@ -244,20 +251,22 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { At(6, 0) incoming.AddChild( ui.NewText("Server address "+ - "(e.g. 'mail.example.org' or 'mail.example.org:1313')").Bold(true)). + "(e.g. 'mail.example.org' or 'mail.example.org:1313')", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(7, 0) incoming.AddChild(wizard.imapServer). At(8, 0) incoming.AddChild(ui.NewFill(' ')). At(9, 0) incoming.AddChild( - ui.NewText("Connection mode").Bold(true)). + ui.NewText("Connection mode", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(10, 0) imapMode := NewSelecter([]string{ "IMAP over SSL/TLS", "IMAP with STARTTLS", "Insecure IMAP", - }, 0).Chooser(true).OnSelect(func(option string) { + }, 0, conf.Ui).Chooser(true).OnSelect(func(option string) { switch option { case "IMAP over SSL/TLS": wizard.imapMode = IMAP_OVER_TLS @@ -269,7 +278,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { wizard.imapUri() }) incoming.AddChild(imapMode).At(11, 0) - selecter = NewSelecter([]string{"Previous", "Next"}, 1). + selecter = NewSelecter([]string{"Previous", "Next"}, 1, conf.Ui). OnChoose(wizard.advance) incoming.AddChild(ui.NewFill(' ')).At(12, 0) incoming.AddChild(wizard.imapStr).At(13, 0) @@ -304,16 +313,19 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { }).Columns([]ui.GridSpec{ {ui.SIZE_WEIGHT, 1}, }) - outgoing.AddChild(ui.NewText("\nConfigure outgoing mail (SMTP)")) + outgoing.AddChild(ui.NewText("\nConfigure outgoing mail (SMTP)", + conf.Ui.GetStyle(config.STYLE_DEFAULT))) outgoing.AddChild( - ui.NewText("Username").Bold(true)). + ui.NewText("Username", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(1, 0) outgoing.AddChild(wizard.smtpUsername). At(2, 0) outgoing.AddChild(ui.NewFill(' ')). At(3, 0) outgoing.AddChild( - ui.NewText("Password").Bold(true)). + ui.NewText("Password", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(4, 0) outgoing.AddChild(wizard.smtpPassword). At(5, 0) @@ -321,20 +333,22 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { At(6, 0) outgoing.AddChild( ui.NewText("Server address "+ - "(e.g. 'mail.example.org' or 'mail.example.org:1313')").Bold(true)). + "(e.g. 'mail.example.org' or 'mail.example.org:1313')", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(7, 0) outgoing.AddChild(wizard.smtpServer). At(8, 0) outgoing.AddChild(ui.NewFill(' ')). At(9, 0) outgoing.AddChild( - ui.NewText("Connection mode").Bold(true)). + ui.NewText("Connection mode", + conf.Ui.GetStyle(config.STYLE_HEADER))). At(10, 0) smtpMode := NewSelecter([]string{ "SMTP over SSL/TLS", "SMTP with STARTTLS", "Insecure SMTP", - }, 0).Chooser(true).OnSelect(func(option string) { + }, 0, conf.Ui).Chooser(true).OnSelect(func(option string) { switch option { case "SMTP over SSL/TLS": wizard.smtpMode = SMTP_OVER_TLS @@ -346,15 +360,15 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { wizard.smtpUri() }) outgoing.AddChild(smtpMode).At(11, 0) - selecter = NewSelecter([]string{"Previous", "Next"}, 1). + selecter = NewSelecter([]string{"Previous", "Next"}, 1, conf.Ui). OnChoose(wizard.advance) outgoing.AddChild(ui.NewFill(' ')).At(12, 0) outgoing.AddChild(wizard.smtpStr).At(13, 0) outgoing.AddChild(ui.NewFill(' ')).At(14, 0) outgoing.AddChild( - ui.NewText("Copy sent messages to 'Sent' folder?").Bold(true)). - At(15, 0) - copySent := NewSelecter([]string{"Yes", "No"}, 0). + ui.NewText("Copy sent messages to 'Sent' folder?", + conf.Ui.GetStyle(config.STYLE_HEADER))).At(15, 0) + copySent := NewSelecter([]string{"Yes", "No"}, 0, conf.Ui). Chooser(true).OnChoose(func(option string) { switch option { case "Yes": @@ -380,15 +394,16 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard { {ui.SIZE_WEIGHT, 1}, }) complete.AddChild(ui.NewText( - "\nConfiguration complete!\n\n" + - "You can go back and double check your settings, or choose 'Finish' to\n" + - "save your settings to accounts.conf.\n\n" + - "To add another account in the future, run ':new-account'.")) + "\nConfiguration complete!\n\n"+ + "You can go back and double check your settings, or choose 'Finish' to\n"+ + "save your settings to accounts.conf.\n\n"+ + "To add another account in the future, run ':new-account'.", + conf.Ui.GetStyle(config.STYLE_DEFAULT))) selecter = NewSelecter([]string{ "Previous", "Finish & open tutorial", "Finish", - }, 1).OnChoose(func(option string) { + }, 1, conf.Ui).OnChoose(func(option string) { switch option { case "Previous": wizard.advance("Previous") @@ -414,7 +429,7 @@ func (wizard *AccountWizard) ConfigureTemporaryAccount(temporary bool) { func (wizard *AccountWizard) errorFor(d ui.Interactive, err error) { if d == nil { - wizard.aerc.PushError(" " + err.Error()) + wizard.aerc.PushError(" "+err.Error(), 10*time.Second) wizard.Invalidate() return } @@ -429,7 +444,7 @@ func (wizard *AccountWizard) errorFor(d ui.Interactive, err error) { wizard.step = step wizard.focus = focus wizard.Focus(true) - wizard.aerc.PushError(" " + err.Error()) + wizard.aerc.PushError(" "+err.Error(), 10*time.Second) wizard.Invalidate() return } @@ -540,7 +555,7 @@ func (wizard *AccountWizard) finish(tutorial bool) { term.OnClose = func(err error) { wizard.aerc.RemoveTab(term) if err != nil { - wizard.aerc.PushError(" " + err.Error()) + wizard.aerc.PushError(" "+err.Error(), 10*time.Second) } } } diff --git a/widgets/account.go b/widgets/account.go index 20ed345..564a95d 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "log" + "time" "github.com/gdamore/tcell" @@ -54,8 +55,7 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon worker, err := worker.NewWorker(acct.Source, logger) if err != nil { - host.SetStatus(fmt.Sprintf("%s: %s", acct.Name, err)). - Color(tcell.ColorDefault, tcell.ColorRed) + host.SetError(fmt.Sprintf("%s: %s", acct.Name, err)) return &AccountView{ acct: acct, aerc: aerc, @@ -67,7 +67,7 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon dirlist := NewDirectoryList(conf, acct, logger, worker) if acctUiConf.SidebarWidth > 0 { - grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT)) + grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT, acctUiConf)) } msglist := NewMessageList(conf, logger, aerc) @@ -280,8 +280,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) { acct.labels = msg.Labels case *types.Error: acct.logger.Printf("%v", msg.Error) - acct.host.SetStatus(fmt.Sprintf("%v", msg.Error)). - Color(tcell.ColorDefault, tcell.ColorRed) + acct.host.SetError(fmt.Sprintf("%v", msg.Error)) } } @@ -291,7 +290,7 @@ func (acct *AccountView) getSortCriteria() []*types.SortCriterion { } criteria, err := sort.GetSortCriteria(acct.UiConfig().Sort) if err != nil { - acct.aerc.PushError(" ui.sort: " + err.Error()) + acct.aerc.PushError(" ui.sort: "+err.Error(), 10*time.Second) return nil } return criteria diff --git a/widgets/aerc.go b/widgets/aerc.go index 829873a..57d6cef 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -51,8 +51,8 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger, tabs := ui.NewTabs(&conf.Ui) - statusbar := ui.NewStack() - statusline := NewStatusLine() + statusbar := ui.NewStack(conf.Ui) + statusline := NewStatusLine(conf.Ui) statusbar.Push(statusline) grid := ui.NewGrid().Rows([]ui.GridSpec{ @@ -76,7 +76,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger, logger: logger, statusbar: statusbar, statusline: statusline, - prompts: ui.NewStack(), + prompts: ui.NewStack(conf.Ui), tabs: tabs, } @@ -382,12 +382,20 @@ func (aerc *Aerc) SetStatus(status string) *StatusMessage { return aerc.statusline.Set(status) } +func (aerc *Aerc) SetError(status string) *StatusMessage { + return aerc.statusline.SetError(status) +} + func (aerc *Aerc) PushStatus(text string, expiry time.Duration) *StatusMessage { return aerc.statusline.Push(text, expiry) } -func (aerc *Aerc) PushError(text string) { - aerc.PushStatus(text, 10*time.Second).Color(tcell.ColorDefault, tcell.ColorRed) +func (aerc *Aerc) PushError(text string, expiry time.Duration) *StatusMessage { + return aerc.statusline.PushError(text, expiry) +} + +func (aerc *Aerc) PushSuccess(text string, expiry time.Duration) *StatusMessage { + return aerc.statusline.PushSuccess(text, expiry) } func (aerc *Aerc) focus(item ui.Interactive) { @@ -416,11 +424,11 @@ func (aerc *Aerc) BeginExCommand(cmd string) { exline := NewExLine(aerc.conf, cmd, func(cmd string) { parts, err := shlex.Split(cmd) if err != nil { - aerc.PushError(" " + err.Error()) + aerc.PushError(" "+err.Error(), 10*time.Second) } err = aerc.cmd(parts) if err != nil { - aerc.PushError(" " + err.Error()) + aerc.PushError(" "+err.Error(), 10*time.Second) } // only add to history if this is an unsimulated command, // ie one not executed from a keybinding @@ -444,7 +452,7 @@ func (aerc *Aerc) RegisterPrompt(prompt string, cmd []string) { } err := aerc.cmd(cmd) if err != nil { - aerc.PushError(" " + err.Error()) + aerc.PushError(" "+err.Error(), 10*time.Second) } }, func(cmd string) []string { return nil // TODO: completions @@ -471,7 +479,7 @@ func (aerc *Aerc) RegisterChoices(choices []Choice) { } err := aerc.cmd(cmd) if err != nil { - aerc.PushError(" " + err.Error()) + aerc.PushError(" "+err.Error(), 10*time.Second) } }, func(cmd string) []string { return nil // TODO: completions @@ -552,11 +560,10 @@ func (aerc *Aerc) CloseDialog() { return } - func (aerc *Aerc) GetPassword(title string, prompt string) (chText chan string, chErr chan error) { chText = make(chan string, 1) chErr = make(chan error, 1) - getPasswd := NewGetPasswd(title, prompt, func(pw string, err error) { + getPasswd := NewGetPasswd(title, prompt, aerc.conf, func(pw string, err error) { defer func() { close(chErr) close(chText) diff --git a/widgets/compose.go b/widgets/compose.go index 01b8dd8..f85e1f3 100644 --- a/widgets/compose.go +++ b/widgets/compose.go @@ -72,10 +72,11 @@ func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig, templateData := templates.ParseTemplateData(defaults, original) cmpl := completer.New(conf.Compose.AddressBookCmd, func(err error) { - aerc.PushError(fmt.Sprintf("could not complete header: %v", err)) + aerc.PushError( + fmt.Sprintf("could not complete header: %v", err), 10*time.Second) worker.Logger.Printf("could not complete header: %v", err) }, aerc.Logger()) - layout, editors, focusable := buildComposeHeader(conf, cmpl, defaults) + layout, editors, focusable := buildComposeHeader(aerc, cmpl, defaults) email, err := ioutil.TempFile("", "aerc-compose-*.eml") if err != nil { @@ -112,21 +113,21 @@ func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig, return c, nil } -func buildComposeHeader(conf *config.AercConfig, cmpl *completer.Completer, +func buildComposeHeader(aerc *Aerc, cmpl *completer.Completer, defaults map[string]string) ( newLayout HeaderLayout, editors map[string]*headerEditor, focusable []ui.MouseableDrawableInteractive, ) { - layout := conf.Compose.HeaderLayout + layout := aerc.conf.Compose.HeaderLayout editors = make(map[string]*headerEditor) focusable = make([]ui.MouseableDrawableInteractive, 0) for _, row := range layout { for _, h := range row { - e := newHeaderEditor(h, "") - if conf.Ui.CompletionPopovers { - e.input.TabComplete(cmpl.ForHeader(h), conf.Ui.CompletionDelay) + e := newHeaderEditor(h, "", aerc.SelectedAccount().UiConfig()) + if aerc.conf.Ui.CompletionPopovers { + e.input.TabComplete(cmpl.ForHeader(h), aerc.SelectedAccount().UiConfig().CompletionDelay) } editors[h] = e switch h { @@ -143,9 +144,9 @@ func buildComposeHeader(conf *config.AercConfig, cmpl *completer.Completer, for _, h := range []string{"Cc", "Bcc"} { if val, ok := defaults[h]; ok && val != "" { if _, ok := editors[h]; !ok { - e := newHeaderEditor(h, "") - if conf.Ui.CompletionPopovers { - e.input.TabComplete(cmpl.ForHeader(h), conf.Ui.CompletionDelay) + e := newHeaderEditor(h, "", aerc.SelectedAccount().UiConfig()) + if aerc.conf.Ui.CompletionPopovers { + e.input.TabComplete(cmpl.ForHeader(h), aerc.SelectedAccount().UiConfig().CompletionDelay) } editors[h] = e focusable = append(focusable, e) @@ -259,7 +260,9 @@ func (c *Composer) readSignatureFromFile() []byte { } signature, err := ioutil.ReadFile(sigFile) if err != nil { - c.aerc.PushError(fmt.Sprintf(" Error loading signature from file: %v", sigFile)) + c.aerc.PushError( + fmt.Sprintf(" Error loading signature from file: %v", sigFile), + 10*time.Second) return nil } return signature @@ -648,7 +651,7 @@ func (c *Composer) AddEditor(header string, value string, appendHeader bool) { } return } - e := newHeaderEditor(header, value) + e := newHeaderEditor(header, value, c.aerc.SelectedAccount().UiConfig()) if c.config.Ui.CompletionPopovers { e.input.TabComplete(c.completer.ForHeader(header), c.config.Ui.CompletionDelay) } @@ -702,23 +705,27 @@ func (c *Composer) reloadEmail() error { } type headerEditor struct { - name string - focused bool - input *ui.TextInput + name string + focused bool + input *ui.TextInput + uiConfig config.UIConfig } -func newHeaderEditor(name string, value string) *headerEditor { +func newHeaderEditor(name string, value string, uiConfig config.UIConfig) *headerEditor { return &headerEditor{ - input: ui.NewTextInput(value), - name: name, + input: ui.NewTextInput(value, uiConfig), + name: name, + uiConfig: uiConfig, } } func (he *headerEditor) Draw(ctx *ui.Context) { name := he.name + " " size := runewidth.StringWidth(name) - ctx.Fill(0, 0, size, ctx.Height(), ' ', tcell.StyleDefault) - ctx.Printf(0, 0, tcell.StyleDefault.Bold(true), "%s", name) + defaultStyle := he.uiConfig.GetStyle(config.STYLE_DEFAULT) + headerStyle := he.uiConfig.GetStyle(config.STYLE_HEADER) + ctx.Fill(0, 0, size, ctx.Height(), ' ', defaultStyle) + ctx.Printf(0, 0, headerStyle, "%s", name) he.input.Draw(ctx.Subcontext(size, 0, ctx.Width()-size, 1)) } @@ -779,21 +786,25 @@ func newReviewMessage(composer *Composer, err error) *reviewMessage { {ui.SIZE_WEIGHT, 1}, }) + uiConfig := composer.config.Ui + if err != nil { - grid.AddChild(ui.NewText(err.Error()). - Color(tcell.ColorRed, tcell.ColorDefault)) - grid.AddChild(ui.NewText("Press [q] to close this tab.")).At(1, 0) + grid.AddChild(ui.NewText(err.Error(), uiConfig.GetStyle(config.STYLE_ERROR))) + grid.AddChild(ui.NewText("Press [q] to close this tab.", + uiConfig.GetStyle(config.STYLE_DEFAULT))).At(1, 0) } else { // TODO: source this from actual keybindings? - grid.AddChild(ui.NewText( - "Send this email? [y]es/[n]o/[p]ostpone/[e]dit/[a]ttach")).At(0, 0) - grid.AddChild(ui.NewText("Attachments:"). - Reverse(true)).At(1, 0) + grid.AddChild(ui.NewText("Send this email? [y]es/[n]o/[e]dit/[a]ttach", + uiConfig.GetStyle(config.STYLE_DEFAULT))).At(0, 0) + grid.AddChild(ui.NewText("Attachments:", + uiConfig.GetStyle(config.STYLE_TITLE))).At(1, 0) if len(composer.attachments) == 0 { - grid.AddChild(ui.NewText("(none)")).At(2, 0) + grid.AddChild(ui.NewText("(none)", + uiConfig.GetStyle(config.STYLE_DEFAULT))).At(2, 0) } else { for i, a := range composer.attachments { - grid.AddChild(ui.NewText(a)).At(i+2, 0) + grid.AddChild(ui.NewText(a, uiConfig.GetStyle(config.STYLE_DEFAULT))). + At(i+2, 0) } } } diff --git a/widgets/dirlist.go b/widgets/dirlist.go index 600b38c..18072fa 100644 --- a/widgets/dirlist.go +++ b/widgets/dirlist.go @@ -194,7 +194,8 @@ func (dirlist *DirectoryList) getRUEString(name string) string { } func (dirlist *DirectoryList) Draw(ctx *ui.Context) { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', + dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT)) if dirlist.spinner.IsRunning() { dirlist.spinner.Draw(ctx) @@ -202,7 +203,7 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) { } if len(dirlist.dirs) == 0 { - style := tcell.StyleDefault + style := dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT) ctx.Printf(0, 0, style, dirlist.UiConfig().EmptyDirlist) return } @@ -212,12 +213,9 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) { if row >= ctx.Height() { break } - style := tcell.StyleDefault + style := dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT) if name == dirlist.selected { - style = style.Reverse(true) - } else if name == dirlist.selecting { - style = style.Reverse(true) - style = style.Foreground(tcell.ColorGray) + style = dirlist.UiConfig().GetStyleSelected(config.STYLE_DIRLIST_DEFAULT) } ctx.Fill(0, row, ctx.Width(), 1, ' ', style) diff --git a/widgets/exline.go b/widgets/exline.go index 6def938..692c8e2 100644 --- a/widgets/exline.go +++ b/widgets/exline.go @@ -15,13 +15,14 @@ type ExLine struct { tabcomplete func(cmd string) []string cmdHistory lib.History input *ui.TextInput + conf *config.AercConfig } func NewExLine(conf *config.AercConfig, cmd string, commit func(cmd string), finish func(), tabcomplete func(cmd string) []string, cmdHistory lib.History) *ExLine { - input := ui.NewTextInput("").Prompt(":").Set(cmd) + input := ui.NewTextInput("", conf.Ui).Prompt(":").Set(cmd) if conf.Ui.CompletionPopovers { input.TabComplete(tabcomplete, conf.Ui.CompletionDelay) } @@ -31,6 +32,7 @@ func NewExLine(conf *config.AercConfig, cmd string, commit func(cmd string), fin tabcomplete: tabcomplete, cmdHistory: cmdHistory, input: input, + conf: conf, } input.OnInvalidate(func(d ui.Drawable) { exline.Invalidate() @@ -41,7 +43,7 @@ func NewExLine(conf *config.AercConfig, cmd string, commit func(cmd string), fin func NewPrompt(conf *config.AercConfig, prompt string, commit func(text string), tabcomplete func(cmd string) []string) *ExLine { - input := ui.NewTextInput("").Prompt(prompt) + input := ui.NewTextInput("", conf.Ui).Prompt(prompt) if conf.Ui.CompletionPopovers { input.TabComplete(tabcomplete, conf.Ui.CompletionDelay) } diff --git a/widgets/getpasswd.go b/widgets/getpasswd.go index 34f8b1f..b3ea9e0 100644 --- a/widgets/getpasswd.go +++ b/widgets/getpasswd.go @@ -5,6 +5,7 @@ import ( "github.com/gdamore/tcell" + "git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/lib/ui" ) @@ -14,14 +15,16 @@ type GetPasswd struct { title string prompt string input *ui.TextInput + conf *config.AercConfig } -func NewGetPasswd(title string, prompt string, cb func(string, error)) *GetPasswd { +func NewGetPasswd(title string, prompt string, conf *config.AercConfig, cb func(string, error)) *GetPasswd { getpasswd := &GetPasswd{ callback: cb, title: title, prompt: prompt, - input: ui.NewTextInput("").Password(true).Prompt("Password: "), + conf: conf, + input: ui.NewTextInput("", conf.Ui).Password(true).Prompt("Password: "), } getpasswd.input.OnInvalidate(func(_ ui.Drawable) { getpasswd.Invalidate() @@ -31,10 +34,13 @@ func NewGetPasswd(title string, prompt string, cb func(string, error)) *GetPassw } func (gp *GetPasswd) Draw(ctx *ui.Context) { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) - ctx.Fill(0, 0, ctx.Width(), 1, ' ', tcell.StyleDefault.Reverse(true)) - ctx.Printf(1, 0, tcell.StyleDefault.Reverse(true), "%s", gp.title) - ctx.Printf(1, 1, tcell.StyleDefault, gp.prompt) + defaultStyle := gp.conf.Ui.GetStyle(config.STYLE_DEFAULT) + titleStyle := gp.conf.Ui.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.input.Draw(ctx.Subcontext(1, 3, ctx.Width()-2, 1)) } diff --git a/widgets/msglist.go b/widgets/msglist.go index 5aedb44..5d12f8e 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -3,6 +3,7 @@ package widgets import ( "fmt" "log" + "time" "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" @@ -49,7 +50,8 @@ func (ml *MessageList) Invalidate() { func (ml *MessageList) Draw(ctx *ui.Context) { ml.height = ctx.Height() - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', + ml.aerc.SelectedAccount().UiConfig().GetStyle(config.STYLE_MSGLIST_DEFAULT)) store := ml.Store() if store == nil { @@ -84,34 +86,50 @@ func (ml *MessageList) Draw(ctx *ui.Context) { continue } - style := tcell.StyleDefault + uiConfig := ml.conf.GetUiConfig(map[config.ContextType]string{ + config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name, + config.UI_CONTEXT_FOLDER: ml.aerc.SelectedAccount().Directories().Selected(), + config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject, + }) + + so := config.STYLE_MSGLIST_DEFAULT - // current row - if row == ml.store.SelectedIndex()-ml.scroll { - style = style.Reverse(true) - } // deleted message if _, ok := store.Deleted[msg.Uid]; ok { - style = style.Foreground(tcell.ColorGray) + so = config.STYLE_MSGLIST_DELETED } // unread message seen := false + flaged := false for _, flag := range msg.Flags { - if flag == models.SeenFlag { + switch flag { + case models.SeenFlag: seen = true + case models.FlaggedFlag: + flaged = true } } if !seen { - style = style.Bold(true) + so = config.STYLE_MSGLIST_UNREAD } - ctx.Fill(0, row, ctx.Width(), 1, ' ', style) - uiConfig := ml.conf.GetUiConfig(map[config.ContextType]string{ - config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name, - config.UI_CONTEXT_FOLDER: ml.aerc.SelectedAccount().Directories().Selected(), - config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject, - }) + if flaged { + so = config.STYLE_MSGLIST_FLAGGED + } + + // marked message + if store.IsMarked(msg.Uid) { + so = config.STYLE_MSGLIST_MARKED + } + style := uiConfig.GetStyle(so) + + // current row + if row == ml.store.SelectedIndex()-ml.scroll { + style = uiConfig.GetStyleSelected(so) + } + + ctx.Fill(0, row, ctx.Width(), 1, ' ', style) fmtStr, args, err := format.ParseMessageFormat( ml.aerc.SelectedAccount().acct.From, uiConfig.IndexFormat, @@ -168,7 +186,7 @@ func (ml *MessageList) MouseEvent(localX int, localY int, event tcell.Event) { lib.NewMessageStoreView(msg, store, ml.aerc.DecryptKeys, func(view lib.MessageView, err error) { if err != nil { - ml.aerc.PushError(err.Error()) + ml.aerc.PushError(err.Error(), 10*time.Second) return } viewer := NewMessageViewer(acct, ml.aerc.Config(), view) @@ -288,7 +306,8 @@ func (ml *MessageList) Scroll() { } func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) { - msg := ml.aerc.SelectedAccount().UiConfig().EmptyMessage + uiConfig := ml.aerc.SelectedAccount().UiConfig() + msg := uiConfig.EmptyMessage ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0, - tcell.StyleDefault, "%s", msg) + uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT), "%s", msg) } diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go index ce85970..6544ddd 100644 --- a/widgets/msgviewer.go +++ b/widgets/msgviewer.go @@ -32,6 +32,7 @@ type MessageViewer struct { grid *ui.Grid switcher *PartSwitcher msg lib.MessageView + uiConfig config.UIConfig } type PartSwitcher struct { @@ -61,9 +62,11 @@ func NewMessageViewer(acct *AccountView, header, headerHeight := layout.grid( func(header string) ui.Drawable { return &HeaderView{ + conf: conf, Name: header, Value: fmtHeader(msg.MessageInfo(), header, acct.UiConfig().TimestampFormat), + uiConfig: acct.UiConfig(), } }, ) @@ -93,15 +96,16 @@ func NewMessageViewer(acct *AccountView, err := createSwitcher(acct, switcher, conf, msg) if err != nil { return &MessageViewer{ - err: err, - grid: grid, - msg: msg, + err: err, + grid: grid, + msg: msg, + uiConfig: acct.UiConfig(), } } grid.AddChild(header).At(0, 0) if msg.PGPDetails() != nil { - grid.AddChild(NewPGPInfo(msg.PGPDetails())).At(1, 0) + grid.AddChild(NewPGPInfo(msg.PGPDetails(), acct.UiConfig())).At(1, 0) grid.AddChild(ui.NewFill(' ')).At(2, 0) grid.AddChild(switcher).At(3, 0) } else { @@ -115,6 +119,7 @@ func NewMessageViewer(acct *AccountView, grid: grid, msg: msg, switcher: switcher, + uiConfig: acct.UiConfig(), } switcher.mv = mv @@ -223,8 +228,9 @@ func createSwitcher(acct *AccountView, switcher *PartSwitcher, func (mv *MessageViewer) Draw(ctx *ui.Context) { if mv.err != nil { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) - ctx.Printf(0, 0, tcell.StyleDefault, "%s", mv.err.Error()) + style := mv.acct.UiConfig().GetStyle(config.STYLE_DEFAULT) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style) + ctx.Printf(0, 0, style, "%s", mv.err.Error()) return } mv.grid.Draw(ctx) @@ -346,7 +352,10 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) { ps.height = ctx.Height() y := ctx.Height() - height for i, part := range ps.parts { - style := tcell.StyleDefault.Reverse(ps.selected == i) + style := ps.mv.uiConfig.GetStyle(config.STYLE_DEFAULT) + if ps.selected == i { + style = ps.mv.uiConfig.GetStyleSelected(config.STYLE_DEFAULT) + } ctx.Fill(0, y+i, ctx.Width(), 1, ' ', style) name := fmt.Sprintf("%s/%s", strings.ToLower(part.part.MIMEType), @@ -435,6 +444,7 @@ func (mv *MessageViewer) Focus(focus bool) { type PartViewer struct { ui.Invalidatable + conf *config.AercConfig err error fetched bool filter *exec.Cmd @@ -449,6 +459,7 @@ type PartViewer struct { term *Terminal selecter *Selecter grid *ui.Grid + uiConfig config.UIConfig } func NewPartViewer(acct *AccountView, conf *config.AercConfig, @@ -518,7 +529,8 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig, {ui.SIZE_WEIGHT, 1}, }) - selecter := NewSelecter([]string{"Save message", "Pipe to command"}, 0). + selecter := NewSelecter([]string{"Save message", "Pipe to command"}, + 0, acct.UiConfig()). OnChoose(func(option string) { switch option { case "Save message": @@ -531,6 +543,7 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig, grid.AddChild(selecter).At(2, 0) pv := &PartViewer{ + conf: conf, filter: filter, index: index, msg: msg, @@ -542,6 +555,7 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig, term: term, selecter: selecter, grid: grid, + uiConfig: acct.UiConfig(), } if term != nil { @@ -639,14 +653,16 @@ func (pv *PartViewer) Invalidate() { } func (pv *PartViewer) Draw(ctx *ui.Context) { + style := pv.uiConfig.GetStyle(config.STYLE_DEFAULT) + styleError := pv.uiConfig.GetStyle(config.STYLE_ERROR) if pv.filter == nil { // TODO: Let them download it directly or something - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) - ctx.Printf(0, 0, tcell.StyleDefault.Foreground(tcell.ColorRed), + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style) + ctx.Printf(0, 0, styleError, "No filter configured for this mimetype ('%s/%s')", pv.part.MIMEType, pv.part.MIMESubType, ) - ctx.Printf(0, 2, tcell.StyleDefault, + ctx.Printf(0, 2, style, "You can still :save the message or :pipe it to an external command") pv.selecter.Focus(true) pv.grid.Draw(ctx) @@ -657,8 +673,8 @@ func (pv *PartViewer) Draw(ctx *ui.Context) { pv.fetched = true } if pv.err != nil { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) - ctx.Printf(0, 0, tcell.StyleDefault, "%s", pv.err.Error()) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style) + ctx.Printf(0, 0, style, "%s", pv.err.Error()) return } pv.term.Draw(ctx) @@ -680,8 +696,10 @@ func (pv *PartViewer) Event(event tcell.Event) bool { type HeaderView struct { ui.Invalidatable - Name string - Value string + conf *config.AercConfig + Name string + Value string + uiConfig config.UIConfig } func (hv *HeaderView) Draw(ctx *ui.Context) { @@ -689,18 +707,15 @@ func (hv *HeaderView) Draw(ctx *ui.Context) { size := runewidth.StringWidth(name) lim := ctx.Width() - size - 1 value := runewidth.Truncate(" "+hv.Value, lim, "…") - var ( - hstyle tcell.Style - vstyle tcell.Style - ) + + vstyle := hv.uiConfig.GetStyle(config.STYLE_DEFAULT) + hstyle := hv.uiConfig.GetStyle(config.STYLE_HEADER) + // TODO: Make this more robust and less dumb if hv.Name == "PGP" { - vstyle = tcell.StyleDefault.Foreground(tcell.ColorGreen) - hstyle = tcell.StyleDefault.Bold(true) - } else { - vstyle = tcell.StyleDefault - hstyle = tcell.StyleDefault.Bold(true) + vstyle = hv.uiConfig.GetStyle(config.STYLE_SUCCESS) } + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', vstyle) ctx.Printf(0, 0, hstyle, "%s", name) ctx.Printf(size, 0, vstyle, "%s", value) diff --git a/widgets/pgpinfo.go b/widgets/pgpinfo.go index 5da9141..94fb877 100644 --- a/widgets/pgpinfo.go +++ b/widgets/pgpinfo.go @@ -3,40 +3,40 @@ package widgets import ( "errors" + "git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/lib/ui" - "github.com/gdamore/tcell" "golang.org/x/crypto/openpgp" pgperrors "golang.org/x/crypto/openpgp/errors" ) type PGPInfo struct { ui.Invalidatable - details *openpgp.MessageDetails + details *openpgp.MessageDetails + uiConfig config.UIConfig } -func NewPGPInfo(details *openpgp.MessageDetails) *PGPInfo { - return &PGPInfo{details: details} +func NewPGPInfo(details *openpgp.MessageDetails, uiConfig config.UIConfig) *PGPInfo { + return &PGPInfo{details: details, uiConfig: uiConfig} } func (p *PGPInfo) DrawSignature(ctx *ui.Context) { - errorStyle := tcell.StyleDefault.Background(tcell.ColorRed). - Foreground(tcell.ColorWhite).Bold(true) - softErrorStyle := tcell.StyleDefault.Foreground(tcell.ColorYellow).Bold(true) - validStyle := tcell.StyleDefault.Foreground(tcell.ColorGreen).Bold(true) + errorStyle := p.uiConfig.GetStyle(config.STYLE_ERROR) + warningStyle := p.uiConfig.GetStyle(config.STYLE_WARNING) + validStyle := p.uiConfig.GetStyle(config.STYLE_SUCCESS) + defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT) // TODO: Nicer prompt for TOFU, fetch from keyserver, etc if errors.Is(p.details.SignatureError, pgperrors.ErrUnknownIssuer) || p.details.SignedBy == nil { - x := ctx.Printf(0, 0, softErrorStyle, "*") - x += ctx.Printf(x, 0, tcell.StyleDefault, + x := ctx.Printf(0, 0, warningStyle, "*") + x += ctx.Printf(x, 0, defaultStyle, " Signed with unknown key (%8X); authenticity unknown", p.details.SignedByKeyId) } else if p.details.SignatureError != nil { x := ctx.Printf(0, 0, errorStyle, "Invalid signature!") - x += ctx.Printf(x, 0, tcell.StyleDefault. - Foreground(tcell.ColorRed).Bold(true), + x += ctx.Printf(x, 0, errorStyle, " This message may have been tampered with! (%s)", p.details.SignatureError.Error()) } else { @@ -44,24 +44,26 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) { ident := entity.PrimaryIdentity() x := ctx.Printf(0, 0, validStyle, "✓ Authentic ") - x += ctx.Printf(x, 0, tcell.StyleDefault, + x += ctx.Printf(x, 0, defaultStyle, "Signature from %s (%8X)", ident.Name, p.details.SignedByKeyId) } } func (p *PGPInfo) DrawEncryption(ctx *ui.Context, y int) { - validStyle := tcell.StyleDefault.Foreground(tcell.ColorGreen).Bold(true) + validStyle := p.uiConfig.GetStyle(config.STYLE_SUCCESS) + defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT) entity := p.details.DecryptedWith.Entity ident := entity.PrimaryIdentity() x := ctx.Printf(0, y, validStyle, "✓ Encrypted ") - x += ctx.Printf(x, y, tcell.StyleDefault, + x += ctx.Printf(x, y, defaultStyle, "To %s (%8X) ", ident.Name, p.details.DecryptedWith.PublicKey.KeyId) } func (p *PGPInfo) Draw(ctx *ui.Context) { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) + defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', defaultStyle) if p.details.IsSigned && p.details.IsEncrypted { p.DrawSignature(ctx) p.DrawEncryption(ctx, 1) diff --git a/widgets/selecter.go b/widgets/selecter.go index 7fae9cd..0faf37e 100644 --- a/widgets/selecter.go +++ b/widgets/selecter.go @@ -3,24 +3,27 @@ package widgets import ( "github.com/gdamore/tcell" + "git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/lib/ui" ) type Selecter struct { ui.Invalidatable - chooser bool - focused bool - focus int - options []string + chooser bool + focused bool + focus int + options []string + uiConfig config.UIConfig onChoose func(option string) onSelect func(option string) } -func NewSelecter(options []string, focus int) *Selecter { +func NewSelecter(options []string, focus int, uiConfig config.UIConfig) *Selecter { return &Selecter{ - focus: focus, - options: options, + focus: focus, + options: options, + uiConfig: uiConfig, } } @@ -34,15 +37,16 @@ func (sel *Selecter) Invalidate() { } func (sel *Selecter) Draw(ctx *ui.Context) { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', + sel.uiConfig.GetStyle(config.STYLE_SELECTER_DEFAULT)) x := 2 for i, option := range sel.options { - style := tcell.StyleDefault + style := sel.uiConfig.GetStyle(config.STYLE_SELECTER_DEFAULT) if sel.focus == i { if sel.focused { - style = style.Reverse(true) + style = sel.uiConfig.GetStyle(config.STYLE_SELECTER_FOCUSED) } else if sel.chooser { - style = style.Bold(true) + style = sel.uiConfig.GetStyle(config.STYLE_SELECTER_CHOOSER) } } x += ctx.Printf(x, 1, style, "[%s]", option) diff --git a/widgets/spinner.go b/widgets/spinner.go index 51b8c1b..0c72422 100644 --- a/widgets/spinner.go +++ b/widgets/spinner.go @@ -16,6 +16,7 @@ type Spinner struct { frame int64 // access via atomic frames []string stop chan struct{} + style tcell.Style } func NewSpinner(uiConf *config.UIConfig) *Spinner { @@ -23,6 +24,7 @@ func NewSpinner(uiConf *config.UIConfig) *Spinner { stop: make(chan struct{}), frame: -1, frames: strings.Split(uiConf.Spinner, uiConf.SpinnerDelimiter), + style: uiConf.GetStyle(config.STYLE_SPINNER), } return &spinner } @@ -70,9 +72,9 @@ func (s *Spinner) Draw(ctx *ui.Context) { cur := int(atomic.LoadInt64(&s.frame) % int64(len(s.frames))) - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', s.style) col := ctx.Width()/2 - len(s.frames[0])/2 + 1 - ctx.Printf(col, 0, tcell.StyleDefault, "%s", s.frames[cur]) + ctx.Printf(col, 0, s.style, "%s", s.frames[cur]) } func (s *Spinner) Invalidate() { diff --git a/widgets/status.go b/widgets/status.go index 8d0a1ae..d6d7761 100644 --- a/widgets/status.go +++ b/widgets/status.go @@ -6,6 +6,7 @@ import ( "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" + "git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/lib/ui" ) @@ -14,21 +15,21 @@ type StatusLine struct { stack []*StatusMessage fallback StatusMessage aerc *Aerc + uiConfig config.UIConfig } type StatusMessage struct { - bg tcell.Color - fg tcell.Color + style tcell.Style message string } -func NewStatusLine() *StatusLine { +func NewStatusLine(uiConfig config.UIConfig) *StatusLine { return &StatusLine{ fallback: StatusMessage{ - bg: tcell.ColorDefault, - fg: tcell.ColorDefault, + style: uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT), message: "Idle", }, + uiConfig: uiConfig, } } @@ -41,9 +42,7 @@ func (status *StatusLine) Draw(ctx *ui.Context) { if len(status.stack) != 0 { line = status.stack[len(status.stack)-1] } - style := tcell.StyleDefault. - Background(line.bg).Foreground(line.fg).Reverse(true) - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', line.style) pendingKeys := "" if status.aerc != nil { for _, pendingKey := range status.aerc.pendingKeys { @@ -51,13 +50,21 @@ func (status *StatusLine) Draw(ctx *ui.Context) { } } message := runewidth.FillRight(line.message, ctx.Width()-len(pendingKeys)-5) - ctx.Printf(0, 0, style, "%s%s", message, pendingKeys) + ctx.Printf(0, 0, line.style, "%s%s", message, pendingKeys) } func (status *StatusLine) Set(text string) *StatusMessage { status.fallback = StatusMessage{ - bg: tcell.ColorDefault, - fg: tcell.ColorDefault, + style: status.uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT), + message: text, + } + status.Invalidate() + return &status.fallback +} + +func (status *StatusLine) SetError(text string) *StatusMessage { + status.fallback = StatusMessage{ + style: status.uiConfig.GetStyle(config.STYLE_STATUSLINE_ERROR), message: text, } status.Invalidate() @@ -66,8 +73,7 @@ func (status *StatusLine) Set(text string) *StatusMessage { func (status *StatusLine) Push(text string, expiry time.Duration) *StatusMessage { msg := &StatusMessage{ - bg: tcell.ColorDefault, - fg: tcell.ColorDefault, + style: status.uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT), message: text, } status.stack = append(status.stack, msg) @@ -84,6 +90,18 @@ func (status *StatusLine) Push(text string, expiry time.Duration) *StatusMessage return msg } +func (status *StatusLine) PushError(text string, expiry time.Duration) *StatusMessage { + msg := status.Push(text, expiry) + msg.Color(status.uiConfig.GetStyle(config.STYLE_STATUSLINE_ERROR)) + return msg +} + +func (status *StatusLine) PushSuccess(text string, expiry time.Duration) *StatusMessage { + msg := status.Push(text, expiry) + msg.Color(status.uiConfig.GetStyle(config.STYLE_STATUSLINE_SUCCESS)) + return msg +} + func (status *StatusLine) Expire() { status.stack = nil } @@ -92,7 +110,6 @@ func (status *StatusLine) SetAerc(aerc *Aerc) { status.aerc = aerc } -func (msg *StatusMessage) Color(bg tcell.Color, fg tcell.Color) { - msg.bg = bg - msg.fg = fg +func (msg *StatusMessage) Color(style tcell.Style) { + msg.style = style } diff --git a/widgets/tabhost.go b/widgets/tabhost.go index 0ac67e5..1322a0a 100644 --- a/widgets/tabhost.go +++ b/widgets/tabhost.go @@ -7,6 +7,9 @@ import ( type TabHost interface { BeginExCommand(cmd string) SetStatus(status string) *StatusMessage + SetError(err string) *StatusMessage PushStatus(text string, expiry time.Duration) *StatusMessage + PushError(text string, expiry time.Duration) *StatusMessage + PushSuccess(text string, expiry time.Duration) *StatusMessage Beep() } |