...

Source file src/gitlab.com/tslocum/cview/application.go

Documentation: gitlab.com/tslocum/cview

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

View as plain text