...

Source file src/github.com/rivo/tview/application.go

Documentation: github.com/rivo/tview

     1  package tview
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/gdamore/tcell/v2"
     8  )
     9  
    10  const (
    11  	// The size of the event/update/redraw channels.
    12  	queueSize = 100
    13  
    14  	// The minimum time between two consecutive redraws.
    15  	redrawPause = 50 * time.Millisecond
    16  )
    17  
    18  // DoubleClickInterval specifies the maximum time between clicks to register a
    19  // double click rather than click.
    20  var DoubleClickInterval = 500 * time.Millisecond
    21  
    22  // MouseAction indicates one of the actions the mouse is logically doing.
    23  type MouseAction int16
    24  
    25  // Available mouse actions.
    26  const (
    27  	MouseMove MouseAction = iota
    28  	MouseLeftDown
    29  	MouseLeftUp
    30  	MouseLeftClick
    31  	MouseLeftDoubleClick
    32  	MouseMiddleDown
    33  	MouseMiddleUp
    34  	MouseMiddleClick
    35  	MouseMiddleDoubleClick
    36  	MouseRightDown
    37  	MouseRightUp
    38  	MouseRightClick
    39  	MouseRightDoubleClick
    40  	MouseScrollUp
    41  	MouseScrollDown
    42  	MouseScrollLeft
    43  	MouseScrollRight
    44  )
    45  
    46  // queuedUpdate represented the execution of f queued by
    47  // Application.QueueUpdate(). If "done" is not nil, it receives exactly one
    48  // element after f has executed.
    49  type queuedUpdate struct {
    50  	f    func()
    51  	done chan struct{}
    52  }
    53  
    54  // Application represents the top node of an application.
    55  //
    56  // It is not strictly required to use this class as none of the other classes
    57  // depend on it. However, it provides useful tools to set up an application and
    58  // plays nicely with all widgets.
    59  //
    60  // The following command displays a primitive p on the screen until Ctrl-C is
    61  // pressed:
    62  //
    63  //	if err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {
    64  //	    panic(err)
    65  //	}
    66  type Application struct {
    67  	sync.RWMutex
    68  
    69  	// The application's screen. Apart from Run(), this variable should never be
    70  	// set directly. Always use the screenReplacement channel after calling
    71  	// Fini(), to set a new screen (or nil to stop the application).
    72  	screen tcell.Screen
    73  
    74  	// The primitive which currently has the keyboard focus.
    75  	focus Primitive
    76  
    77  	// The root primitive to be seen on the screen.
    78  	root Primitive
    79  
    80  	// Whether or not the application resizes the root primitive.
    81  	rootFullscreen bool
    82  
    83  	// Set to true if mouse events are enabled.
    84  	enableMouse bool
    85  
    86  	// An optional capture function which receives a key event and returns the
    87  	// event to be forwarded to the default input handler (nil if nothing should
    88  	// be forwarded).
    89  	inputCapture func(event *tcell.EventKey) *tcell.EventKey
    90  
    91  	// An optional callback function which is invoked just before the root
    92  	// primitive is drawn.
    93  	beforeDraw func(screen tcell.Screen) bool
    94  
    95  	// An optional callback function which is invoked after the root primitive
    96  	// was drawn.
    97  	afterDraw func(screen tcell.Screen)
    98  
    99  	// Used to send screen events from separate goroutine to main event loop
   100  	events chan tcell.Event
   101  
   102  	// Functions queued from goroutines, used to serialize updates to primitives.
   103  	updates chan queuedUpdate
   104  
   105  	// An object that the screen variable will be set to after Fini() was called.
   106  	// Use this channel to set a new screen object for the application
   107  	// (screen.Init() and draw() will be called implicitly). A value of nil will
   108  	// stop the application.
   109  	screenReplacement chan tcell.Screen
   110  
   111  	// An optional capture function which receives a mouse event and returns the
   112  	// event to be forwarded to the default mouse handler (nil if nothing should
   113  	// be forwarded).
   114  	mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)
   115  
   116  	mouseCapturingPrimitive Primitive        // A Primitive returned by a MouseHandler which will capture future mouse events.
   117  	lastMouseX, lastMouseY  int              // The last position of the mouse.
   118  	mouseDownX, mouseDownY  int              // The position of the mouse when its button was last pressed.
   119  	lastMouseClick          time.Time        // The time when a mouse button was last clicked.
   120  	lastMouseButtons        tcell.ButtonMask // The last mouse button state.
   121  }
   122  
   123  // NewApplication creates and returns a new application.
   124  func NewApplication() *Application {
   125  	return &Application{
   126  		events:            make(chan tcell.Event, queueSize),
   127  		updates:           make(chan queuedUpdate, queueSize),
   128  		screenReplacement: make(chan tcell.Screen, 1),
   129  	}
   130  }
   131  
   132  // SetInputCapture sets a function which captures all key events before they are
   133  // forwarded to the key event handler of the primitive which currently has
   134  // focus. This function can then choose to forward that key event (or a
   135  // different one) by returning it or stop the key event processing by returning
   136  // nil.
   137  //
   138  // The only default global key event is Ctrl-C which stops the application. It
   139  // requires special handling:
   140  //
   141  //   - If you do not wish to change the default behavior, return the original
   142  //     event object passed to your input capture function.
   143  //   - If you wish to block Ctrl-C from any functionality, return nil.
   144  //   - If you do not wish Ctrl-C to stop the application but still want to
   145  //     forward the Ctrl-C event to primitives down the hierarchy, return a new
   146  //     key event with the same key and modifiers, e.g.
   147  //     tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone).
   148  func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
   149  	a.inputCapture = capture
   150  	return a
   151  }
   152  
   153  // GetInputCapture returns the function installed with SetInputCapture() or nil
   154  // if no such function has been installed.
   155  func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
   156  	return a.inputCapture
   157  }
   158  
   159  // SetMouseCapture sets a function which captures mouse events (consisting of
   160  // the original tcell mouse event and the semantic mouse action) before they are
   161  // forwarded to the appropriate mouse event handler. This function can then
   162  // choose to forward that event (or a different one) by returning it or stop
   163  // the event processing by returning a nil mouse event.
   164  func (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) *Application {
   165  	a.mouseCapture = capture
   166  	return a
   167  }
   168  
   169  // GetMouseCapture returns the function installed with SetMouseCapture() or nil
   170  // if no such function has been installed.
   171  func (a *Application) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {
   172  	return a.mouseCapture
   173  }
   174  
   175  // SetScreen allows you to provide your own tcell.Screen object. For most
   176  // applications, this is not needed and you should be familiar with
   177  // tcell.Screen when using this function.
   178  //
   179  // This function is typically called before the first call to Run(). Init() need
   180  // not be called on the screen.
   181  func (a *Application) SetScreen(screen tcell.Screen) *Application {
   182  	if screen == nil {
   183  		return a // Invalid input. Do nothing.
   184  	}
   185  
   186  	a.Lock()
   187  	if a.screen == nil {
   188  		// Run() has not been called yet.
   189  		a.screen = screen
   190  		a.Unlock()
   191  		screen.Init()
   192  		return a
   193  	}
   194  
   195  	// Run() is already in progress. Exchange screen.
   196  	oldScreen := a.screen
   197  	a.Unlock()
   198  	oldScreen.Fini()
   199  	a.screenReplacement <- screen
   200  
   201  	return a
   202  }
   203  
   204  // EnableMouse enables mouse events or disables them (if "false" is provided).
   205  func (a *Application) EnableMouse(enable bool) *Application {
   206  	a.Lock()
   207  	defer a.Unlock()
   208  	if enable != a.enableMouse && a.screen != nil {
   209  		if enable {
   210  			a.screen.EnableMouse()
   211  		} else {
   212  			a.screen.DisableMouse()
   213  		}
   214  	}
   215  	a.enableMouse = enable
   216  	return a
   217  }
   218  
   219  // Run starts the application and thus the event loop. This function returns
   220  // when Stop() was called.
   221  func (a *Application) Run() error {
   222  	var (
   223  		err, appErr error
   224  		lastRedraw  time.Time   // The time the screen was last redrawn.
   225  		redrawTimer *time.Timer // A timer to schedule the next redraw.
   226  	)
   227  	a.Lock()
   228  
   229  	// Make a screen if there is none yet.
   230  	if a.screen == nil {
   231  		a.screen, err = tcell.NewScreen()
   232  		if err != nil {
   233  			a.Unlock()
   234  			return err
   235  		}
   236  		if err = a.screen.Init(); err != nil {
   237  			a.Unlock()
   238  			return err
   239  		}
   240  		if a.enableMouse {
   241  			a.screen.EnableMouse()
   242  		}
   243  	}
   244  
   245  	// We catch panics to clean up because they mess up the terminal.
   246  	defer func() {
   247  		if p := recover(); p != nil {
   248  			if a.screen != nil {
   249  				a.screen.Fini()
   250  			}
   251  			panic(p)
   252  		}
   253  	}()
   254  
   255  	// Draw the screen for the first time.
   256  	a.Unlock()
   257  	a.draw()
   258  
   259  	// Separate loop to wait for screen events.
   260  	var wg sync.WaitGroup
   261  	wg.Add(1)
   262  	go func() {
   263  		defer wg.Done()
   264  		for {
   265  			a.RLock()
   266  			screen := a.screen
   267  			a.RUnlock()
   268  			if screen == nil {
   269  				// We have no screen. Let's stop.
   270  				a.QueueEvent(nil)
   271  				break
   272  			}
   273  
   274  			// Wait for next event and queue it.
   275  			event := screen.PollEvent()
   276  			if event != nil {
   277  				// Regular event. Queue.
   278  				a.QueueEvent(event)
   279  				continue
   280  			}
   281  
   282  			// A screen was finalized (event is nil). Wait for a new scren.
   283  			screen = <-a.screenReplacement
   284  			if screen == nil {
   285  				// No new screen. We're done.
   286  				a.QueueEvent(nil)
   287  				return
   288  			}
   289  
   290  			// We have a new screen. Keep going.
   291  			a.Lock()
   292  			a.screen = screen
   293  			enableMouse := a.enableMouse
   294  			a.Unlock()
   295  
   296  			// Initialize and draw this screen.
   297  			if err := screen.Init(); err != nil {
   298  				panic(err)
   299  			}
   300  			if enableMouse {
   301  				screen.EnableMouse()
   302  			}
   303  			a.draw()
   304  		}
   305  	}()
   306  
   307  	// Start event loop.
   308  EventLoop:
   309  	for {
   310  		select {
   311  		case event := <-a.events:
   312  			if event == nil {
   313  				break EventLoop
   314  			}
   315  
   316  			switch event := event.(type) {
   317  			case *tcell.EventKey:
   318  				a.RLock()
   319  				root := a.root
   320  				inputCapture := a.inputCapture
   321  				a.RUnlock()
   322  
   323  				// Intercept keys.
   324  				var draw bool
   325  				originalEvent := event
   326  				if inputCapture != nil {
   327  					event = inputCapture(event)
   328  					if event == nil {
   329  						a.draw()
   330  						continue // Don't forward event.
   331  					}
   332  					draw = true
   333  				}
   334  
   335  				// Ctrl-C closes the application.
   336  				if event == originalEvent && event.Key() == tcell.KeyCtrlC {
   337  					a.Stop()
   338  					break
   339  				}
   340  
   341  				// Pass other key events to the root primitive.
   342  				if root != nil && root.HasFocus() {
   343  					if handler := root.InputHandler(); handler != nil {
   344  						handler(event, func(p Primitive) {
   345  							a.SetFocus(p)
   346  						})
   347  						draw = true
   348  					}
   349  				}
   350  
   351  				// Redraw.
   352  				if draw {
   353  					a.draw()
   354  				}
   355  			case *tcell.EventResize:
   356  				if time.Since(lastRedraw) < redrawPause {
   357  					if redrawTimer != nil {
   358  						redrawTimer.Stop()
   359  					}
   360  					redrawTimer = time.AfterFunc(redrawPause, func() {
   361  						a.events <- event
   362  					})
   363  				}
   364  				a.RLock()
   365  				screen := a.screen
   366  				a.RUnlock()
   367  				if screen == nil {
   368  					continue
   369  				}
   370  				lastRedraw = time.Now()
   371  				screen.Clear()
   372  				a.draw()
   373  			case *tcell.EventMouse:
   374  				consumed, isMouseDownAction := a.fireMouseActions(event)
   375  				if consumed {
   376  					a.draw()
   377  				}
   378  				a.lastMouseButtons = event.Buttons()
   379  				if isMouseDownAction {
   380  					a.mouseDownX, a.mouseDownY = event.Position()
   381  				}
   382  			case *tcell.EventError:
   383  				appErr = event
   384  				a.Stop()
   385  			}
   386  
   387  		// If we have updates, now is the time to execute them.
   388  		case update := <-a.updates:
   389  			update.f()
   390  			if update.done != nil {
   391  				update.done <- struct{}{}
   392  			}
   393  		}
   394  	}
   395  
   396  	// Wait for the event loop to finish.
   397  	wg.Wait()
   398  	a.screen = nil
   399  
   400  	return appErr
   401  }
   402  
   403  // fireMouseActions analyzes the provided mouse event, derives mouse actions
   404  // from it and then forwards them to the corresponding primitives.
   405  func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) {
   406  	// We want to relay follow-up events to the same target primitive.
   407  	var targetPrimitive Primitive
   408  
   409  	// Helper function to fire a mouse action.
   410  	fire := func(action MouseAction) {
   411  		switch action {
   412  		case MouseLeftDown, MouseMiddleDown, MouseRightDown:
   413  			isMouseDownAction = true
   414  		}
   415  
   416  		// Intercept event.
   417  		if a.mouseCapture != nil {
   418  			event, action = a.mouseCapture(event, action)
   419  			if event == nil {
   420  				consumed = true
   421  				return // Don't forward event.
   422  			}
   423  		}
   424  
   425  		// Determine the target primitive.
   426  		var primitive, capturingPrimitive Primitive
   427  		if a.mouseCapturingPrimitive != nil {
   428  			primitive = a.mouseCapturingPrimitive
   429  			targetPrimitive = a.mouseCapturingPrimitive
   430  		} else if targetPrimitive != nil {
   431  			primitive = targetPrimitive
   432  		} else {
   433  			primitive = a.root
   434  		}
   435  		if primitive != nil {
   436  			if handler := primitive.MouseHandler(); handler != nil {
   437  				var wasConsumed bool
   438  				wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {
   439  					a.SetFocus(p)
   440  				})
   441  				if wasConsumed {
   442  					consumed = true
   443  				}
   444  			}
   445  		}
   446  		a.mouseCapturingPrimitive = capturingPrimitive
   447  	}
   448  
   449  	x, y := event.Position()
   450  	buttons := event.Buttons()
   451  	clickMoved := x != a.mouseDownX || y != a.mouseDownY
   452  	buttonChanges := buttons ^ a.lastMouseButtons
   453  
   454  	if x != a.lastMouseX || y != a.lastMouseY {
   455  		fire(MouseMove)
   456  		a.lastMouseX = x
   457  		a.lastMouseY = y
   458  	}
   459  
   460  	for _, buttonEvent := range []struct {
   461  		button                  tcell.ButtonMask
   462  		down, up, click, dclick MouseAction
   463  	}{
   464  		{tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick},
   465  		{tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},
   466  		{tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},
   467  	} {
   468  		if buttonChanges&buttonEvent.button != 0 {
   469  			if buttons&buttonEvent.button != 0 {
   470  				fire(buttonEvent.down)
   471  			} else {
   472  				fire(buttonEvent.up) // A user override might set event to nil.
   473  				if !clickMoved && event != nil {
   474  					if a.lastMouseClick.Add(DoubleClickInterval).Before(time.Now()) {
   475  						fire(buttonEvent.click)
   476  						a.lastMouseClick = time.Now()
   477  					} else {
   478  						fire(buttonEvent.dclick)
   479  						a.lastMouseClick = time.Time{} // reset
   480  					}
   481  				}
   482  			}
   483  		}
   484  	}
   485  
   486  	for _, wheelEvent := range []struct {
   487  		button tcell.ButtonMask
   488  		action MouseAction
   489  	}{
   490  		{tcell.WheelUp, MouseScrollUp},
   491  		{tcell.WheelDown, MouseScrollDown},
   492  		{tcell.WheelLeft, MouseScrollLeft},
   493  		{tcell.WheelRight, MouseScrollRight}} {
   494  		if buttons&wheelEvent.button != 0 {
   495  			fire(wheelEvent.action)
   496  		}
   497  	}
   498  
   499  	return consumed, isMouseDownAction
   500  }
   501  
   502  // Stop stops the application, causing Run() to return.
   503  func (a *Application) Stop() {
   504  	a.Lock()
   505  	defer a.Unlock()
   506  	screen := a.screen
   507  	if screen == nil {
   508  		return
   509  	}
   510  	a.screen = nil
   511  	screen.Fini()
   512  	a.screenReplacement <- nil
   513  }
   514  
   515  // Suspend temporarily suspends the application by exiting terminal UI mode and
   516  // invoking the provided function "f". When "f" returns, terminal UI mode is
   517  // entered again and the application resumes.
   518  //
   519  // A return value of true indicates that the application was suspended and "f"
   520  // was called. If false is returned, the application was already suspended,
   521  // terminal UI mode was not exited, and "f" was not called.
   522  func (a *Application) Suspend(f func()) bool {
   523  	a.RLock()
   524  	screen := a.screen
   525  	a.RUnlock()
   526  	if screen == nil {
   527  		return false // Screen has not yet been initialized.
   528  	}
   529  
   530  	// Enter suspended mode.
   531  	if err := screen.Suspend(); err != nil {
   532  		return false // Suspension failed.
   533  	}
   534  
   535  	// Wait for "f" to return.
   536  	f()
   537  
   538  	// If the screen object has changed in the meantime, we need to do more.
   539  	a.RLock()
   540  	defer a.RUnlock()
   541  	if a.screen != screen {
   542  		// Calling Stop() while in suspend mode currently still leads to a
   543  		// panic, see https://github.com/gdamore/tcell/issues/440.
   544  		screen.Fini()
   545  		if a.screen == nil {
   546  			return true // If stop was called (a.screen is nil), we're done already.
   547  		}
   548  	} else {
   549  		// It hasn't changed. Resume.
   550  		screen.Resume() // Not much we can do in case of an error.
   551  	}
   552  
   553  	// Continue application loop.
   554  	return true
   555  }
   556  
   557  // Draw refreshes the screen (during the next update cycle). It calls the Draw()
   558  // function of the application's root primitive and then syncs the screen
   559  // buffer. It is almost never necessary to call this function. It can actually
   560  // deadlock your application if you call it from the main thread (e.g. in a
   561  // callback function of a widget). Please see
   562  // https://github.com/rivo/tview/wiki/Concurrency for details.
   563  func (a *Application) Draw() *Application {
   564  	a.QueueUpdate(func() {
   565  		a.draw()
   566  	})
   567  	return a
   568  }
   569  
   570  // ForceDraw refreshes the screen immediately. Use this function with caution as
   571  // it may lead to race conditions with updates to primitives in other
   572  // goroutines. It is always preferrable to use Draw() instead. Never call this
   573  // function from a goroutine.
   574  //
   575  // It is safe to call this function during queued updates and direct event
   576  // handling.
   577  func (a *Application) ForceDraw() *Application {
   578  	return a.draw()
   579  }
   580  
   581  // draw actually does what Draw() promises to do.
   582  func (a *Application) draw() *Application {
   583  	a.Lock()
   584  	defer a.Unlock()
   585  
   586  	screen := a.screen
   587  	root := a.root
   588  	fullscreen := a.rootFullscreen
   589  	before := a.beforeDraw
   590  	after := a.afterDraw
   591  
   592  	// Maybe we're not ready yet or not anymore.
   593  	if screen == nil || root == nil {
   594  		return a
   595  	}
   596  
   597  	// Resize if requested.
   598  	if fullscreen && root != nil {
   599  		width, height := screen.Size()
   600  		root.SetRect(0, 0, width, height)
   601  	}
   602  
   603  	// Call before handler if there is one.
   604  	if before != nil {
   605  		if before(screen) {
   606  			screen.Show()
   607  			return a
   608  		}
   609  	}
   610  
   611  	// Draw all primitives.
   612  	root.Draw(screen)
   613  
   614  	// Call after handler if there is one.
   615  	if after != nil {
   616  		after(screen)
   617  	}
   618  
   619  	// Sync screen.
   620  	screen.Show()
   621  
   622  	return a
   623  }
   624  
   625  // Sync forces a full re-sync of the screen buffer with the actual screen during
   626  // the next event cycle. This is useful for when the terminal screen is
   627  // corrupted so you may want to offer your users a keyboard shortcut to refresh
   628  // the screen.
   629  func (a *Application) Sync() *Application {
   630  	a.updates <- queuedUpdate{f: func() {
   631  		a.RLock()
   632  		screen := a.screen
   633  		a.RUnlock()
   634  		if screen == nil {
   635  			return
   636  		}
   637  		screen.Sync()
   638  	}}
   639  	return a
   640  }
   641  
   642  // SetBeforeDrawFunc installs a callback function which is invoked just before
   643  // the root primitive is drawn during screen updates. If the function returns
   644  // true, drawing will not continue, i.e. the root primitive will not be drawn
   645  // (and an after-draw-handler will not be called).
   646  //
   647  // Note that the screen is not cleared by the application. To clear the screen,
   648  // you may call screen.Clear().
   649  //
   650  // Provide nil to uninstall the callback function.
   651  func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) *Application {
   652  	a.beforeDraw = handler
   653  	return a
   654  }
   655  
   656  // GetBeforeDrawFunc returns the callback function installed with
   657  // SetBeforeDrawFunc() or nil if none has been installed.
   658  func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
   659  	return a.beforeDraw
   660  }
   661  
   662  // SetAfterDrawFunc installs a callback function which is invoked after the root
   663  // primitive was drawn during screen updates.
   664  //
   665  // Provide nil to uninstall the callback function.
   666  func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Application {
   667  	a.afterDraw = handler
   668  	return a
   669  }
   670  
   671  // GetAfterDrawFunc returns the callback function installed with
   672  // SetAfterDrawFunc() or nil if none has been installed.
   673  func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
   674  	return a.afterDraw
   675  }
   676  
   677  // SetRoot sets the root primitive for this application. If "fullscreen" is set
   678  // to true, the root primitive's position will be changed to fill the screen.
   679  //
   680  // This function must be called at least once or nothing will be displayed when
   681  // the application starts.
   682  //
   683  // It also calls SetFocus() on the primitive.
   684  func (a *Application) SetRoot(root Primitive, fullscreen bool) *Application {
   685  	a.Lock()
   686  	a.root = root
   687  	a.rootFullscreen = fullscreen
   688  	if a.screen != nil {
   689  		a.screen.Clear()
   690  	}
   691  	a.Unlock()
   692  
   693  	a.SetFocus(root)
   694  
   695  	return a
   696  }
   697  
   698  // ResizeToFullScreen resizes the given primitive such that it fills the entire
   699  // screen.
   700  func (a *Application) ResizeToFullScreen(p Primitive) *Application {
   701  	a.RLock()
   702  	width, height := a.screen.Size()
   703  	a.RUnlock()
   704  	p.SetRect(0, 0, width, height)
   705  	return a
   706  }
   707  
   708  // SetFocus sets the focus to a new primitive. All key events will be directed
   709  // down the hierarchy (starting at the root) until a primitive handles them,
   710  // which per default goes towards the focused primitive.
   711  //
   712  // Blur() will be called on the previously focused primitive. Focus() will be
   713  // called on the new primitive.
   714  func (a *Application) SetFocus(p Primitive) *Application {
   715  	a.Lock()
   716  	if a.focus != nil {
   717  		a.focus.Blur()
   718  	}
   719  	a.focus = p
   720  	if a.screen != nil {
   721  		a.screen.HideCursor()
   722  	}
   723  	a.Unlock()
   724  	if p != nil {
   725  		p.Focus(func(p Primitive) {
   726  			a.SetFocus(p)
   727  		})
   728  	}
   729  
   730  	return a
   731  }
   732  
   733  // GetFocus returns the primitive which has the current focus. If none has it,
   734  // nil is returned.
   735  func (a *Application) GetFocus() Primitive {
   736  	a.RLock()
   737  	defer a.RUnlock()
   738  	return a.focus
   739  }
   740  
   741  // QueueUpdate is used to synchronize access to primitives from non-main
   742  // goroutines. The provided function will be executed as part of the event loop
   743  // and thus will not cause race conditions with other such update functions or
   744  // the Draw() function.
   745  //
   746  // Note that Draw() is not implicitly called after the execution of f as that
   747  // may not be desirable. You can call Draw() from f if the screen should be
   748  // refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
   749  // up with an immediate refresh of the screen.
   750  //
   751  // This function returns after f has executed.
   752  func (a *Application) QueueUpdate(f func()) *Application {
   753  	ch := make(chan struct{})
   754  	a.updates <- queuedUpdate{f: f, done: ch}
   755  	<-ch
   756  	return a
   757  }
   758  
   759  // QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
   760  // immediately after executing f.
   761  func (a *Application) QueueUpdateDraw(f func()) *Application {
   762  	a.QueueUpdate(func() {
   763  		f()
   764  		a.draw()
   765  	})
   766  	return a
   767  }
   768  
   769  // QueueEvent sends an event to the Application event loop.
   770  //
   771  // It is not recommended for event to be nil.
   772  func (a *Application) QueueEvent(event tcell.Event) *Application {
   773  	a.events <- event
   774  	return a
   775  }
   776  

View as plain text