Newer
Older
GoModules / TabTitleMenu / panels.go
0xRoM on 7 Feb 2023 10 KB TabTitleMenu's added
  1. package main
  2.  
  3. import (
  4. "sync"
  5.  
  6. "github.com/gdamore/tcell/v2"
  7. "code.rocketnine.space/tslocum/cview"
  8. )
  9.  
  10. // panel represents a single panel of a Panels object.
  11. type panel struct {
  12. Name string // The panel's name.
  13. Item cview.Primitive // The panel's cview.Primitive.
  14. Resize bool // Whether or not to resize the panel when it is drawn.
  15. Visible bool // Whether or not this panel is visible.
  16. }
  17.  
  18. // Panels is a container for other cview.Primitives often used as the application's
  19. // root cview.Primitive. It allows to easily switch the visibility of the contained
  20. // cview.Primitives.
  21. type Panels struct {
  22. *cview.Box
  23.  
  24. // The contained panels. (Visible) panels are drawn from back to front.
  25. panels []*panel
  26.  
  27. // We keep a reference to the function which allows us to set the focus to
  28. // a newly visible panel.
  29. setFocus func(p cview.Primitive)
  30.  
  31. // An optional handler which is called whenever the visibility or the order of
  32. // panels changes.
  33. changed func()
  34.  
  35. sync.RWMutex
  36. }
  37.  
  38. // NewPanels returns a new Panels object.
  39. func NewPanels() *Panels {
  40. p := &Panels{
  41. Box: cview.NewBox(),
  42. }
  43. //p.focus = p
  44. return p
  45. }
  46.  
  47. // SetChangedFunc sets a handler which is called whenever the visibility or the
  48. // order of any visible panels changes. This can be used to redraw the panels.
  49. func (p *Panels) SetChangedFunc(handler func()) {
  50. p.Lock()
  51. defer p.Unlock()
  52.  
  53. p.changed = handler
  54. }
  55.  
  56. // GetPanelCount returns the number of panels currently stored in this object.
  57. func (p *Panels) GetPanelCount() int {
  58. p.RLock()
  59. defer p.RUnlock()
  60.  
  61. return len(p.panels)
  62. }
  63.  
  64. // AddPanel adds a new panel with the given name and cview.Primitive. If there was
  65. // previously a panel with the same name, it is overwritten. Leaving the name
  66. // empty may cause conflicts in other functions so always specify a non-empty
  67. // name.
  68. //
  69. // Visible panels will be drawn in the order they were added (unless that order
  70. // was changed in one of the other functions). If "resize" is set to true, the
  71. // cview.Primitive will be set to the size available to the Panels cview.Primitive whenever
  72. // the panels are drawn.
  73. func (p *Panels) AddPanel(name string, item cview.Primitive, resize, visible bool) {
  74. hasFocus := p.HasFocus()
  75.  
  76. p.Lock()
  77. defer p.Unlock()
  78.  
  79. var added bool
  80. for i, pg := range p.panels {
  81. if pg.Name == name {
  82. p.panels[i] = &panel{Item: item, Name: name, Resize: resize, Visible: visible}
  83. added = true
  84. break
  85. }
  86. }
  87. if !added {
  88. p.panels = append(p.panels, &panel{Item: item, Name: name, Resize: resize, Visible: visible})
  89. }
  90. if p.changed != nil {
  91. p.Unlock()
  92. p.changed()
  93. p.Lock()
  94. }
  95. if hasFocus {
  96. p.Unlock()
  97. p.Focus(p.setFocus)
  98. p.Lock()
  99. }
  100. }
  101.  
  102. // RemovePanel removes the panel with the given name. If that panel was the only
  103. // visible panel, visibility is assigned to the last panel.
  104. func (p *Panels) RemovePanel(name string) {
  105. hasFocus := p.HasFocus()
  106.  
  107. p.Lock()
  108. defer p.Unlock()
  109.  
  110. var isVisible bool
  111. for index, panel := range p.panels {
  112. if panel.Name == name {
  113. isVisible = panel.Visible
  114. p.panels = append(p.panels[:index], p.panels[index+1:]...)
  115. if panel.Visible && p.changed != nil {
  116. p.Unlock()
  117. p.changed()
  118. p.Lock()
  119. }
  120. break
  121. }
  122. }
  123. if isVisible {
  124. for index, panel := range p.panels {
  125. if index < len(p.panels)-1 {
  126. if panel.Visible {
  127. break // There is a remaining visible panel.
  128. }
  129. } else {
  130. panel.Visible = true // We need at least one visible panel.
  131. }
  132. }
  133. }
  134. if hasFocus {
  135. p.Unlock()
  136. p.Focus(p.setFocus)
  137. p.Lock()
  138. }
  139. }
  140.  
  141. // HasPanel returns true if a panel with the given name exists in this object.
  142. func (p *Panels) HasPanel(name string) bool {
  143. p.RLock()
  144. defer p.RUnlock()
  145.  
  146. for _, panel := range p.panels {
  147. if panel.Name == name {
  148. return true
  149. }
  150. }
  151. return false
  152. }
  153.  
  154. // ShowPanel sets a panel's visibility to "true" (in addition to any other panels
  155. // which are already visible).
  156. func (p *Panels) ShowPanel(name string) {
  157. hasFocus := p.HasFocus()
  158.  
  159. p.Lock()
  160. defer p.Unlock()
  161.  
  162. for _, panel := range p.panels {
  163. if panel.Name == name {
  164. panel.Visible = true
  165. if p.changed != nil {
  166. p.Unlock()
  167. p.changed()
  168. p.Lock()
  169. }
  170. break
  171. }
  172. }
  173. if hasFocus {
  174. p.Unlock()
  175. p.Focus(p.setFocus)
  176. p.Lock()
  177. }
  178. }
  179.  
  180. // HidePanel sets a panel's visibility to "false".
  181. func (p *Panels) HidePanel(name string) {
  182. hasFocus := p.HasFocus()
  183.  
  184. p.Lock()
  185. defer p.Unlock()
  186.  
  187. for _, panel := range p.panels {
  188. if panel.Name == name {
  189. panel.Visible = false
  190. if p.changed != nil {
  191. p.Unlock()
  192. p.changed()
  193. p.Lock()
  194. }
  195. break
  196. }
  197. }
  198. if hasFocus {
  199. p.Unlock()
  200. p.Focus(p.setFocus)
  201. p.Lock()
  202. }
  203. }
  204.  
  205. // SetCurrentPanel sets a panel's visibility to "true" and all other panels'
  206. // visibility to "false".
  207. func (p *Panels) SetCurrentPanel(name string) {
  208. hasFocus := p.HasFocus()
  209.  
  210. p.Lock()
  211. defer p.Unlock()
  212.  
  213. for _, panel := range p.panels {
  214. if panel.Name == name {
  215. panel.Visible = true
  216. } else {
  217. panel.Visible = false
  218. }
  219. }
  220. if p.changed != nil {
  221. p.Unlock()
  222. p.changed()
  223. p.Lock()
  224. }
  225. if hasFocus {
  226. p.Unlock()
  227. p.Focus(p.setFocus)
  228. p.Lock()
  229. }
  230. }
  231.  
  232. // SendToFront changes the order of the panels such that the panel with the given
  233. // name comes last, causing it to be drawn last with the next update (if
  234. // visible).
  235. func (p *Panels) SendToFront(name string) {
  236. hasFocus := p.HasFocus()
  237.  
  238. p.Lock()
  239. defer p.Unlock()
  240.  
  241. for index, panel := range p.panels {
  242. if panel.Name == name {
  243. if index < len(p.panels)-1 {
  244. p.panels = append(append(p.panels[:index], p.panels[index+1:]...), panel)
  245. }
  246. if panel.Visible && p.changed != nil {
  247. p.Unlock()
  248. p.changed()
  249. p.Lock()
  250. }
  251. break
  252. }
  253. }
  254. if hasFocus {
  255. p.Unlock()
  256. p.Focus(p.setFocus)
  257. p.Lock()
  258. }
  259. }
  260.  
  261. // SendToBack changes the order of the panels such that the panel with the given
  262. // name comes first, causing it to be drawn first with the next update (if
  263. // visible).
  264. func (p *Panels) SendToBack(name string) {
  265. hasFocus := p.HasFocus()
  266.  
  267. p.Lock()
  268. defer p.Unlock()
  269.  
  270. for index, pg := range p.panels {
  271. if pg.Name == name {
  272. if index > 0 {
  273. p.panels = append(append([]*panel{pg}, p.panels[:index]...), p.panels[index+1:]...)
  274. }
  275. if pg.Visible && p.changed != nil {
  276. p.Unlock()
  277. p.changed()
  278. p.Lock()
  279. }
  280. break
  281. }
  282. }
  283. if hasFocus {
  284. p.Unlock()
  285. p.Focus(p.setFocus)
  286. p.Lock()
  287. }
  288. }
  289.  
  290. // GetFrontPanel returns the front-most visible panel. If there are no visible
  291. // panels, ("", nil) is returned.
  292. func (p *Panels) GetFrontPanel() (name string, item cview.Primitive) {
  293. p.RLock()
  294. defer p.RUnlock()
  295.  
  296. for index := len(p.panels) - 1; index >= 0; index-- {
  297. if p.panels[index].Visible {
  298. return p.panels[index].Name, p.panels[index].Item
  299. }
  300. }
  301. return
  302. }
  303.  
  304. // HasFocus returns whether or not this cview.Primitive has focus.
  305. func (p *Panels) HasFocus() bool {
  306. p.RLock()
  307. defer p.RUnlock()
  308.  
  309. for _, panel := range p.panels {
  310. if panel.Item.GetFocusable().HasFocus() {
  311. return true
  312. }
  313. }
  314. return false
  315. }
  316.  
  317. // Focus is called by the application when the cview.Primitive receives focus.
  318. func (p *Panels) Focus(delegate func(p cview.Primitive)) {
  319. p.Lock()
  320. defer p.Unlock()
  321.  
  322. if delegate == nil {
  323. return // We cannot delegate so we cannot focus.
  324. }
  325. p.setFocus = delegate
  326. var topItem cview.Primitive
  327. for _, panel := range p.panels {
  328. if panel.Visible {
  329. topItem = panel.Item
  330. }
  331. }
  332. if topItem != nil {
  333. p.Unlock()
  334. delegate(topItem)
  335. p.Lock()
  336. }
  337. }
  338.  
  339. // Draw draws this cview.Primitive onto the screen.
  340. func (p *Panels) Draw(screen tcell.Screen) {
  341. if !p.GetVisible() {
  342. return
  343. }
  344.  
  345. p.Box.Draw(screen)
  346.  
  347. p.Lock()
  348. defer p.Unlock()
  349.  
  350. x, y, width, height := p.GetInnerRect()
  351.  
  352. for _, panel := range p.panels {
  353. if !panel.Visible {
  354. continue
  355. }
  356. if panel.Resize {
  357. panel.Item.SetRect(x, y, width, height)
  358. }
  359. panel.Item.Draw(screen)
  360. }
  361. }
  362.  
  363. // MouseHandler returns the mouse handler for this cview.Primitive.
  364. func (p *Panels) MouseHandler() func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
  365. return p.WrapMouseHandler(func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
  366. if !p.InRect(event.Position()) {
  367. return false, nil
  368. }
  369.  
  370. // Pass mouse events along to the last visible panel item that takes it.
  371. for index := len(p.panels) - 1; index >= 0; index-- {
  372. panel := p.panels[index]
  373. if panel.Visible {
  374. consumed, capture = panel.Item.MouseHandler()(action, event, setFocus)
  375. if consumed {
  376. return
  377. }
  378. }
  379. }
  380.  
  381. return
  382. })
  383. }
  384.  
  385. // Support backwards compatibility with Pages.
  386. type page = panel
  387.  
  388. // Pages is a wrapper around Panels.
  389. //
  390. // Deprecated: This type is provided for backwards compatibility.
  391. // Developers should use Panels instead.
  392. type Pages struct {
  393. *Panels
  394. }
  395.  
  396. // NewPages returns a new Panels object.
  397. //
  398. // Deprecated: This function is provided for backwards compatibility.
  399. // Developers should use NewPanels instead.
  400. func NewPages() *Pages {
  401. return &Pages{NewPanels()}
  402. }
  403.  
  404. // GetPageCount returns the number of panels currently stored in this object.
  405. func (p *Pages) GetPageCount() int {
  406. return p.GetPanelCount()
  407. }
  408.  
  409. // AddPage adds a new panel with the given name and cview.Primitive.
  410. func (p *Pages) AddPage(name string, item cview.Primitive, resize, visible bool) {
  411. p.AddPanel(name, item, resize, visible)
  412. }
  413.  
  414. // AddAndSwitchToPage calls Add(), then SwitchTo() on that newly added panel.
  415. func (p *Pages) AddAndSwitchToPage(name string, item cview.Primitive, resize bool) {
  416. p.AddPanel(name, item, resize, true)
  417. p.SetCurrentPanel(name)
  418. }
  419.  
  420. // RemovePage removes the panel with the given name.
  421. func (p *Pages) RemovePage(name string) {
  422. p.RemovePanel(name)
  423. }
  424.  
  425. // HasPage returns true if a panel with the given name exists in this object.
  426. func (p *Pages) HasPage(name string) bool {
  427. return p.HasPanel(name)
  428. }
  429.  
  430. // ShowPage sets a panel's visibility to "true".
  431. func (p *Pages) ShowPage(name string) {
  432. p.ShowPanel(name)
  433. }
  434.  
  435. // HidePage sets a panel's visibility to "false".
  436. func (p *Pages) HidePage(name string) {
  437. p.HidePanel(name)
  438. }
  439.  
  440. // SwitchToPage sets a panel's visibility to "true" and all other panels'
  441. // visibility to "false".
  442. func (p *Pages) SwitchToPage(name string) {
  443. p.SetCurrentPanel(name)
  444. }
  445.  
  446. // GetFrontPage returns the front-most visible panel.
  447. func (p *Pages) GetFrontPage() (name string, item cview.Primitive) {
  448. return p.GetFrontPanel()
  449. }
Buy Me A Coffee