diff options
Diffstat (limited to 'app/quake.go')
-rw-r--r-- | app/quake.go | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/app/quake.go b/app/quake.go new file mode 100644 index 00000000..9ceb6457 --- /dev/null +++ b/app/quake.go @@ -0,0 +1,243 @@ +package app + +import ( + "os/exec" + "sync" + "sync/atomic" + "time" + + "git.sr.ht/~rjarry/aerc/config" + "git.sr.ht/~rjarry/aerc/lib/log" + "git.sr.ht/~rjarry/aerc/lib/ui" + "git.sr.ht/~rockorager/vaxis" + "github.com/riywo/loginshell" +) + +var qt quakeTerminal + +type quakeTerminal struct { + mu sync.Mutex + rolling int32 + visible bool + term *Terminal +} + +func ToggleQuake() { + handleErr := func(err error) { + log.Errorf("quake-terminal: %v", err) + } + if !qt.HasTerm() { + shell, err := loginshell.Shell() + if err != nil { + handleErr(err) + return + } + args := []string{shell} + cmd := exec.Command(args[0], args[1:]...) + term, err := NewTerminal(cmd) + if err != nil { + handleErr(err) + return + } + term.OnClose = func(err error) { + if err != nil { + aerc.PushError(err.Error()) + } + qt.Hide() + qt.SetTerm(nil) + } + qt.SetTerm(term) + } + + if qt.Rolling() { + return + } + + if qt.Visible() { + qt.Hide() + } else { + qt.Show() + } +} + +func (q *quakeTerminal) Rolling() bool { + return atomic.LoadInt32(&q.rolling) > 0 +} + +func (q *quakeTerminal) SetTerm(t *Terminal) { + q.mu.Lock() + defer q.mu.Unlock() + + q.term = t +} + +func (q *quakeTerminal) HasTerm() bool { + q.mu.Lock() + defer q.mu.Unlock() + + return q.term != nil +} + +func (q *quakeTerminal) Visible() bool { + q.mu.Lock() + defer q.mu.Unlock() + + return q.visible +} + +// inputReturn is helper function to create dialog boxes. +func inputReturn() func(int) int { + return func(x int) int { return x } +} + +// fixReturn is helper function to create dialog boxes. +func fixReturn(x int) func(int) int { + return func(_ int) int { return x } +} + +func (q *quakeTerminal) Show() { + q.mu.Lock() + defer q.mu.Unlock() + + if q.term == nil { + return + } + + uiConfig := SelectedAccountUiConfig() + h := uiConfig.QuakeHeight + + termBox := NewDialog( + ui.NewBox(q.term, "", "", uiConfig), + fixReturn(0), + fixReturn(0), + inputReturn(), + fixReturn(h), + ) + + f := Roller{ + span: 100 * time.Millisecond, + done: func() { + log.Tracef("restore after show") + atomic.StoreInt32(&q.rolling, 0) + ui.QueueFunc(func() { + CloseDialog() + AddDialog(termBox) + }) + }, + } + + atomic.StoreInt32(&q.rolling, 1) + emptyBox := NewDialog( + ui.NewBox(&EmptyInteractive{}, "", "", uiConfig), + fixReturn(0), + fixReturn(0), + inputReturn(), + f.Roll(1, h), + ) + + q.visible = true + if q.term != nil { + q.term.Show(q.visible) + q.term.Focus(q.visible) + } + + CloseDialog() + AddDialog(emptyBox) +} + +func (q *quakeTerminal) Hide() { + uiConfig := SelectedAccountUiConfig() + f := Roller{ + span: 100 * time.Millisecond, + done: func() { + atomic.StoreInt32(&q.rolling, 0) + ui.QueueFunc(CloseDialog) + log.Tracef("restore after hide") + }, + } + + atomic.StoreInt32(&q.rolling, 1) + emptyBox := NewDialog( + ui.NewBox(&EmptyInteractive{}, "", "", uiConfig), + fixReturn(0), + fixReturn(0), + inputReturn(), + f.Roll(uiConfig.QuakeHeight, 2), + ) + + q.mu.Lock() + q.visible = false + if q.term != nil { + q.term.Focus(q.visible) + q.term.Show(q.visible) + } + q.mu.Unlock() + + ui.QueueFunc(func() { + CloseDialog() + AddDialog(emptyBox) + }) +} + +type EmptyInteractive struct{} + +func (e *EmptyInteractive) Draw(ctx *ui.Context) { + w := ctx.Width() + h := ctx.Height() + if w == 0 || h == 0 { + return + } + style := SelectedAccountUiConfig().GetStyle(config.STYLE_DEFAULT) + ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style) +} + +func (e *EmptyInteractive) Invalidate() { +} + +func (e *EmptyInteractive) MouseEvent(_ int, _ int, _ vaxis.Event) { +} + +func (e *EmptyInteractive) Event(_ vaxis.Event) bool { + return true +} + +func (e *EmptyInteractive) Focus(_ bool) { +} + +type Roller struct { + span time.Duration + done func() + value int64 +} + +func (f *Roller) Roll(start, end int) func(int) int { + nsteps := end - start + + var step int64 = 1 + if end < start { + step = -1 + nsteps = -nsteps + } + + span := f.span.Milliseconds() / int64(nsteps) + refresh := time.Duration(span) * time.Millisecond + + atomic.StoreInt64(&f.value, int64(start)) + + go func() { + defer log.PanicHandler() + for i := 0; i < int(nsteps); i++ { + aerc.Invalidate() + time.Sleep(refresh) + atomic.AddInt64(&f.value, step) + } + if f.done != nil { + ui.QueueFunc(f.done) + } + }() + + return func(_ int) int { + log.Tracef("in roller") + return int(atomic.LoadInt64(&f.value)) + } +} |