...

Source file src/code.rocketnine.space/tslocum/cview/tabbedpanels.go

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

View as plain text