tab_bar.go RawHistoryFolder
1package component
2
3import (
4 "sync"
5
6 "github.com/bnema/dumber/internal/domain/entity"
7 "github.com/jwijenbergh/puregotk/v4/gtk"
8)
9
10// TabBar represents the horizontal tab bar widget.
11type TabBar struct {
12 box *gtk.Box
13 buttons map[entity.TabID]*TabButton
14
15 // Active tab tracking
16 activeTabID entity.TabID
17
18 // Callbacks
19 onSwitch func(tabID entity.TabID)
20 onClose func(tabID entity.TabID)
21 onCreate func()
22
23 mu sync.RWMutex
24}
25
26// NewTabBar creates a new tab bar widget.
27func NewTabBar() *TabBar {
28 tb := &TabBar{
29 buttons: make(map[entity.TabID]*TabButton),
30 }
31
32 // Create horizontal box container
33 tb.box = gtk.NewBox(gtk.OrientationHorizontalValue, 0)
34 if tb.box == nil {
35 return nil
36 }
37
38 // Configure the box
39 tb.box.SetHexpand(true)
40 tb.box.SetVexpand(false)
41 tb.box.SetVisible(true)
42 tb.box.AddCssClass("tab-bar")
43
44 return tb
45}
46
47// Widget returns the underlying GTK widget for embedding.
48func (tb *TabBar) Widget() *gtk.Widget {
49 return &tb.box.Widget
50}
51
52// Box returns the underlying GTK box.
53func (tb *TabBar) Box() *gtk.Box {
54 return tb.box
55}
56
57// AddTab adds a new tab button to the bar.
58func (tb *TabBar) AddTab(tab *entity.Tab) {
59 tb.mu.Lock()
60 defer tb.mu.Unlock()
61
62 // Check if already exists
63 if _, exists := tb.buttons[tab.ID]; exists {
64 return
65 }
66
67 // Create the button
68 button := NewTabButton(tab)
69 if button == nil {
70 return
71 }
72
73 // Set click handler
74 button.SetOnClick(func(tabID entity.TabID) {
75 if tb.onSwitch != nil {
76 tb.onSwitch(tabID)
77 }
78 })
79
80 // Add to container
81 tb.box.Append(button.Widget())
82 tb.buttons[tab.ID] = button
83}
84
85// RemoveTab removes a tab button from the bar.
86func (tb *TabBar) RemoveTab(tabID entity.TabID) {
87 tb.mu.Lock()
88 defer tb.mu.Unlock()
89
90 button, exists := tb.buttons[tabID]
91 if !exists {
92 return
93 }
94
95 // Remove from container
96 tb.box.Remove(button.Widget())
97 button.Destroy()
98 delete(tb.buttons, tabID)
99}
100
101// SetActive updates which tab is shown as active.
102func (tb *TabBar) SetActive(tabID entity.TabID) {
103 tb.mu.Lock()
104 defer tb.mu.Unlock()
105
106 // Deactivate previous
107 if tb.activeTabID != "" && tb.activeTabID != tabID {
108 if prevButton, exists := tb.buttons[tb.activeTabID]; exists {
109 prevButton.SetActive(false)
110 }
111 }
112
113 // Activate new
114 if button, exists := tb.buttons[tabID]; exists {
115 button.SetActive(true)
116 }
117
118 tb.activeTabID = tabID
119}
120
121// UpdateTitle updates the title of a specific tab button.
122func (tb *TabBar) UpdateTitle(tabID entity.TabID, title string) {
123 tb.mu.RLock()
124 defer tb.mu.RUnlock()
125
126 if button, exists := tb.buttons[tabID]; exists {
127 button.SetTitle(title)
128 }
129}
130
131// SetOnSwitch sets the callback for tab switch events.
132func (tb *TabBar) SetOnSwitch(fn func(tabID entity.TabID)) {
133 tb.onSwitch = fn
134}
135
136// SetOnClose sets the callback for tab close events.
137func (tb *TabBar) SetOnClose(fn func(tabID entity.TabID)) {
138 tb.onClose = fn
139}
140
141// SetOnCreate sets the callback for new tab creation.
142func (tb *TabBar) SetOnCreate(fn func()) {
143 tb.onCreate = fn
144}
145
146// Count returns the number of tabs in the bar.
147func (tb *TabBar) Count() int {
148 tb.mu.RLock()
149 defer tb.mu.RUnlock()
150 return len(tb.buttons)
151}
152
153// ActiveTabID returns the currently active tab ID.
154func (tb *TabBar) ActiveTabID() entity.TabID {
155 tb.mu.RLock()
156 defer tb.mu.RUnlock()
157 return tb.activeTabID
158}
159
160// SetVisible shows or hides the tab bar.
161func (tb *TabBar) SetVisible(visible bool) {
162 if tb.box == nil {
163 return
164 }
165 tb.box.SetVisible(visible)
166}
167
168// Destroy cleans up all tab bar resources.
169func (tb *TabBar) Destroy() {
170 tb.mu.Lock()
171 defer tb.mu.Unlock()
172
173 for _, button := range tb.buttons {
174 button.Destroy()
175 }
176 tb.buttons = nil
177
178 if tb.box != nil {
179 tb.box.Unref()
180 tb.box = nil
181 }
182}