1 package tview
2
3 import (
4 "image"
5
6 "github.com/gdamore/tcell/v2"
7 )
8
9 var (
10
11
12
13 DefaultFormFieldWidth = 10
14
15
16
17 DefaultFormFieldHeight = 5
18 )
19
20
21
22 type FormItem interface {
23 Primitive
24
25
26 GetLabel() string
27
28
29 SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
30
31
32
33
34
35 GetFieldWidth() int
36
37
38
39 GetFieldHeight() int
40
41
42
43
44
45
46
47 SetFinishedFunc(handler func(key tcell.Key)) FormItem
48
49
50
51 SetDisabled(disabled bool) FormItem
52 }
53
54
55
56
57
58
59
60 type Form struct {
61 *Box
62
63
64 items []FormItem
65
66
67 buttons []*Button
68
69
70
71 horizontal bool
72
73
74 buttonsAlign int
75
76
77 itemPadding int
78
79
80
81
82 focusedElement int
83
84
85 labelColor tcell.Color
86
87
88 fieldBackgroundColor tcell.Color
89
90
91 fieldTextColor tcell.Color
92
93
94 buttonStyle tcell.Style
95
96
97 buttonActivatedStyle tcell.Style
98
99
100 buttonDisabledStyle tcell.Style
101
102
103
104 lastFinishedKey tcell.Key
105
106
107 cancel func()
108 }
109
110
111 func NewForm() *Form {
112 box := NewBox().SetBorderPadding(1, 1, 1, 1)
113
114 f := &Form{
115 Box: box,
116 itemPadding: 1,
117 labelColor: Styles.SecondaryTextColor,
118 fieldBackgroundColor: Styles.ContrastBackgroundColor,
119 fieldTextColor: Styles.PrimaryTextColor,
120 buttonStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
121 buttonActivatedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),
122 lastFinishedKey: tcell.KeyTab,
123 }
124
125 return f
126 }
127
128
129
130
131 func (f *Form) SetItemPadding(padding int) *Form {
132 f.itemPadding = padding
133 return f
134 }
135
136
137
138
139
140 func (f *Form) SetHorizontal(horizontal bool) *Form {
141 f.horizontal = horizontal
142 return f
143 }
144
145
146 func (f *Form) SetLabelColor(color tcell.Color) *Form {
147 f.labelColor = color
148 return f
149 }
150
151
152 func (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {
153 f.fieldBackgroundColor = color
154 return f
155 }
156
157
158 func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
159 f.fieldTextColor = color
160 return f
161 }
162
163
164
165 func (f *Form) SetButtonsAlign(align int) *Form {
166 f.buttonsAlign = align
167 return f
168 }
169
170
171
172 func (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {
173 f.buttonStyle = f.buttonStyle.Background(color)
174 f.buttonActivatedStyle = f.buttonActivatedStyle.Foreground(color)
175 return f
176 }
177
178
179
180 func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
181 f.buttonStyle = f.buttonStyle.Foreground(color)
182 f.buttonActivatedStyle = f.buttonActivatedStyle.Background(color)
183 return f
184 }
185
186
187 func (f *Form) SetButtonStyle(style tcell.Style) *Form {
188 f.buttonStyle = style
189 return f
190 }
191
192
193 func (f *Form) SetButtonActivatedStyle(style tcell.Style) *Form {
194 f.buttonActivatedStyle = style
195 return f
196 }
197
198
199
200
201 func (f *Form) SetFocus(index int) *Form {
202 if index < 0 {
203 f.focusedElement = 0
204 } else if index >= len(f.items)+len(f.buttons) {
205 f.focusedElement = len(f.items) + len(f.buttons)
206 } else {
207 f.focusedElement = index
208 }
209 return f
210 }
211
212
213
214
215
216
217
218
219
220
221
222 func (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLength int, changed func(text string)) *Form {
223 if fieldHeight == 0 {
224 fieldHeight = DefaultFormFieldHeight
225 }
226 textArea := NewTextArea().
227 SetLabel(label).
228 SetSize(fieldHeight, fieldWidth).
229 SetMaxLength(maxLength)
230 if text != "" {
231 textArea.SetText(text, true)
232 }
233 if changed != nil {
234 textArea.SetChangedFunc(func() {
235 changed(textArea.GetText())
236 })
237 }
238 f.items = append(f.items, textArea)
239 return f
240 }
241
242
243
244
245
246
247
248 func (f *Form) AddTextView(label, text string, fieldWidth, fieldHeight int, dynamicColors, scrollable bool) *Form {
249 if fieldHeight == 0 {
250 fieldHeight = DefaultFormFieldHeight
251 }
252 textArea := NewTextView().
253 SetLabel(label).
254 SetSize(fieldHeight, fieldWidth).
255 SetDynamicColors(dynamicColors).
256 SetScrollable(scrollable).
257 SetText(text)
258 f.items = append(f.items, textArea)
259 return f
260 }
261
262
263
264
265
266
267 func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) *Form {
268 f.items = append(f.items, NewInputField().
269 SetLabel(label).
270 SetText(value).
271 SetFieldWidth(fieldWidth).
272 SetAcceptanceFunc(accept).
273 SetChangedFunc(changed))
274 return f
275 }
276
277
278
279
280
281
282
283 func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) *Form {
284 if mask == 0 {
285 mask = '*'
286 }
287 f.items = append(f.items, NewInputField().
288 SetLabel(label).
289 SetText(value).
290 SetFieldWidth(fieldWidth).
291 SetMaskCharacter(mask).
292 SetChangedFunc(changed))
293 return f
294 }
295
296
297
298
299
300 func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {
301 f.items = append(f.items, NewDropDown().
302 SetLabel(label).
303 SetOptions(options, selected).
304 SetCurrentOption(initialOption))
305 return f
306 }
307
308
309
310
311 func (f *Form) AddCheckbox(label string, checked bool, changed func(checked bool)) *Form {
312 f.items = append(f.items, NewCheckbox().
313 SetLabel(label).
314 SetChecked(checked).
315 SetChangedFunc(changed))
316 return f
317 }
318
319
320
321
322
323
324 func (f *Form) AddImage(label string, image image.Image, width, height, colors int) *Form {
325 f.items = append(f.items, NewImage().
326 SetLabel(label).
327 SetImage(image).
328 SetSize(height, width).
329 SetAlign(AlignTop, AlignLeft).
330 SetColors(colors))
331 return f
332 }
333
334
335
336 func (f *Form) AddButton(label string, selected func()) *Form {
337 f.buttons = append(f.buttons, NewButton(label).SetSelectedFunc(selected))
338 return f
339 }
340
341
342
343
344 func (f *Form) GetButton(index int) *Button {
345 return f.buttons[index]
346 }
347
348
349
350 func (f *Form) RemoveButton(index int) *Form {
351 f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
352 return f
353 }
354
355
356 func (f *Form) GetButtonCount() int {
357 return len(f.buttons)
358 }
359
360
361
362
363 func (f *Form) GetButtonIndex(label string) int {
364 for index, button := range f.buttons {
365 if button.GetLabel() == label {
366 return index
367 }
368 }
369 return -1
370 }
371
372
373
374 func (f *Form) Clear(includeButtons bool) *Form {
375 f.items = nil
376 if includeButtons {
377 f.ClearButtons()
378 }
379 f.focusedElement = 0
380 return f
381 }
382
383
384 func (f *Form) ClearButtons() *Form {
385 f.buttons = nil
386 return f
387 }
388
389
390
391
392
393
394
395
396
397
398
399 func (f *Form) AddFormItem(item FormItem) *Form {
400 f.items = append(f.items, item)
401 return f
402 }
403
404
405
406 func (f *Form) GetFormItemCount() int {
407 return len(f.items)
408 }
409
410
411
412
413 func (f *Form) GetFormItem(index int) FormItem {
414 return f.items[index]
415 }
416
417
418
419
420 func (f *Form) RemoveFormItem(index int) *Form {
421 f.items = append(f.items[:index], f.items[index+1:]...)
422 return f
423 }
424
425
426
427
428 func (f *Form) GetFormItemByLabel(label string) FormItem {
429 for _, item := range f.items {
430 if item.GetLabel() == label {
431 return item
432 }
433 }
434 return nil
435 }
436
437
438
439
440 func (f *Form) GetFormItemIndex(label string) int {
441 for index, item := range f.items {
442 if item.GetLabel() == label {
443 return index
444 }
445 }
446 return -1
447 }
448
449
450
451 func (f *Form) GetFocusedItemIndex() (formItem, button int) {
452 index := f.focusIndex()
453 if index < 0 {
454 return -1, -1
455 }
456 if index < len(f.items) {
457 return index, -1
458 }
459 return -1, index - len(f.items)
460 }
461
462
463
464 func (f *Form) SetCancelFunc(callback func()) *Form {
465 f.cancel = callback
466 return f
467 }
468
469
470 func (f *Form) Draw(screen tcell.Screen) {
471 f.Box.DrawForSubclass(screen, f)
472
473
474 if index := f.focusIndex(); index >= 0 {
475 f.focusedElement = index
476 }
477
478
479 x, y, width, height := f.GetInnerRect()
480 topLimit := y
481 bottomLimit := y + height
482 rightLimit := x + width
483 startX := x
484
485
486 var maxLabelWidth int
487 for _, item := range f.items {
488 labelWidth := TaggedStringWidth(item.GetLabel())
489 if labelWidth > maxLabelWidth {
490 maxLabelWidth = labelWidth
491 }
492 }
493 maxLabelWidth++
494
495
496 type position struct{ x, y, width, height int }
497 positions := make([]position, len(f.items)+len(f.buttons))
498 var (
499 focusedPosition position
500 lineHeight = 1
501 )
502 for index, item := range f.items {
503
504 labelWidth := TaggedStringWidth(item.GetLabel())
505 var itemWidth int
506 if f.horizontal {
507 fieldWidth := item.GetFieldWidth()
508 if fieldWidth <= 0 {
509 fieldWidth = DefaultFormFieldWidth
510 }
511 labelWidth++
512 itemWidth = labelWidth + fieldWidth
513 } else {
514
515 labelWidth = maxLabelWidth
516 itemWidth = width
517 }
518 itemHeight := item.GetFieldHeight()
519 if itemHeight <= 0 {
520 itemHeight = DefaultFormFieldHeight
521 }
522
523
524 if f.horizontal && x+labelWidth+1 >= rightLimit {
525 x = startX
526 y += lineHeight + 1
527 lineHeight = itemHeight
528 }
529
530
531 if itemHeight > lineHeight {
532 lineHeight = itemHeight
533 }
534
535
536 if x+itemWidth >= rightLimit {
537 itemWidth = rightLimit - x
538 }
539 item.SetFormAttributes(
540 labelWidth,
541 f.labelColor,
542 f.backgroundColor,
543 f.fieldTextColor,
544 f.fieldBackgroundColor,
545 )
546
547
548 positions[index].x = x
549 positions[index].y = y
550 positions[index].width = itemWidth
551 positions[index].height = itemHeight
552 if item.HasFocus() {
553 focusedPosition = positions[index]
554 }
555
556
557 if f.horizontal {
558 x += itemWidth + f.itemPadding
559 } else {
560 y += itemHeight + f.itemPadding
561 }
562 }
563
564
565 buttonWidths := make([]int, len(f.buttons))
566 buttonsWidth := 0
567 for index, button := range f.buttons {
568 w := TaggedStringWidth(button.GetLabel()) + 4
569 buttonWidths[index] = w
570 buttonsWidth += w + 1
571 }
572 buttonsWidth--
573
574
575 if !f.horizontal && x+buttonsWidth < rightLimit {
576 if f.buttonsAlign == AlignRight {
577 x = rightLimit - buttonsWidth
578 } else if f.buttonsAlign == AlignCenter {
579 x = (x + rightLimit - buttonsWidth) / 2
580 }
581
582
583 if f.itemPadding == 0 {
584 y++
585 }
586 }
587
588
589 for index, button := range f.buttons {
590 space := rightLimit - x
591 buttonWidth := buttonWidths[index]
592 if f.horizontal {
593 if space < buttonWidth-4 {
594 x = startX
595 y += lineHeight + 1
596 space = width
597 lineHeight = 1
598 }
599 } else {
600 if space < 1 {
601 break
602 }
603 }
604 if buttonWidth > space {
605 buttonWidth = space
606 }
607 button.SetStyle(f.buttonStyle).
608 SetActivatedStyle(f.buttonActivatedStyle)
609
610 buttonIndex := index + len(f.items)
611 positions[buttonIndex].x = x
612 positions[buttonIndex].y = y
613 positions[buttonIndex].width = buttonWidth
614 positions[buttonIndex].height = 1
615
616 if button.HasFocus() {
617 focusedPosition = positions[buttonIndex]
618 }
619
620 x += buttonWidth + 1
621 }
622
623
624 var offset int
625 if focusedPosition.y+focusedPosition.height > bottomLimit {
626 offset = focusedPosition.y + focusedPosition.height - bottomLimit
627 if focusedPosition.y-offset < topLimit {
628 offset = focusedPosition.y - topLimit
629 }
630 }
631
632
633 for index, item := range f.items {
634
635 y := positions[index].y - offset
636 height := positions[index].height
637 item.SetRect(positions[index].x, y, positions[index].width, height)
638
639
640 if y+height <= topLimit || y >= bottomLimit {
641 continue
642 }
643
644
645 if item.HasFocus() {
646 defer item.Draw(screen)
647 } else {
648 item.Draw(screen)
649 }
650 }
651
652
653 for index, button := range f.buttons {
654
655 buttonIndex := index + len(f.items)
656 y := positions[buttonIndex].y - offset
657 height := positions[buttonIndex].height
658 button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
659
660
661 if y+height <= topLimit || y >= bottomLimit {
662 continue
663 }
664
665
666 button.Draw(screen)
667 }
668 }
669
670
671 func (f *Form) Focus(delegate func(p Primitive)) {
672
673 if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
674 f.focusedElement = 0
675 }
676 var handler func(key tcell.Key)
677 handler = func(key tcell.Key) {
678 if key >= 0 {
679 f.lastFinishedKey = key
680 }
681 switch key {
682 case tcell.KeyTab, tcell.KeyEnter:
683 f.focusedElement++
684 f.Focus(delegate)
685 case tcell.KeyBacktab:
686 f.focusedElement--
687 if f.focusedElement < 0 {
688 f.focusedElement = len(f.items) + len(f.buttons) - 1
689 }
690 f.Focus(delegate)
691 case tcell.KeyEscape:
692 if f.cancel != nil {
693 f.cancel()
694 } else {
695 f.focusedElement = 0
696 f.Focus(delegate)
697 }
698 default:
699 if key < 0 && f.lastFinishedKey >= 0 {
700
701 handler(f.lastFinishedKey)
702 }
703 }
704 }
705
706
707 var itemFocused bool
708 f.hasFocus = false
709
710
711 for index, button := range f.buttons {
712 button.SetExitFunc(handler)
713 if f.focusedElement == index+len(f.items) {
714 if button.IsDisabled() {
715 f.focusedElement++
716 if f.focusedElement >= len(f.items)+len(f.buttons) {
717 f.focusedElement = 0
718 }
719 continue
720 }
721
722 itemFocused = true
723 func(b *Button) {
724 defer delegate(b)
725 }(button)
726 }
727 }
728 for index, item := range f.items {
729 item.SetFinishedFunc(handler)
730 if f.focusedElement == index {
731 itemFocused = true
732 func(i FormItem) {
733 defer delegate(i)
734 }(item)
735 }
736 }
737
738
739 if !itemFocused {
740 f.Box.Focus(delegate)
741 }
742 }
743
744
745 func (f *Form) HasFocus() bool {
746 if f.focusIndex() >= 0 {
747 return true
748 }
749 return f.Box.HasFocus()
750 }
751
752
753
754
755 func (f *Form) focusIndex() int {
756 for index, item := range f.items {
757 if item.HasFocus() {
758 return index
759 }
760 }
761 for index, button := range f.buttons {
762 if button.HasFocus() {
763 return len(f.items) + index
764 }
765 }
766 return -1
767 }
768
769
770 func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
771 return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
772
773 defer func() {
774 if consumed {
775 index := f.focusIndex()
776 if index >= 0 {
777 f.focusedElement = index
778 }
779 }
780 }()
781
782
783 for _, item := range f.items {
784
785
786 if _, ok := item.(*TextView); ok && action == MouseLeftDown {
787 continue
788 }
789
790 consumed, capture = item.MouseHandler()(action, event, setFocus)
791 if consumed {
792 return
793 }
794 }
795 for _, button := range f.buttons {
796 consumed, capture = button.MouseHandler()(action, event, setFocus)
797 if consumed {
798 return
799 }
800 }
801
802
803
804 if action == MouseLeftDown && f.InRect(event.Position()) {
805 f.Focus(setFocus)
806 consumed = true
807 }
808
809 return
810 })
811 }
812
813
814 func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
815 return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
816 for _, item := range f.items {
817 if item != nil && item.HasFocus() {
818 if handler := item.InputHandler(); handler != nil {
819 handler(event, setFocus)
820 return
821 }
822 }
823 }
824
825 for _, button := range f.buttons {
826 if button.HasFocus() {
827 if handler := button.InputHandler(); handler != nil {
828 handler(event, setFocus)
829 return
830 }
831 }
832 }
833 })
834 }
835
View as plain text