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 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
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
73
74 func (t *TabbedPanels) SetChangedFunc(handler func()) {
75 t.panels.SetChangedFunc(handler)
76 }
77
78
79
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
91 func (t *TabbedPanels) RemoveTab(name string) {
92 t.panels.RemovePanel(name)
93
94 t.updateAll()
95 }
96
97
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
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
140 func (t *TabbedPanels) GetCurrentTab() string {
141 t.RLock()
142 defer t.RUnlock()
143 return t.currentTab
144 }
145
146
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
160 func (t *TabbedPanels) SetTabTextColor(color tcell.Color) {
161 t.Switcher.SetTextColor(color)
162 }
163
164
165 func (t *TabbedPanels) SetTabTextColorFocused(color tcell.Color) {
166 t.Switcher.SetHighlightForegroundColor(color)
167 }
168
169
170 func (t *TabbedPanels) SetTabBackgroundColor(color tcell.Color) {
171 t.Switcher.SetBackgroundColor(color)
172 }
173
174
175
176 func (t *TabbedPanels) SetTabBackgroundColorFocused(color tcell.Color) {
177 t.Switcher.SetHighlightBackgroundColor(color)
178 }
179
180
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
188
189
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
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
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
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
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
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