1 package cview
2
3 import (
4 "bytes"
5 "fmt"
6 "sync"
7
8 "github.com/gdamore/tcell/v2"
9 )
10
11
12
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
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
73
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
85 func (t *TabbedPanels) RemoveTab(name string) {
86 t.panels.RemovePanel(name)
87
88 t.updateAll()
89 }
90
91
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
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
123 func (t *TabbedPanels) GetCurrentTab() string {
124 t.RLock()
125 defer t.RUnlock()
126 return t.currentTab
127 }
128
129
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
143 func (t *TabbedPanels) SetTabTextColor(color tcell.Color) {
144 t.Lock()
145 defer t.Unlock()
146 t.tabTextColor = color
147 }
148
149
150 func (t *TabbedPanels) SetTabTextColorFocused(color tcell.Color) {
151 t.Lock()
152 defer t.Unlock()
153 t.tabTextColorFocused = color
154 }
155
156
157 func (t *TabbedPanels) SetTabBackgroundColor(color tcell.Color) {
158 t.Lock()
159 defer t.Unlock()
160 t.tabBackgroundColor = color
161 }
162
163
164
165 func (t *TabbedPanels) SetTabBackgroundColorFocused(color tcell.Color) {
166 t.Lock()
167 defer t.Unlock()
168 t.tabBackgroundColorFocused = color
169 }
170
171
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
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
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
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
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
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