diff --git a/cmd/aerc/main.go b/cmd/aerc/main.go index 5e537fb..009b5eb 100644 --- a/cmd/aerc/main.go +++ b/cmd/aerc/main.go @@ -73,10 +73,22 @@ func main() { grid.AddChild(tabs.TabContent).At(1, 1) statusbar := libui.NewStack() - exline := widgets.NewExLine() - statusbar.Push(exline) grid.AddChild(statusbar).At(2, 1) + statusline := widgets.NewStatusLine() + statusline.Push("test status!", 6*time.Second) + statusline.Push("test error!", 3*time.Second). + Color(tb.ColorRed, tb.ColorBlack) + statusbar.Push(statusline) + + exline := widgets.NewExLine(func(command string) { + statusbar.Pop() + logger.Printf("TODO: execute command: %s\n", command) + }, func() { + statusbar.Pop() + }) + statusbar.Push(exline) + ui, err := libui.Initialize(conf, grid) if err != nil { panic(err) diff --git a/widgets/exline.go b/widgets/exline.go index e13b50d..0522371 100644 --- a/widgets/exline.go +++ b/widgets/exline.go @@ -9,21 +9,24 @@ import ( // TODO: history // TODO: tab completion -// TODO: commit -// TODO: cancel (via esc/ctrl+c) // TODO: scrolling type ExLine struct { command []rune - commit func(cmd *string) + commit func(cmd string) + cancel func() index int scroll int onInvalidate func(d ui.Drawable) } -func NewExLine() *ExLine { - return &ExLine{command: []rune{}} +func NewExLine(commit func (cmd string), cancel func()) *ExLine { + return &ExLine{ + cancel: cancel, + commit: commit, + command: []rune{}, + } } func (ex *ExLine) OnInvalidate(onInvalidate func(d ui.Drawable)) { @@ -118,6 +121,12 @@ func (ex *ExLine) Event(event tb.Event) bool { ex.Invalidate() case tb.KeyCtrlW: ex.deleteWord() + case tb.KeyEnter: + tb.HideCursor() + ex.commit(string(ex.command)) + case tb.KeyEsc, tb.KeyCtrlC: + tb.HideCursor() + ex.cancel() default: if event.Ch != 0 { ex.insert(event.Ch) diff --git a/widgets/status.go b/widgets/status.go new file mode 100644 index 0000000..bb87d33 --- /dev/null +++ b/widgets/status.go @@ -0,0 +1,91 @@ +package widgets + +import ( + "time" + + tb "github.com/nsf/termbox-go" + + "git.sr.ht/~sircmpwn/aerc2/lib/ui" +) + +type StatusLine struct { + stack []*StatusMessage + fallback StatusMessage + + onInvalidate func(d ui.Drawable) +} + +type StatusMessage struct { + bg tb.Attribute + fg tb.Attribute + message string +} + +func NewStatusLine() *StatusLine { + return &StatusLine{ + fallback: StatusMessage{ + bg: tb.ColorWhite, + fg: tb.ColorBlack, + message: "Idle", + }, + } +} + +func (status *StatusLine) OnInvalidate(onInvalidate func (d ui.Drawable)) { + status.onInvalidate = onInvalidate +} + +func (status *StatusLine) Invalidate() { + if status.onInvalidate != nil { + status.onInvalidate(status) + } +} + +func (status *StatusLine) Draw(ctx *ui.Context) { + line := &status.fallback + if len(status.stack) != 0 { + line = status.stack[len(status.stack)-1] + } + cell := tb.Cell{ + Fg: line.fg, + Bg: line.bg, + Ch: ' ', + } + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), cell) + ctx.Printf(0, 0, cell, "%s", line.message) +} + +func (status *StatusLine) Set(text string) *StatusMessage { + status.fallback = StatusMessage{ + bg: tb.ColorWhite, + fg: tb.ColorBlack, + message: text, + } + status.Invalidate() + return &status.fallback +} + +func (status *StatusLine) Push(text string, expiry time.Duration) *StatusMessage { + msg := &StatusMessage{ + bg: tb.ColorWhite, + fg: tb.ColorBlack, + message: text, + } + status.stack = append(status.stack, msg) + go (func () { + time.Sleep(expiry) + for i, m := range status.stack { + if m == msg { + status.stack = append(status.stack[:i], status.stack[i+1:]...) + break + } + } + status.Invalidate() + })() + return msg +} + +func (msg *StatusMessage) Color(bg tb.Attribute, fg tb.Attribute) { + msg.bg = bg + msg.fg = fg +}