Newer
Older
GoModules / TabTitleMenu / tabbedpanels.go
0xRoM on 7 Feb 2023 9 KB TabTitleMenu's added
  1. package main
  2.  
  3. import (
  4. "bytes"
  5. "fmt"
  6. "sync"
  7. "strings"
  8.  
  9. "github.com/gdamore/tcell/v2"
  10. "code.rocketnine.space/tslocum/cview"
  11. )
  12.  
  13. // TabbedPanels is a tabbed container for other cview.Primitives. The tab switcher
  14. // may be positioned vertically or horizontally, before or after the content.
  15. type TabbedPanels struct {
  16. *cview.Flex
  17. Switcher *cview.TextView
  18. panels *Panels
  19.  
  20. tabLabels map[string]string
  21. currentTab string
  22.  
  23. dividerStart string
  24. dividerMid string
  25. dividerEnd string
  26.  
  27. switcherVertical bool
  28. switcherAfterContent bool
  29. switcherHeight int
  30.  
  31. width, lastWidth int
  32.  
  33. setFocus func(cview.Primitive)
  34.  
  35. sync.RWMutex
  36. }
  37.  
  38. // NewTabbedPanels returns a new TabbedPanels object.
  39. func NewTabbedPanels() *TabbedPanels {
  40. t := &TabbedPanels{
  41. Flex: cview.NewFlex(),
  42. Switcher: cview.NewTextView(),
  43. panels: NewPanels(),
  44. dividerStart: string(cview.BoxDrawingsLightDownAndRight),
  45. dividerMid: string("-"),
  46. dividerEnd: string(cview.BoxDrawingsLightDownAndLeft),
  47. tabLabels: make(map[string]string),
  48. }
  49.  
  50. s := t.Switcher
  51. s.SetDynamicColors(true)
  52. s.SetHighlightForegroundColor(tcell.Color226) // yellow
  53. s.SetHighlightBackgroundColor(tcell.Color16) // black
  54. s.SetRegions(true)
  55. s.SetScrollable(true)
  56. s.SetWrap(true)
  57. s.SetWordWrap(true)
  58. s.SetHighlightedFunc(func(added, removed, remaining []string) {
  59. if len(added) == 0 {
  60. return
  61. }
  62.  
  63. s.ScrollToHighlight()
  64. t.SetCurrentTab(added[0])
  65. if t.setFocus != nil {
  66. t.setFocus(t.panels)
  67. }
  68. })
  69.  
  70. t.rebuild()
  71.  
  72. return t
  73. }
  74.  
  75. // SetChangedFunc sets a handler which is called whenever a tab is added,
  76. // selected, reordered or removed.
  77. func (t *TabbedPanels) SetChangedFunc(handler func()) {
  78. t.panels.SetChangedFunc(handler)
  79. }
  80.  
  81. // AddTab adds a new tab. Tab names should consist only of letters, numbers
  82. // and spaces.
  83. func (t *TabbedPanels) AddTab(name, label string, item cview.Primitive) {
  84. t.Lock()
  85. t.tabLabels[name] = label
  86. t.Unlock()
  87.  
  88. t.panels.AddPanel(name, item, true, false)
  89.  
  90. t.updateAll()
  91. }
  92.  
  93. // RemoveTab removes a tab.
  94. func (t *TabbedPanels) RemoveTab(name string) {
  95. t.panels.RemovePanel(name)
  96.  
  97. t.updateAll()
  98. }
  99.  
  100. // HasTab returns true if a tab with the given name exists in this object.
  101. func (t *TabbedPanels) HasTab(name string) bool {
  102. t.RLock()
  103. defer t.RUnlock()
  104.  
  105. for _, panel := range t.panels.panels {
  106. if panel.Name == name {
  107. return true
  108. }
  109. }
  110. return false
  111. }
  112.  
  113. // SetCurrentTab sets the currently visible tab.
  114. func (t *TabbedPanels) SetCurrentTab(name string) {
  115. t.Lock()
  116.  
  117. if t.currentTab == name {
  118. t.Unlock()
  119. return
  120. }
  121.  
  122. t.currentTab = name
  123.  
  124. t.updateAll()
  125.  
  126. t.Unlock()
  127.  
  128. h := t.Switcher.GetHighlights()
  129. var found bool
  130. for _, hl := range h {
  131. if hl == name {
  132. found = true
  133. break
  134. }
  135. }
  136. if !found {
  137. t.Switcher.Highlight(t.currentTab)
  138. }
  139. t.Switcher.ScrollToHighlight()
  140. }
  141.  
  142. // GetCurrentTab returns the currently visible tab.
  143. func (t *TabbedPanels) GetCurrentTab() string {
  144. t.RLock()
  145. defer t.RUnlock()
  146. return t.currentTab
  147. }
  148.  
  149. // SetTabLabel sets the label of a tab.
  150. func (t *TabbedPanels) SetTabLabel(name, label string) {
  151. t.Lock()
  152. defer t.Unlock()
  153.  
  154. if t.tabLabels[name] == label {
  155. return
  156. }
  157.  
  158. t.tabLabels[name] = label
  159. t.updateTabLabels()
  160. }
  161.  
  162. // SetTabTextColor sets the color of the tab text.
  163. func (t *TabbedPanels) SetTabTextColor(color tcell.Color) {
  164. t.Switcher.SetTextColor(color)
  165. }
  166.  
  167. // SetTabTextColorFocused sets the color of the tab text when the tab is in focus.
  168. func (t *TabbedPanels) SetTabTextColorFocused(color tcell.Color) {
  169. t.Switcher.SetHighlightForegroundColor(color)
  170. }
  171.  
  172. // SetTabBackgroundColor sets the background color of the tab.
  173. func (t *TabbedPanels) SetTabBackgroundColor(color tcell.Color) {
  174. t.Switcher.SetBackgroundColor(color)
  175. }
  176.  
  177. // SetTabBackgroundColorFocused sets the background color of the tab when the
  178. // tab is in focus.
  179. func (t *TabbedPanels) SetTabBackgroundColorFocused(color tcell.Color) {
  180. t.Switcher.SetHighlightBackgroundColor(color)
  181. }
  182.  
  183. // SetTabSwitcherDivider sets the tab switcher divider text. Color tags are supported.
  184. func (t *TabbedPanels) SetTabSwitcherDivider(start, mid, end string) {
  185. t.Lock()
  186. defer t.Unlock()
  187. t.dividerStart, t.dividerMid, t.dividerEnd = start, mid, end
  188. }
  189.  
  190. // SetTabSwitcherHeight sets the tab switcher height. This setting only applies
  191. // when rendering horizontally. A value of 0 (the default) indicates the height
  192. // should automatically adjust to fit all of the tab labels.
  193. func (t *TabbedPanels) SetTabSwitcherHeight(height int) {
  194. t.Lock()
  195. defer t.Unlock()
  196.  
  197. t.switcherHeight = height
  198. t.rebuild()
  199. }
  200.  
  201. // SetTabSwitcherVertical sets the orientation of the tab switcher.
  202. func (t *TabbedPanels) SetTabSwitcherVertical(vertical bool) {
  203. t.Lock()
  204. defer t.Unlock()
  205. if t.switcherVertical == vertical {
  206. return
  207. }
  208.  
  209. t.switcherVertical = vertical
  210. t.rebuild()
  211. }
  212.  
  213. // SetTabSwitcherAfterContent sets whether the tab switcher is positioned after content.
  214. func (t *TabbedPanels) SetTabSwitcherAfterContent(after bool) {
  215. t.Lock()
  216. defer t.Unlock()
  217.  
  218. if t.switcherAfterContent == after {
  219. return
  220. }
  221.  
  222. t.switcherAfterContent = after
  223. t.rebuild()
  224. }
  225.  
  226. func (t *TabbedPanels) rebuild() {
  227. f := t.Flex
  228. if t.switcherVertical {
  229. f.SetDirection(cview.FlexColumn)
  230. } else {
  231. f.SetDirection(cview.FlexRow)
  232. }
  233. f.RemoveItem(t.panels)
  234. f.RemoveItem(t.Switcher)
  235. if t.switcherAfterContent {
  236. f.AddItem(t.panels, 0, 1, true)
  237. f.AddItem(t.Switcher, 1, 1, false)
  238. } else {
  239. f.AddItem(t.Switcher, 1, 1, false)
  240. f.AddItem(t.panels, 0, 1, true)
  241. }
  242.  
  243. t.updateTabLabels()
  244.  
  245. t.Switcher.SetMaxLines(t.switcherHeight)
  246. }
  247.  
  248. func (t *TabbedPanels) updateTabLabels() {
  249. if len(t.panels.panels) == 0 {
  250. t.Switcher.SetText("")
  251. t.Flex.ResizeItem(t.Switcher, 0, 1)
  252. return
  253. }
  254.  
  255. maxWidth := 0
  256. for _, panel := range t.panels.panels {
  257. label := t.tabLabels[panel.Name]
  258. if len(label) > maxWidth {
  259. maxWidth = len(label)
  260. }
  261. }
  262.  
  263. var b bytes.Buffer
  264. if !t.switcherVertical {
  265. b.WriteString(t.dividerStart)
  266. }
  267. l := len(t.panels.panels)
  268. spacer := []byte(" ")
  269. for i, panel := range t.panels.panels {
  270. if i > 0 && t.switcherVertical {
  271. b.WriteRune('\n')
  272. }
  273.  
  274. if t.switcherVertical && t.switcherAfterContent {
  275. b.WriteString(t.dividerMid)
  276. b.WriteRune(' ')
  277. }
  278.  
  279. label := t.tabLabels[panel.Name]
  280. if !t.switcherVertical {
  281. label = " " + label
  282. }
  283.  
  284. if t.switcherVertical {
  285. spacer = bytes.Repeat([]byte(" "), maxWidth-len(label)+1)
  286. }
  287. b.WriteString(fmt.Sprintf(`["%s"]%s%s[""]`, panel.Name, label, spacer))
  288. if i == l-1 && !t.switcherVertical {
  289. //fmt.Println("t.width", t.width, "maxwidth", maxWidth, "label", label, "l", l)
  290. /***
  291. * This did not work! (trying to make when panel highlighted borders change)
  292. */
  293. /*
  294. spacer_char := ""
  295. div_end := ""
  296. if t.setFocus != nil { // not in focus!
  297. spacer_char = string(cview.BoxDrawingsLightHorizontal)
  298. div_end = t.dividerEnd
  299. }else{
  300. spacer_char = "[::b]"+string(cview.BoxDrawingsHeavyHorizontal)+"[::-]"
  301. div_end = string(cview.BoxDrawingsHeavyDownAndLeft)
  302. }
  303. */
  304. spacer_char := string(cview.BoxDrawingsLightHorizontal)
  305. div_end := t.dividerEnd
  306.  
  307. spacer_str := ""
  308. if t.width > 0 {
  309. spacer_len := 0
  310. for _, panel_test := range t.panels.panels{
  311. spacer_len += len(t.tabLabels[panel_test.Name])+2
  312. }
  313. spacer_str = strings.Repeat(spacer_char, t.width-maxWidth-spacer_len+2)
  314. }else{
  315. spacer_str = strings.Repeat("─", maxWidth+1)
  316. }
  317. b.WriteString(spacer_str + div_end)
  318. } else if !t.switcherAfterContent {
  319. b.WriteString(t.dividerMid)
  320. }
  321. }
  322. t.Switcher.SetText(b.String())
  323.  
  324. var reqLines int
  325. if t.switcherVertical {
  326. reqLines = maxWidth + 2
  327. } else {
  328. if t.switcherHeight > 0 {
  329. reqLines = t.switcherHeight
  330. } else {
  331. reqLines = len(cview.WordWrap(t.Switcher.GetText(true), t.width))
  332. if reqLines < 1 {
  333. reqLines = 1
  334. }
  335. }
  336. }
  337. t.Flex.ResizeItem(t.Switcher, reqLines, 1)
  338. }
  339.  
  340. func (t *TabbedPanels) updateVisibleTabs() {
  341. allPanels := t.panels.panels
  342.  
  343. var newTab string
  344.  
  345. var foundCurrent bool
  346. for _, panel := range allPanels {
  347. if panel.Name == t.currentTab {
  348. newTab = panel.Name
  349. foundCurrent = true
  350. break
  351. }
  352. }
  353. if !foundCurrent {
  354. for _, panel := range allPanels {
  355. if panel.Name != "" {
  356. newTab = panel.Name
  357. break
  358. }
  359. }
  360. }
  361.  
  362. if t.currentTab != newTab {
  363. t.SetCurrentTab(newTab)
  364. return
  365. }
  366.  
  367. for _, panel := range allPanels {
  368. if panel.Name == t.currentTab {
  369. t.panels.ShowPanel(panel.Name)
  370. } else {
  371. t.panels.HidePanel(panel.Name)
  372. }
  373. }
  374. }
  375.  
  376. func (t *TabbedPanels) updateAll() {
  377. t.updateTabLabels()
  378. t.updateVisibleTabs()
  379. }
  380.  
  381. // Draw draws this cview.Primitive onto the screen.
  382. func (t *TabbedPanels) Draw(screen tcell.Screen) {
  383. if !t.GetVisible() {
  384. return
  385. }
  386.  
  387. t.Box.Draw(screen)
  388.  
  389. _, _, t.width, _ = t.GetInnerRect()
  390. if t.width != t.lastWidth {
  391. t.updateTabLabels()
  392. }
  393. t.lastWidth = t.width
  394.  
  395. t.Flex.Draw(screen)
  396. }
  397.  
  398. // InputHandler returns the handler for this cview.Primitive.
  399. func (t *TabbedPanels) InputHandler() func(event *tcell.EventKey, setFocus func(p cview.Primitive)) {
  400. return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p cview.Primitive)) {
  401. if t.setFocus == nil {
  402. t.setFocus = setFocus
  403. }
  404. t.Flex.InputHandler()(event, setFocus)
  405. })
  406. }
  407.  
  408. // MouseHandler returns the mouse handler for this cview.Primitive.
  409. func (t *TabbedPanels) MouseHandler() func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
  410. return t.WrapMouseHandler(func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
  411. if t.setFocus == nil {
  412. t.setFocus = setFocus
  413. }
  414.  
  415. x, y := event.Position()
  416. if !t.InRect(x, y) {
  417. return false, nil
  418. }
  419.  
  420. if t.Switcher.InRect(x, y) {
  421. if t.setFocus != nil {
  422. defer t.setFocus(t.panels)
  423. }
  424. defer t.Switcher.MouseHandler()(action, event, setFocus)
  425. return true, nil
  426. }
  427.  
  428. return t.Flex.MouseHandler()(action, event, setFocus)
  429. })
  430. }
Buy Me A Coffee