...

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

View as plain text