...

Source file src/codeberg.org/tslocum/cview/application.go

Documentation: codeberg.org/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/gdamore/tcell/v3"
     9  )
    10  
    11  const (
    12  	// The size of the event/update/redraw channels.
    13  	queueSize = 100
    14  
    15  	// The minimum duration between resize event callbacks.
    16  	resizeEventThrottle = 50 * time.Millisecond
    17  )
    18  
    19  // Application represents the top node of an application.
    20  //
    21  // It is not strictly required to use this class as none of the other classes
    22  // depend on it. However, it provides useful tools to set up an application and
    23  // plays nicely with all widgets.
    24  //
    25  // The following command displays a primitive p on the screen until Ctrl-C is
    26  // pressed:
    27  //
    28  //	if err := cview.NewApplication().SetRoot(p, true).Run(); err != nil {
    29  //	    panic(err)
    30  //	}
    31  type Application struct {
    32  	// The application's screen. Apart from Run(), this variable should never be
    33  	// set directly. Always use the screenReplacement channel after calling
    34  	// Fini(), to set a new screen (or nil to stop the application).
    35  	screen tcell.Screen
    36  
    37  	// The size of the application's screen.
    38  	width, height int
    39  
    40  	// The primitive which currently has the keyboard focus.
    41  	focus Primitive
    42  
    43  	// The root primitive to be seen on the screen.
    44  	root Primitive
    45  
    46  	// Whether or not the application resizes the root primitive.
    47  	rootFullscreen bool
    48  
    49  	// Whether or not to enable bracketed paste mode.
    50  	enableBracketedPaste bool
    51  
    52  	// Whether or not to enable mouse events.
    53  	enableMouse bool
    54  
    55  	// An optional capture function which receives a key event and returns the
    56  	// event to be forwarded to the default input handler (nil if nothing should
    57  	// be forwarded).
    58  	inputCapture func(event *tcell.EventKey) *tcell.EventKey
    59  
    60  	// Time a resize event was last processed.
    61  	lastResize time.Time
    62  
    63  	// Timer limiting how quickly resize events are processed.
    64  	throttleResize *time.Timer
    65  
    66  	// An optional callback function which is invoked when the application's
    67  	// window is initialized, and when the application's window size changes.
    68  	// After invoking this callback the screen is cleared and the application
    69  	// is drawn.
    70  	afterResize func(width int, height int)
    71  
    72  	// An optional callback function which is invoked before the application's
    73  	// focus changes.
    74  	beforeFocus func(p Primitive) bool
    75  
    76  	// An optional callback function which is invoked after the application's
    77  	// focus changes.
    78  	afterFocus func(p Primitive)
    79  
    80  	// An optional callback function which is invoked just before the root
    81  	// primitive is drawn.
    82  	beforeDraw func(screen tcell.Screen) bool
    83  
    84  	// An optional callback function which is invoked after the root primitive
    85  	// was drawn.
    86  	afterDraw func(screen tcell.Screen)
    87  
    88  	// Used to send screen events from separate goroutine to main event loop
    89  	events chan tcell.Event
    90  
    91  	// Functions queued from goroutines, used to serialize updates to primitives.
    92  	updates chan func()
    93  
    94  	// An object that the screen variable will be set to after Fini() was called.
    95  	// Use this channel to set a new screen object for the application
    96  	// (screen.Init() and draw() will be called implicitly). A value of nil will
    97  	// stop the application.
    98  	screenReplacement chan tcell.Screen
    99  
   100  	// An optional capture function which receives a mouse event and returns the
   101  	// event to be forwarded to the default mouse handler (nil if nothing should
   102  	// be forwarded).
   103  	mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)
   104  
   105  	// doubleClickInterval specifies the maximum time between clicks to register a
   106  	// double click rather than a single click.
   107  	doubleClickInterval time.Duration
   108  
   109  	mouseCapturingPrimitive Primitive        // A Primitive returned by a MouseHandler which will capture future mouse events.
   110  	lastMouseX, lastMouseY  int              // The last position of the mouse.
   111  	mouseDownX, mouseDownY  int              // The position of the mouse when its button was last pressed.
   112  	lastMouseClick          time.Time        // The time when a mouse button was last clicked.
   113  	lastMouseButtons        tcell.ButtonMask // The last mouse button state.
   114  
   115  	sync.RWMutex
   116  }
   117  
   118  // NewApplication creates and returns a new application.
   119  func NewApplication() *Application {
   120  	return &Application{
   121  		enableBracketedPaste: true,
   122  		events:               make(chan tcell.Event, queueSize),
   123  		updates:              make(chan func(), queueSize),
   124  		screenReplacement:    make(chan tcell.Screen, 1),
   125  	}
   126  }
   127  
   128  // HandlePanic (when deferred at the start of a goroutine) handles panics
   129  // gracefully. The terminal is returned to its original state before the panic
   130  // message is printed.
   131  //
   132  // Panics may only be handled by the panicking goroutine. Because of this,
   133  // HandlePanic must be deferred at the start of each goroutine (including main).
   134  func (a *Application) HandlePanic() {
   135  	p := recover()
   136  	if p == nil {
   137  		return
   138  	}
   139  
   140  	a.finalizeScreen()
   141  
   142  	panic(p)
   143  }
   144  
   145  // SetInputCapture sets a function which captures all key events before they are
   146  // forwarded to the key event handler of the primitive which currently has
   147  // focus. This function can then choose to forward that key event (or a
   148  // different one) by returning it or stop the key event processing by returning
   149  // nil.
   150  //
   151  // Note that this also affects the default event handling of the application
   152  // itself: Such a handler can intercept the Ctrl-C event which closes the
   153  // application.
   154  func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) {
   155  	a.Lock()
   156  	defer a.Unlock()
   157  
   158  	a.inputCapture = capture
   159  
   160  }
   161  
   162  // GetInputCapture returns the function installed with SetInputCapture() or nil
   163  // if no such function has been installed.
   164  func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
   165  	a.RLock()
   166  	defer a.RUnlock()
   167  
   168  	return a.inputCapture
   169  }
   170  
   171  // SetMouseCapture sets a function which captures mouse events (consisting of
   172  // the original tcell mouse event and the semantic mouse action) before they are
   173  // forwarded to the appropriate mouse event handler. This function can then
   174  // choose to forward that event (or a different one) by returning it or stop
   175  // the event processing by returning a nil mouse event.
   176  func (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) {
   177  	a.mouseCapture = capture
   178  
   179  }
   180  
   181  // GetMouseCapture returns the function installed with SetMouseCapture() or nil
   182  // if no such function has been installed.
   183  func (a *Application) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {
   184  	return a.mouseCapture
   185  }
   186  
   187  // SetDoubleClickInterval sets the maximum time between clicks to register a
   188  // double click rather than a single click. A standard duration is provided as
   189  // StandardDoubleClick. No interval is set by default, disabling double clicks.
   190  func (a *Application) SetDoubleClickInterval(interval time.Duration) {
   191  	a.doubleClickInterval = interval
   192  }
   193  
   194  // SetScreen allows you to provide your own tcell.Screen object. For most
   195  // applications, this is not needed and you should be familiar with
   196  // tcell.Screen when using this function.
   197  //
   198  // This function is typically called before the first call to Run(). Init() need
   199  // not be called on the screen.
   200  func (a *Application) SetScreen(screen tcell.Screen) {
   201  	if screen == nil {
   202  		return // Invalid input. Do nothing.
   203  	}
   204  
   205  	a.Lock()
   206  	if a.screen == nil {
   207  		// Run() has not been called yet.
   208  		a.screen = screen
   209  		a.Unlock()
   210  		return
   211  	}
   212  
   213  	// Run() is already in progress. Exchange screen.
   214  	oldScreen := a.screen
   215  	a.Unlock()
   216  	oldScreen.Fini()
   217  	a.screenReplacement <- screen
   218  }
   219  
   220  // GetScreen returns the current tcell.Screen of the application. Lock the
   221  // application when manipulating the screen to prevent race conditions. This
   222  // value is only available after calling Init or Run.
   223  func (a *Application) GetScreen() tcell.Screen {
   224  	a.RLock()
   225  	defer a.RUnlock()
   226  	return a.screen
   227  }
   228  
   229  // GetScreenSize returns the size of the application's screen. These values are
   230  // only available after calling Init or Run.
   231  func (a *Application) GetScreenSize() (width, height int) {
   232  	a.RLock()
   233  	defer a.RUnlock()
   234  	return a.width, a.height
   235  }
   236  
   237  // Init initializes the application screen. Calling Init before running is not
   238  // required. Its primary use is to populate screen dimensions before running an
   239  // application.
   240  func (a *Application) Init() error {
   241  	a.Lock()
   242  	defer a.Unlock()
   243  	return a.init()
   244  }
   245  
   246  func (a *Application) init() error {
   247  	if a.screen != nil {
   248  		return nil
   249  	}
   250  
   251  	var err error
   252  	a.screen, err = tcell.NewScreen()
   253  	if err != nil {
   254  		return err
   255  	}
   256  	if err = a.screen.Init(); err != nil {
   257  		return err
   258  	}
   259  	a.width, a.height = a.screen.Size()
   260  	if a.enableBracketedPaste {
   261  		a.screen.EnablePaste()
   262  	}
   263  	if a.enableMouse {
   264  		a.screen.EnableMouse()
   265  	}
   266  	return nil
   267  }
   268  
   269  // EnableBracketedPaste enables bracketed paste mode, which is enabled by default.
   270  func (a *Application) EnableBracketedPaste(enable bool) {
   271  	a.Lock()
   272  	defer a.Unlock()
   273  	if enable != a.enableBracketedPaste && a.screen != nil {
   274  		if enable {
   275  			a.screen.EnablePaste()
   276  		} else {
   277  			a.screen.DisablePaste()
   278  		}
   279  	}
   280  	a.enableBracketedPaste = enable
   281  }
   282  
   283  // EnableMouse enables mouse events.
   284  func (a *Application) EnableMouse(enable bool) {
   285  	a.Lock()
   286  	defer a.Unlock()
   287  	if enable != a.enableMouse && a.screen != nil {
   288  		if enable {
   289  			a.screen.EnableMouse()
   290  		} else {
   291  			a.screen.DisableMouse()
   292  		}
   293  	}
   294  	a.enableMouse = enable
   295  }
   296  
   297  // Run starts the application and thus the event loop. This function returns
   298  // when Stop() was called.
   299  func (a *Application) Run() error {
   300  	a.Lock()
   301  
   302  	// Initialize screen
   303  	err := a.init()
   304  	if err != nil {
   305  		a.Unlock()
   306  		return err
   307  	}
   308  
   309  	defer a.HandlePanic()
   310  
   311  	// Draw the screen for the first time.
   312  	a.Unlock()
   313  	a.draw()
   314  
   315  	// Separate loop to wait for screen replacement events.
   316  	var wg sync.WaitGroup
   317  	wg.Add(1)
   318  	go func() {
   319  		defer a.HandlePanic()
   320  
   321  		defer wg.Done()
   322  		for {
   323  			a.RLock()
   324  			screen := a.screen
   325  			a.RUnlock()
   326  			if screen == nil {
   327  				// We have no screen. Let's stop.
   328  				a.QueueEvent(nil)
   329  				break
   330  			}
   331  
   332  			// A screen was finalized (event is nil). Wait for a new screen.
   333  			screen = <-a.screenReplacement
   334  			if screen == nil {
   335  				// No new screen. We're done.
   336  				a.QueueEvent(nil)
   337  				return
   338  			}
   339  
   340  			// We have a new screen. Keep going.
   341  			a.Lock()
   342  			a.screen = screen
   343  			a.Unlock()
   344  
   345  			// Initialize and draw this screen.
   346  			if err := screen.Init(); err != nil {
   347  				panic(err)
   348  			}
   349  			if a.enableBracketedPaste {
   350  				screen.EnablePaste()
   351  			}
   352  			if a.enableMouse {
   353  				screen.EnableMouse()
   354  			}
   355  
   356  			a.draw()
   357  		}
   358  	}()
   359  
   360  	handle := func(event interface{}) {
   361  		a.RLock()
   362  		p := a.focus
   363  		inputCapture := a.inputCapture
   364  		screen := a.screen
   365  		a.RUnlock()
   366  
   367  		switch event := event.(type) {
   368  		case *tcell.EventKey:
   369  			// Intercept keys.
   370  			if inputCapture != nil {
   371  				event = inputCapture(event)
   372  				if event == nil {
   373  					a.draw()
   374  					return // Don't forward event.
   375  				}
   376  			}
   377  
   378  			// Ctrl-C closes the application.
   379  			if event.Key() == tcell.KeyCtrlC {
   380  				a.Stop()
   381  				return
   382  			}
   383  
   384  			// Pass other key events to the currently focused primitive.
   385  			if p != nil {
   386  				if handler := p.InputHandler(); handler != nil {
   387  					handler(event, func(p Primitive) {
   388  						a.SetFocus(p)
   389  					})
   390  					a.draw()
   391  				}
   392  			}
   393  		case *tcell.EventResize:
   394  			// Throttle resize events.
   395  			if time.Since(a.lastResize) < resizeEventThrottle {
   396  				// Stop timer
   397  				if a.throttleResize != nil && !a.throttleResize.Stop() {
   398  					select {
   399  					case <-a.throttleResize.C:
   400  					default:
   401  					}
   402  				}
   403  
   404  				event := event // Capture
   405  
   406  				// Start timer
   407  				a.throttleResize = time.AfterFunc(resizeEventThrottle, func() {
   408  					a.events <- event
   409  				})
   410  
   411  				return
   412  			}
   413  
   414  			a.lastResize = time.Now()
   415  
   416  			if screen == nil {
   417  				return
   418  			}
   419  
   420  			screen.Clear()
   421  			a.width, a.height = event.Size()
   422  
   423  			// Call afterResize handler if there is one.
   424  			if a.afterResize != nil {
   425  				a.afterResize(a.width, a.height)
   426  			}
   427  
   428  			a.draw()
   429  		case *tcell.EventMouse:
   430  			consumed, isMouseDownAction := a.fireMouseActions(event)
   431  			if consumed {
   432  				a.draw()
   433  			}
   434  			a.lastMouseButtons = event.Buttons()
   435  			if isMouseDownAction {
   436  				a.mouseDownX, a.mouseDownY = event.Position()
   437  			}
   438  		}
   439  	}
   440  
   441  	semaphore := &sync.Mutex{}
   442  
   443  	go func() {
   444  		defer a.HandlePanic()
   445  
   446  		for update := range a.updates {
   447  			semaphore.Lock()
   448  			update()
   449  			semaphore.Unlock()
   450  		}
   451  	}()
   452  
   453  	go func() {
   454  		defer a.HandlePanic()
   455  
   456  		for event := range a.events {
   457  			semaphore.Lock()
   458  			handle(event)
   459  			semaphore.Unlock()
   460  		}
   461  	}()
   462  
   463  	// Start screen event loop.
   464  	a.Lock()
   465  	screen := a.screen
   466  	a.Unlock()
   467  	if screen != nil {
   468  		for event := range screen.EventQ() {
   469  			if event == nil {
   470  				break
   471  			}
   472  
   473  			semaphore.Lock()
   474  			handle(event)
   475  			semaphore.Unlock()
   476  		}
   477  	}
   478  
   479  	// Wait for the screen replacement event loop to finish.
   480  	wg.Wait()
   481  	a.Lock()
   482  	a.screen = nil
   483  	a.Unlock()
   484  
   485  	return nil
   486  }
   487  
   488  // fireMouseActions analyzes the provided mouse event, derives mouse actions
   489  // from it and then forwards them to the corresponding primitives.
   490  func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) {
   491  	// We want to relay follow-up events to the same target primitive.
   492  	var targetPrimitive Primitive
   493  
   494  	// Helper function to fire a mouse action.
   495  	fire := func(action MouseAction) {
   496  		switch action {
   497  		case MouseLeftDown, MouseMiddleDown, MouseRightDown:
   498  			isMouseDownAction = true
   499  		}
   500  
   501  		// Intercept event.
   502  		if a.mouseCapture != nil {
   503  			event, action = a.mouseCapture(event, action)
   504  			if event == nil {
   505  				consumed = true
   506  				return // Don't forward event.
   507  			}
   508  		}
   509  
   510  		// Determine the target primitive.
   511  		var primitive, capturingPrimitive Primitive
   512  		if a.mouseCapturingPrimitive != nil {
   513  			primitive = a.mouseCapturingPrimitive
   514  			targetPrimitive = a.mouseCapturingPrimitive
   515  		} else if targetPrimitive != nil {
   516  			primitive = targetPrimitive
   517  		} else {
   518  			primitive = a.root
   519  		}
   520  		if primitive != nil {
   521  			if handler := primitive.MouseHandler(); handler != nil {
   522  				var wasConsumed bool
   523  				wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {
   524  					a.SetFocus(p)
   525  				})
   526  				if wasConsumed {
   527  					consumed = true
   528  				}
   529  			}
   530  		}
   531  		a.mouseCapturingPrimitive = capturingPrimitive
   532  	}
   533  
   534  	x, y := event.Position()
   535  	buttons := event.Buttons()
   536  	clickMoved := x != a.mouseDownX || y != a.mouseDownY
   537  	buttonChanges := buttons ^ a.lastMouseButtons
   538  
   539  	if x != a.lastMouseX || y != a.lastMouseY {
   540  		fire(MouseMove)
   541  		a.lastMouseX = x
   542  		a.lastMouseY = y
   543  	}
   544  
   545  	for _, buttonEvent := range []struct {
   546  		button                  tcell.ButtonMask
   547  		down, up, click, dclick MouseAction
   548  	}{
   549  		{tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick},
   550  		{tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},
   551  		{tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},
   552  	} {
   553  		if buttonChanges&buttonEvent.button != 0 {
   554  			if buttons&buttonEvent.button != 0 {
   555  				fire(buttonEvent.down)
   556  			} else {
   557  				fire(buttonEvent.up)
   558  				if !clickMoved {
   559  					if a.doubleClickInterval == 0 || a.lastMouseClick.Add(a.doubleClickInterval).Before(time.Now()) {
   560  						fire(buttonEvent.click)
   561  						a.lastMouseClick = time.Now()
   562  					} else {
   563  						fire(buttonEvent.dclick)
   564  						a.lastMouseClick = time.Time{} // reset
   565  					}
   566  				}
   567  			}
   568  		}
   569  	}
   570  
   571  	for _, wheelEvent := range []struct {
   572  		button tcell.ButtonMask
   573  		action MouseAction
   574  	}{
   575  		{tcell.WheelUp, MouseScrollUp},
   576  		{tcell.WheelDown, MouseScrollDown},
   577  		{tcell.WheelLeft, MouseScrollLeft},
   578  		{tcell.WheelRight, MouseScrollRight}} {
   579  		if buttons&wheelEvent.button != 0 {
   580  			fire(wheelEvent.action)
   581  		}
   582  	}
   583  
   584  	return consumed, isMouseDownAction
   585  }
   586  
   587  // Stop stops the application, causing Run() to return.
   588  func (a *Application) Stop() {
   589  	a.Lock()
   590  	defer a.Unlock()
   591  
   592  	a.finalizeScreen()
   593  	a.screenReplacement <- nil
   594  }
   595  
   596  func (a *Application) finalizeScreen() {
   597  	screen := a.screen
   598  	if screen == nil {
   599  		return
   600  	}
   601  
   602  	a.screen = nil
   603  	screen.Fini()
   604  }
   605  
   606  // Suspend temporarily suspends the application by exiting terminal UI mode and
   607  // invoking the provided function "f". When "f" returns, terminal UI mode is
   608  // entered again and the application resumes.
   609  //
   610  // A return value of true indicates that the application was suspended and "f"
   611  // was called. If false is returned, the application was already suspended,
   612  // terminal UI mode was not exited, and "f" was not called.
   613  func (a *Application) Suspend(f func()) bool {
   614  	a.Lock()
   615  	if a.screen == nil {
   616  		a.Unlock()
   617  		return false // Screen has not yet been initialized.
   618  	}
   619  	err := a.screen.Suspend()
   620  	a.Unlock()
   621  	if err != nil {
   622  		panic(err)
   623  	}
   624  
   625  	// Wait for "f" to return.
   626  	f()
   627  
   628  	a.Lock()
   629  	err = a.screen.Resume()
   630  	a.Unlock()
   631  	if err != nil {
   632  		panic(err)
   633  	}
   634  
   635  	return true
   636  }
   637  
   638  // Draw draws the provided primitives on the screen, or when no primitives are
   639  // provided, draws the application's root primitive (i.e. the entire screen).
   640  //
   641  // When one or more primitives are supplied, the Draw functions of the
   642  // primitives are called. Handlers set via BeforeDrawFunc and AfterDrawFunc are
   643  // not called.
   644  //
   645  // When no primitives are provided, the Draw function of the application's root
   646  // primitive is called. This results in drawing the entire screen. Handlers set
   647  // via BeforeDrawFunc and AfterDrawFunc are also called.
   648  func (a *Application) Draw(p ...Primitive) {
   649  	a.QueueUpdate(func() {
   650  		if len(p) == 0 {
   651  			a.draw()
   652  			return
   653  		}
   654  
   655  		a.Lock()
   656  		if a.screen != nil {
   657  			for _, primitive := range p {
   658  				primitive.Draw(a.screen)
   659  			}
   660  			a.screen.Show()
   661  		}
   662  		a.Unlock()
   663  	})
   664  }
   665  
   666  // draw actually does what Draw() promises to do.
   667  func (a *Application) draw() {
   668  	a.Lock()
   669  
   670  	screen := a.screen
   671  	root := a.root
   672  	fullscreen := a.rootFullscreen
   673  	before := a.beforeDraw
   674  	after := a.afterDraw
   675  
   676  	// Maybe we're not ready yet or not anymore.
   677  	if screen == nil || root == nil {
   678  		a.Unlock()
   679  		return
   680  	}
   681  
   682  	// Resize if requested.
   683  	if fullscreen {
   684  		root.SetRect(0, 0, a.width, a.height)
   685  	}
   686  
   687  	// Call before handler if there is one.
   688  	if before != nil {
   689  		a.Unlock()
   690  		if before(screen) {
   691  			screen.Show()
   692  			return
   693  		}
   694  	} else {
   695  		a.Unlock()
   696  	}
   697  
   698  	// Draw all primitives.
   699  	root.Draw(screen)
   700  
   701  	// Call after handler if there is one.
   702  	if after != nil {
   703  		after(screen)
   704  	}
   705  
   706  	// Sync screen.
   707  	screen.Show()
   708  }
   709  
   710  // SetBeforeDrawFunc installs a callback function which is invoked just before
   711  // the root primitive is drawn during screen updates. If the function returns
   712  // true, drawing will not continue, i.e. the root primitive will not be drawn
   713  // (and an after-draw-handler will not be called).
   714  //
   715  // Note that the screen is not cleared by the application. To clear the screen,
   716  // you may call screen.Clear().
   717  //
   718  // Provide nil to uninstall the callback function.
   719  func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) {
   720  	a.Lock()
   721  	defer a.Unlock()
   722  
   723  	a.beforeDraw = handler
   724  }
   725  
   726  // GetBeforeDrawFunc returns the callback function installed with
   727  // SetBeforeDrawFunc() or nil if none has been installed.
   728  func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
   729  	a.RLock()
   730  	defer a.RUnlock()
   731  
   732  	return a.beforeDraw
   733  }
   734  
   735  // SetAfterDrawFunc installs a callback function which is invoked after the root
   736  // primitive was drawn during screen updates.
   737  //
   738  // Provide nil to uninstall the callback function.
   739  func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) {
   740  	a.Lock()
   741  	defer a.Unlock()
   742  
   743  	a.afterDraw = handler
   744  }
   745  
   746  // GetAfterDrawFunc returns the callback function installed with
   747  // SetAfterDrawFunc() or nil if none has been installed.
   748  func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
   749  	a.RLock()
   750  	defer a.RUnlock()
   751  
   752  	return a.afterDraw
   753  }
   754  
   755  // SetRoot sets the root primitive for this application. If "fullscreen" is set
   756  // to true, the root primitive's position will be changed to fill the screen.
   757  //
   758  // This function must be called at least once or nothing will be displayed when
   759  // the application starts.
   760  //
   761  // It also calls SetFocus() on the primitive and draws the application.
   762  func (a *Application) SetRoot(root Primitive, fullscreen bool) {
   763  	a.Lock()
   764  	a.root = root
   765  	a.rootFullscreen = fullscreen
   766  	if a.screen != nil {
   767  		a.screen.Clear()
   768  	}
   769  	a.Unlock()
   770  
   771  	a.SetFocus(root)
   772  
   773  	a.Draw()
   774  }
   775  
   776  // ResizeToFullScreen resizes the given primitive such that it fills the entire
   777  // screen.
   778  func (a *Application) ResizeToFullScreen(p Primitive) {
   779  	a.RLock()
   780  	width, height := a.width, a.height
   781  	a.RUnlock()
   782  	p.SetRect(0, 0, width, height)
   783  }
   784  
   785  // SetAfterResizeFunc installs a callback function which is invoked when the
   786  // application's window is initialized, and when the application's window size
   787  // changes. After invoking this callback the screen is cleared and the
   788  // application is drawn.
   789  //
   790  // Provide nil to uninstall the callback function.
   791  func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) {
   792  	a.Lock()
   793  	defer a.Unlock()
   794  
   795  	a.afterResize = handler
   796  }
   797  
   798  // GetAfterResizeFunc returns the callback function installed with
   799  // SetAfterResizeFunc() or nil if none has been installed.
   800  func (a *Application) GetAfterResizeFunc() func(width int, height int) {
   801  	a.RLock()
   802  	defer a.RUnlock()
   803  
   804  	return a.afterResize
   805  }
   806  
   807  // SetFocus sets the focus on a new primitive. All key events will be redirected
   808  // to that primitive. Callers must ensure that the primitive will handle key
   809  // events.
   810  //
   811  // Blur() will be called on the previously focused primitive. Focus() will be
   812  // called on the new primitive.
   813  func (a *Application) SetFocus(p Primitive) {
   814  	a.Lock()
   815  
   816  	if a.beforeFocus != nil {
   817  		a.Unlock()
   818  		ok := a.beforeFocus(p)
   819  		if !ok {
   820  			return
   821  		}
   822  		a.Lock()
   823  	}
   824  
   825  	if a.focus != nil {
   826  		a.focus.Blur()
   827  	}
   828  
   829  	a.focus = p
   830  
   831  	if a.screen != nil {
   832  		a.screen.HideCursor()
   833  	}
   834  
   835  	if a.afterFocus != nil {
   836  		a.Unlock()
   837  
   838  		a.afterFocus(p)
   839  	} else {
   840  		a.Unlock()
   841  	}
   842  
   843  	if p != nil {
   844  		p.Focus(func(p Primitive) {
   845  			a.SetFocus(p)
   846  		})
   847  	}
   848  }
   849  
   850  // GetFocus returns the primitive which has the current focus. If none has it,
   851  // nil is returned.
   852  func (a *Application) GetFocus() Primitive {
   853  	a.RLock()
   854  	defer a.RUnlock()
   855  
   856  	return a.focus
   857  }
   858  
   859  // SetBeforeFocusFunc installs a callback function which is invoked before the
   860  // application's focus changes. Return false to maintain the current focus.
   861  //
   862  // Provide nil to uninstall the callback function.
   863  func (a *Application) SetBeforeFocusFunc(handler func(p Primitive) bool) {
   864  	a.Lock()
   865  	defer a.Unlock()
   866  
   867  	a.beforeFocus = handler
   868  }
   869  
   870  // SetAfterFocusFunc installs a callback function which is invoked after the
   871  // application's focus changes.
   872  //
   873  // Provide nil to uninstall the callback function.
   874  func (a *Application) SetAfterFocusFunc(handler func(p Primitive)) {
   875  	a.Lock()
   876  	defer a.Unlock()
   877  
   878  	a.afterFocus = handler
   879  }
   880  
   881  // QueueUpdate queues a function to be executed as part of the event loop.
   882  //
   883  // Note that Draw() is not implicitly called after the execution of f as that
   884  // may not be desirable. You can call Draw() from f if the screen should be
   885  // refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
   886  // up with an immediate refresh of the screen.
   887  func (a *Application) QueueUpdate(f func()) {
   888  	a.updates <- f
   889  }
   890  
   891  // QueueUpdateDraw works like QueueUpdate() except, when one or more primitives
   892  // are provided, the primitives are drawn after the provided function returns.
   893  // When no primitives are provided, the entire screen is drawn after the
   894  // provided function returns.
   895  func (a *Application) QueueUpdateDraw(f func(), p ...Primitive) {
   896  	a.QueueUpdate(func() {
   897  		f()
   898  
   899  		if len(p) == 0 {
   900  			a.draw()
   901  			return
   902  		}
   903  		a.Lock()
   904  		if a.screen != nil {
   905  			for _, primitive := range p {
   906  				primitive.Draw(a.screen)
   907  			}
   908  			a.screen.Show()
   909  		}
   910  		a.Unlock()
   911  	})
   912  }
   913  
   914  // QueueEvent sends an event to the Application event loop.
   915  //
   916  // It is not recommended for event to be nil.
   917  func (a *Application) QueueEvent(event tcell.Event) {
   918  	a.events <- event
   919  }
   920  
   921  // RingBell sends a bell code to the terminal.
   922  func (a *Application) RingBell() {
   923  	a.QueueUpdate(func() {
   924  		fmt.Print(string(byte(7)))
   925  	})
   926  }
   927  

View as plain text