| | 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) |
---|
| | }) |
---|
| | } |
---|
| | |
---|
| | |