2017-12-15 22:29:21 +08:00
|
|
|
package tview
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/gdamore/tcell"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Application represents the top node of an application.
|
2017-12-27 00:34:58 +08:00
|
|
|
//
|
|
|
|
// 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.
|
2017-12-15 22:29:21 +08:00
|
|
|
type Application struct {
|
2017-12-27 00:34:58 +08:00
|
|
|
sync.RWMutex
|
2017-12-15 22:29:21 +08:00
|
|
|
|
|
|
|
// 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
|
2017-12-27 00:34:58 +08:00
|
|
|
|
2017-12-27 23:04:21 +08:00
|
|
|
// Whether or not the application resizes the root primitive.
|
|
|
|
rootAutoSize bool
|
|
|
|
|
2018-01-15 04:29:34 +08:00
|
|
|
// An optional capture function which receives a key event and returns the
|
|
|
|
// event to be forwarded to the default input handler (nil if nothing should
|
|
|
|
// be forwarded).
|
|
|
|
inputCapture func(event *tcell.EventKey) *tcell.EventKey
|
2017-12-15 22:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewApplication creates and returns a new application.
|
|
|
|
func NewApplication() *Application {
|
2018-01-15 04:29:34 +08:00
|
|
|
return &Application{}
|
2017-12-27 00:34:58 +08:00
|
|
|
}
|
|
|
|
|
2018-01-15 04:29:34 +08:00
|
|
|
// SetInputCapture sets a function which captures all key events before they are
|
|
|
|
// forwarded to the key event handler of the primitive which currently has
|
|
|
|
// focus. This function can then choose to forward that key event (or a
|
|
|
|
// different one) by returning it or stop the key event processing by returning
|
|
|
|
// nil.
|
2017-12-27 00:34:58 +08:00
|
|
|
//
|
2018-01-15 04:29:34 +08:00
|
|
|
// Note that this also affects the default event handling of the application
|
|
|
|
// itself: Such a handler can intercept the Ctrl-C event which closes the
|
|
|
|
// applicaton.
|
|
|
|
func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
|
|
|
|
a.inputCapture = capture
|
2017-12-27 00:34:58 +08:00
|
|
|
return a
|
2017-12-15 22:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts the application and thus the event loop. This function returns
|
|
|
|
// when Stop() was called.
|
|
|
|
func (a *Application) Run() error {
|
|
|
|
var err error
|
2017-12-24 07:08:52 +08:00
|
|
|
a.Lock()
|
2017-12-15 22:29:21 +08:00
|
|
|
|
|
|
|
// Make a screen.
|
|
|
|
a.screen, err = tcell.NewScreen()
|
|
|
|
if err != nil {
|
2017-12-24 07:08:52 +08:00
|
|
|
a.Unlock()
|
2017-12-15 22:29:21 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = a.screen.Init(); err != nil {
|
2017-12-24 07:08:52 +08:00
|
|
|
a.Unlock()
|
2017-12-15 22:29:21 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We catch panics to clean up because they mess up the terminal.
|
|
|
|
defer func() {
|
|
|
|
if p := recover(); p != nil {
|
2017-12-19 03:04:52 +08:00
|
|
|
if a.screen != nil {
|
|
|
|
a.screen.Fini()
|
|
|
|
}
|
2017-12-15 22:29:21 +08:00
|
|
|
panic(p)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Draw the screen for the first time.
|
2017-12-24 07:08:52 +08:00
|
|
|
a.Unlock()
|
2017-12-15 22:29:21 +08:00
|
|
|
a.Draw()
|
|
|
|
|
|
|
|
// Start event loop.
|
|
|
|
for {
|
2017-12-27 00:34:58 +08:00
|
|
|
a.RLock()
|
2018-01-11 18:52:27 +08:00
|
|
|
screen := a.screen
|
|
|
|
a.RUnlock()
|
|
|
|
if screen == nil {
|
2017-12-19 03:04:52 +08:00
|
|
|
break
|
|
|
|
}
|
2018-01-11 18:52:27 +08:00
|
|
|
|
|
|
|
// Wait for next event.
|
2017-12-15 22:29:21 +08:00
|
|
|
event := a.screen.PollEvent()
|
|
|
|
if event == nil {
|
|
|
|
break // The screen was finalized.
|
|
|
|
}
|
2018-01-11 18:52:27 +08:00
|
|
|
|
2017-12-15 22:29:21 +08:00
|
|
|
switch event := event.(type) {
|
|
|
|
case *tcell.EventKey:
|
2017-12-27 00:34:58 +08:00
|
|
|
a.RLock()
|
|
|
|
p := a.focus
|
|
|
|
a.RUnlock()
|
|
|
|
|
|
|
|
// Intercept keys.
|
2018-01-15 04:29:34 +08:00
|
|
|
if a.inputCapture != nil {
|
|
|
|
event = a.inputCapture(event)
|
|
|
|
if event == nil {
|
|
|
|
break // Don't forward event.
|
2017-12-27 00:34:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ctrl-C closes the application.
|
2017-12-15 22:29:21 +08:00
|
|
|
if event.Key() == tcell.KeyCtrlC {
|
2017-12-27 00:34:58 +08:00
|
|
|
a.Stop()
|
2017-12-15 22:29:21 +08:00
|
|
|
}
|
2017-12-27 00:34:58 +08:00
|
|
|
|
|
|
|
// Pass other key events to the currently focused primitive.
|
2017-12-15 22:29:21 +08:00
|
|
|
if p != nil {
|
|
|
|
if handler := p.InputHandler(); handler != nil {
|
2017-12-19 03:04:52 +08:00
|
|
|
handler(event, func(p Primitive) {
|
|
|
|
a.SetFocus(p)
|
|
|
|
})
|
2017-12-15 22:29:21 +08:00
|
|
|
a.Draw()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *tcell.EventResize:
|
2018-01-13 19:43:12 +08:00
|
|
|
a.Lock()
|
|
|
|
screen := a.screen
|
2017-12-27 23:04:21 +08:00
|
|
|
if a.rootAutoSize && a.root != nil {
|
2018-01-13 19:43:12 +08:00
|
|
|
width, height := screen.Size()
|
2017-12-27 23:04:21 +08:00
|
|
|
a.root.SetRect(0, 0, width, height)
|
|
|
|
}
|
2018-01-13 19:43:12 +08:00
|
|
|
a.Unlock()
|
|
|
|
screen.Clear()
|
2017-12-26 08:07:30 +08:00
|
|
|
a.Draw()
|
2017-12-15 22:29:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops the application, causing Run() to return.
|
|
|
|
func (a *Application) Stop() {
|
2017-12-27 00:34:58 +08:00
|
|
|
a.RLock()
|
|
|
|
defer a.RUnlock()
|
2017-12-19 03:04:52 +08:00
|
|
|
if a.screen == nil {
|
|
|
|
return
|
|
|
|
}
|
2017-12-15 22:29:21 +08:00
|
|
|
a.screen.Fini()
|
2017-12-19 03:04:52 +08:00
|
|
|
a.screen = nil
|
2017-12-15 22:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2017-12-27 00:34:58 +08:00
|
|
|
a.RLock()
|
|
|
|
defer a.RUnlock()
|
2017-12-15 22:29:21 +08:00
|
|
|
|
2017-12-19 03:04:52 +08:00
|
|
|
// Maybe we're not ready yet or not anymore.
|
2017-12-24 07:08:52 +08:00
|
|
|
if a.screen == nil || a.root == nil {
|
2017-12-15 22:29:21 +08:00
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2018-01-02 04:50:20 +08:00
|
|
|
// Resize if requested.
|
|
|
|
if a.rootAutoSize && a.root != nil {
|
|
|
|
width, height := a.screen.Size()
|
|
|
|
a.root.SetRect(0, 0, width, height)
|
|
|
|
}
|
|
|
|
|
2017-12-15 22:29:21 +08:00
|
|
|
// Draw all primitives.
|
2017-12-24 07:08:52 +08:00
|
|
|
a.root.Draw(a.screen)
|
2017-12-15 22:29:21 +08:00
|
|
|
|
|
|
|
// 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.
|
2018-01-02 00:16:36 +08:00
|
|
|
//
|
|
|
|
// It also calls SetFocus() on the primitive.
|
2017-12-27 23:04:21 +08:00
|
|
|
func (a *Application) SetRoot(root Primitive, autoSize bool) *Application {
|
2017-12-15 22:29:21 +08:00
|
|
|
|
2018-01-02 00:16:36 +08:00
|
|
|
a.Lock()
|
2017-12-15 22:29:21 +08:00
|
|
|
a.root = root
|
2017-12-27 23:04:21 +08:00
|
|
|
a.rootAutoSize = autoSize
|
2018-01-02 04:50:20 +08:00
|
|
|
if a.screen != nil {
|
|
|
|
a.screen.Clear()
|
|
|
|
}
|
2018-01-02 00:16:36 +08:00
|
|
|
a.Unlock()
|
|
|
|
|
|
|
|
a.SetFocus(root)
|
2017-12-15 22:29:21 +08:00
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2017-12-27 23:04:21 +08:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-12-15 22:29:21 +08:00
|
|
|
// 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
|
2017-12-29 05:19:36 +08:00
|
|
|
if a.screen != nil {
|
|
|
|
a.screen.HideCursor()
|
|
|
|
}
|
2017-12-15 22:29:21 +08:00
|
|
|
a.Unlock()
|
2017-12-19 03:04:52 +08:00
|
|
|
p.Focus(func(p Primitive) {
|
|
|
|
a.SetFocus(p)
|
|
|
|
})
|
2017-12-15 22:29:21 +08:00
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
2017-12-21 03:54:49 +08:00
|
|
|
|
|
|
|
// GetFocus returns the primitive which has the current focus. If none has it,
|
|
|
|
// nil is returned.
|
|
|
|
func (a *Application) GetFocus() Primitive {
|
2017-12-27 00:34:58 +08:00
|
|
|
a.RLock()
|
|
|
|
defer a.RUnlock()
|
2017-12-21 03:54:49 +08:00
|
|
|
return a.focus
|
|
|
|
}
|