gitstack

grindlemire/go-tui code browser

11.8 KB Go 456 lines 2026-06-05 · 31ba6dc raw
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
package tui

// Option configures an Element.
type Option func(*Element)

// --- Dimension Options ---

// WithWidth sets a fixed width in terminal cells.
func WithWidth(cells int) Option {
	return func(e *Element) {
		e.style.Width = Fixed(cells)
	}
}

// WithWidthPercent sets width as a percentage of parent's available width.
func WithWidthPercent(percent float64) Option {
	return func(e *Element) {
		e.style.Width = Percent(percent)
	}
}

// WithHeight sets a fixed height in terminal cells.
func WithHeight(cells int) Option {
	return func(e *Element) {
		e.style.Height = Fixed(cells)
	}
}

// WithHeightPercent sets height as a percentage of parent's available height.
func WithHeightPercent(percent float64) Option {
	return func(e *Element) {
		e.style.Height = Percent(percent)
	}
}

// WithSize sets both width and height in terminal cells.
func WithSize(width, height int) Option {
	return func(e *Element) {
		e.style.Width = Fixed(width)
		e.style.Height = Fixed(height)
	}
}

// WithMinWidth sets the minimum width in terminal cells.
func WithMinWidth(cells int) Option {
	return func(e *Element) {
		e.style.MinWidth = Fixed(cells)
	}
}

// WithMinHeight sets the minimum height in terminal cells.
func WithMinHeight(cells int) Option {
	return func(e *Element) {
		e.style.MinHeight = Fixed(cells)
	}
}

// WithMaxWidth sets the maximum width in terminal cells.
func WithMaxWidth(cells int) Option {
	return func(e *Element) {
		e.style.MaxWidth = Fixed(cells)
	}
}

// WithMaxHeight sets the maximum height in terminal cells.
func WithMaxHeight(cells int) Option {
	return func(e *Element) {
		e.style.MaxHeight = Fixed(cells)
	}
}

// --- Display Options ---

// WithDisplay sets the layout mode (block or flex).
func WithDisplay(d Display) Option {
	return func(e *Element) {
		e.style.Display = d
	}
}

// --- Flex Container Options ---

// WithDirection sets the main axis direction for laying out children.
func WithDirection(d Direction) Option {
	return func(e *Element) {
		e.style.Direction = d
	}
}

// WithJustify sets how children are distributed along the main axis.
func WithJustify(j Justify) Option {
	return func(e *Element) {
		e.style.JustifyContent = j
	}
}

// WithAlign sets how children are positioned on the cross axis.
func WithAlign(a Align) Option {
	return func(e *Element) {
		e.style.AlignItems = a
	}
}

// WithGap sets the space between children on the main axis.
func WithGap(cells int) Option {
	return func(e *Element) {
		e.style.Gap = cells
	}
}

// WithFlexWrap sets whether flex items wrap to new lines when they overflow.
func WithFlexWrap(w FlexWrap) Option {
	return func(e *Element) {
		e.style.FlexWrap = w
	}
}

// WithAlignContent sets how flex lines are distributed along the cross axis.
// Only applies when FlexWrap is Wrap or WrapReverse.
func WithAlignContent(a AlignContent) Option {
	return func(e *Element) {
		e.style.AlignContent = a
	}
}

// --- Flex Item Options ---

// WithFlexGrow sets how much this element should grow relative to siblings.
func WithFlexGrow(factor float64) Option {
	return func(e *Element) {
		e.style.FlexGrow = factor
	}
}

// WithFlexShrink sets how much this element should shrink relative to siblings.
func WithFlexShrink(factor float64) Option {
	return func(e *Element) {
		e.style.FlexShrink = factor
	}
}

// WithAlignSelf overrides the parent's AlignItems for this element.
func WithAlignSelf(a Align) Option {
	return func(e *Element) {
		e.style.AlignSelf = &a
	}
}

// --- Spacing Options ---

// WithPadding sets uniform padding on all sides.
func WithPadding(cells int) Option {
	return func(e *Element) {
		e.style.Padding = EdgeAll(cells)
	}
}

// WithPaddingTRBL sets padding using CSS order: Top, Right, Bottom, Left.
func WithPaddingTRBL(top, right, bottom, left int) Option {
	return func(e *Element) {
		e.style.Padding = EdgeTRBL(top, right, bottom, left)
	}
}

// WithMargin sets uniform margin on all sides.
func WithMargin(cells int) Option {
	return func(e *Element) {
		e.style.Margin = EdgeAll(cells)
	}
}

// WithMarginTRBL sets margin using CSS order: Top, Right, Bottom, Left.
func WithMarginTRBL(top, right, bottom, left int) Option {
	return func(e *Element) {
		e.style.Margin = EdgeTRBL(top, right, bottom, left)
	}
}

// --- Visual Options ---

// WithBorder sets the border style (e.g., BorderSingle, BorderRounded).
func WithBorder(style BorderStyle) Option {
	return func(e *Element) {
		e.border = style
	}
}

// WithBorderTitle sets a title string drawn in the top border of the box.
// When WithBorderGradient is also set, the gradient is drawn first and the title
// overwrites the gradient characters on the top border line.
func WithBorderTitle(title string) Option {
	return func(e *Element) {
		e.borderTitle = title
	}
}

// WithBorderStyle sets the color/attributes for the border.
func WithBorderStyle(style Style) Option {
	return func(e *Element) {
		e.borderStyle = style
	}
}

// WithBackground sets the background style.
func WithBackground(style Style) Option {
	return func(e *Element) {
		e.background = &style
	}
}

// --- Text Options ---

// WithText sets the text content.
// Width and Height remain Auto so the flex algorithm uses IntrinsicSize(),
// which correctly accounts for text dimensions, padding, and border.
func WithText(content string) Option {
	return func(e *Element) {
		e.text = content
		e.richText = nil
	}
}

// WithTextStyle sets the style for text content.
// Setting this explicitly prevents inheritance from the parent element.
func WithTextStyle(style Style) Option {
	return func(e *Element) {
		e.textStyle = style
		e.textStyleSet = true
	}
}

// WithTextAlign sets text alignment within the content area.
func WithTextAlign(align TextAlign) Option {
	return func(e *Element) {
		e.textAlign = align
	}
}

// --- Focus Options ---

// WithOnFocus sets the callback for when this element gains focus.
// The handler receives the element as its first parameter (self-inject).
// Implicitly sets focusable = true and tabStop = true.
func WithOnFocus(fn func(*Element)) Option {
	return func(e *Element) {
		e.focusable = true
		e.tabStop = true
		e.onFocus = fn
	}
}

// WithOnBlur sets the callback for when this element loses focus.
// The handler receives the element as its first parameter (self-inject).
// Implicitly sets focusable = true and tabStop = true.
func WithOnBlur(fn func(*Element)) Option {
	return func(e *Element) {
		e.focusable = true
		e.tabStop = true
		e.onBlur = fn
	}
}

// WithFocusable sets whether this element can receive focus and appear in Tab navigation.
func WithFocusable(focusable bool) Option {
	return func(e *Element) {
		e.focusable = focusable
		e.tabStop = focusable
	}
}

// WithAutoFocus sets whether this element should automatically receive focus
// when the element tree is first applied. Only the first autoFocus element
// in tree order takes effect. Implies focusable and tabStop.
func WithAutoFocus(auto bool) Option {
	return func(e *Element) {
		e.autoFocus = auto
		if auto {
			e.focusable = true
			e.tabStop = true
		}
	}
}

// WithTabStop sets whether this element appears in Tab/Shift+Tab navigation.
// Use this to override the default tabStop behavior set by WithFocusable or WithScrollable.
func WithTabStop(tabStop bool) Option {
	return func(e *Element) {
		e.tabStop = tabStop
	}
}

// WithOnActivate sets a callback for when this element is activated (Enter while focused).
// Implicitly sets focusable = true and tabStop = true.
func WithOnActivate(fn func()) Option {
	return func(e *Element) {
		e.focusable = true
		e.tabStop = true
		e.onActivate = fn
	}
}

// --- Scroll Options ---

// WithScrollable enables scrolling in the specified mode.
// Implicitly sets focusable = true so the element can receive scroll events,
// but does NOT set tabStop (scrollable elements are not in the Tab cycle by default).
func WithScrollable(mode ScrollMode) Option {
	return func(e *Element) {
		e.scrollMode = mode
		e.focusable = true
		e.scrollbarStyle = NewStyle().Foreground(BrightBlack)
		e.scrollbarThumbStyle = NewStyle().Foreground(White)
	}
}

// WithScrollOffset sets the initial scroll offset for a scrollable element.
// This is useful in the component model where elements are recreated each render
// and scroll state needs to be preserved via State[int].
// The offset is clamped to valid range during layout.
func WithScrollOffset(x, y int) Option {
	return func(e *Element) {
		e.scrollX = x
		e.scrollY = y
	}
}

// WithScrollbarStyle sets the style for the scrollbar track.
func WithScrollbarStyle(style Style) Option {
	return func(e *Element) {
		e.scrollbarStyle = style
	}
}

// WithScrollbarThumbStyle sets the style for the scrollbar thumb.
func WithScrollbarThumbStyle(style Style) Option {
	return func(e *Element) {
		e.scrollbarThumbStyle = style
	}
}

// WithScrollbarHidden hides the scrollbar on a scrollable element.
// When hidden, the scrollbar is neither drawn nor reserves a column of content width,
// so the viewport uses the full available width. Scrolling still works via keyboard,
// mouse wheel, and programmatic ScrollTo/ScrollBy.
func WithScrollbarHidden(hidden bool) Option {
	return func(e *Element) {
		e.scrollbarHidden = hidden
	}
}

// --- HR Options ---

// WithHR configures an element as a horizontal rule.
// The element renders a horizontal line character across its width.
// Uses ─ (U+2500) by default, or other characters based on border style:
//   - BorderDouble → ═ (U+2550)
//   - BorderThick  → ━ (U+2501)
//
// Sets AlignSelf to Stretch so HR fills container width regardless
// of parent's AlignItems setting.
func WithHR() Option {
	return func(e *Element) {
		e.hr = true
		e.style.Height = Fixed(1)
		stretch := AlignStretch
		e.style.AlignSelf = &stretch // Always stretch to fill width
	}
}

// --- Truncate Options ---

// WithTruncate enables text truncation with ellipsis when text overflows.
func WithTruncate(truncate bool) Option {
	return func(e *Element) {
		e.truncate = truncate
	}
}

// --- Wrap Options ---

// WithWrap sets whether text content should wrap within the element's width.
// Wrapping is enabled by default. Use WithWrap(false) or the "nowrap" class to disable.
func WithWrap(wrap bool) Option {
	return func(e *Element) {
		e.noWrap = !wrap
	}
}

// --- Hidden Options ---

// WithHidden sets whether this element is excluded from layout and rendering.
func WithHidden(hidden bool) Option {
	return func(e *Element) {
		e.hidden = hidden
	}
}

// --- Overflow Options ---

// WithOverflow sets how the element handles content that exceeds its bounds.
func WithOverflow(mode OverflowMode) Option {
	return func(e *Element) {
		e.overflow = mode
	}
}

// --- OnUpdate Hook Options ---

// WithOnUpdate sets a function called before each render.
// Useful for polling channels, updating animations, etc.
func WithOnUpdate(fn func()) Option {
	return func(e *Element) {
		e.onUpdate = fn
	}
}

// --- Gradient Options ---

// WithTextGradient sets a gradient for text (overrides textStyle.Fg).
// The gradient is applied per-character horizontally by default.
func WithTextGradient(g Gradient) Option {
	return func(e *Element) {
		e.textGradient = &g
	}
}

// WithBackgroundGradient sets a gradient for the background.
// The gradient direction determines how it's applied across the element.
func WithBackgroundGradient(g Gradient) Option {
	return func(e *Element) {
		e.bgGradient = &g
	}
}

// WithBorderGradient sets a gradient for the border color.
// The gradient is applied around the perimeter of the border.
func WithBorderGradient(g Gradient) Option {
	return func(e *Element) {
		e.borderGradient = &g
	}
}

// WithOverlay marks the element as an overlay that renders in the overlay pass.
func WithOverlay(overlay bool) Option {
	return func(e *Element) {
		e.overlay = overlay
	}
}

// WithTag sets the element tag for layout dispatch.
// Used by generated code to identify table elements.
func WithTag(tag string) Option {
	return func(e *Element) {
		e.tag = tag
	}
}