aboutsummaryrefslogtreecommitdiffstats
path: root/app/quake.go
diff options
context:
space:
mode:
Diffstat (limited to 'app/quake.go')
-rw-r--r--app/quake.go243
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))
+ }
+}