...

Source file src/code.rocketnine.space/tslocum/cview/application.go

Documentation: code.rocketnine.space/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  // 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  		a.Unlock()
   255  		return err
   256  	}
   257  	if err = a.screen.Init(); err != nil {
   258  		a.Unlock()
   259  		return err
   260  	}
   261  	a.width, a.height = a.screen.Size()
   262  	if a.enableBracketedPaste {
   263  		a.screen.EnablePaste()
   264  	}
   265  	if a.enableMouse {
   266  		a.screen.EnableMouse()
   267  	}
   268  	return nil
   269  }
   270  
   271  // EnableBracketedPaste enables bracketed paste mode, which is enabled by default.
   272  func (a *Application) EnableBracketedPaste(enable bool) {
   273  	a.Lock()
   274  	defer a.Unlock()
   275  	if enable != a.enableBracketedPaste && a.screen != nil {
   276  		if enable {
   277  			a.screen.EnablePaste()
   278  		} else {
   279  			a.screen.DisablePaste()
   280  		}
   281  	}
   282  	a.enableBracketedPaste = enable
   283  }
   284  
   285  // EnableMouse enables mouse events.
   286  func (a *Application) EnableMouse(enable bool) {
   287  	a.Lock()
   288  	defer a.Unlock()
   289  	if enable != a.enableMouse && a.screen != nil {
   290  		if enable {
   291  			a.screen.EnableMouse()
   292  		} else {
   293  			a.screen.DisableMouse()
   294  		}
   295  	}
   296  	a.enableMouse = enable
   297  }
   298  
   299  // Run starts the application and thus the event loop. This function returns
   300  // when Stop() was called.
   301  func (a *Application) Run() error {
   302  	a.Lock()
   303  
   304  	// Initialize screen
   305  	err := a.init()
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	defer a.HandlePanic()
   311  
   312  	// Draw the screen for the first time.
   313  	a.Unlock()
   314  	a.draw()
   315  
   316  	// Separate loop to wait for screen replacement events.
   317  	var wg sync.WaitGroup
   318  	wg.Add(1)
   319  	go func() {
   320  		defer a.HandlePanic()
   321  
   322  		defer wg.Done()
   323  		for {
   324  			a.RLock()
   325  			screen := a.screen
   326  			a.RUnlock()
   327  			if screen == nil {
   328  				// We have no screen. Let's stop.
   329  				a.QueueEvent(nil)
   330  				break
   331  			}
   332  
   333  			// A screen was finalized (event is nil). Wait for a new screen.
   334  			screen = <-a.screenReplacement
   335  			if screen == nil {
   336  				// No new screen. We're done.
   337  				a.QueueEvent(nil)
   338  				return
   339  			}
   340  
   341  			// We have a new screen. Keep going.
   342  			a.Lock()
   343  			a.screen = screen
   344  			a.Unlock()
   345  
   346  			// Initialize and draw this screen.
   347  			if err := screen.Init(); err != nil {
   348  				panic(err)
   349  			}
   350  			if a.enableBracketedPaste {
   351  				screen.EnablePaste()
   352  			}
   353  			if a.enableMouse {
   354  				screen.EnableMouse()
   355  			}
   356  
   357  			a.draw()
   358  		}
   359  	}()
   360  
   361  	handle := func(event interface{}) {
   362  		a.RLock()
   363  		p := a.focus
   364  		inputCapture := a.inputCapture
   365  		screen := a.screen
   366  		a.RUnlock()
   367  
   368  		switch event := event.(type) {
   369  		case *tcell.EventKey:
   370  			// Intercept keys.
   371  			if inputCapture != nil {
   372  				event = inputCapture(event)
   373  				if event == nil {
   374  					a.draw()
   375  					return // Don't forward event.
   376  				}
   377  			}
   378  
   379  			// Ctrl-C closes the application.
   380  			if event.Key() == tcell.KeyCtrlC {
   381  				a.Stop()
   382  				return
   383  			}
   384  
   385  			// Pass other key events to the currently focused primitive.
   386  			if p != nil {
   387  				if handler := p.InputHandler(); handler != nil {
   388  					handler(event, func(p Primitive) {
   389  						a.SetFocus(p)
   390  					})
   391  					a.draw()
   392  				}
   393  			}
   394  		case *tcell.EventResize:
   395  			// Throttle resize events.
   396  			if time.Since(a.lastResize) < resizeEventThrottle {
   397  				// Stop timer
   398  				if a.throttleResize != nil && !a.throttleResize.Stop() {
   399  					select {
   400  					case <-a.throttleResize.C:
   401  					default:
   402  					}
   403  				}
   404  
   405  				event := event // Capture
   406  
   407  				// Start timer
   408  				a.throttleResize = time.AfterFunc(resizeEventThrottle, func() {
   409  					a.events <- event
   410  				})
   411  
   412  				return
   413  			}
   414  
   415  			a.lastResize = time.Now()
   416  
   417  			if screen == nil {
   418  				return
   419  			}
   420  
   421  			screen.Clear()
   422  			a.width, a.height = event.Size()
   423  
   424  			// Call afterResize handler if there is one.
   425  			if a.afterResize != nil {
   426  				a.afterResize(a.width, a.height)
   427  			}
   428  
   429  			a.draw()
   430  		case *tcell.EventMouse:
   431  			consumed, isMouseDownAction := a.fireMouseActions(event)
   432  			if consumed {
   433  				a.draw()
   434  			}
   435  			a.lastMouseButtons = event.Buttons()
   436  			if isMouseDownAction {
   437  				a.mouseDownX, a.mouseDownY = event.Position()
   438  			}
   439  		}
   440  	}
   441  
   442  	semaphore := &sync.Mutex{}
   443  
   444  	go func() {
   445  		defer a.HandlePanic()
   446  
   447  		for update := range a.updates {
   448  			semaphore.Lock()
   449  			update()
   450  			semaphore.Unlock()
   451  		}
   452  	}()
   453  
   454  	go func() {
   455  		defer a.HandlePanic()
   456  
   457  		for event := range a.events {
   458  			semaphore.Lock()
   459  			handle(event)
   460  			semaphore.Unlock()
   461  		}
   462  	}()
   463  
   464  	// Start screen event loop.
   465  	for {
   466  		a.Lock()
   467  		screen := a.screen
   468  		a.Unlock()
   469  
   470  		if screen == nil {
   471  			break
   472  		}
   473  
   474  		// Wait for next event.
   475  		event := screen.PollEvent()
   476  		if event == nil {
   477  			break
   478  		}
   479  
   480  		semaphore.Lock()
   481  		handle(event)
   482  		semaphore.Unlock()
   483  	}
   484  
   485  	// Wait for the screen replacement event loop to finish.
   486  	wg.Wait()
   487  	a.screen = nil
   488  
   489  	return nil
   490  }
   491  
   492  // fireMouseActions analyzes the provided mouse event, derives mouse actions
   493  // from it and then forwards them to the corresponding primitives.
   494  func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) {
   495  	// We want to relay follow-up events to the same target primitive.
   496  	var targetPrimitive Primitive
   497  
   498  	// Helper function to fire a mouse action.
   499  	fire := func(action MouseAction) {
   500  		switch action {
   501  		case MouseLeftDown, MouseMiddleDown, MouseRightDown:
   502  			isMouseDownAction = true
   503  		}
   504  
   505  		// Intercept event.
   506  		if a.mouseCapture != nil {
   507  			event, action = a.mouseCapture(event, action)
   508  			if event == nil {
   509  				consumed = true
   510  				return // Don't forward event.
   511  			}
   512  		}
   513  
   514  		// Determine the target primitive.
   515  		var primitive, capturingPrimitive Primitive
   516  		if a.mouseCapturingPrimitive != nil {
   517  			primitive = a.mouseCapturingPrimitive
   518  			targetPrimitive = a.mouseCapturingPrimitive
   519  		} else if targetPrimitive != nil {
   520  			primitive = targetPrimitive
   521  		} else {
   522  			primitive = a.root
   523  		}
   524  		if primitive != nil {
   525  			if handler := primitive.MouseHandler(); handler != nil {
   526  				var wasConsumed bool
   527  				wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {
   528  					a.SetFocus(p)
   529  				})
   530  				if wasConsumed {
   531  					consumed = true
   532  				}
   533  			}
   534  		}
   535  		a.mouseCapturingPrimitive = capturingPrimitive
   536  	}
   537  
   538  	x, y := event.Position()
   539  	buttons := event.Buttons()
   540  	clickMoved := x != a.mouseDownX || y != a.mouseDownY
   541  	buttonChanges := buttons ^ a.lastMouseButtons
   542  
   543  	if x != a.lastMouseX || y != a.lastMouseY {
   544  		fire(MouseMove)
   545  		a.lastMouseX = x
   546  		a.lastMouseY = y
   547  	}
   548  
   549  	for _, buttonEvent := range []struct {
   550  		button                  tcell.ButtonMask
   551  		down, up, click, dclick MouseAction
   552  	}{
   553  		{tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick},
   554  		{tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},
   555  		{tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},
   556  	} {
   557  		if buttonChanges&buttonEvent.button != 0 {
   558  			if buttons&buttonEvent.button != 0 {
   559  				fire(buttonEvent.down)
   560  			} else {
   561  				fire(buttonEvent.up)
   562  				if !clickMoved {
   563  					if a.doubleClickInterval == 0 || a.lastMouseClick.Add(a.doubleClickInterval).Before(time.Now()) {
   564  						fire(buttonEvent.click)
   565  						a.lastMouseClick = time.Now()
   566  					} else {
   567  						fire(buttonEvent.dclick)
   568  						a.lastMouseClick = time.Time{} // reset
   569  					}
   570  				}
   571  			}
   572  		}
   573  	}
   574  
   575  	for _, wheelEvent := range []struct {
   576  		button tcell.ButtonMask
   577  		action MouseAction
   578  	}{
   579  		{tcell.WheelUp, MouseScrollUp},
   580  		{tcell.WheelDown, MouseScrollDown},
   581  		{tcell.WheelLeft, MouseScrollLeft},
   582  		{tcell.WheelRight, MouseScrollRight}} {
   583  		if buttons&wheelEvent.button != 0 {
   584  			fire(wheelEvent.action)
   585  		}
   586  	}
   587  
   588  	return consumed, isMouseDownAction
   589  }
   590  
   591  // Stop stops the application, causing Run() to return.
   592  func (a *Application) Stop() {
   593  	a.Lock()
   594  	defer a.Unlock()
   595  
   596  	a.finalizeScreen()
   597  	a.screenReplacement <- nil
   598  }
   599  
   600  func (a *Application) finalizeScreen() {
   601  	screen := a.screen
   602  	if screen == nil {
   603  		return
   604  	}
   605  
   606  	a.screen = nil
   607  	screen.Fini()
   608  }
   609  
   610  // Suspend temporarily suspends the application by exiting terminal UI mode and
   611  // invoking the provided function "f". When "f" returns, terminal UI mode is
   612  // entered again and the application resumes.
   613  //
   614  // A return value of true indicates that the application was suspended and "f"
   615  // was called. If false is returned, the application was already suspended,
   616  // terminal UI mode was not exited, and "f" was not called.
   617  func (a *Application) Suspend(f func()) bool {
   618  	a.Lock()
   619  	if a.screen == nil {
   620  		a.Unlock()
   621  		return false // Screen has not yet been initialized.
   622  	}
   623  	err := a.screen.Suspend()
   624  	a.Unlock()
   625  	if err != nil {
   626  		panic(err)
   627  	}
   628  
   629  	// Wait for "f" to return.
   630  	f()
   631  
   632  	a.Lock()
   633  	err = a.screen.Resume()
   634  	a.Unlock()
   635  	if err != nil {
   636  		panic(err)
   637  	}
   638  
   639  	return true
   640  }
   641  
   642  // Draw draws the provided primitives on the screen, or when no primitives are
   643  // provided, draws the application's root primitive (i.e. the entire screen).
   644  //
   645  // When one or more primitives are supplied, the Draw functions of the
   646  // primitives are called. Handlers set via BeforeDrawFunc and AfterDrawFunc are
   647  // not called.
   648  //
   649  // When no primitives are provided, the Draw function of the application's root
   650  // primitive is called. This results in drawing the entire screen. Handlers set
   651  // via BeforeDrawFunc and AfterDrawFunc are also called.
   652  func (a *Application) Draw(p ...Primitive) {
   653  	a.QueueUpdate(func() {
   654  		if len(p) == 0 {
   655  			a.draw()
   656  			return
   657  		}
   658  
   659  		a.Lock()
   660  		if a.screen != nil {
   661  			for _, primitive := range p {
   662  				primitive.Draw(a.screen)
   663  			}
   664  			a.screen.Show()
   665  		}
   666  		a.Unlock()
   667  	})
   668  }
   669  
   670  // draw actually does what Draw() promises to do.
   671  func (a *Application) draw() {
   672  	a.Lock()
   673  
   674  	screen := a.screen
   675  	root := a.root
   676  	fullscreen := a.rootFullscreen
   677  	before := a.beforeDraw
   678  	after := a.afterDraw
   679  
   680  	// Maybe we're not ready yet or not anymore.
   681  	if screen == nil || root == nil {
   682  		a.Unlock()
   683  		return
   684  	}
   685  
   686  	// Resize if requested.
   687  	if fullscreen {
   688  		root.SetRect(0, 0, a.width, a.height)
   689  	}
   690  
   691  	// Call before handler if there is one.
   692  	if before != nil {
   693  		a.Unlock()
   694  		if before(screen) {
   695  			screen.Show()
   696  			return
   697  		}
   698  	} else {
   699  		a.Unlock()
   700  	}
   701  
   702  	// Draw all primitives.
   703  	root.Draw(screen)
   704  
   705  	// Call after handler if there is one.
   706  	if after != nil {
   707  		after(screen)
   708  	}
   709  
   710  	// Sync screen.
   711  	screen.Show()
   712  }
   713  
   714  // SetBeforeDrawFunc installs a callback function which is invoked just before
   715  // the root primitive is drawn during screen updates. If the function returns
   716  // true, drawing will not continue, i.e. the root primitive will not be drawn
   717  // (and an after-draw-handler will not be called).
   718  //
   719  // Note that the screen is not cleared by the application. To clear the screen,
   720  // you may call screen.Clear().
   721  //
   722  // Provide nil to uninstall the callback function.
   723  func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) {
   724  	a.Lock()
   725  	defer a.Unlock()
   726  
   727  	a.beforeDraw = handler
   728  }
   729  
   730  // GetBeforeDrawFunc returns the callback function installed with
   731  // SetBeforeDrawFunc() or nil if none has been installed.
   732  func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
   733  	a.RLock()
   734  	defer a.RUnlock()
   735  
   736  	return a.beforeDraw
   737  }
   738  
   739  // SetAfterDrawFunc installs a callback function which is invoked after the root
   740  // primitive was drawn during screen updates.
   741  //
   742  // Provide nil to uninstall the callback function.
   743  func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) {
   744  	a.Lock()
   745  	defer a.Unlock()
   746  
   747  	a.afterDraw = handler
   748  }
   749  
   750  // GetAfterDrawFunc returns the callback function installed with
   751  // SetAfterDrawFunc() or nil if none has been installed.
   752  func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
   753  	a.RLock()
   754  	defer a.RUnlock()
   755  
   756  	return a.afterDraw
   757  }
   758  
   759  // SetRoot sets the root primitive for this application. If "fullscreen" is set
   760  // to true, the root primitive's position will be changed to fill the screen.
   761  //
   762  // This function must be called at least once or nothing will be displayed when
   763  // the application starts.
   764  //
   765  // It also calls SetFocus() on the primitive and draws the application.
   766  func (a *Application) SetRoot(root Primitive, fullscreen bool) {
   767  	a.Lock()
   768  	a.root = root
   769  	a.rootFullscreen = fullscreen
   770  	if a.screen != nil {
   771  		a.screen.Clear()
   772  	}
   773  	a.Unlock()
   774  
   775  	a.SetFocus(root)
   776  
   777  	a.Draw()
   778  }
   779  
   780  // ResizeToFullScreen resizes the given primitive such that it fills the entire
   781  // screen.
   782  func (a *Application) ResizeToFullScreen(p Primitive) {
   783  	a.RLock()
   784  	width, height := a.width, a.height
   785  	a.RUnlock()
   786  	p.SetRect(0, 0, width, height)
   787  }
   788  
   789  // SetAfterResizeFunc installs a callback function which is invoked when the
   790  // application's window is initialized, and when the application's window size
   791  // changes. After invoking this callback the screen is cleared and the
   792  // application is drawn.
   793  //
   794  // Provide nil to uninstall the callback function.
   795  func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) {
   796  	a.Lock()
   797  	defer a.Unlock()
   798  
   799  	a.afterResize = handler
   800  }
   801  
   802  // GetAfterResizeFunc returns the callback function installed with
   803  // SetAfterResizeFunc() or nil if none has been installed.
   804  func (a *Application) GetAfterResizeFunc() func(width int, height int) {
   805  	a.RLock()
   806  	defer a.RUnlock()
   807  
   808  	return a.afterResize
   809  }
   810  
   811  // SetFocus sets the focus on a new primitive. All key events will be redirected
   812  // to that primitive. Callers must ensure that the primitive will handle key
   813  // events.
   814  //
   815  // Blur() will be called on the previously focused primitive. Focus() will be
   816  // called on the new primitive.
   817  func (a *Application) SetFocus(p Primitive) {
   818  	a.Lock()
   819  
   820  	if a.beforeFocus != nil {
   821  		a.Unlock()
   822  		ok := a.beforeFocus(p)
   823  		if !ok {
   824  			return
   825  		}
   826  		a.Lock()
   827  	}
   828  
   829  	if a.focus != nil {
   830  		a.focus.Blur()
   831  	}
   832  
   833  	a.focus = p
   834  
   835  	if a.screen != nil {
   836  		a.screen.HideCursor()
   837  	}
   838  
   839  	if a.afterFocus != nil {
   840  		a.Unlock()
   841  
   842  		a.afterFocus(p)
   843  	} else {
   844  		a.Unlock()
   845  	}
   846  
   847  	if p != nil {
   848  		p.Focus(func(p Primitive) {
   849  			a.SetFocus(p)
   850  		})
   851  	}
   852  }
   853  
   854  // GetFocus returns the primitive which has the current focus. If none has it,
   855  // nil is returned.
   856  func (a *Application) GetFocus() Primitive {
   857  	a.RLock()
   858  	defer a.RUnlock()
   859  
   860  	return a.focus
   861  }
   862  
   863  // SetBeforeFocusFunc installs a callback function which is invoked before the
   864  // application's focus changes. Return false to maintain the current focus.
   865  //
   866  // Provide nil to uninstall the callback function.
   867  func (a *Application) SetBeforeFocusFunc(handler func(p Primitive) bool) {
   868  	a.Lock()
   869  	defer a.Unlock()
   870  
   871  	a.beforeFocus = handler
   872  }
   873  
   874  // SetAfterFocusFunc installs a callback function which is invoked after the
   875  // application's focus changes.
   876  //
   877  // Provide nil to uninstall the callback function.
   878  func (a *Application) SetAfterFocusFunc(handler func(p Primitive)) {
   879  	a.Lock()
   880  	defer a.Unlock()
   881  
   882  	a.afterFocus = handler
   883  }
   884  
   885  // QueueUpdate queues a function to be executed as part of the event loop.
   886  //
   887  // Note that Draw() is not implicitly called after the execution of f as that
   888  // may not be desirable. You can call Draw() from f if the screen should be
   889  // refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
   890  // up with an immediate refresh of the screen.
   891  func (a *Application) QueueUpdate(f func()) {
   892  	a.updates <- f
   893  }
   894  
   895  // QueueUpdateDraw works like QueueUpdate() except, when one or more primitives
   896  // are provided, the primitives are drawn after the provided function returns.
   897  // When no primitives are provided, the entire screen is drawn after the
   898  // provided function returns.
   899  func (a *Application) QueueUpdateDraw(f func(), p ...Primitive) {
   900  	a.QueueUpdate(func() {
   901  		f()
   902  
   903  		if len(p) == 0 {
   904  			a.draw()
   905  			return
   906  		}
   907  		a.Lock()
   908  		if a.screen != nil {
   909  			for _, primitive := range p {
   910  				primitive.Draw(a.screen)
   911  			}
   912  			a.screen.Show()
   913  		}
   914  		a.Unlock()
   915  	})
   916  }
   917  
   918  // QueueEvent sends an event to the Application event loop.
   919  //
   920  // It is not recommended for event to be nil.
   921  func (a *Application) QueueEvent(event tcell.Event) {
   922  	a.events <- event
   923  }
   924  
   925  // RingBell sends a bell code to the terminal.
   926  func (a *Application) RingBell() {
   927  	a.QueueUpdate(func() {
   928  		fmt.Print(string(byte(7)))
   929  	})
   930  }
   931  

View as plain text