tview/application.go

280 lines
6.3 KiB
Go

package tview
import (
"sync"
"github.com/gdamore/tcell"
)
// Application represents the top node of an application.
//
// It is not strictly required to use this class as none of the other classes
// depend on it. However, it provides useful tools to set up an application and
// plays nicely with all widgets.
type Application struct {
sync.RWMutex
// The application's screen.
screen tcell.Screen
// The primitive which currently has the keyboard focus.
focus Primitive
// The root primitive to be seen on the screen.
root Primitive
// Whether or not the application resizes the root primitive.
rootAutoSize bool
// Key overrides.
keyOverrides map[tcell.Key]func(p Primitive) bool
// Rune overrides.
runeOverrides map[rune]func(p Primitive) bool
}
// NewApplication creates and returns a new application.
func NewApplication() *Application {
return &Application{
keyOverrides: make(map[tcell.Key]func(p Primitive) bool),
runeOverrides: make(map[rune]func(p Primitive) bool),
}
}
// SetKeyCapture installs a global capture function for the given key. It
// intercepts all events for the given key and routes them to the handler.
// The handler receives the Primitive to which the key is originally redirected,
// the one which has focus, or nil if it was not directed to a Primitive. The
// handler also returns whether or not the key event is then forwarded to that
// Primitive. Draw() is called implicitly if the event is not forwarded.
//
// Special keys (e.g. Escape, Enter, or Ctrl-A) are defined by the "key"
// argument. The "ch" rune is ignored. Other keys (e.g. "a", "h", or "5") are
// specified by their rune, with key set to tcell.KeyRune. See also
// https://godoc.org/github.com/gdamore/tcell#EventKey for more information.
//
// To remove a handler again, provide a nil handler for the same key.
//
// The application itself will exit when Ctrl-C is pressed. You can intercept
// this with this function as well.
func (a *Application) SetKeyCapture(key tcell.Key, ch rune, handler func(p Primitive) bool) *Application {
if key == tcell.KeyRune {
if handler != nil {
a.runeOverrides[ch] = handler
} else {
if _, ok := a.runeOverrides[ch]; ok {
delete(a.runeOverrides, ch)
}
}
} else {
if handler != nil {
a.keyOverrides[key] = handler
} else {
if _, ok := a.keyOverrides[key]; ok {
delete(a.keyOverrides, key)
}
}
}
return a
}
// Run starts the application and thus the event loop. This function returns
// when Stop() was called.
func (a *Application) Run() error {
var err error
a.Lock()
// Make a screen.
a.screen, err = tcell.NewScreen()
if err != nil {
a.Unlock()
return err
}
if err = a.screen.Init(); err != nil {
a.Unlock()
return err
}
// We catch panics to clean up because they mess up the terminal.
defer func() {
if p := recover(); p != nil {
if a.screen != nil {
a.screen.Fini()
}
panic(p)
}
}()
// Draw the screen for the first time.
a.Unlock()
a.Draw()
// Start event loop.
for {
a.RLock()
screen := a.screen
a.RUnlock()
if screen == nil {
break
}
// Wait for next event.
event := a.screen.PollEvent()
if event == nil {
break // The screen was finalized.
}
switch event := event.(type) {
case *tcell.EventKey:
a.RLock()
p := a.focus
a.RUnlock()
// Intercept keys.
if event.Key() == tcell.KeyRune {
if handler, ok := a.runeOverrides[event.Rune()]; ok {
if !handler(p) {
a.Draw()
break
}
}
} else {
if handler, ok := a.keyOverrides[event.Key()]; ok {
pr := p
if event.Key() == tcell.KeyCtrlC {
pr = nil
}
if !handler(pr) {
a.Draw()
break
}
}
}
// Ctrl-C closes the application.
if event.Key() == tcell.KeyCtrlC {
a.Stop()
}
// Pass other key events to the currently focused primitive.
if p != nil {
if handler := p.InputHandler(); handler != nil {
handler(event, func(p Primitive) {
a.SetFocus(p)
})
a.Draw()
}
}
case *tcell.EventResize:
a.Lock()
screen := a.screen
if a.rootAutoSize && a.root != nil {
width, height := screen.Size()
a.root.SetRect(0, 0, width, height)
}
a.Unlock()
screen.Clear()
a.Draw()
}
}
return nil
}
// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
a.RLock()
defer a.RUnlock()
if a.screen == nil {
return
}
a.screen.Fini()
a.screen = nil
}
// Draw refreshes the screen. It calls the Draw() function of the application's
// root primitive and then syncs the screen buffer.
func (a *Application) Draw() *Application {
a.RLock()
defer a.RUnlock()
// Maybe we're not ready yet or not anymore.
if a.screen == nil || a.root == nil {
return a
}
// Resize if requested.
if a.rootAutoSize && a.root != nil {
width, height := a.screen.Size()
a.root.SetRect(0, 0, width, height)
}
// Draw all primitives.
a.root.Draw(a.screen)
// Sync screen.
a.screen.Show()
return a
}
// SetRoot sets the root primitive for this application. This function must be
// called or nothing will be displayed when the application starts.
//
// It also calls SetFocus() on the primitive.
func (a *Application) SetRoot(root Primitive, autoSize bool) *Application {
a.Lock()
a.root = root
a.rootAutoSize = autoSize
if a.screen != nil {
a.screen.Clear()
}
a.Unlock()
a.SetFocus(root)
return a
}
// ResizeToFullScreen resizes the given primitive such that it fills the entire
// screen.
func (a *Application) ResizeToFullScreen(p Primitive) *Application {
a.RLock()
width, height := a.screen.Size()
a.RUnlock()
p.SetRect(0, 0, width, height)
return a
}
// SetFocus sets the focus on a new primitive. All key events will be redirected
// to that primitive. Callers must ensure that the primitive will handle key
// events.
//
// Blur() will be called on the previously focused primitive. Focus() will be
// called on the new primitive.
func (a *Application) SetFocus(p Primitive) *Application {
a.Lock()
if a.focus != nil {
a.focus.Blur()
}
a.focus = p
if a.screen != nil {
a.screen.HideCursor()
}
a.Unlock()
p.Focus(func(p Primitive) {
a.SetFocus(p)
})
return a
}
// GetFocus returns the primitive which has the current focus. If none has it,
// nil is returned.
func (a *Application) GetFocus() Primitive {
a.RLock()
defer a.RUnlock()
return a.focus
}