- package main
-
- import (
- "bytes"
- "fmt"
- "sync"
- "strings"
-
- "github.com/gdamore/tcell/v2"
- "code.rocketnine.space/tslocum/cview"
- )
-
- // TabbedPanels is a tabbed container for other cview.Primitives. The tab switcher
- // may be positioned vertically or horizontally, before or after the content.
- type TabbedPanels struct {
- *cview.Flex
- Switcher *cview.TextView
- panels *Panels
-
- tabLabels map[string]string
- currentTab string
-
- dividerStart string
- dividerMid string
- dividerEnd string
-
- switcherVertical bool
- switcherAfterContent bool
- switcherHeight int
-
- width, lastWidth int
-
- setFocus func(cview.Primitive)
-
- sync.RWMutex
- }
-
- // NewTabbedPanels returns a new TabbedPanels object.
- func NewTabbedPanels() *TabbedPanels {
- t := &TabbedPanels{
- Flex: cview.NewFlex(),
- Switcher: cview.NewTextView(),
- panels: NewPanels(),
- dividerStart: string(cview.BoxDrawingsLightDownAndRight),
- dividerMid: string("-"),
- dividerEnd: string(cview.BoxDrawingsLightDownAndLeft),
- tabLabels: make(map[string]string),
- }
-
- s := t.Switcher
- s.SetDynamicColors(true)
- s.SetHighlightForegroundColor(tcell.Color226) // yellow
- s.SetHighlightBackgroundColor(tcell.Color16) // black
- s.SetRegions(true)
- s.SetScrollable(true)
- s.SetWrap(true)
- s.SetWordWrap(true)
- s.SetHighlightedFunc(func(added, removed, remaining []string) {
- if len(added) == 0 {
- return
- }
-
- s.ScrollToHighlight()
- t.SetCurrentTab(added[0])
- if t.setFocus != nil {
- t.setFocus(t.panels)
- }
- })
-
- t.rebuild()
-
- return t
- }
-
- // SetChangedFunc sets a handler which is called whenever a tab is added,
- // selected, reordered or removed.
- func (t *TabbedPanels) SetChangedFunc(handler func()) {
- t.panels.SetChangedFunc(handler)
- }
-
- // AddTab adds a new tab. Tab names should consist only of letters, numbers
- // and spaces.
- func (t *TabbedPanels) AddTab(name, label string, item cview.Primitive) {
- t.Lock()
- t.tabLabels[name] = label
- t.Unlock()
-
- t.panels.AddPanel(name, item, true, false)
-
- t.updateAll()
- }
-
- // RemoveTab removes a tab.
- func (t *TabbedPanels) RemoveTab(name string) {
- t.panels.RemovePanel(name)
-
- t.updateAll()
- }
-
- // HasTab returns true if a tab with the given name exists in this object.
- func (t *TabbedPanels) HasTab(name string) bool {
- t.RLock()
- defer t.RUnlock()
-
- for _, panel := range t.panels.panels {
- if panel.Name == name {
- return true
- }
- }
- return false
- }
-
- // SetCurrentTab sets the currently visible tab.
- func (t *TabbedPanels) SetCurrentTab(name string) {
- t.Lock()
-
- if t.currentTab == name {
- t.Unlock()
- return
- }
-
- t.currentTab = name
-
- t.updateAll()
-
- t.Unlock()
-
- h := t.Switcher.GetHighlights()
- var found bool
- for _, hl := range h {
- if hl == name {
- found = true
- break
- }
- }
- if !found {
- t.Switcher.Highlight(t.currentTab)
- }
- t.Switcher.ScrollToHighlight()
- }
-
- // GetCurrentTab returns the currently visible tab.
- func (t *TabbedPanels) GetCurrentTab() string {
- t.RLock()
- defer t.RUnlock()
- return t.currentTab
- }
-
- // SetTabLabel sets the label of a tab.
- func (t *TabbedPanels) SetTabLabel(name, label string) {
- t.Lock()
- defer t.Unlock()
-
- if t.tabLabels[name] == label {
- return
- }
-
- t.tabLabels[name] = label
- t.updateTabLabels()
- }
-
- // SetTabTextColor sets the color of the tab text.
- func (t *TabbedPanels) SetTabTextColor(color tcell.Color) {
- t.Switcher.SetTextColor(color)
- }
-
- // SetTabTextColorFocused sets the color of the tab text when the tab is in focus.
- func (t *TabbedPanels) SetTabTextColorFocused(color tcell.Color) {
- t.Switcher.SetHighlightForegroundColor(color)
- }
-
- // SetTabBackgroundColor sets the background color of the tab.
- func (t *TabbedPanels) SetTabBackgroundColor(color tcell.Color) {
- t.Switcher.SetBackgroundColor(color)
- }
-
- // SetTabBackgroundColorFocused sets the background color of the tab when the
- // tab is in focus.
- func (t *TabbedPanels) SetTabBackgroundColorFocused(color tcell.Color) {
- t.Switcher.SetHighlightBackgroundColor(color)
- }
-
- // SetTabSwitcherDivider sets the tab switcher divider text. Color tags are supported.
- func (t *TabbedPanels) SetTabSwitcherDivider(start, mid, end string) {
- t.Lock()
- defer t.Unlock()
- t.dividerStart, t.dividerMid, t.dividerEnd = start, mid, end
- }
-
- // SetTabSwitcherHeight sets the tab switcher height. This setting only applies
- // when rendering horizontally. A value of 0 (the default) indicates the height
- // should automatically adjust to fit all of the tab labels.
- func (t *TabbedPanels) SetTabSwitcherHeight(height int) {
- t.Lock()
- defer t.Unlock()
-
- t.switcherHeight = height
- t.rebuild()
- }
-
- // SetTabSwitcherVertical sets the orientation of the tab switcher.
- func (t *TabbedPanels) SetTabSwitcherVertical(vertical bool) {
- t.Lock()
- defer t.Unlock()
- if t.switcherVertical == vertical {
- return
- }
-
- t.switcherVertical = vertical
- t.rebuild()
- }
-
- // SetTabSwitcherAfterContent sets whether the tab switcher is positioned after content.
- func (t *TabbedPanels) SetTabSwitcherAfterContent(after bool) {
- t.Lock()
- defer t.Unlock()
-
- if t.switcherAfterContent == after {
- return
- }
-
- t.switcherAfterContent = after
- t.rebuild()
- }
-
- func (t *TabbedPanels) rebuild() {
- f := t.Flex
- if t.switcherVertical {
- f.SetDirection(cview.FlexColumn)
- } else {
- f.SetDirection(cview.FlexRow)
- }
- f.RemoveItem(t.panels)
- f.RemoveItem(t.Switcher)
- if t.switcherAfterContent {
- f.AddItem(t.panels, 0, 1, true)
- f.AddItem(t.Switcher, 1, 1, false)
- } else {
- f.AddItem(t.Switcher, 1, 1, false)
- f.AddItem(t.panels, 0, 1, true)
- }
-
- t.updateTabLabels()
-
- t.Switcher.SetMaxLines(t.switcherHeight)
- }
-
- func (t *TabbedPanels) updateTabLabels() {
- if len(t.panels.panels) == 0 {
- t.Switcher.SetText("")
- t.Flex.ResizeItem(t.Switcher, 0, 1)
- return
- }
-
- maxWidth := 0
- for _, panel := range t.panels.panels {
- label := t.tabLabels[panel.Name]
- if len(label) > maxWidth {
- maxWidth = len(label)
- }
- }
-
- var b bytes.Buffer
- if !t.switcherVertical {
- b.WriteString(t.dividerStart)
- }
- l := len(t.panels.panels)
- spacer := []byte(" ")
- for i, panel := range t.panels.panels {
- if i > 0 && t.switcherVertical {
- b.WriteRune('\n')
- }
-
- if t.switcherVertical && t.switcherAfterContent {
- b.WriteString(t.dividerMid)
- b.WriteRune(' ')
- }
-
- label := t.tabLabels[panel.Name]
- if !t.switcherVertical {
- label = " " + label
- }
-
- if t.switcherVertical {
- spacer = bytes.Repeat([]byte(" "), maxWidth-len(label)+1)
- }
- b.WriteString(fmt.Sprintf(`["%s"]%s%s[""]`, panel.Name, label, spacer))
-
- if i == l-1 && !t.switcherVertical {
- //fmt.Println("t.width", t.width, "maxwidth", maxWidth, "label", label, "l", l)
- /***
- * This did not work! (trying to make when panel highlighted borders change)
- */
- /*
- spacer_char := ""
- div_end := ""
- if t.setFocus != nil { // not in focus!
- spacer_char = string(cview.BoxDrawingsLightHorizontal)
- div_end = t.dividerEnd
- }else{
- spacer_char = "[::b]"+string(cview.BoxDrawingsHeavyHorizontal)+"[::-]"
- div_end = string(cview.BoxDrawingsHeavyDownAndLeft)
- }
- */
- spacer_char := string(cview.BoxDrawingsLightHorizontal)
- div_end := t.dividerEnd
-
- spacer_str := ""
- if t.width > 0 {
- spacer_len := 0
- for _, panel_test := range t.panels.panels{
- spacer_len += len(t.tabLabels[panel_test.Name])+2
- }
-
- spacer_str = strings.Repeat(spacer_char, t.width-maxWidth-spacer_len+2)
-
- }else{
- spacer_str = strings.Repeat("─", maxWidth+1)
- }
- b.WriteString(spacer_str + div_end)
- } else if !t.switcherAfterContent {
- b.WriteString(t.dividerMid)
- }
- }
- t.Switcher.SetText(b.String())
-
- var reqLines int
- if t.switcherVertical {
- reqLines = maxWidth + 2
- } else {
- if t.switcherHeight > 0 {
- reqLines = t.switcherHeight
- } else {
- reqLines = len(cview.WordWrap(t.Switcher.GetText(true), t.width))
- if reqLines < 1 {
- reqLines = 1
- }
- }
- }
- t.Flex.ResizeItem(t.Switcher, reqLines, 1)
- }
-
- func (t *TabbedPanels) updateVisibleTabs() {
- allPanels := t.panels.panels
-
- var newTab string
-
- var foundCurrent bool
- for _, panel := range allPanels {
- if panel.Name == t.currentTab {
- newTab = panel.Name
- foundCurrent = true
- break
- }
- }
- if !foundCurrent {
- for _, panel := range allPanels {
- if panel.Name != "" {
- newTab = panel.Name
- break
- }
- }
- }
-
- if t.currentTab != newTab {
- t.SetCurrentTab(newTab)
- return
- }
-
- for _, panel := range allPanels {
- if panel.Name == t.currentTab {
- t.panels.ShowPanel(panel.Name)
- } else {
- t.panels.HidePanel(panel.Name)
- }
- }
- }
-
- func (t *TabbedPanels) updateAll() {
- t.updateTabLabels()
- t.updateVisibleTabs()
- }
-
- // Draw draws this cview.Primitive onto the screen.
- func (t *TabbedPanels) Draw(screen tcell.Screen) {
- if !t.GetVisible() {
- return
- }
-
- t.Box.Draw(screen)
-
- _, _, t.width, _ = t.GetInnerRect()
- if t.width != t.lastWidth {
- t.updateTabLabels()
- }
- t.lastWidth = t.width
-
- t.Flex.Draw(screen)
- }
-
- // InputHandler returns the handler for this cview.Primitive.
- func (t *TabbedPanels) InputHandler() func(event *tcell.EventKey, setFocus func(p cview.Primitive)) {
- return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p cview.Primitive)) {
- if t.setFocus == nil {
- t.setFocus = setFocus
- }
- t.Flex.InputHandler()(event, setFocus)
- })
- }
-
- // MouseHandler returns the mouse handler for this cview.Primitive.
- func (t *TabbedPanels) MouseHandler() func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
- return t.WrapMouseHandler(func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
- if t.setFocus == nil {
- t.setFocus = setFocus
- }
-
- x, y := event.Position()
- if !t.InRect(x, y) {
- return false, nil
- }
-
- if t.Switcher.InRect(x, y) {
- if t.setFocus != nil {
- defer t.setFocus(t.panels)
- }
- defer t.Switcher.MouseHandler()(action, event, setFocus)
- return true, nil
- }
-
- return t.Flex.MouseHandler()(action, event, setFocus)
- })
- }