From c326cc4c1646c17dd6149f6533866da5fbe62bf9 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Fri, 16 Oct 2020 13:06:09 -0700 Subject: [PATCH] Support custom TabbedPanels tab styling --- DESIGN.md | 4 +- tabbedpanels.go | 120 ++++++++++++++++++++++++++++++++++++++---------- util.go | 11 +++++ 3 files changed, 109 insertions(+), 26 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index e6e9010..d355140 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -2,8 +2,8 @@ This document lists architectural details of cview. # Focus-related style attributes are unset by default -This applies to all widgets except Buttons, which require a style change to -indicate focus. See [ColorUnset](https://docs.rocketnine.space/gitlab.com/tslocum/cview#pkg-variables). +This applies to all widgets except Button and TabbedPanels, which require a +style change to indicate focus. See [ColorUnset](https://docs.rocketnine.space/gitlab.com/tslocum/cview#pkg-variables). # Widgets always use `sync.RWMutex` diff --git a/tabbedpanels.go b/tabbedpanels.go index db8e142..1e61d67 100644 --- a/tabbedpanels.go +++ b/tabbedpanels.go @@ -12,12 +12,21 @@ import ( // may be displayed at the top or bottom of the container. type TabbedPanels struct { *Flex - panels *Panels - tabs *TextView + Switcher *TextView + panels *Panels tabLabels map[string]string currentTab string + tabTextColor tcell.Color + tabTextColorFocused tcell.Color + tabBackgroundColor tcell.Color + tabBackgroundColorFocused tcell.Color + + tabDividerStart string + tabDividerMid string + tabDividerEnd string + bottomTabSwitcher bool width, lastWidth int @@ -30,17 +39,24 @@ type TabbedPanels struct { // NewTabbedPanels returns a new TabbedPanels object. func NewTabbedPanels() *TabbedPanels { t := &TabbedPanels{ - Flex: NewFlex(), - panels: NewPanels(), - tabs: NewTextView(), - tabLabels: make(map[string]string), + Flex: NewFlex(), + Switcher: NewTextView(), + panels: NewPanels(), + tabTextColor: Styles.PrimaryTextColor, + tabTextColorFocused: Styles.InverseTextColor, + tabBackgroundColor: ColorUnset, + tabBackgroundColorFocused: Styles.PrimaryTextColor, + tabDividerMid: string(BoxDrawingsDoubleVertical), + tabDividerEnd: string(BoxDrawingsLightVertical), + tabLabels: make(map[string]string), } - t.tabs.SetDynamicColors(true) - t.tabs.SetRegions(true) - t.tabs.SetWrap(true) - t.tabs.SetWordWrap(true) - t.tabs.SetHighlightedFunc(func(added, removed, remaining []string) { + s := t.Switcher + s.SetDynamicColors(true) + s.SetRegions(true) + s.SetWrap(true) + s.SetWordWrap(true) + s.SetHighlightedFunc(func(added, removed, remaining []string) { t.SetCurrentTab(added[0]) if t.setFocus != nil { t.setFocus(t.panels) @@ -49,7 +65,7 @@ func NewTabbedPanels() *TabbedPanels { f := t.Flex f.SetDirection(FlexRow) - f.AddItem(t.tabs, 1, 1, false) + f.AddItem(t.Switcher, 1, 1, false) f.AddItem(t.panels, 0, 1, true) return t @@ -89,7 +105,7 @@ func (t *TabbedPanels) SetCurrentTab(name string) { t.Unlock() - t.tabs.Highlight(t.currentTab) + t.Switcher.Highlight(t.currentTab) } // GetCurrentTab returns the currently visible tab. @@ -112,6 +128,42 @@ func (t *TabbedPanels) SetTabLabel(name, label string) { t.updateTabLabels() } +// SetTabTextColor sets the color of the tab text. +func (t *TabbedPanels) SetTabTextColor(color tcell.Color) { + t.Lock() + defer t.Unlock() + t.tabTextColor = color +} + +// SetTabTextColorFocused sets the color of the tab text when the tab is in focus. +func (t *TabbedPanels) SetTabTextColorFocused(color tcell.Color) { + t.Lock() + defer t.Unlock() + t.tabTextColorFocused = color +} + +// SetTabBackgroundColor sets the background color of the tab. +func (t *TabbedPanels) SetTabBackgroundColor(color tcell.Color) { + t.Lock() + defer t.Unlock() + t.tabBackgroundColor = color +} + +// SetTabBackgroundColorFocused sets the background color of the tab when the +// tab is in focus. +func (t *TabbedPanels) SetTabBackgroundColorFocused(color tcell.Color) { + t.Lock() + defer t.Unlock() + t.tabBackgroundColorFocused = 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.tabDividerStart, t.tabDividerMid, t.tabDividerEnd = start, mid, end +} + // SetTabSwitcherPosition sets the position of the tab switcher. func (t *TabbedPanels) SetTabSwitcherPosition(bottom bool) { t.Lock() @@ -125,12 +177,12 @@ func (t *TabbedPanels) SetTabSwitcherPosition(bottom bool) { f := t.Flex f.RemoveItem(t.panels) - f.RemoveItem(t.tabs) + f.RemoveItem(t.Switcher) if t.bottomTabSwitcher { f.AddItem(t.panels, 0, 1, true) - f.AddItem(t.tabs, 1, 1, false) + f.AddItem(t.Switcher, 1, 1, false) } else { - f.AddItem(t.tabs, 1, 1, false) + f.AddItem(t.Switcher, 1, 1, false) f.AddItem(t.panels, 0, 1, true) } @@ -138,17 +190,37 @@ func (t *TabbedPanels) SetTabSwitcherPosition(bottom bool) { } func (t *TabbedPanels) updateTabLabels() { - var b bytes.Buffer - for _, panel := range t.panels.panels { - b.WriteString(fmt.Sprintf(`["%s"][darkcyan] %s [white][""]|`, panel.Name, t.tabLabels[panel.Name])) + if len(t.panels.panels) == 0 { + t.Switcher.SetText("") + t.Flex.ResizeItem(t.Switcher, 0, 1) + return } - t.tabs.SetText(b.String()) - reqLines := len(WordWrap(t.tabs.GetText(true), t.width)) + var b bytes.Buffer + b.WriteString(t.tabDividerStart) + l := len(t.panels.panels) + for i, panel := range t.panels.panels { + textColor := t.tabTextColor + backgroundColor := t.tabBackgroundColor + if panel.Name == t.currentTab { + textColor = t.tabTextColorFocused + backgroundColor = t.tabBackgroundColorFocused + } + b.WriteString(fmt.Sprintf(`["%s"][%s:%s] %s [-:-][""]`, panel.Name, ColorHex(textColor), ColorHex(backgroundColor), t.tabLabels[panel.Name])) + + if i == l-1 { + b.WriteString(t.tabDividerEnd) + } else { + b.WriteString(t.tabDividerMid) + } + } + t.Switcher.SetText(b.String()) + + reqLines := len(WordWrap(t.Switcher.GetText(true), t.width)) if reqLines < 1 { reqLines = 1 } - t.Flex.ResizeItem(t.tabs, reqLines, 1) + t.Flex.ResizeItem(t.Switcher, reqLines, 1) } func (t *TabbedPanels) updateVisibleTabs() { @@ -227,11 +299,11 @@ func (t *TabbedPanels) MouseHandler() func(action MouseAction, event *tcell.Even return false, nil } - if t.tabs.InRect(x, y) { + if t.Switcher.InRect(x, y) { if t.setFocus != nil { defer t.setFocus(t.panels) } - defer t.tabs.MouseHandler()(action, event, setFocus) + defer t.Switcher.MouseHandler()(action, event, setFocus) return true, nil } diff --git a/util.go b/util.go index 7c8a6c6..43bd29a 100644 --- a/util.go +++ b/util.go @@ -1,6 +1,7 @@ package cview import ( + "fmt" "math" "regexp" "sort" @@ -96,6 +97,16 @@ func init() { } } +// ColorHex returns the hexadecimal value of a color as a string, prefixed with #. +// If the color is invalid, a blank string is returned. +func ColorHex(c tcell.Color) string { + if !c.Valid() { + return "" + } + r, g, b := c.RGB() + return fmt.Sprintf("#%02x%02x%02x", r, g, b) +} + // styleFromTag takes the given style, defined by a foreground color (fgColor), // a background color (bgColor), and style attributes, and modifies it based on // the substrings (tagSubstrings) extracted by the regular expression for color