2017-12-15 15:29:21 +01:00
package tview
import (
2018-03-13 08:16:09 +01:00
"fmt"
"os"
2017-12-15 15:29:21 +01:00
"sync"
"github.com/gdamore/tcell"
)
// Application represents the top node of an application.
2017-12-26 17:34:58 +01: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 15:29:21 +01:00
type Application struct {
2017-12-26 17:34:58 +01:00
sync . RWMutex
2017-12-15 15:29:21 +01:00
// The application's screen.
screen tcell . Screen
2018-09-05 13:26:22 +02:00
// Indicates whether the application's screen is currently active.
running bool
2017-12-15 15:29:21 +01:00
// The primitive which currently has the keyboard focus.
focus Primitive
// The root primitive to be seen on the screen.
root Primitive
2017-12-26 17:34:58 +01:00
2017-12-27 16:04:21 +01:00
// Whether or not the application resizes the root primitive.
2018-03-05 16:37:10 +01:00
rootFullscreen bool
2017-12-27 16:04:21 +01:00
2018-01-14 21:29:34 +01: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
2018-02-20 17:15:17 +01:00
// An optional callback function which is invoked just before the root
// primitive is drawn.
beforeDraw func ( screen tcell . Screen ) bool
// An optional callback function which is invoked after the root primitive
// was drawn.
afterDraw func ( screen tcell . Screen )
2018-03-13 08:16:09 +01:00
2018-06-28 14:49:42 +02:00
// Halts the event loop during suspended mode.
2018-06-20 16:54:24 +02:00
suspendMutex sync . Mutex
2018-10-09 00:02:02 -04:00
// Used to send screen events from separate goroutine to main event loop
events chan tcell . Event
// Used to send primitive updates from separate goroutines to the main event loop
updates chan func ( )
2017-12-15 15:29:21 +01:00
}
// NewApplication creates and returns a new application.
func NewApplication ( ) * Application {
2018-10-09 00:02:02 -04:00
return & Application {
events : make ( chan tcell . Event , 100 ) ,
updates : make ( chan func ( ) , 100 ) ,
}
2017-12-26 17:34:58 +01:00
}
2018-01-14 21:29:34 +01: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-26 17:34:58 +01:00
//
2018-01-14 21:29:34 +01: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
2018-01-17 21:17:59 +01:00
// applicatoon.
2018-01-14 21:29:34 +01:00
func ( a * Application ) SetInputCapture ( capture func ( event * tcell . EventKey ) * tcell . EventKey ) * Application {
a . inputCapture = capture
2017-12-26 17:34:58 +01:00
return a
2017-12-15 15:29:21 +01:00
}
2018-03-10 23:30:42 +01:00
// GetInputCapture returns the function installed with SetInputCapture() or nil
// if no such function has been installed.
func ( a * Application ) GetInputCapture ( ) func ( event * tcell . EventKey ) * tcell . EventKey {
return a . inputCapture
}
2018-09-05 13:26:22 +02:00
// SetScreen allows you to provide your own tcell.Screen object. For most
// applications, this is not needed and you should be familiar with
// tcell.Screen when using this function. Run() will call Init() and Fini() on
// the provided screen object.
//
// This function is typically called before calling Run(). Calling it while an
// application is running will switch the application to the new screen. Fini()
// will be called on the old screen and Init() on the new screen (errors
// returned by Init() will lead to a panic).
//
// Note that calling Suspend() will invoke Fini() on your screen object and it
// will not be restored when suspended mode ends. Instead, a new default screen
// object will be created.
func ( a * Application ) SetScreen ( screen tcell . Screen ) * Application {
a . Lock ( )
defer a . Unlock ( )
if a . running {
a . screen . Fini ( )
}
a . screen = screen
if a . running {
if err := a . screen . Init ( ) ; err != nil {
panic ( err )
}
}
return a
}
2017-12-15 15:29:21 +01: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 00:08:52 +01:00
a . Lock ( )
2017-12-15 15:29:21 +01:00
2018-09-05 13:26:22 +02:00
// Make a screen if there is none yet.
if a . screen == nil {
a . screen , err = tcell . NewScreen ( )
if err != nil {
a . Unlock ( )
return err
}
2017-12-15 15:29:21 +01:00
}
if err = a . screen . Init ( ) ; err != nil {
2017-12-24 00:08:52 +01:00
a . Unlock ( )
2017-12-15 15:29:21 +01:00
return err
}
2018-09-05 13:26:22 +02:00
a . running = true
2017-12-15 15:29:21 +01:00
// We catch panics to clean up because they mess up the terminal.
defer func ( ) {
if p := recover ( ) ; p != nil {
2017-12-18 20:04:52 +01:00
if a . screen != nil {
a . screen . Fini ( )
}
2018-09-05 13:26:22 +02:00
a . running = false
2017-12-15 15:29:21 +01:00
panic ( p )
}
} ( )
// Draw the screen for the first time.
2017-12-24 00:08:52 +01:00
a . Unlock ( )
2017-12-15 15:29:21 +01:00
a . Draw ( )
2018-10-09 00:02:02 -04:00
// Separate loop to wait for screen events
go func ( ) {
for {
// Do not poll events during suspend mode
a . suspendMutex . Lock ( )
a . RLock ( )
screen := a . screen
a . RUnlock ( )
if screen == nil {
a . suspendMutex . Unlock ( )
// send signal to stop main event loop
a . QueueEvent ( nil )
break
}
2018-01-11 11:52:27 +01:00
2018-10-09 00:02:02 -04:00
// Wait for next event.
a . QueueEvent ( screen . PollEvent ( ) )
a . suspendMutex . Unlock ( )
2017-12-15 15:29:21 +01:00
}
2018-10-09 00:02:02 -04:00
} ( )
2018-01-11 11:52:27 +01:00
2018-10-09 00:02:02 -04:00
// Start event loop.
loop :
for {
select {
case event := <- a . events :
if event == nil {
// The screen was finalized. Exit the loop.
break loop
}
2017-12-26 17:34:58 +01:00
2018-10-09 00:02:02 -04:00
switch event := event . ( type ) {
case * tcell . EventKey :
a . RLock ( )
p := a . focus
a . RUnlock ( )
// Intercept keys.
if a . inputCapture != nil {
event = a . inputCapture ( event )
if event == nil {
break loop // Don't forward event.
}
2017-12-26 17:34:58 +01:00
}
2018-10-09 00:02:02 -04:00
// Ctrl-C closes the application.
if event . Key ( ) == tcell . KeyCtrlC {
a . Stop ( )
}
2017-12-26 17:34:58 +01:00
2018-10-09 00:02:02 -04:00
// 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 ( )
}
2017-12-15 15:29:21 +01:00
}
2018-10-09 00:02:02 -04:00
case * tcell . EventResize :
a . RLock ( )
screen := a . screen
a . RUnlock ( )
screen . Clear ( )
a . Draw ( )
2017-12-15 15:29:21 +01:00
}
2018-10-09 00:02:02 -04:00
case updater := <- a . updates :
updater ( )
2017-12-26 01:07:30 +01:00
a . Draw ( )
2017-12-15 15:29:21 +01:00
}
2018-10-09 00:02:02 -04:00
2017-12-15 15:29:21 +01:00
}
return nil
}
// Stop stops the application, causing Run() to return.
func ( a * Application ) Stop ( ) {
2018-06-28 14:49:42 +02:00
a . Lock ( )
defer a . Unlock ( )
2017-12-18 20:04:52 +01:00
if a . screen == nil {
return
}
2017-12-15 15:29:21 +01:00
a . screen . Fini ( )
2017-12-18 20:04:52 +01:00
a . screen = nil
2018-09-05 13:26:22 +02:00
a . running = false
2017-12-15 15:29:21 +01:00
}
2018-03-13 08:16:09 +01:00
// Suspend temporarily suspends the application by exiting terminal UI mode and
// invoking the provided function "f". When "f" returns, terminal UI mode is
// entered again and the application resumes.
//
// A return value of true indicates that the application was suspended and "f"
// was called. If false is returned, the application was already suspended,
// terminal UI mode was not exited, and "f" was not called.
func ( a * Application ) Suspend ( f func ( ) ) bool {
2018-06-28 14:49:42 +02:00
a . RLock ( )
2018-03-13 08:16:09 +01:00
2018-06-20 16:54:24 +02:00
if a . screen == nil {
2018-06-28 14:49:42 +02:00
// Screen has not yet been initialized.
a . RUnlock ( )
2018-03-13 08:16:09 +01:00
return false
}
// Enter suspended mode.
2018-06-20 16:54:24 +02:00
a . suspendMutex . Lock ( )
2018-06-20 19:32:19 +02:00
defer a . suspendMutex . Unlock ( )
2018-06-28 14:49:42 +02:00
a . RUnlock ( )
2018-03-13 08:16:09 +01:00
a . Stop ( )
// Deal with panics during suspended mode. Exit the program.
defer func ( ) {
if p := recover ( ) ; p != nil {
fmt . Println ( p )
os . Exit ( 1 )
}
} ( )
// Wait for "f" to return.
f ( )
// Make a new screen and redraw.
a . Lock ( )
var err error
a . screen , err = tcell . NewScreen ( )
if err != nil {
a . Unlock ( )
panic ( err )
}
if err = a . screen . Init ( ) ; err != nil {
a . Unlock ( )
panic ( err )
}
2018-09-05 13:26:22 +02:00
a . running = true
2018-03-13 08:16:09 +01:00
a . Unlock ( )
a . Draw ( )
// Continue application loop.
return true
}
2017-12-15 15:29:21 +01: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 {
2018-07-27 16:30:50 +02:00
a . Lock ( )
defer a . Unlock ( )
2018-02-20 17:15:17 +01:00
screen := a . screen
root := a . root
2018-03-05 16:37:10 +01:00
fullscreen := a . rootFullscreen
2018-02-20 17:15:17 +01:00
before := a . beforeDraw
after := a . afterDraw
2017-12-15 15:29:21 +01:00
2017-12-18 20:04:52 +01:00
// Maybe we're not ready yet or not anymore.
2018-02-20 17:15:17 +01:00
if screen == nil || root == nil {
2017-12-15 15:29:21 +01:00
return a
}
2018-01-01 21:50:20 +01:00
// Resize if requested.
2018-02-20 17:15:17 +01:00
if fullscreen && root != nil {
width , height := screen . Size ( )
root . SetRect ( 0 , 0 , width , height )
}
// Call before handler if there is one.
if before != nil {
if before ( screen ) {
screen . Show ( )
return a
}
2018-01-01 21:50:20 +01:00
}
2017-12-15 15:29:21 +01:00
// Draw all primitives.
2018-02-20 17:15:17 +01:00
root . Draw ( screen )
// Call after handler if there is one.
if after != nil {
after ( screen )
}
2017-12-15 15:29:21 +01:00
// Sync screen.
2018-02-20 17:15:17 +01:00
screen . Show ( )
return a
}
2017-12-15 15:29:21 +01:00
2018-02-20 17:15:17 +01:00
// SetBeforeDrawFunc installs a callback function which is invoked just before
// the root primitive is drawn during screen updates. If the function returns
// true, drawing will not continue, i.e. the root primitive will not be drawn
// (and an after-draw-handler will not be called).
//
// Note that the screen is not cleared by the application. To clear the screen,
// you may call screen.Clear().
//
// Provide nil to uninstall the callback function.
func ( a * Application ) SetBeforeDrawFunc ( handler func ( screen tcell . Screen ) bool ) * Application {
a . beforeDraw = handler
return a
}
2018-03-10 23:30:42 +01:00
// GetBeforeDrawFunc returns the callback function installed with
// SetBeforeDrawFunc() or nil if none has been installed.
func ( a * Application ) GetBeforeDrawFunc ( ) func ( screen tcell . Screen ) bool {
return a . beforeDraw
}
2018-02-20 17:15:17 +01:00
// SetAfterDrawFunc installs a callback function which is invoked after the root
// primitive was drawn during screen updates.
//
// Provide nil to uninstall the callback function.
func ( a * Application ) SetAfterDrawFunc ( handler func ( screen tcell . Screen ) ) * Application {
a . afterDraw = handler
2017-12-15 15:29:21 +01:00
return a
}
2018-03-10 23:30:42 +01:00
// GetAfterDrawFunc returns the callback function installed with
// SetAfterDrawFunc() or nil if none has been installed.
func ( a * Application ) GetAfterDrawFunc ( ) func ( screen tcell . Screen ) {
return a . afterDraw
}
2018-03-05 16:37:10 +01:00
// SetRoot sets the root primitive for this application. If "fullscreen" is set
// to true, the root primitive's position will be changed to fill the screen.
//
// This function must be called at least once or nothing will be displayed when
// the application starts.
2018-01-01 17:16:36 +01:00
//
// It also calls SetFocus() on the primitive.
2018-03-05 16:37:10 +01:00
func ( a * Application ) SetRoot ( root Primitive , fullscreen bool ) * Application {
2018-01-01 17:16:36 +01:00
a . Lock ( )
2017-12-15 15:29:21 +01:00
a . root = root
2018-03-05 16:37:10 +01:00
a . rootFullscreen = fullscreen
2018-01-01 21:50:20 +01:00
if a . screen != nil {
a . screen . Clear ( )
}
2018-01-01 17:16:36 +01:00
a . Unlock ( )
a . SetFocus ( root )
2017-12-15 15:29:21 +01:00
return a
}
2017-12-27 16:04:21 +01: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 15:29:21 +01: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-28 22:19:36 +01:00
if a . screen != nil {
a . screen . HideCursor ( )
}
2017-12-15 15:29:21 +01:00
a . Unlock ( )
2018-03-10 12:59:42 +01:00
if p != nil {
p . Focus ( func ( p Primitive ) {
a . SetFocus ( p )
} )
}
2017-12-15 15:29:21 +01:00
return a
}
2017-12-20 20:54:49 +01:00
// GetFocus returns the primitive which has the current focus. If none has it,
// nil is returned.
func ( a * Application ) GetFocus ( ) Primitive {
2017-12-26 17:34:58 +01:00
a . RLock ( )
defer a . RUnlock ( )
2017-12-20 20:54:49 +01:00
return a . focus
}
2018-10-09 00:02:02 -04:00
// QueueUpdate is used to synchronize changes to primitives by carrying an update function from separate goroutine to the Application event loop via channel
func ( a * Application ) QueueUpdate ( f func ( ) ) * Application {
a . updates <- f
return a
}
// QueueEvent takes an Event instance and sends it to the Application event loop via channel
func ( a * Application ) QueueEvent ( e tcell . Event ) * Application {
a . events <- e
return a
}