This is the absolute bomb semi-graphical text user interface library, I had to mirror it.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

application.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. package tview
  2. import (
  3. "sync"
  4. "git.parallelcoin.io/dev/tcell"
  5. )
  6. // The size of the event/update/redraw channels.
  7. const queueSize = 100
  8. // Application represents the top node of an application.
  9. //
  10. // It is not strictly required to use this class as none of the other classes
  11. // depend on it. However, it provides useful tools to set up an application and
  12. // plays nicely with all widgets.
  13. //
  14. // The following command displays a primitive p on the screen until Ctrl-C is
  15. // pressed:
  16. //
  17. // if err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {
  18. // panic(err)
  19. // }
  20. type Application struct {
  21. sync.RWMutex
  22. // The application's screen. Apart from Run(), this variable should never be
  23. // set directly. Always use the screenReplacement channel after calling
  24. // Fini(), to set a new screen (or nil to stop the application).
  25. screen tcell.Screen
  26. // The primitive which currently has the keyboard focus.
  27. focus Primitive
  28. // The root primitive to be seen on the screen.
  29. root Primitive
  30. // Whether or not the application resizes the root primitive.
  31. rootFullscreen bool
  32. // An optional capture function which receives a key event and returns the
  33. // event to be forwarded to the default input handler (nil if nothing should
  34. // be forwarded).
  35. inputCapture func(event *tcell.EventKey) *tcell.EventKey
  36. // An optional callback function which is invoked just before the root
  37. // primitive is drawn.
  38. beforeDraw func(screen tcell.Screen) bool
  39. // An optional callback function which is invoked after the root primitive
  40. // was drawn.
  41. afterDraw func(screen tcell.Screen)
  42. // Used to send screen events from separate goroutine to main event loop
  43. events chan tcell.Event
  44. // Functions queued from goroutines, used to serialize updates to primitives.
  45. updates chan func()
  46. // An object that the screen variable will be set to after Fini() was called.
  47. // Use this channel to set a new screen object for the application
  48. // (screen.Init() and draw() will be called implicitly). A value of nil will
  49. // stop the application.
  50. screenReplacement chan tcell.Screen
  51. }
  52. // NewApplication creates and returns a new application.
  53. func NewApplication() *Application {
  54. return &Application{
  55. events: make(chan tcell.Event, queueSize),
  56. updates: make(chan func(), queueSize),
  57. screenReplacement: make(chan tcell.Screen, 1),
  58. }
  59. }
  60. // SetInputCapture sets a function which captures all key events before they are
  61. // forwarded to the key event handler of the primitive which currently has
  62. // focus. This function can then choose to forward that key event (or a
  63. // different one) by returning it or stop the key event processing by returning
  64. // nil.
  65. //
  66. // Note that this also affects the default event handling of the application
  67. // itself: Such a handler can intercept the Ctrl-C event which closes the
  68. // applicatoon.
  69. func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
  70. a.inputCapture = capture
  71. return a
  72. }
  73. // GetInputCapture returns the function installed with SetInputCapture() or nil
  74. // if no such function has been installed.
  75. func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
  76. return a.inputCapture
  77. }
  78. // SetScreen allows you to provide your own tcell.Screen object. For most
  79. // applications, this is not needed and you should be familiar with
  80. // tcell.Screen when using this function.
  81. //
  82. // This function is typically called before the first call to Run(). Init() need
  83. // not be called on the screen.
  84. func (a *Application) SetScreen(screen tcell.Screen) *Application {
  85. if screen == nil {
  86. return a // Invalid input. Do nothing.
  87. }
  88. a.Lock()
  89. if a.screen == nil {
  90. // Run() has not been called yet.
  91. a.screen = screen
  92. a.Unlock()
  93. return a
  94. }
  95. // Run() is already in progress. Exchange screen.
  96. oldScreen := a.screen
  97. a.Unlock()
  98. oldScreen.Fini()
  99. a.screenReplacement <- screen
  100. return a
  101. }
  102. // Run starts the application and thus the event loop. This function returns
  103. // when Stop() was called.
  104. func (a *Application) Run() error {
  105. var err error
  106. a.Lock()
  107. // Make a screen if there is none yet.
  108. if a.screen == nil {
  109. a.screen, err = tcell.NewScreen()
  110. if err != nil {
  111. a.Unlock()
  112. return err
  113. }
  114. if err = a.screen.Init(); err != nil {
  115. a.Unlock()
  116. return err
  117. }
  118. }
  119. // We catch panics to clean up because they mess up the terminal.
  120. defer func() {
  121. if p := recover(); p != nil {
  122. if a.screen != nil {
  123. a.screen.Fini()
  124. }
  125. panic(p)
  126. }
  127. }()
  128. // Draw the screen for the first time.
  129. a.Unlock()
  130. a.draw()
  131. // Separate loop to wait for screen events.
  132. var wg sync.WaitGroup
  133. wg.Add(1)
  134. go func() {
  135. defer wg.Done()
  136. for {
  137. a.RLock()
  138. screen := a.screen
  139. a.RUnlock()
  140. if screen == nil {
  141. // We have no screen. Let's stop.
  142. a.QueueEvent(nil)
  143. break
  144. }
  145. // Wait for next event and queue it.
  146. event := screen.PollEvent()
  147. if event != nil {
  148. // Regular event. Queue.
  149. a.QueueEvent(event)
  150. continue
  151. }
  152. // A screen was finalized (event is nil). Wait for a new scren.
  153. screen = <-a.screenReplacement
  154. if screen == nil {
  155. // No new screen. We're done.
  156. a.QueueEvent(nil)
  157. return
  158. }
  159. // We have a new screen. Keep going.
  160. a.Lock()
  161. a.screen = screen
  162. a.Unlock()
  163. // Initialize and draw this screen.
  164. if err := screen.Init(); err != nil {
  165. panic(err)
  166. }
  167. a.draw()
  168. }
  169. }()
  170. // Start event loop.
  171. EventLoop:
  172. for {
  173. select {
  174. case event := <-a.events:
  175. if event == nil {
  176. break EventLoop
  177. }
  178. switch event := event.(type) {
  179. case *tcell.EventKey:
  180. a.RLock()
  181. p := a.focus
  182. inputCapture := a.inputCapture
  183. a.RUnlock()
  184. // Intercept keys.
  185. if inputCapture != nil {
  186. event = inputCapture(event)
  187. if event == nil {
  188. a.draw()
  189. continue // Don't forward event.
  190. }
  191. }
  192. // Ctrl-C closes the application.
  193. if event.Key() == tcell.KeyCtrlC {
  194. a.Stop()
  195. }
  196. // Pass other key events to the currently focused primitive.
  197. if p != nil {
  198. if handler := p.InputHandler(); handler != nil {
  199. handler(event, func(p Primitive) {
  200. a.SetFocus(p)
  201. })
  202. a.draw()
  203. }
  204. }
  205. case *tcell.EventResize:
  206. a.RLock()
  207. screen := a.screen
  208. a.RUnlock()
  209. if screen == nil {
  210. continue
  211. }
  212. screen.Clear()
  213. a.draw()
  214. }
  215. // If we have updates, now is the time to execute them.
  216. case updater := <-a.updates:
  217. updater()
  218. }
  219. }
  220. // Wait for the event loop to finish.
  221. wg.Wait()
  222. a.screen = nil
  223. return nil
  224. }
  225. // Stop stops the application, causing Run() to return.
  226. func (a *Application) Stop() {
  227. a.Lock()
  228. defer a.Unlock()
  229. screen := a.screen
  230. if screen == nil {
  231. return
  232. }
  233. a.screen = nil
  234. screen.Fini()
  235. a.screenReplacement <- nil
  236. }
  237. // Suspend temporarily suspends the application by exiting terminal UI mode and
  238. // invoking the provided function "f". When "f" returns, terminal UI mode is
  239. // entered again and the application resumes.
  240. //
  241. // A return value of true indicates that the application was suspended and "f"
  242. // was called. If false is returned, the application was already suspended,
  243. // terminal UI mode was not exited, and "f" was not called.
  244. func (a *Application) Suspend(f func()) bool {
  245. a.RLock()
  246. screen := a.screen
  247. a.RUnlock()
  248. if screen == nil {
  249. return false // Screen has not yet been initialized.
  250. }
  251. // Enter suspended mode.
  252. screen.Fini()
  253. // Wait for "f" to return.
  254. f()
  255. // Make a new screen.
  256. var err error
  257. screen, err = tcell.NewScreen()
  258. if err != nil {
  259. panic(err)
  260. }
  261. a.screenReplacement <- screen
  262. // One key event will get lost, see https://git.parallelcoin.io/dev/tview/issues/194
  263. // Continue application loop.
  264. return true
  265. }
  266. // Draw refreshes the screen (during the next update cycle). It calls the Draw()
  267. // function of the application's root primitive and then syncs the screen
  268. // buffer.
  269. func (a *Application) Draw() *Application {
  270. a.QueueUpdate(func() {
  271. a.draw()
  272. })
  273. return a
  274. }
  275. // ForceDraw refreshes the screen immediately. Use this function with caution as
  276. // it may lead to race conditions with updates to primitives in other
  277. // goroutines. It is always preferrable to use Draw() instead. Never call this
  278. // function from a goroutine.
  279. //
  280. // It is safe to call this function during queued updates and direct event
  281. // handling.
  282. func (a *Application) ForceDraw() *Application {
  283. return a.draw()
  284. }
  285. // draw actually does what Draw() promises to do.
  286. func (a *Application) draw() *Application {
  287. a.Lock()
  288. defer a.Unlock()
  289. screen := a.screen
  290. root := a.root
  291. fullscreen := a.rootFullscreen
  292. before := a.beforeDraw
  293. after := a.afterDraw
  294. // Maybe we're not ready yet or not anymore.
  295. if screen == nil || root == nil {
  296. return a
  297. }
  298. // Resize if requested.
  299. if fullscreen && root != nil {
  300. width, height := screen.Size()
  301. root.SetRect(0, 0, width, height)
  302. }
  303. // Call before handler if there is one.
  304. if before != nil {
  305. if before(screen) {
  306. screen.Show()
  307. return a
  308. }
  309. }
  310. // Draw all primitives.
  311. root.Draw(screen)
  312. // Call after handler if there is one.
  313. if after != nil {
  314. after(screen)
  315. }
  316. // Sync screen.
  317. screen.Show()
  318. return a
  319. }
  320. // SetBeforeDrawFunc installs a callback function which is invoked just before
  321. // the root primitive is drawn during screen updates. If the function returns
  322. // true, drawing will not continue, i.e. the root primitive will not be drawn
  323. // (and an after-draw-handler will not be called).
  324. //
  325. // Note that the screen is not cleared by the application. To clear the screen,
  326. // you may call screen.Clear().
  327. //
  328. // Provide nil to uninstall the callback function.
  329. func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) *Application {
  330. a.beforeDraw = handler
  331. return a
  332. }
  333. // GetBeforeDrawFunc returns the callback function installed with
  334. // SetBeforeDrawFunc() or nil if none has been installed.
  335. func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
  336. return a.beforeDraw
  337. }
  338. // SetAfterDrawFunc installs a callback function which is invoked after the root
  339. // primitive was drawn during screen updates.
  340. //
  341. // Provide nil to uninstall the callback function.
  342. func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Application {
  343. a.afterDraw = handler
  344. return a
  345. }
  346. // GetAfterDrawFunc returns the callback function installed with
  347. // SetAfterDrawFunc() or nil if none has been installed.
  348. func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
  349. return a.afterDraw
  350. }
  351. // SetRoot sets the root primitive for this application. If "fullscreen" is set
  352. // to true, the root primitive's position will be changed to fill the screen.
  353. //
  354. // This function must be called at least once or nothing will be displayed when
  355. // the application starts.
  356. //
  357. // It also calls SetFocus() on the primitive.
  358. func (a *Application) SetRoot(root Primitive, fullscreen bool) *Application {
  359. a.Lock()
  360. a.root = root
  361. a.rootFullscreen = fullscreen
  362. if a.screen != nil {
  363. a.screen.Clear()
  364. }
  365. a.Unlock()
  366. a.SetFocus(root)
  367. return a
  368. }
  369. // ResizeToFullScreen resizes the given primitive such that it fills the entire
  370. // screen.
  371. func (a *Application) ResizeToFullScreen(p Primitive) *Application {
  372. a.RLock()
  373. width, height := a.screen.Size()
  374. a.RUnlock()
  375. p.SetRect(0, 0, width, height)
  376. return a
  377. }
  378. // SetFocus sets the focus on a new primitive. All key events will be redirected
  379. // to that primitive. Callers must ensure that the primitive will handle key
  380. // events.
  381. //
  382. // Blur() will be called on the previously focused primitive. Focus() will be
  383. // called on the new primitive.
  384. func (a *Application) SetFocus(p Primitive) *Application {
  385. a.Lock()
  386. if a.focus != nil {
  387. a.focus.Blur()
  388. }
  389. a.focus = p
  390. if a.screen != nil {
  391. a.screen.HideCursor()
  392. }
  393. a.Unlock()
  394. if p != nil {
  395. p.Focus(func(p Primitive) {
  396. a.SetFocus(p)
  397. })
  398. }
  399. return a
  400. }
  401. // GetFocus returns the primitive which has the current focus. If none has it,
  402. // nil is returned.
  403. func (a *Application) GetFocus() Primitive {
  404. a.RLock()
  405. defer a.RUnlock()
  406. return a.focus
  407. }
  408. // QueueUpdate is used to synchronize access to primitives from non-main
  409. // goroutines. The provided function will be executed as part of the event loop
  410. // and thus will not cause race conditions with other such update functions or
  411. // the Draw() function.
  412. //
  413. // Note that Draw() is not implicitly called after the execution of f as that
  414. // may not be desirable. You can call Draw() from f if the screen should be
  415. // refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
  416. // up with an immediate refresh of the screen.
  417. func (a *Application) QueueUpdate(f func()) *Application {
  418. a.updates <- f
  419. return a
  420. }
  421. // QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
  422. // immediately after executing f.
  423. func (a *Application) QueueUpdateDraw(f func()) *Application {
  424. a.QueueUpdate(func() {
  425. f()
  426. a.draw()
  427. })
  428. return a
  429. }
  430. // QueueEvent sends an event to the Application event loop.
  431. //
  432. // It is not recommended for event to be nil.
  433. func (a *Application) QueueEvent(event tcell.Event) *Application {
  434. a.events <- event
  435. return a
  436. }