2021-01-10 21:52:29 +01:00
|
|
|
package tui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2023-04-24 11:55:04 +02:00
|
|
|
"git.milar.in/milarin/adverr"
|
|
|
|
"git.milar.in/milarin/buf2d"
|
|
|
|
"git.milar.in/milarin/ds"
|
2021-01-10 21:52:29 +01:00
|
|
|
"github.com/gdamore/tcell"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Screen struct {
|
2022-05-04 14:16:08 +02:00
|
|
|
EventTmpl
|
|
|
|
|
2022-04-04 14:47:15 +02:00
|
|
|
scr tcell.Screen
|
|
|
|
buf *ViewBuffer
|
|
|
|
|
|
|
|
stopCh chan error
|
|
|
|
redrawCh chan struct{}
|
2023-04-24 11:41:38 +02:00
|
|
|
started bool
|
|
|
|
|
2023-04-24 11:55:04 +02:00
|
|
|
modals ds.Stack[View]
|
2022-04-04 14:47:15 +02:00
|
|
|
|
|
|
|
// Root is the root view which is currently shown on screen
|
|
|
|
Root View
|
2021-01-10 21:52:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewScreen(root View) (*Screen, error) {
|
|
|
|
scr, err := tcell.NewScreen()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s := &Screen{
|
2022-04-04 14:47:15 +02:00
|
|
|
Root: root,
|
|
|
|
scr: scr,
|
|
|
|
stopCh: make(chan error, 1),
|
|
|
|
redrawCh: make(chan struct{}, 1),
|
2023-04-24 11:55:04 +02:00
|
|
|
modals: ds.NewArrayStack[View](),
|
2021-01-10 21:52:29 +01:00
|
|
|
}
|
|
|
|
|
2022-05-03 17:59:34 +02:00
|
|
|
s.KeyPressed = CloseOnCtrlC(s)
|
|
|
|
|
2021-01-10 21:52:29 +01:00
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Screen) Start() error {
|
|
|
|
err := s.scr.Init()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-05-04 14:40:04 +02:00
|
|
|
|
2022-04-01 20:10:51 +02:00
|
|
|
defer s.scr.Fini()
|
2023-04-24 11:41:38 +02:00
|
|
|
defer close(s.redrawCh)
|
2022-04-01 20:10:51 +02:00
|
|
|
|
2022-04-03 16:29:01 +02:00
|
|
|
s.scr.EnableMouse()
|
2023-04-24 14:16:12 +02:00
|
|
|
|
|
|
|
go s.eventloop()
|
|
|
|
go s.drawloop()
|
|
|
|
|
2023-04-24 11:41:38 +02:00
|
|
|
s.started = true
|
2021-01-10 21:52:29 +01:00
|
|
|
return <-s.stopCh
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Screen) Stop() {
|
|
|
|
s.StopWithError(nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Screen) StopWithError(err error) {
|
|
|
|
s.stopCh <- err
|
|
|
|
}
|
|
|
|
|
2022-04-02 13:01:41 +02:00
|
|
|
func (s *Screen) onKeyPressed(event *KeyEvent) {
|
2023-04-24 11:41:38 +02:00
|
|
|
if !s.modals.Empty() {
|
|
|
|
s.modals.Peek().OnKeyPressed(event)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-04-02 13:01:41 +02:00
|
|
|
if s.KeyPressed == nil || !s.KeyPressed(event) {
|
2022-05-04 14:40:04 +02:00
|
|
|
s.Root.OnKeyPressed(event)
|
2022-04-02 13:01:41 +02:00
|
|
|
}
|
|
|
|
s.Redraw()
|
|
|
|
}
|
|
|
|
|
2022-05-04 14:16:08 +02:00
|
|
|
func (s *Screen) onMouseEvent(event *MouseEvent) {
|
2022-04-04 14:23:17 +02:00
|
|
|
if event.Button != MouseButtonNone {
|
2023-04-24 11:41:38 +02:00
|
|
|
defer s.Redraw()
|
|
|
|
}
|
|
|
|
|
|
|
|
if !s.modals.Empty() {
|
|
|
|
s.modals.Peek().OnMouseEvent(event)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.MouseEvent != nil && s.MouseEvent(event) {
|
|
|
|
return
|
2022-04-04 14:23:17 +02:00
|
|
|
}
|
2023-04-24 11:41:38 +02:00
|
|
|
|
|
|
|
s.Root.OnMouseEvent(event)
|
2022-04-03 16:29:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func convertMouseEvent(original *tcell.EventMouse) *MouseEvent {
|
|
|
|
x, y := original.Position()
|
|
|
|
return &MouseEvent{
|
2022-05-04 12:03:51 +02:00
|
|
|
Position: P(x, y),
|
2022-04-03 16:29:01 +02:00
|
|
|
Button: convertMouseButton(original.Buttons()),
|
|
|
|
Modifiers: original.Modifiers(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-10 21:52:29 +01:00
|
|
|
func (s *Screen) Redraw() {
|
2023-04-24 11:41:38 +02:00
|
|
|
if s.started {
|
|
|
|
s.redrawCh <- struct{}{}
|
|
|
|
}
|
2022-04-04 14:47:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Screen) eventloop() {
|
2022-04-04 15:05:35 +02:00
|
|
|
defer s.handlePanic("panicked while handling event")
|
2022-04-02 13:01:41 +02:00
|
|
|
|
2022-04-04 14:47:15 +02:00
|
|
|
for evt := s.scr.PollEvent(); evt != nil; evt = s.scr.PollEvent() {
|
|
|
|
switch event := evt.(type) {
|
|
|
|
case *tcell.EventResize:
|
2022-04-04 15:05:35 +02:00
|
|
|
s.Redraw()
|
2022-04-04 14:47:15 +02:00
|
|
|
case *tcell.EventKey:
|
2022-05-04 14:40:04 +02:00
|
|
|
s.startPanicSafeThread("panicked while handling key event", func() { s.onKeyPressed(event) })
|
2022-04-04 14:47:15 +02:00
|
|
|
case *tcell.EventMouse:
|
2022-05-04 14:40:04 +02:00
|
|
|
s.startPanicSafeThread("panicked while handling mouse event", func() { s.onMouseEvent(convertMouseEvent(event)) })
|
2022-04-04 14:47:15 +02:00
|
|
|
default:
|
2022-05-04 10:11:40 +02:00
|
|
|
s.StopWithError(fmt.Errorf("%#v", event))
|
2022-04-04 14:47:15 +02:00
|
|
|
}
|
2022-04-02 13:01:41 +02:00
|
|
|
}
|
2022-04-04 14:47:15 +02:00
|
|
|
}
|
|
|
|
|
2022-05-04 14:40:04 +02:00
|
|
|
func (s *Screen) startPanicSafeThread(errorMessage string, f func()) {
|
|
|
|
go func() {
|
|
|
|
defer s.handlePanic(errorMessage)
|
|
|
|
f()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2022-04-04 14:47:15 +02:00
|
|
|
func (s *Screen) drawloop() {
|
2022-04-04 15:05:35 +02:00
|
|
|
defer s.handlePanic("panicked while redrawing")
|
2022-04-04 14:47:15 +02:00
|
|
|
|
|
|
|
for range s.redrawCh {
|
|
|
|
w, h := s.scr.Size()
|
|
|
|
|
|
|
|
if s.buf == nil || s.buf.Width() != w || s.buf.Height() != h {
|
|
|
|
s.buf = buf2d.NewBuffer(w, h, DefaultRune)
|
2022-05-04 11:53:08 +02:00
|
|
|
} else {
|
|
|
|
s.buf.Fill(DefaultRune)
|
2022-04-04 14:47:15 +02:00
|
|
|
}
|
2022-04-02 13:01:41 +02:00
|
|
|
|
2023-04-24 11:41:38 +02:00
|
|
|
// draw root view
|
2022-04-04 14:47:15 +02:00
|
|
|
rw, rh := s.Root.Layout()
|
|
|
|
s.Root.Draw(truncateBuffer(s.buf, rw, rh))
|
2023-04-24 11:41:38 +02:00
|
|
|
|
|
|
|
// draw modals
|
|
|
|
for i := 0; i < s.modals.Size(); i++ {
|
|
|
|
v := s.modals.PeekAt(i)
|
|
|
|
mw, mh := v.Layout()
|
|
|
|
v.Draw(s.buf.Sub(0, 0, iff(mw >= 0, mw, s.buf.Width()), iff(mh >= 0, mh, s.buf.Height())))
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw buffer onto screen
|
2022-04-04 14:47:15 +02:00
|
|
|
drawBuffer(s.scr, s.buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-04 15:05:35 +02:00
|
|
|
func (s *Screen) handlePanic(msg string) {
|
2022-04-04 14:47:15 +02:00
|
|
|
if err := recover(); err != nil {
|
2022-04-04 15:05:35 +02:00
|
|
|
if e, ok := err.(error); ok {
|
|
|
|
s.StopWithError(adverr.Wrap(msg, e))
|
|
|
|
} else {
|
|
|
|
s.StopWithError(adverr.Wrap(msg, fmt.Errorf("%v", err)))
|
|
|
|
}
|
2022-04-04 14:47:15 +02:00
|
|
|
}
|
2021-01-10 21:52:29 +01:00
|
|
|
}
|
2023-04-24 11:41:38 +02:00
|
|
|
|
|
|
|
func (s *Screen) OpenModal(v View) {
|
|
|
|
s.modals.Push(v)
|
|
|
|
s.Redraw()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Screen) CloseModal() {
|
|
|
|
s.modals.Pop()
|
|
|
|
s.Redraw()
|
|
|
|
}
|