...

Source file src/gitlab.com/tslocum/cview/tabbedpanels.go

Documentation: gitlab.com/tslocum/cview

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

View as plain text