1 package cview
2
3 import (
4 "reflect"
5 "sync"
6
7 "github.com/gdamore/tcell/v3"
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 switch f.buttonsAlign {
744 case AlignRight:
745 x = rightLimit - buttonsWidth
746 case AlignCenter:
747 x = (x + rightLimit - buttonsWidth) / 2
748 }
749
750
751 if f.itemPadding == 0 {
752 y++
753 }
754 }
755
756
757 for index, button := range f.buttons {
758 if !button.GetVisible() {
759 continue
760 }
761
762 space := rightLimit - x
763 buttonWidth := buttonWidths[index]
764 if f.horizontal {
765 if space < buttonWidth-4 {
766 x = startX
767 y += 2
768 space = width
769 }
770 } else {
771 if space < 1 {
772 break
773 }
774 }
775 if buttonWidth > space {
776 buttonWidth = space
777 }
778 button.SetLabelColor(f.buttonTextColor)
779 button.SetLabelColorFocused(f.buttonTextColorFocused)
780 button.SetBackgroundColorFocused(f.buttonBackgroundColorFocused)
781 button.SetBackgroundColor(f.buttonBackgroundColor)
782
783 buttonIndex := index + len(f.items)
784 positions[buttonIndex].x = x
785 positions[buttonIndex].y = y
786 positions[buttonIndex].width = buttonWidth
787 positions[buttonIndex].height = 1
788
789 if button.HasFocus() {
790 focusedPosition = positions[buttonIndex]
791 }
792
793 x += buttonWidth + 1
794 }
795
796
797 var offset int
798 if focusedPosition.y+focusedPosition.height > bottomLimit {
799 offset = focusedPosition.y + focusedPosition.height - bottomLimit
800 if focusedPosition.y-offset < topLimit {
801 offset = focusedPosition.y - topLimit
802 }
803 }
804
805
806 for index, item := range f.items {
807 if !item.GetVisible() {
808 continue
809 }
810
811
812 y := positions[index].y - offset
813 height := positions[index].height
814 item.SetRect(positions[index].x, y, positions[index].width, height)
815
816
817 if y+height <= topLimit || y >= bottomLimit {
818 continue
819 }
820
821
822 if item.GetFocusable().HasFocus() {
823 defer item.Draw(screen)
824 } else {
825 item.Draw(screen)
826 }
827 }
828
829
830 for index, button := range f.buttons {
831 if !button.GetVisible() {
832 continue
833 }
834
835
836 buttonIndex := index + len(f.items)
837 y := positions[buttonIndex].y - offset
838 height := positions[buttonIndex].height
839 button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
840
841
842 if y+height <= topLimit || y >= bottomLimit {
843 continue
844 }
845
846
847 button.Draw(screen)
848 }
849 }
850
851 func (f *Form) updateFocusedElement(decreasing bool) {
852 li := len(f.items)
853 l := len(f.items) + len(f.buttons)
854 for i := 0; i < l; i++ {
855 if f.focusedElement < 0 {
856 if f.wrapAround {
857 f.focusedElement = l - 1
858 } else {
859 f.focusedElement = 0
860 }
861 } else if f.focusedElement >= l {
862 if f.wrapAround {
863 f.focusedElement = 0
864 } else {
865 f.focusedElement = l - 1
866 }
867 }
868
869 if f.focusedElement < li {
870 item := f.items[f.focusedElement]
871 if item.GetVisible() {
872 break
873 }
874 } else {
875 button := f.buttons[f.focusedElement-li]
876 if button.GetVisible() {
877 break
878 }
879 }
880
881 if decreasing {
882 f.focusedElement--
883 } else {
884 f.focusedElement++
885 }
886 }
887
888 }
889
890 func (f *Form) formItemInputHandler(delegate func(p Primitive)) func(key tcell.Key) {
891 return func(key tcell.Key) {
892 f.Lock()
893
894 switch key {
895 case tcell.KeyTab, tcell.KeyEnter:
896 f.focusedElement++
897 f.updateFocusedElement(false)
898 f.Unlock()
899 f.Focus(delegate)
900 f.Lock()
901 case tcell.KeyBacktab:
902 f.focusedElement--
903 f.updateFocusedElement(true)
904 f.Unlock()
905 f.Focus(delegate)
906 f.Lock()
907 case tcell.KeyEscape:
908 if f.cancel != nil {
909 f.Unlock()
910 f.cancel()
911 f.Lock()
912 } else {
913 f.focusedElement = 0
914 f.updateFocusedElement(true)
915 f.Unlock()
916 f.Focus(delegate)
917 f.Lock()
918 }
919 }
920
921 f.Unlock()
922 }
923 }
924
925
926 func (f *Form) Focus(delegate func(p Primitive)) {
927 f.Lock()
928 if len(f.items)+len(f.buttons) == 0 {
929 f.hasFocus = true
930 f.Unlock()
931 return
932 }
933 f.hasFocus = false
934
935
936 if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
937 f.focusedElement = 0
938 }
939
940 if f.focusedElement < len(f.items) {
941
942 item := f.items[f.focusedElement]
943
944 attributes := f.getAttributes()
945 attributes.FinishedFunc = f.formItemInputHandler(delegate)
946
947 f.Unlock()
948
949 setFormItemAttributes(item, attributes)
950 delegate(item)
951 } else {
952
953 button := f.buttons[f.focusedElement-len(f.items)]
954 button.SetBlurFunc(f.formItemInputHandler(delegate))
955
956 f.Unlock()
957
958 delegate(button)
959 }
960 }
961
962
963 func (f *Form) HasFocus() bool {
964 f.Lock()
965 defer f.Unlock()
966
967 if f.hasFocus {
968 return true
969 }
970 return f.focusIndex() >= 0
971 }
972
973
974
975
976 func (f *Form) focusIndex() int {
977 for index, item := range f.items {
978 if item.GetVisible() && item.GetFocusable().HasFocus() {
979 return index
980 }
981 }
982 for index, button := range f.buttons {
983 if button.GetVisible() && button.focus.HasFocus() {
984 return len(f.items) + index
985 }
986 }
987 return -1
988 }
989
990
991 func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
992 return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
993 if !f.InRect(event.Position()) {
994 return false, nil
995 }
996
997
998 for _, item := range f.items {
999 consumed, capture = item.MouseHandler()(action, event, setFocus)
1000 if consumed {
1001 return
1002 }
1003 }
1004 for _, button := range f.buttons {
1005 consumed, capture = button.MouseHandler()(action, event, setFocus)
1006 if consumed {
1007 return
1008 }
1009 }
1010
1011
1012
1013 if action == MouseLeftClick {
1014 if f.focusedElement < len(f.items) {
1015 setFocus(f.items[f.focusedElement])
1016 } else if f.focusedElement < len(f.items)+len(f.buttons) {
1017 setFocus(f.buttons[f.focusedElement-len(f.items)])
1018 }
1019 consumed = true
1020 }
1021
1022 return
1023 })
1024 }
1025
1026 func setFormItemAttributes(item FormItem, attrs *FormItemAttributes) {
1027 item.SetLabelWidth(attrs.LabelWidth)
1028 item.SetBackgroundColor(attrs.BackgroundColor)
1029 item.SetLabelColor(attrs.LabelColor)
1030 item.SetLabelColorFocused(attrs.LabelColorFocused)
1031 item.SetFieldTextColor(attrs.FieldTextColor)
1032 item.SetFieldTextColorFocused(attrs.FieldTextColorFocused)
1033 item.SetFieldBackgroundColor(attrs.FieldBackgroundColor)
1034 item.SetFieldBackgroundColorFocused(attrs.FieldBackgroundColorFocused)
1035
1036 if attrs.FinishedFunc != nil {
1037 item.SetFinishedFunc(attrs.FinishedFunc)
1038 }
1039 }
1040
View as plain text