...

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  //
   591  // BUG(tslocum) First key event is lost when resuming a suspended application.
   592  //
   593  // Issue: https://github.com/gdamore/tcell/issues/194
   594  func (a *Application) Suspend(f func()) bool {
   595  	a.RLock()
   596  	screen := a.screen
   597  	a.RUnlock()
   598  	if screen == nil {
   599  		return false // Screen has not yet been initialized.
   600  	}
   601  
   602  	// Enter suspended mode.
   603  	screen.Fini()
   604  
   605  	// Wait for "f" to return.
   606  	f()
   607  
   608  	// Make a new screen.
   609  	var err error
   610  	screen, err = tcell.NewScreen()
   611  	if err != nil {
   612  		panic(err)
   613  	}
   614  	a.screenReplacement <- screen
   615  
   616  	// Continue application loop.
   617  	return true
   618  }
   619  
   620  // Draw refreshes the screen (during the next update cycle). It calls the Draw()
   621  // function of the application's root primitive and then syncs the screen
   622  // buffer.
   623  func (a *Application) Draw() {
   624  	a.QueueUpdate(func() {
   625  		a.draw()
   626  	})
   627  }
   628  
   629  // draw actually does what Draw() promises to do.
   630  func (a *Application) draw() {
   631  	a.Lock()
   632  
   633  	screen := a.screen
   634  	root := a.root
   635  	fullscreen := a.rootFullscreen
   636  	before := a.beforeDraw
   637  	after := a.afterDraw
   638  
   639  	// Maybe we're not ready yet or not anymore.
   640  	if screen == nil || root == nil {
   641  		a.Unlock()
   642  		return
   643  	}
   644  
   645  	// Resize if requested.
   646  	if fullscreen {
   647  		root.SetRect(0, 0, a.width, a.height)
   648  	}
   649  
   650  	// Call before handler if there is one.
   651  	if before != nil {
   652  		a.Unlock()
   653  		if before(screen) {
   654  			screen.Show()
   655  			return
   656  		}
   657  	} else {
   658  		a.Unlock()
   659  	}
   660  
   661  	// Draw all primitives.
   662  	root.Draw(screen)
   663  
   664  	// Call after handler if there is one.
   665  	if after != nil {
   666  		after(screen)
   667  	}
   668  
   669  	// Sync screen.
   670  	screen.Show()
   671  }
   672  
   673  // SetBeforeDrawFunc installs a callback function which is invoked just before
   674  // the root primitive is drawn during screen updates. If the function returns
   675  // true, drawing will not continue, i.e. the root primitive will not be drawn
   676  // (and an after-draw-handler will not be called).
   677  //
   678  // Note that the screen is not cleared by the application. To clear the screen,
   679  // you may call screen.Clear().
   680  //
   681  // Provide nil to uninstall the callback function.
   682  func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) {
   683  	a.Lock()
   684  	defer a.Unlock()
   685  
   686  	a.beforeDraw = handler
   687  }
   688  
   689  // GetBeforeDrawFunc returns the callback function installed with
   690  // SetBeforeDrawFunc() or nil if none has been installed.
   691  func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
   692  	a.RLock()
   693  	defer a.RUnlock()
   694  
   695  	return a.beforeDraw
   696  }
   697  
   698  // SetAfterDrawFunc installs a callback function which is invoked after the root
   699  // primitive was drawn during screen updates.
   700  //
   701  // Provide nil to uninstall the callback function.
   702  func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) {
   703  	a.Lock()
   704  	defer a.Unlock()
   705  
   706  	a.afterDraw = handler
   707  }
   708  
   709  // GetAfterDrawFunc returns the callback function installed with
   710  // SetAfterDrawFunc() or nil if none has been installed.
   711  func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
   712  	a.RLock()
   713  	defer a.RUnlock()
   714  
   715  	return a.afterDraw
   716  }
   717  
   718  // SetRoot sets the root primitive for this application. If "fullscreen" is set
   719  // to true, the root primitive's position will be changed to fill the screen.
   720  //
   721  // This function must be called at least once or nothing will be displayed when
   722  // the application starts.
   723  //
   724  // It also calls SetFocus() on the primitive.
   725  func (a *Application) SetRoot(root Primitive, fullscreen bool) {
   726  	a.Lock()
   727  	a.root = root
   728  	a.rootFullscreen = fullscreen
   729  	if a.screen != nil {
   730  		a.screen.Clear()
   731  	}
   732  	a.Unlock()
   733  
   734  	a.SetFocus(root)
   735  }
   736  
   737  // ResizeToFullScreen resizes the given primitive such that it fills the entire
   738  // screen.
   739  func (a *Application) ResizeToFullScreen(p Primitive) {
   740  	a.RLock()
   741  	width, height := a.width, a.height
   742  	a.RUnlock()
   743  	p.SetRect(0, 0, width, height)
   744  }
   745  
   746  // SetAfterResizeFunc installs a callback function which is invoked when the
   747  // application's window is initialized, and when the application's window size
   748  // changes. After invoking this callback the screen is cleared and the
   749  // application is drawn.
   750  //
   751  // Provide nil to uninstall the callback function.
   752  func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) {
   753  	a.Lock()
   754  	defer a.Unlock()
   755  
   756  	a.afterResize = handler
   757  }
   758  
   759  // GetAfterResizeFunc returns the callback function installed with
   760  // SetAfterResizeFunc() or nil if none has been installed.
   761  func (a *Application) GetAfterResizeFunc() func(width int, height int) {
   762  	a.RLock()
   763  	defer a.RUnlock()
   764  
   765  	return a.afterResize
   766  }
   767  
   768  // SetFocus sets the focus on a new primitive. All key events will be redirected
   769  // to that primitive. Callers must ensure that the primitive will handle key
   770  // events.
   771  //
   772  // Blur() will be called on the previously focused primitive. Focus() will be
   773  // called on the new primitive.
   774  func (a *Application) SetFocus(p Primitive) {
   775  	a.Lock()
   776  
   777  	if a.beforeFocus != nil {
   778  		a.Unlock()
   779  		ok := a.beforeFocus(p)
   780  		if !ok {
   781  			return
   782  		}
   783  		a.Lock()
   784  	}
   785  
   786  	if a.focus != nil {
   787  		a.focus.Blur()
   788  	}
   789  
   790  	a.focus = p
   791  
   792  	if a.screen != nil {
   793  		a.screen.HideCursor()
   794  	}
   795  
   796  	if a.afterFocus != nil {
   797  		a.Unlock()
   798  
   799  		a.afterFocus(p)
   800  	} else {
   801  		a.Unlock()
   802  	}
   803  
   804  	if p != nil {
   805  		p.Focus(func(p Primitive) {
   806  			a.SetFocus(p)
   807  		})
   808  	}
   809  }
   810  
   811  // GetFocus returns the primitive which has the current focus. If none has it,
   812  // nil is returned.
   813  func (a *Application) GetFocus() Primitive {
   814  	a.RLock()
   815  	defer a.RUnlock()
   816  
   817  	return a.focus
   818  }
   819  
   820  // SetBeforeFocusFunc installs a callback function which is invoked before the
   821  // application's focus changes. Return false to maintain the current focus.
   822  //
   823  // Provide nil to uninstall the callback function.
   824  func (a *Application) SetBeforeFocusFunc(handler func(p Primitive) bool) {
   825  	a.Lock()
   826  	defer a.Unlock()
   827  
   828  	a.beforeFocus = handler
   829  }
   830  
   831  // SetAfterFocusFunc installs a callback function which is invoked after the
   832  // application's focus changes.
   833  //
   834  // Provide nil to uninstall the callback function.
   835  func (a *Application) SetAfterFocusFunc(handler func(p Primitive)) {
   836  	a.Lock()
   837  	defer a.Unlock()
   838  
   839  	a.afterFocus = handler
   840  }
   841  
   842  // QueueUpdate queues a function to be executed as part of the event loop.
   843  //
   844  // Note that Draw() is not implicitly called after the execution of f as that
   845  // may not be desirable. You can call Draw() from f if the screen should be
   846  // refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
   847  // up with an immediate refresh of the screen.
   848  func (a *Application) QueueUpdate(f func()) {
   849  	a.updates <- f
   850  }
   851  
   852  // QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
   853  // immediately after executing f.
   854  func (a *Application) QueueUpdateDraw(f func()) {
   855  	a.QueueUpdate(func() {
   856  		f()
   857  		a.draw()
   858  	})
   859  }
   860  
   861  // QueueEvent sends an event to the Application event loop.
   862  //
   863  // It is not recommended for event to be nil.
   864  func (a *Application) QueueEvent(event tcell.Event) {
   865  	a.events <- event
   866  }
   867  
   868  // RingBell sends a bell code to the terminal.
   869  func (a *Application) RingBell() {
   870  	a.QueueUpdate(func() {
   871  		fmt.Print(string(byte(7)))
   872  	})
   873  }
   874  

View as plain text