-
Notifications
You must be signed in to change notification settings - Fork 1
/
tabframe.go
300 lines (260 loc) · 7.5 KB
/
tabframe.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package ui
import (
"fmt"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/style"
"git.kirsle.net/go/ui/theme"
)
// TabFrame is a tabbed notebook of multiple frames showing
// tab names along the top and clicking them reveals each
// named tab.
type TabFrame struct {
Name string
Frame
supervisor *Supervisor
style *style.Button
// Child widgets.
header *Frame
content *Frame
tabButtons []*Button
tabFrames []*Frame
currentTabKey string
}
// NewTabFrame creates a new Frame.
func NewTabFrame(name string) *TabFrame {
w := &TabFrame{
Name: name,
style: Theme.TabFrame,
header: NewFrame(name + " Header"),
content: NewFrame(name + " Content"),
tabButtons: []*Button{},
tabFrames: []*Frame{},
}
// Initialize the root frame of this widget.
w.Frame.Setup()
// Pack the high-level layout into the root frame.
// Only root needs to Present for this widget.
w.Frame.Pack(w.header, Pack{
Side: N,
FillX: true,
})
w.Frame.Pack(w.content, Pack{
Side: N,
Fill: true,
Expand: true,
})
w.SetBackground(render.RGBA(1, 0, 0, 0)) // invisible default BG
w.IDFunc(func() string {
return fmt.Sprintf("TabFrame<%s>",
name,
)
})
return w
}
// AddTab creates a new content tab. The key is a unique identifier
// for the tab and is how the TabFrame knows which tab is selected.
//
// The child widget would probably be a Label or Image but could be
// any other kind of widget.
//
// The first tab added becomes the selected tab by default.
func (w *TabFrame) AddTab(key string, child Widget) *Frame {
// Create the tab button for this tab.
button := NewButton(key, child)
button.SetStyle(w.style)
button.FixedColor = true
button.SetOutlineSize(0)
button.SetBorderSize(1)
w.setButtonStyle(button, len(w.tabButtons) == 0)
w.header.Pack(button, Pack{
Side: W,
})
button.Handle(MouseDown, func(ed EventData) error {
w.SetTab(key)
return nil
})
// Create the frame of the tab's body.
frame := NewFrame(key)
frame.Configure(Config{
BorderSize: w.style.BorderSize,
Background: w.style.Background,
BorderStyle: BorderRaised,
})
if len(w.tabFrames) > 0 {
frame.Hide()
}
// Pack this frame into the content part of the widget.
w.content.Pack(frame, Pack{
Side: N,
FillX: true,
})
w.tabButtons = append(w.tabButtons, button)
w.tabFrames = append(w.tabFrames, frame)
return frame
}
// SetTabsHidden can hide the tab buttons and reveal only their frames.
// It would be up to the caller to SetTab between the frames, using the
// TabFrame only for placement and tab handling.
func (w *TabFrame) SetTabsHidden(hidden bool) {
if hidden {
w.header.Hide()
} else {
w.header.Show()
}
}
// Header returns access to the ui.Frame that holds the tab buttons. Use
// at your own risk -- the UI arrangement in this Frame is not guaranteed
// stable.
func (w *TabFrame) Header() *Frame {
return w.header
}
// set the tab style between active and inactive
func (w *TabFrame) setButtonStyle(button *Button, active bool) {
var style = button.GetStyle()
if active {
button.SetBackground(style.Background)
button.SetBorderStyle(BorderRaised)
if label, ok := button.child.(*Label); ok {
label.Font.Color = style.Foreground
}
} else {
button.SetBackground(style.Background.Darken(theme.BorderColorOffset))
button.SetBorderStyle(BorderSolid)
if label, ok := button.child.(*Label); ok {
label.Font.Color = style.Foreground
}
}
}
// SetTab changes the selected tab to the new value. If the
// tab doesn't exist, the first tab is selected.
func (w *TabFrame) SetTab(key string) bool {
var found bool
for i, frame := range w.tabFrames {
button := w.tabButtons[i]
if frame.Name == key {
frame.Show()
w.setButtonStyle(button, true)
w.currentTabKey = key
found = true
} else {
frame.Hide()
w.setButtonStyle(button, false)
}
}
if !found && len(w.tabFrames) > 0 {
w.tabFrames[0].Show()
w.currentTabKey = w.tabFrames[0].Name
}
return found
}
// Supervise activates the tab frame using your supervisor. If you
// don't call this, the tab buttons won't be clickable!
//
// Call this AFTER adding all tabs. This function calls Supervisor.Add
// on all tab buttons.
func (w *TabFrame) Supervise(supervisor *Supervisor) {
for _, button := range w.tabButtons {
supervisor.Add(button)
}
}
// SetStyle controls the visual styling of the tab button bar.
func (w *TabFrame) SetStyle(style *style.Button) {
w.style = style
for _, button := range w.tabButtons {
button.SetStyle(style)
w.setButtonStyle(button, !button.Hidden())
}
}
// Compute the size of the Frame.
func (w *TabFrame) Compute(e render.Engine) {
// Compute all the child frames.
w.Frame.Compute(e)
// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}
// Present the Frame.
func (w *TabFrame) Present(e render.Engine, P render.Point) {
if w.Hidden() {
return
}
var (
S = w.Size()
)
// Draw the widget's border and everything.
w.DrawBox(e, P)
// Draw the background color.
e.DrawBox(w.Background(), render.Rect{
X: P.X + w.BoxThickness(1),
Y: P.Y + w.BoxThickness(1),
W: S.W - w.BoxThickness(2),
H: S.H - w.BoxThickness(2),
})
// Present the root frame.
w.Frame.Present(e, P)
// Draw the borders over the tabs.
w.presentBorders(e, P)
// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, P)
}
/*
presentBorders handles drawing the borders around tab buttons.
The tabs are simple Button widgets but drawn with no borders. Instead,
borders are painted on post-hoc in the Present function.
*/
func (w *TabFrame) presentBorders(e render.Engine, P render.Point) {
if len(w.tabButtons) == 0 || w.header.Hidden() {
return
}
// Prep some variables.
var (
// The 1st and last tab button widgets.
first = w.tabButtons[0]
last = w.tabButtons[len(w.tabButtons)-1]
topLeft = AbsolutePosition(first)
bottomRight = AbsolutePosition(last)
// The absolute bounding box of the tabs part of the UI,
// from the top-left corner of Tab #1 to the bottom-right
// corner of the final tab.
bounding = render.Rect{
X: P.X, //topLeft.X + first.BoxThickness(4),
Y: P.Y, //topLeft.Y + first.BoxThickness(4),
W: bottomRight.X + last.Size().W - topLeft.X,
H: bottomRight.Y + last.Size().H - topLeft.Y,
}
// The very bottom edge of the whole tab bar,
// to overlap the BorderSize=1 along their buttons.
bottomLine = []render.Point{
render.NewPoint(P.X+1, bounding.Y+bounding.H-1),
render.NewPoint(bounding.X+bounding.W-1, bounding.Y+bounding.H-1),
}
)
// Draw a shadow border on all the inactive tabs' right edges,
// so they don't all blend together in solid grey.
// Note: the active button has a BorderSize=1 and others are 0.
for i, button := range w.tabButtons {
if button.Name != w.currentTabKey {
// If it immediately precedes the current tab, do not draw the line,
// it would cover the highlight color of the current tab's button.
if i+1 < len(w.tabButtons) && w.tabButtons[i+1].Name == w.currentTabKey {
continue
}
var (
abs = AbsolutePosition(button)
size = button.BoxSize()
points = []render.Point{
render.NewPoint(abs.X+size.W-1, abs.Y+2),
render.NewPoint(abs.X+size.W-1, abs.Y+size.H-2),
}
)
e.DrawLine(button.Background().Darken(theme.BorderColorOffset), points[0], points[1])
}
}
// Erase the button edge from all tabs.
e.DrawLine(w.style.Background, bottomLine[0], bottomLine[1])
e.DrawBox(w.style.Background, render.Rect{
X: bottomLine[0].X + 1,
Y: bottomLine[0].Y,
W: bounding.W - 2,
H: 4,
})
}