package tui import ( "fmt" "git.tordarus.net/Tordarus/adverr" "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), } s.KeyPressed = CloseOnCtrlC(s) 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) { go s.Root.OnKeyPressed(event) } s.Redraw() } func (s *Screen) onMouseClicked(event *MouseEvent) { if s.MouseClicked == nil || !s.MouseClicked(event) { go 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.handlePanic("panicked while handling event") for evt := s.scr.PollEvent(); evt != nil; evt = s.scr.PollEvent() { switch event := evt.(type) { case *tcell.EventResize: s.Redraw() case *tcell.EventKey: s.onKeyPressed(event) case *tcell.EventMouse: s.onMouseClicked(convertMouseEvent(event)) default: s.StopWithError(fmt.Errorf("%#v", event)) } } } func (s *Screen) drawloop() { defer s.handlePanic("panicked while redrawing") 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) } else { s.buf.Fill(DefaultRune) } rw, rh := s.Root.Layout() s.Root.Draw(truncateBuffer(s.buf, rw, rh)) drawBuffer(s.scr, s.buf) } } func (s *Screen) handlePanic(msg string) { if err := recover(); err != nil { if e, ok := err.(error); ok { s.StopWithError(adverr.Wrap(msg, e)) } else { s.StopWithError(adverr.Wrap(msg, fmt.Errorf("%v", err))) } } }