package tui import ( "errors" "fmt" "git.tordarus.net/Tordarus/buf2d" "github.com/gdamore/tcell" ) type Screen struct { scr tcell.Screen buf *ViewBuffer stopCh chan error redrawCh chan struct{} // Root is the root view which is currently shown on screen Root View // KeyPressed is called every time a key or key-combination is pressed. KeyPressed func(event *KeyEvent) (consumed bool) // MouseClicked is called every time a mouse button was pressed. MouseClicked func(event *MouseEvent) (consumed bool) } func NewScreen(root View) (*Screen, error) { scr, err := tcell.NewScreen() if err != nil { return nil, err } s := &Screen{ Root: root, scr: scr, stopCh: make(chan error, 1), redrawCh: make(chan struct{}, 1), } go s.eventloop() go s.drawloop() return s, nil } func (s *Screen) Start() error { err := s.scr.Init() if err != nil { return err } defer s.scr.Fini() s.scr.EnableMouse() return <-s.stopCh } func (s *Screen) Stop() { s.StopWithError(nil) } func (s *Screen) StopWithError(err error) { s.stopCh <- err } func (s *Screen) onKeyPressed(event *KeyEvent) { if s.KeyPressed == nil || !s.KeyPressed(event) { s.Root.OnKeyPressed(event) } s.Redraw() } func (s *Screen) onMouseClicked(event *MouseEvent) { if s.MouseClicked == nil || !s.MouseClicked(event) { s.Root.OnMouseClicked(event) } if event.Button != MouseButtonNone { s.Redraw() } } func convertMouseEvent(original *tcell.EventMouse) *MouseEvent { x, y := original.Position() return &MouseEvent{ X: x, Y: y, Button: convertMouseButton(original.Buttons()), Modifiers: original.Modifiers(), } } func (s *Screen) Redraw() { s.redrawCh <- struct{}{} } func (s *Screen) eventloop() { defer s.stopOnPanic() for evt := s.scr.PollEvent(); evt != nil; evt = s.scr.PollEvent() { switch event := evt.(type) { case *tcell.EventResize: go s.Redraw() case *tcell.EventKey: go s.onKeyPressed(event) case *tcell.EventMouse: go s.onMouseClicked(convertMouseEvent(event)) default: s.StopWithError(errors.New(fmt.Sprintf("%#v", event))) } } s.StopWithError(errors.New("unknown error occured")) } func (s *Screen) drawloop() { defer s.stopOnPanic() 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) } rw, rh := s.Root.Layout() s.Root.Draw(truncateBuffer(s.buf, rw, rh)) drawBuffer(s.scr, s.buf) } } func (s *Screen) stopOnPanic() { if err := recover(); err != nil { s.StopWithError(fmt.Errorf("%v", err)) } }