1 package cview
2
3 import (
4 "reflect"
5 "sync"
6
7 "github.com/gdamore/tcell/v2"
8 )
9
10
11
12
13 var DefaultFormFieldWidth = 10
14
15
16 type FormItemAttributes struct {
17
18
19 LabelWidth int
20
21 BackgroundColor tcell.Color
22 LabelColor tcell.Color
23 LabelColorFocused tcell.Color
24 FieldBackgroundColor tcell.Color
25 FieldBackgroundColorFocused tcell.Color
26 FieldTextColor tcell.Color
27 FieldTextColorFocused tcell.Color
28
29 FinishedFunc func(key tcell.Key)
30 }
31
32
33
34 type FormItem interface {
35 Primitive
36
37
38 GetLabel() string
39
40
41
42 SetLabelWidth(int)
43
44
45 SetLabelColor(tcell.Color)
46
47
48 SetLabelColorFocused(tcell.Color)
49
50
51
52
53
54 GetFieldWidth() int
55
56
57 GetFieldHeight() int
58
59
60 SetFieldTextColor(tcell.Color)
61
62
63 SetFieldTextColorFocused(tcell.Color)
64
65
66 SetFieldBackgroundColor(tcell.Color)
67
68
69 SetFieldBackgroundColorFocused(tcell.Color)
70
71
72 SetBackgroundColor(tcell.Color)
73
74
75 SetFinishedFunc(func(key tcell.Key))
76 }
77
78
79
80
81
82 type Form struct {
83 *Box
84
85
86 items []FormItem
87
88
89 buttons []*Button
90
91
92
93 horizontal bool
94
95
96 buttonsAlign int
97
98
99 itemPadding int
100
101
102
103
104 focusedElement int
105
106
107 wrapAround bool
108
109
110 labelColor tcell.Color
111
112
113 labelColorFocused tcell.Color
114
115
116 fieldBackgroundColor tcell.Color
117
118
119 fieldBackgroundColorFocused tcell.Color
120
121
122 fieldTextColor tcell.Color
123
124
125 fieldTextColorFocused tcell.Color
126
127
128 buttonBackgroundColor tcell.Color
129
130
131 buttonBackgroundColorFocused tcell.Color
132
133
134 buttonTextColor tcell.Color
135
136
137 buttonTextColorFocused tcell.Color
138
139
140 cancel func()
141
142 sync.RWMutex
143 }
144
145
146 func NewForm() *Form {
147 box := NewBox()
148 box.SetPadding(1, 1, 1, 1)
149
150 f := &Form{
151 Box: box,
152 itemPadding: 1,
153 labelColor: Styles.SecondaryTextColor,
154 fieldBackgroundColor: Styles.MoreContrastBackgroundColor,
155 fieldBackgroundColorFocused: Styles.ContrastBackgroundColor,
156 fieldTextColor: Styles.PrimaryTextColor,
157 fieldTextColorFocused: Styles.PrimaryTextColor,
158 buttonBackgroundColor: Styles.MoreContrastBackgroundColor,
159 buttonBackgroundColorFocused: Styles.ContrastBackgroundColor,
160 buttonTextColor: Styles.PrimaryTextColor,
161 buttonTextColorFocused: Styles.PrimaryTextColor,
162 labelColorFocused: ColorUnset,
163 }
164
165 f.focus = f
166 return f
167 }
168
169
170
171
172 func (f *Form) SetItemPadding(padding int) {
173 f.Lock()
174 defer f.Unlock()
175
176 f.itemPadding = padding
177 }
178
179
180
181
182
183 func (f *Form) SetHorizontal(horizontal bool) {
184 f.Lock()
185 defer f.Unlock()
186
187 f.horizontal = horizontal
188 }
189
190
191 func (f *Form) SetLabelColor(color tcell.Color) {
192 f.Lock()
193 defer f.Unlock()
194
195 f.labelColor = color
196 }
197
198
199 func (f *Form) SetLabelColorFocused(color tcell.Color) {
200 f.Lock()
201 defer f.Unlock()
202
203 f.labelColorFocused = color
204 }
205
206
207 func (f *Form) SetFieldBackgroundColor(color tcell.Color) {
208 f.Lock()
209 defer f.Unlock()
210
211 f.fieldBackgroundColor = color
212 }
213
214
215 func (f *Form) SetFieldBackgroundColorFocused(color tcell.Color) {
216 f.Lock()
217 defer f.Unlock()
218
219 f.fieldBackgroundColorFocused = color
220 }
221
222
223 func (f *Form) SetFieldTextColor(color tcell.Color) {
224 f.Lock()
225 defer f.Unlock()
226
227 f.fieldTextColor = color
228 }
229
230
231 func (f *Form) SetFieldTextColorFocused(color tcell.Color) {
232 f.Lock()
233 defer f.Unlock()
234
235 f.fieldTextColorFocused = color
236 }
237
238
239
240 func (f *Form) SetButtonsAlign(align int) {
241 f.Lock()
242 defer f.Unlock()
243
244 f.buttonsAlign = align
245 }
246
247
248 func (f *Form) SetButtonBackgroundColor(color tcell.Color) {
249 f.Lock()
250 defer f.Unlock()
251
252 f.buttonBackgroundColor = color
253 }
254
255
256 func (f *Form) SetButtonBackgroundColorFocused(color tcell.Color) {
257 f.Lock()
258 defer f.Unlock()
259
260 f.buttonBackgroundColorFocused = color
261 }
262
263
264 func (f *Form) SetButtonTextColor(color tcell.Color) {
265 f.Lock()
266 defer f.Unlock()
267
268 f.buttonTextColor = color
269 }
270
271
272 func (f *Form) SetButtonTextColorFocused(color tcell.Color) {
273 f.Lock()
274 defer f.Unlock()
275
276 f.buttonTextColorFocused = color
277 }
278
279
280
281
282 func (f *Form) SetFocus(index int) {
283 f.Lock()
284 defer f.Unlock()
285
286 if index < 0 {
287 f.focusedElement = 0
288 } else if index >= len(f.items)+len(f.buttons) {
289 f.focusedElement = len(f.items) + len(f.buttons)
290 } else {
291 f.focusedElement = index
292 }
293 }
294
295
296
297
298
299
300 func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) {
301 f.Lock()
302 defer f.Unlock()
303
304 inputField := NewInputField()
305 inputField.SetLabel(label)
306 inputField.SetText(value)
307 inputField.SetFieldWidth(fieldWidth)
308 inputField.SetAcceptanceFunc(accept)
309 inputField.SetChangedFunc(changed)
310
311 f.items = append(f.items, inputField)
312 }
313
314
315
316
317
318
319
320 func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) {
321 f.Lock()
322 defer f.Unlock()
323
324 if mask == 0 {
325 mask = '*'
326 }
327
328 passwordField := NewInputField()
329 passwordField.SetLabel(label)
330 passwordField.SetText(value)
331 passwordField.SetFieldWidth(fieldWidth)
332 passwordField.SetMaskCharacter(mask)
333 passwordField.SetChangedFunc(changed)
334
335 f.items = append(f.items, passwordField)
336 }
337
338
339
340
341
342 func (f *Form) AddDropDownSimple(label string, initialOption int, selected func(index int, option *DropDownOption), options ...string) {
343 f.Lock()
344 defer f.Unlock()
345
346 dd := NewDropDown()
347 dd.SetLabel(label)
348 dd.SetOptionsSimple(selected, options...)
349 dd.SetCurrentOption(initialOption)
350
351 f.items = append(f.items, dd)
352 }
353
354
355
356
357
358 func (f *Form) AddDropDown(label string, initialOption int, selected func(index int, option *DropDownOption), options []*DropDownOption) {
359 f.Lock()
360 defer f.Unlock()
361
362 dd := NewDropDown()
363 dd.SetLabel(label)
364 dd.SetOptions(selected, options...)
365 dd.SetCurrentOption(initialOption)
366
367 f.items = append(f.items, dd)
368 }
369
370
371
372
373 func (f *Form) AddCheckBox(label string, message string, checked bool, changed func(checked bool)) {
374 f.Lock()
375 defer f.Unlock()
376
377 c := NewCheckBox()
378 c.SetLabel(label)
379 c.SetMessage(message)
380 c.SetChecked(checked)
381 c.SetChangedFunc(changed)
382
383 f.items = append(f.items, c)
384 }
385
386
387
388
389
390 func (f *Form) AddSlider(label string, current, max, increment int, changed func(value int)) {
391 f.Lock()
392 defer f.Unlock()
393
394 s := NewSlider()
395 s.SetLabel(label)
396 s.SetMax(max)
397 s.SetProgress(current)
398 s.SetIncrement(increment)
399 s.SetChangedFunc(changed)
400
401 f.items = append(f.items, s)
402 }
403
404
405
406 func (f *Form) AddButton(label string, selected func()) {
407 f.Lock()
408 defer f.Unlock()
409
410 button := NewButton(label)
411 button.SetSelectedFunc(selected)
412 f.buttons = append(f.buttons, button)
413 }
414
415
416
417
418 func (f *Form) GetButton(index int) *Button {
419 f.RLock()
420 defer f.RUnlock()
421
422 return f.buttons[index]
423 }
424
425
426
427 func (f *Form) RemoveButton(index int) {
428 f.Lock()
429 defer f.Unlock()
430
431 f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
432 }
433
434
435 func (f *Form) GetButtonCount() int {
436 f.RLock()
437 defer f.RUnlock()
438
439 return len(f.buttons)
440 }
441
442
443
444
445 func (f *Form) GetButtonIndex(label string) int {
446 f.RLock()
447 defer f.RUnlock()
448
449 for index, button := range f.buttons {
450 if button.GetLabel() == label {
451 return index
452 }
453 }
454 return -1
455 }
456
457
458
459 func (f *Form) Clear(includeButtons bool) {
460 f.Lock()
461 defer f.Unlock()
462
463 f.items = nil
464 if includeButtons {
465 f.buttons = nil
466 }
467 f.focusedElement = 0
468 }
469
470
471 func (f *Form) ClearButtons() {
472 f.Lock()
473 defer f.Unlock()
474
475 f.buttons = nil
476 }
477
478
479
480
481
482
483
484
485
486
487
488 func (f *Form) AddFormItem(item FormItem) {
489 f.Lock()
490 defer f.Unlock()
491
492 if reflect.ValueOf(item).IsNil() {
493 panic("Invalid FormItem")
494 }
495
496 f.items = append(f.items, item)
497 }
498
499
500
501 func (f *Form) GetFormItemCount() int {
502 f.RLock()
503 defer f.RUnlock()
504
505 return len(f.items)
506 }
507
508
509 func (f *Form) IndexOfFormItem(item FormItem) int {
510 f.l.RLock()
511 defer f.l.RUnlock()
512 for index, formItem := range f.items {
513 if item == formItem {
514 return index
515 }
516 }
517 return -1
518 }
519
520
521
522
523 func (f *Form) GetFormItem(index int) FormItem {
524 f.RLock()
525 defer f.RUnlock()
526 if index > len(f.items)-1 || index < 0 {
527 return nil
528 }
529 return f.items[index]
530 }
531
532
533
534
535 func (f *Form) RemoveFormItem(index int) {
536 f.Lock()
537 defer f.Unlock()
538
539 f.items = append(f.items[:index], f.items[index+1:]...)
540 }
541
542
543
544
545 func (f *Form) GetFormItemByLabel(label string) FormItem {
546 f.RLock()
547 defer f.RUnlock()
548
549 for _, item := range f.items {
550 if item.GetLabel() == label {
551 return item
552 }
553 }
554 return nil
555 }
556
557
558
559
560 func (f *Form) GetFormItemIndex(label string) int {
561 f.RLock()
562 defer f.RUnlock()
563
564 for index, item := range f.items {
565 if item.GetLabel() == label {
566 return index
567 }
568 }
569 return -1
570 }
571
572
573
574 func (f *Form) GetFocusedItemIndex() (formItem, button int) {
575 f.RLock()
576 defer f.RUnlock()
577
578 index := f.focusIndex()
579 if index < 0 {
580 return -1, -1
581 }
582 if index < len(f.items) {
583 return index, -1
584 }
585 return -1, index - len(f.items)
586 }
587
588
589
590
591
592
593 func (f *Form) SetWrapAround(wrapAround bool) {
594 f.Lock()
595 defer f.Unlock()
596
597 f.wrapAround = wrapAround
598 }
599
600
601
602 func (f *Form) SetCancelFunc(callback func()) {
603 f.Lock()
604 defer f.Unlock()
605
606 f.cancel = callback
607 }
608
609
610 func (f *Form) GetAttributes() *FormItemAttributes {
611 f.Lock()
612 defer f.Unlock()
613
614 return f.getAttributes()
615 }
616
617 func (f *Form) getAttributes() *FormItemAttributes {
618 attrs := &FormItemAttributes{
619 BackgroundColor: f.backgroundColor,
620 LabelColor: f.labelColor,
621 FieldBackgroundColor: f.fieldBackgroundColor,
622 FieldTextColor: f.fieldTextColor,
623 }
624 if f.labelColorFocused == ColorUnset {
625 attrs.LabelColorFocused = f.labelColor
626 } else {
627 attrs.LabelColorFocused = f.labelColorFocused
628 }
629 if f.fieldBackgroundColorFocused == ColorUnset {
630 attrs.FieldBackgroundColorFocused = f.fieldTextColor
631 } else {
632 attrs.FieldBackgroundColorFocused = f.fieldBackgroundColorFocused
633 }
634 if f.fieldTextColorFocused == ColorUnset {
635 attrs.FieldTextColorFocused = f.fieldBackgroundColor
636 } else {
637 attrs.FieldTextColorFocused = f.fieldTextColorFocused
638 }
639 return attrs
640 }
641
642
643 func (f *Form) Draw(screen tcell.Screen) {
644 if !f.GetVisible() {
645 return
646 }
647
648 f.Box.Draw(screen)
649
650 f.Lock()
651 defer f.Unlock()
652
653
654 if index := f.focusIndex(); index >= 0 {
655 f.focusedElement = index
656 }
657
658
659 x, y, width, height := f.GetInnerRect()
660 topLimit := y
661 bottomLimit := y + height
662 rightLimit := x + width
663 startX := x
664
665
666 var maxLabelWidth int
667 for _, item := range f.items {
668 labelWidth := TaggedStringWidth(item.GetLabel())
669 if labelWidth > maxLabelWidth {
670 maxLabelWidth = labelWidth
671 }
672 }
673 maxLabelWidth++
674
675
676 positions := make([]struct{ x, y, width, height int }, len(f.items)+len(f.buttons))
677 var focusedPosition struct{ x, y, width, height int }
678 for index, item := range f.items {
679 if !item.GetVisible() {
680 continue
681 }
682
683
684 labelWidth := TaggedStringWidth(item.GetLabel())
685 var itemWidth int
686 if f.horizontal {
687 fieldWidth := item.GetFieldWidth()
688 if fieldWidth == 0 {
689 fieldWidth = DefaultFormFieldWidth
690 }
691 labelWidth++
692 itemWidth = labelWidth + fieldWidth
693 } else {
694
695 labelWidth = maxLabelWidth
696 itemWidth = width
697 }
698
699
700 if f.horizontal && x+labelWidth+1 >= rightLimit {
701 x = startX
702 y += 2
703 }
704
705
706 if x+itemWidth >= rightLimit {
707 itemWidth = rightLimit - x
708 }
709
710 attributes := f.getAttributes()
711 attributes.LabelWidth = labelWidth
712 setFormItemAttributes(item, attributes)
713
714
715 positions[index].x = x
716 positions[index].y = y
717 positions[index].width = itemWidth
718 positions[index].height = 1
719 if item.GetFocusable().HasFocus() {
720 focusedPosition = positions[index]
721 }
722
723
724 if f.horizontal {
725 x += itemWidth + f.itemPadding
726 } else {
727 y += item.GetFieldHeight() + f.itemPadding
728 }
729 }
730
731
732 buttonWidths := make([]int, len(f.buttons))
733 buttonsWidth := 0
734 for index, button := range f.buttons {
735 w := TaggedStringWidth(button.GetLabel()) + 4
736 buttonWidths[index] = w
737 buttonsWidth += w + 1
738 }
739 buttonsWidth--
740
741
742 if !f.horizontal && x+buttonsWidth < rightLimit {
743 if f.buttonsAlign == AlignRight {
744 x = rightLimit - buttonsWidth
745 } else if f.buttonsAlign == AlignCenter {
746 x = (x + rightLimit - buttonsWidth) / 2
747 }
748
749
750 if f.itemPadding == 0 {
751 y++
752 }
753 }
754
755
756 for index, button := range f.buttons {
757 if !button.GetVisible() {
758 continue
759 }
760
761 space := rightLimit - x
762 buttonWidth := buttonWidths[index]
763 if f.horizontal {
764 if space < buttonWidth-4 {
765 x = startX
766 y += 2
767 space = width
768 }
769 } else {
770 if space < 1 {
771 break
772 }
773 }
774 if buttonWidth > space {
775 buttonWidth = space
776 }
777 button.SetLabelColor(f.buttonTextColor)
778 button.SetLabelColorFocused(f.buttonTextColorFocused)
779 button.SetBackgroundColorFocused(f.buttonBackgroundColorFocused)
780 button.SetBackgroundColor(f.buttonBackgroundColor)
781
782 buttonIndex := index + len(f.items)
783 positions[buttonIndex].x = x
784 positions[buttonIndex].y = y
785 positions[buttonIndex].width = buttonWidth
786 positions[buttonIndex].height = 1
787
788 if button.HasFocus() {
789 focusedPosition = positions[buttonIndex]
790 }
791
792 x += buttonWidth + 1
793 }
794
795
796 var offset int
797 if focusedPosition.y+focusedPosition.height > bottomLimit {
798 offset = focusedPosition.y + focusedPosition.height - bottomLimit
799 if focusedPosition.y-offset < topLimit {
800 offset = focusedPosition.y - topLimit
801 }
802 }
803
804
805 for index, item := range f.items {
806 if !item.GetVisible() {
807 continue
808 }
809
810
811 y := positions[index].y - offset
812 height := positions[index].height
813 item.SetRect(positions[index].x, y, positions[index].width, height)
814
815
816 if y+height <= topLimit || y >= bottomLimit {
817 continue
818 }
819
820
821 if item.GetFocusable().HasFocus() {
822 defer item.Draw(screen)
823 } else {
824 item.Draw(screen)
825 }
826 }
827
828
829 for index, button := range f.buttons {
830 if !button.GetVisible() {
831 continue
832 }
833
834
835 buttonIndex := index + len(f.items)
836 y := positions[buttonIndex].y - offset
837 height := positions[buttonIndex].height
838 button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
839
840
841 if y+height <= topLimit || y >= bottomLimit {
842 continue
843 }
844
845
846 button.Draw(screen)
847 }
848 }
849
850 func (f *Form) updateFocusedElement(decreasing bool) {
851 li := len(f.items)
852 l := len(f.items) + len(f.buttons)
853 for i := 0; i < l; i++ {
854 if f.focusedElement < 0 {
855 if f.wrapAround {
856 f.focusedElement = l - 1
857 } else {
858 f.focusedElement = 0
859 }
860 } else if f.focusedElement >= l {
861 if f.wrapAround {
862 f.focusedElement = 0
863 } else {
864 f.focusedElement = l - 1
865 }
866 }
867
868 if f.focusedElement < li {
869 item := f.items[f.focusedElement]
870 if item.GetVisible() {
871 break
872 }
873 } else {
874 button := f.buttons[f.focusedElement-li]
875 if button.GetVisible() {
876 break
877 }
878 }
879
880 if decreasing {
881 f.focusedElement--
882 } else {
883 f.focusedElement++
884 }
885 }
886
887 }
888
889 func (f *Form) formItemInputHandler(delegate func(p Primitive)) func(key tcell.Key) {
890 return func(key tcell.Key) {
891 f.Lock()
892
893 switch key {
894 case tcell.KeyTab, tcell.KeyEnter:
895 f.focusedElement++
896 f.updateFocusedElement(false)
897 f.Unlock()
898 f.Focus(delegate)
899 f.Lock()
900 case tcell.KeyBacktab:
901 f.focusedElement--
902 f.updateFocusedElement(true)
903 f.Unlock()
904 f.Focus(delegate)
905 f.Lock()
906 case tcell.KeyEscape:
907 if f.cancel != nil {
908 f.Unlock()
909 f.cancel()
910 f.Lock()
911 } else {
912 f.focusedElement = 0
913 f.updateFocusedElement(true)
914 f.Unlock()
915 f.Focus(delegate)
916 f.Lock()
917 }
918 }
919
920 f.Unlock()
921 }
922 }
923
924
925 func (f *Form) Focus(delegate func(p Primitive)) {
926 f.Lock()
927 if len(f.items)+len(f.buttons) == 0 {
928 f.hasFocus = true
929 f.Unlock()
930 return
931 }
932 f.hasFocus = false
933
934
935 if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
936 f.focusedElement = 0
937 }
938
939 if f.focusedElement < len(f.items) {
940
941 item := f.items[f.focusedElement]
942
943 attributes := f.getAttributes()
944 attributes.FinishedFunc = f.formItemInputHandler(delegate)
945
946 f.Unlock()
947
948 setFormItemAttributes(item, attributes)
949 delegate(item)
950 } else {
951
952 button := f.buttons[f.focusedElement-len(f.items)]
953 button.SetBlurFunc(f.formItemInputHandler(delegate))
954
955 f.Unlock()
956
957 delegate(button)
958 }
959 }
960
961
962 func (f *Form) HasFocus() bool {
963 f.Lock()
964 defer f.Unlock()
965
966 if f.hasFocus {
967 return true
968 }
969 return f.focusIndex() >= 0
970 }
971
972
973
974
975 func (f *Form) focusIndex() int {
976 for index, item := range f.items {
977 if item.GetVisible() && item.GetFocusable().HasFocus() {
978 return index
979 }
980 }
981 for index, button := range f.buttons {
982 if button.GetVisible() && button.focus.HasFocus() {
983 return len(f.items) + index
984 }
985 }
986 return -1
987 }
988
989
990 func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
991 return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
992 if !f.InRect(event.Position()) {
993 return false, nil
994 }
995
996
997 for _, item := range f.items {
998 consumed, capture = item.MouseHandler()(action, event, setFocus)
999 if consumed {
1000 return
1001 }
1002 }
1003 for _, button := range f.buttons {
1004 consumed, capture = button.MouseHandler()(action, event, setFocus)
1005 if consumed {
1006 return
1007 }
1008 }
1009
1010
1011
1012 if action == MouseLeftClick {
1013 if f.focusedElement < len(f.items) {
1014 setFocus(f.items[f.focusedElement])
1015 } else if f.focusedElement < len(f.items)+len(f.buttons) {
1016 setFocus(f.buttons[f.focusedElement-len(f.items)])
1017 }
1018 consumed = true
1019 }
1020
1021 return
1022 })
1023 }
1024
1025 func setFormItemAttributes(item FormItem, attrs *FormItemAttributes) {
1026 item.SetLabelWidth(attrs.LabelWidth)
1027 item.SetBackgroundColor(attrs.BackgroundColor)
1028 item.SetLabelColor(attrs.LabelColor)
1029 item.SetLabelColorFocused(attrs.LabelColorFocused)
1030 item.SetFieldTextColor(attrs.FieldTextColor)
1031 item.SetFieldTextColorFocused(attrs.FieldTextColorFocused)
1032 item.SetFieldBackgroundColor(attrs.FieldBackgroundColor)
1033 item.SetFieldBackgroundColorFocused(attrs.FieldBackgroundColorFocused)
1034
1035 if attrs.FinishedFunc != nil {
1036 item.SetFinishedFunc(attrs.FinishedFunc)
1037 }
1038 }
1039
View as plain text