1 package cview
2
3 import (
4 "bytes"
5 "fmt"
6 "strings"
7 "sync"
8
9 "github.com/gdamore/tcell/v2"
10 )
11
12
13 type ListItem struct {
14 disabled bool
15 mainText []byte
16 secondaryText []byte
17 shortcut rune
18 selected func()
19 reference interface{}
20
21 sync.RWMutex
22 }
23
24
25 func NewListItem(mainText string) *ListItem {
26 return &ListItem{
27 mainText: []byte(mainText),
28 }
29 }
30
31
32 func (l *ListItem) SetMainBytes(val []byte) {
33 l.Lock()
34 defer l.Unlock()
35
36 l.mainText = val
37 }
38
39
40 func (l *ListItem) SetMainText(val string) {
41 l.SetMainBytes([]byte(val))
42 }
43
44
45 func (l *ListItem) GetMainBytes() []byte {
46 l.RLock()
47 defer l.RUnlock()
48
49 return l.mainText
50 }
51
52
53 func (l *ListItem) GetMainText() string {
54 return string(l.GetMainBytes())
55 }
56
57
58 func (l *ListItem) SetSecondaryBytes(val []byte) {
59 l.Lock()
60 defer l.Unlock()
61
62 l.secondaryText = val
63 }
64
65
66 func (l *ListItem) SetSecondaryText(val string) {
67 l.SetSecondaryBytes([]byte(val))
68 }
69
70
71 func (l *ListItem) GetSecondaryBytes() []byte {
72 l.RLock()
73 defer l.RUnlock()
74
75 return l.secondaryText
76 }
77
78
79 func (l *ListItem) GetSecondaryText() string {
80 return string(l.GetSecondaryBytes())
81 }
82
83
84 func (l *ListItem) SetShortcut(val rune) {
85 l.Lock()
86 defer l.Unlock()
87
88 l.shortcut = val
89 }
90
91
92 func (l *ListItem) GetShortcut() rune {
93 l.RLock()
94 defer l.RUnlock()
95
96 return l.shortcut
97 }
98
99
100 func (l *ListItem) SetSelectedFunc(handler func()) {
101 l.Lock()
102 defer l.Unlock()
103
104 l.selected = handler
105 }
106
107
108 func (l *ListItem) SetReference(val interface{}) {
109 l.Lock()
110 defer l.Unlock()
111
112 l.reference = val
113 }
114
115
116 func (l *ListItem) GetReference() interface{} {
117 l.RLock()
118 defer l.RUnlock()
119
120 return l.reference
121 }
122
123
124 type List struct {
125 *Box
126 *ContextMenu
127
128
129 items []*ListItem
130
131
132 currentItem int
133
134
135 showSecondaryText bool
136
137
138 mainTextColor tcell.Color
139
140
141 secondaryTextColor tcell.Color
142
143
144 shortcutColor tcell.Color
145
146
147 selectedTextColor tcell.Color
148
149
150 selectedTextAttributes tcell.AttrMask
151
152
153 scrollBarVisibility ScrollBarVisibility
154
155
156 scrollBarColor tcell.Color
157
158
159 selectedBackgroundColor tcell.Color
160
161
162 selectedFocusOnly bool
163
164
165 selectedAlwaysVisible bool
166
167
168 selectedAlwaysCentered bool
169
170
171 highlightFullLine bool
172
173
174 wrapAround bool
175
176
177 hover bool
178
179
180
181 itemOffset, columnOffset int
182
183
184
185 changed func(index int, item *ListItem)
186
187
188
189 selected func(index int, item *ListItem)
190
191
192 done func()
193
194
195 height int
196
197
198 unselectedPrefix, unselectedSuffix []byte
199
200
201 selectedPrefix, selectedSuffix []byte
202
203
204 prefixWidth, suffixWidth int
205
206 sync.RWMutex
207 }
208
209
210 func NewList() *List {
211 l := &List{
212 Box: NewBox(),
213 showSecondaryText: true,
214 scrollBarVisibility: ScrollBarAuto,
215 mainTextColor: Styles.PrimaryTextColor,
216 secondaryTextColor: Styles.TertiaryTextColor,
217 shortcutColor: Styles.SecondaryTextColor,
218 selectedTextColor: Styles.PrimitiveBackgroundColor,
219 scrollBarColor: Styles.ScrollBarColor,
220 selectedBackgroundColor: Styles.PrimaryTextColor,
221 }
222
223 l.ContextMenu = NewContextMenu(l)
224 l.focus = l
225
226 return l
227 }
228
229
230
231
232
233
234
235 func (l *List) SetCurrentItem(index int) {
236 l.Lock()
237
238 if index < 0 {
239 index = len(l.items) + index
240 }
241 if index >= len(l.items) {
242 index = len(l.items) - 1
243 }
244 if index < 0 {
245 index = 0
246 }
247
248 previousItem := l.currentItem
249 l.currentItem = index
250
251 l.updateOffset()
252
253 if index != previousItem && index < len(l.items) && l.changed != nil {
254 item := l.items[index]
255 l.Unlock()
256 l.changed(index, item)
257 } else {
258 l.Unlock()
259 }
260 }
261
262
263
264 func (l *List) GetCurrentItem() *ListItem {
265 l.RLock()
266 defer l.RUnlock()
267
268 if len(l.items) == 0 || l.currentItem >= len(l.items) {
269 return nil
270 }
271 return l.items[l.currentItem]
272 }
273
274
275
276 func (l *List) GetCurrentItemIndex() int {
277 l.RLock()
278 defer l.RUnlock()
279 return l.currentItem
280 }
281
282
283 func (l *List) GetItems() []*ListItem {
284 l.RLock()
285 defer l.RUnlock()
286 return l.items
287 }
288
289
290
291
292
293
294
295
296
297 func (l *List) RemoveItem(index int) {
298 l.Lock()
299
300 if len(l.items) == 0 {
301 l.Unlock()
302 return
303 }
304
305
306 if index < 0 {
307 index = len(l.items) + index
308 }
309 if index >= len(l.items) {
310 index = len(l.items) - 1
311 }
312 if index < 0 {
313 index = 0
314 }
315
316
317 l.items = append(l.items[:index], l.items[index+1:]...)
318
319
320 if len(l.items) == 0 {
321 l.Unlock()
322 return
323 }
324
325
326 previousItem := l.currentItem
327 if l.currentItem >= index && l.currentItem > 0 {
328 l.currentItem--
329 }
330
331
332 if previousItem == index && index < len(l.items) && l.changed != nil {
333 item := l.items[l.currentItem]
334 l.Unlock()
335 l.changed(l.currentItem, item)
336 } else {
337 l.Unlock()
338 }
339 }
340
341
342
343 func (l *List) SetOffset(items, columns int) {
344 l.Lock()
345 defer l.Unlock()
346
347 if items < 0 {
348 items = 0
349 }
350 if columns < 0 {
351 columns = 0
352 }
353
354 l.itemOffset, l.columnOffset = items, columns
355 }
356
357
358
359 func (l *List) GetOffset() (int, int) {
360 l.Lock()
361 defer l.Unlock()
362
363 return l.itemOffset, l.columnOffset
364 }
365
366
367 func (l *List) SetMainTextColor(color tcell.Color) {
368 l.Lock()
369 defer l.Unlock()
370
371 l.mainTextColor = color
372 }
373
374
375 func (l *List) SetSecondaryTextColor(color tcell.Color) {
376 l.Lock()
377 defer l.Unlock()
378
379 l.secondaryTextColor = color
380 }
381
382
383 func (l *List) SetShortcutColor(color tcell.Color) {
384 l.Lock()
385 defer l.Unlock()
386
387 l.shortcutColor = color
388 }
389
390
391 func (l *List) SetSelectedTextColor(color tcell.Color) {
392 l.Lock()
393 defer l.Unlock()
394
395 l.selectedTextColor = color
396 }
397
398
399 func (l *List) SetSelectedTextAttributes(attr tcell.AttrMask) {
400 l.Lock()
401 defer l.Unlock()
402
403 l.selectedTextAttributes = attr
404 }
405
406
407 func (l *List) SetSelectedBackgroundColor(color tcell.Color) {
408 l.Lock()
409 defer l.Unlock()
410
411 l.selectedBackgroundColor = color
412 }
413
414
415
416
417 func (l *List) SetSelectedFocusOnly(focusOnly bool) {
418 l.Lock()
419 defer l.Unlock()
420
421 l.selectedFocusOnly = focusOnly
422 }
423
424
425
426 func (l *List) SetSelectedAlwaysVisible(alwaysVisible bool) {
427 l.Lock()
428 defer l.Unlock()
429
430 l.selectedAlwaysVisible = alwaysVisible
431 }
432
433
434
435 func (l *List) SetSelectedAlwaysCentered(alwaysCentered bool) {
436 l.Lock()
437 defer l.Unlock()
438
439 l.selectedAlwaysCentered = alwaysCentered
440 }
441
442
443
444
445
446 func (l *List) SetHighlightFullLine(highlight bool) {
447 l.Lock()
448 defer l.Unlock()
449
450 l.highlightFullLine = highlight
451 }
452
453
454 func (l *List) ShowSecondaryText(show bool) {
455 l.Lock()
456 defer l.Unlock()
457
458 l.showSecondaryText = show
459 return
460 }
461
462
463 func (l *List) SetScrollBarVisibility(visibility ScrollBarVisibility) {
464 l.Lock()
465 defer l.Unlock()
466
467 l.scrollBarVisibility = visibility
468 }
469
470
471 func (l *List) SetScrollBarColor(color tcell.Color) {
472 l.Lock()
473 defer l.Unlock()
474
475 l.scrollBarColor = color
476 }
477
478
479
480 func (l *List) SetHover(hover bool) {
481 l.Lock()
482 defer l.Unlock()
483
484 l.hover = hover
485 }
486
487
488
489
490
491
492 func (l *List) SetWrapAround(wrapAround bool) {
493 l.Lock()
494 defer l.Unlock()
495
496 l.wrapAround = wrapAround
497 }
498
499
500
501
502
503
504
505 func (l *List) SetChangedFunc(handler func(index int, item *ListItem)) {
506 l.Lock()
507 defer l.Unlock()
508
509 l.changed = handler
510 }
511
512
513
514
515 func (l *List) SetSelectedFunc(handler func(int, *ListItem)) {
516 l.Lock()
517 defer l.Unlock()
518
519 l.selected = handler
520 }
521
522
523
524 func (l *List) SetDoneFunc(handler func()) {
525 l.Lock()
526 defer l.Unlock()
527
528 l.done = handler
529 }
530
531
532 func (l *List) AddItem(item *ListItem) {
533 l.InsertItem(-1, item)
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558 func (l *List) InsertItem(index int, item *ListItem) {
559 l.Lock()
560
561
562 if index < 0 {
563 index = len(l.items) + index + 1
564 }
565 if index < 0 {
566 index = 0
567 } else if index > len(l.items) {
568 index = len(l.items)
569 }
570
571
572 if l.currentItem < len(l.items) && l.currentItem >= index {
573 l.currentItem++
574 }
575
576
577 l.items = append(l.items, nil)
578 if index < len(l.items)-1 {
579 copy(l.items[index+1:], l.items[index:])
580 }
581 l.items[index] = item
582
583
584 if len(l.items) == 1 && l.changed != nil {
585 item := l.items[0]
586 l.Unlock()
587 l.changed(0, item)
588 } else {
589 l.Unlock()
590 }
591 }
592
593
594
595 func (l *List) GetItem(index int) *ListItem {
596 if index > len(l.items)-1 {
597 return nil
598 }
599 return l.items[index]
600 }
601
602
603 func (l *List) GetItemCount() int {
604 l.RLock()
605 defer l.RUnlock()
606
607 return len(l.items)
608 }
609
610
611
612 func (l *List) GetItemText(index int) (main, secondary string) {
613 l.RLock()
614 defer l.RUnlock()
615 return string(l.items[index].mainText), string(l.items[index].secondaryText)
616 }
617
618
619
620 func (l *List) SetItemText(index int, main, secondary string) {
621 l.Lock()
622 defer l.Unlock()
623
624 item := l.items[index]
625 item.mainText = []byte(main)
626 item.secondaryText = []byte(secondary)
627 }
628
629
630
631 func (l *List) SetItemEnabled(index int, enabled bool) {
632 l.Lock()
633 defer l.Unlock()
634
635 item := l.items[index]
636 item.disabled = !enabled
637 }
638
639
640 func (l *List) SetIndicators(selectedPrefix, selectedSuffix, unselectedPrefix, unselectedSuffix string) {
641 l.Lock()
642 defer l.Unlock()
643 l.selectedPrefix = []byte(selectedPrefix)
644 l.selectedSuffix = []byte(selectedSuffix)
645 l.unselectedPrefix = []byte(unselectedPrefix)
646 l.unselectedSuffix = []byte(unselectedSuffix)
647 l.prefixWidth = len(selectedPrefix)
648 if len(unselectedPrefix) > l.prefixWidth {
649 l.prefixWidth = len(unselectedPrefix)
650 }
651 l.suffixWidth = len(selectedSuffix)
652 if len(unselectedSuffix) > l.suffixWidth {
653 l.suffixWidth = len(unselectedSuffix)
654 }
655 }
656
657
658
659
660
661
662
663
664
665
666
667 func (l *List) FindItems(mainSearch, secondarySearch string, mustContainBoth, ignoreCase bool) (indices []int) {
668 l.RLock()
669 defer l.RUnlock()
670
671 if mainSearch == "" && secondarySearch == "" {
672 return
673 }
674
675 if ignoreCase {
676 mainSearch = strings.ToLower(mainSearch)
677 secondarySearch = strings.ToLower(secondarySearch)
678 }
679
680 mainSearchBytes := []byte(mainSearch)
681 secondarySearchBytes := []byte(secondarySearch)
682
683 for index, item := range l.items {
684 mainText := item.mainText
685 secondaryText := item.secondaryText
686 if ignoreCase {
687 mainText = bytes.ToLower(mainText)
688 secondaryText = bytes.ToLower(secondaryText)
689 }
690
691
692 mainContained := bytes.Contains(mainText, mainSearchBytes)
693 secondaryContained := bytes.Contains(secondaryText, secondarySearchBytes)
694 if mustContainBoth && mainContained && secondaryContained ||
695 !mustContainBoth && (len(mainText) > 0 && mainContained || len(secondaryText) > 0 && secondaryContained) {
696 indices = append(indices, index)
697 }
698 }
699
700 return
701 }
702
703
704 func (l *List) Clear() {
705 l.Lock()
706 defer l.Unlock()
707
708 l.items = nil
709 l.currentItem = 0
710 l.itemOffset = 0
711 l.columnOffset = 0
712 }
713
714
715 func (l *List) Focus(delegate func(p Primitive)) {
716 l.Box.Focus(delegate)
717 if l.ContextMenu.open {
718 delegate(l.ContextMenu.list)
719 }
720 }
721
722
723 func (l *List) HasFocus() bool {
724 if l.ContextMenu.open {
725 return l.ContextMenu.list.HasFocus()
726 }
727
728 l.RLock()
729 defer l.RUnlock()
730 return l.hasFocus
731 }
732
733
734 func (l *List) Transform(tr Transformation) {
735 l.Lock()
736
737 previousItem := l.currentItem
738
739 l.transform(tr)
740
741 if l.currentItem != previousItem && l.currentItem < len(l.items) && l.changed != nil {
742 item := l.items[l.currentItem]
743 l.Unlock()
744 l.changed(l.currentItem, item)
745 } else {
746 l.Unlock()
747 }
748 }
749
750 func (l *List) transform(tr Transformation) {
751 var decreasing bool
752
753 pageItems := l.height
754 if l.showSecondaryText {
755 pageItems /= 2
756 }
757 if pageItems < 1 {
758 pageItems = 1
759 }
760
761 switch tr {
762 case TransformFirstItem:
763 l.currentItem = 0
764 l.itemOffset = 0
765 decreasing = true
766 case TransformLastItem:
767 l.currentItem = len(l.items) - 1
768 case TransformPreviousItem:
769 l.currentItem--
770 decreasing = true
771 case TransformNextItem:
772 l.currentItem++
773 case TransformPreviousPage:
774 l.currentItem -= pageItems
775 decreasing = true
776 case TransformNextPage:
777 l.currentItem += pageItems
778 l.itemOffset += pageItems
779 }
780
781 for i := 0; i < len(l.items); i++ {
782 if l.currentItem < 0 {
783 if l.wrapAround {
784 l.currentItem = len(l.items) - 1
785 } else {
786 l.currentItem = 0
787 l.itemOffset = 0
788 }
789 } else if l.currentItem >= len(l.items) {
790 if l.wrapAround {
791 l.currentItem = 0
792 l.itemOffset = 0
793 } else {
794 l.currentItem = len(l.items) - 1
795 }
796 }
797
798 item := l.items[l.currentItem]
799 if !item.disabled && (item.shortcut > 0 || len(item.mainText) > 0 || len(item.secondaryText) > 0) {
800 break
801 }
802
803 if decreasing {
804 l.currentItem--
805 } else {
806 l.currentItem++
807 }
808 }
809
810 l.updateOffset()
811 }
812
813 func (l *List) updateOffset() {
814 _, _, _, l.height = l.GetInnerRect()
815
816 h := l.height
817 if l.selectedAlwaysCentered {
818 h /= 2
819 }
820
821 if l.currentItem < l.itemOffset {
822 l.itemOffset = l.currentItem
823 } else if l.showSecondaryText {
824 if 2*(l.currentItem-l.itemOffset) >= h-1 {
825 l.itemOffset = (2*l.currentItem + 3 - h) / 2
826 }
827 } else {
828 if l.currentItem-l.itemOffset >= h {
829 l.itemOffset = l.currentItem + 1 - h
830 }
831 }
832
833 if l.showSecondaryText {
834 if l.itemOffset > len(l.items)-(l.height/2) {
835 l.itemOffset = len(l.items) - l.height/2
836 }
837 } else {
838 if l.itemOffset > len(l.items)-l.height {
839 l.itemOffset = len(l.items) - l.height
840 }
841 }
842
843 if l.itemOffset < 0 {
844 l.itemOffset = 0
845 }
846
847
848 maxWidth := 0
849 for _, option := range l.items {
850 strWidth := TaggedTextWidth(option.mainText)
851 secondaryWidth := TaggedTextWidth(option.secondaryText)
852 if secondaryWidth > strWidth {
853 strWidth = secondaryWidth
854 }
855 if option.shortcut != 0 {
856 strWidth += 4
857 }
858
859 if strWidth > maxWidth {
860 maxWidth = strWidth
861 }
862 }
863
864
865 addWidth := 0
866 if l.scrollBarVisibility == ScrollBarAlways ||
867 (l.scrollBarVisibility == ScrollBarAuto &&
868 ((!l.showSecondaryText && len(l.items) > l.innerHeight) ||
869 (l.showSecondaryText && len(l.items) > l.innerHeight/2))) {
870 addWidth = 1
871 }
872
873 if l.columnOffset > (maxWidth-l.innerWidth)+addWidth {
874 l.columnOffset = (maxWidth - l.innerWidth) + addWidth
875 }
876 if l.columnOffset < 0 {
877 l.columnOffset = 0
878 }
879 }
880
881
882 func (l *List) Draw(screen tcell.Screen) {
883 if !l.GetVisible() {
884 return
885 }
886
887 l.Box.Draw(screen)
888 hasFocus := l.GetFocusable().HasFocus()
889
890 l.Lock()
891 defer l.Unlock()
892
893
894 x, y, width, height := l.GetInnerRect()
895 leftEdge := x
896 fullWidth := width + l.paddingLeft + l.paddingRight + l.prefixWidth + l.suffixWidth
897 bottomLimit := y + height
898
899 l.height = height
900
901 screenWidth, _ := screen.Size()
902 scrollBarHeight := height
903 scrollBarX := x + (width - 1) + l.paddingLeft + l.paddingRight
904 if scrollBarX > screenWidth-1 {
905 scrollBarX = screenWidth - 1
906 }
907
908
909 if l.showSecondaryText {
910 scrollBarHeight /= 2
911 }
912
913
914 var showShortcuts bool
915 for _, item := range l.items {
916 if item.shortcut != 0 {
917 showShortcuts = true
918 x += 4
919 width -= 4
920 break
921 }
922 }
923
924
925 if l.selectedAlwaysVisible || l.selectedAlwaysCentered {
926 l.updateOffset()
927 }
928
929 scrollBarCursor := int(float64(len(l.items)) * (float64(l.itemOffset) / float64(len(l.items)-height)))
930
931
932 for index, item := range l.items {
933 if index < l.itemOffset {
934 continue
935 }
936
937 if y >= bottomLimit {
938 break
939 }
940
941 mainText := item.mainText
942 secondaryText := item.secondaryText
943 if l.columnOffset > 0 {
944 if l.columnOffset < len(mainText) {
945 mainText = mainText[l.columnOffset:]
946 } else {
947 mainText = nil
948 }
949 if l.columnOffset < len(secondaryText) {
950 secondaryText = secondaryText[l.columnOffset:]
951 } else {
952 secondaryText = nil
953 }
954 }
955
956 if len(item.mainText) == 0 && len(item.secondaryText) == 0 && item.shortcut == 0 {
957 Print(screen, []byte(string(tcell.RuneLTee)), leftEdge-2, y, 1, AlignLeft, l.mainTextColor)
958 Print(screen, bytes.Repeat([]byte(string(tcell.RuneHLine)), fullWidth), leftEdge-1, y, fullWidth, AlignLeft, l.mainTextColor)
959 Print(screen, []byte(string(tcell.RuneRTee)), leftEdge+fullWidth-1, y, 1, AlignLeft, l.mainTextColor)
960
961 RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
962 y++
963 continue
964 }
965
966 if index == l.currentItem {
967 if len(l.selectedPrefix) > 0 {
968 mainText = append(l.selectedPrefix, mainText...)
969 }
970 if len(l.selectedSuffix) > 0 {
971 mainText = append(mainText, l.selectedSuffix...)
972 }
973
974 } else {
975 if len(l.unselectedPrefix) > 0 {
976 mainText = append(l.unselectedPrefix, mainText...)
977 }
978 if len(l.unselectedSuffix) > 0 {
979 mainText = append(mainText, l.unselectedSuffix...)
980 }
981 }
982 if item.disabled {
983
984 if showShortcuts && item.shortcut != 0 {
985 Print(screen, []byte(fmt.Sprintf("(%c)", item.shortcut)), x-5, y, 4, AlignRight, tcell.ColorDarkSlateGray.TrueColor())
986 }
987
988
989 Print(screen, mainText, x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
990
991 RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
992 y++
993 continue
994 }
995
996
997 if showShortcuts && item.shortcut != 0 {
998 Print(screen, []byte(fmt.Sprintf("(%c)", item.shortcut)), x-5, y, 4, AlignRight, l.shortcutColor)
999 }
1000
1001
1002 Print(screen, mainText, x, y, width, AlignLeft, l.mainTextColor)
1003
1004
1005 if index == l.currentItem && (!l.selectedFocusOnly || hasFocus) {
1006 textWidth := width
1007 if !l.highlightFullLine {
1008 if w := TaggedTextWidth(mainText); w < textWidth {
1009 textWidth = w
1010 }
1011 }
1012
1013 for bx := 0; bx < textWidth; bx++ {
1014 m, c, style, _ := screen.GetContent(x+bx, y)
1015 fg, _, _ := style.Decompose()
1016 if fg == l.mainTextColor {
1017 fg = l.selectedTextColor
1018 }
1019 style = SetAttributes(style.Background(l.selectedBackgroundColor).Foreground(fg), l.selectedTextAttributes)
1020 screen.SetContent(x+bx, y, m, c, style)
1021 }
1022 }
1023
1024 RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
1025
1026 y++
1027
1028 if y >= bottomLimit {
1029 break
1030 }
1031
1032
1033 if l.showSecondaryText {
1034 Print(screen, secondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
1035
1036 RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
1037
1038 y++
1039 }
1040 }
1041
1042
1043 for y < bottomLimit {
1044 RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, bottomLimit-y, l.hasFocus, l.scrollBarColor)
1045
1046 y++
1047 }
1048
1049
1050 if hasFocus && l.ContextMenu.open {
1051 ctx := l.ContextMenuList()
1052
1053 x, y, width, height = l.GetInnerRect()
1054
1055
1056 maxWidth := 0
1057 for _, option := range ctx.items {
1058 strWidth := TaggedTextWidth(option.mainText)
1059 if option.shortcut != 0 {
1060 strWidth += 4
1061 }
1062 if strWidth > maxWidth {
1063 maxWidth = strWidth
1064 }
1065 }
1066
1067 lheight := len(ctx.items)
1068 lwidth := maxWidth
1069
1070
1071 lwidth += 2
1072 lheight += 2
1073
1074 lwidth += ctx.paddingLeft + ctx.paddingRight
1075 lheight += ctx.paddingTop + ctx.paddingBottom
1076
1077 cx, cy := l.ContextMenu.x, l.ContextMenu.y
1078 if cx < 0 || cy < 0 {
1079 offsetX := 7
1080 if showShortcuts {
1081 offsetX += 4
1082 }
1083 offsetY := l.currentItem
1084 if l.showSecondaryText {
1085 offsetY *= 2
1086 }
1087 x, y, _, _ := l.GetInnerRect()
1088 cx, cy = x+offsetX, y+offsetY
1089 }
1090
1091 _, sheight := screen.Size()
1092 if cy+lheight >= sheight && cy-2 > lheight-cy {
1093 for i := (cy + lheight) - sheight; i > 0; i-- {
1094 cy--
1095 if cy+lheight < sheight {
1096 break
1097 }
1098 }
1099 if cy < 0 {
1100 cy = 0
1101 }
1102 }
1103 if cy+lheight >= sheight {
1104 lheight = sheight - cy
1105 }
1106
1107 if ctx.scrollBarVisibility == ScrollBarAlways || (ctx.scrollBarVisibility == ScrollBarAuto && len(ctx.items) > lheight) {
1108 lwidth++
1109 }
1110
1111 ctx.SetRect(cx, cy, lwidth, lheight)
1112 ctx.Draw(screen)
1113 }
1114 }
1115
1116
1117 func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
1118 return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
1119 l.Lock()
1120
1121 if HitShortcut(event, Keys.Cancel) {
1122 if l.ContextMenu.open {
1123 l.Unlock()
1124
1125 l.ContextMenu.hide(setFocus)
1126 return
1127 }
1128
1129 if l.done != nil {
1130 l.Unlock()
1131 l.done()
1132 } else {
1133 l.Unlock()
1134 }
1135 return
1136 } else if HitShortcut(event, Keys.Select, Keys.Select2) {
1137 if l.currentItem >= 0 && l.currentItem < len(l.items) {
1138 item := l.items[l.currentItem]
1139 if !item.disabled {
1140 if item.selected != nil {
1141 l.Unlock()
1142 item.selected()
1143 l.Lock()
1144 }
1145 if l.selected != nil {
1146 l.Unlock()
1147 l.selected(l.currentItem, item)
1148 l.Lock()
1149 }
1150 }
1151 }
1152 } else if HitShortcut(event, Keys.ShowContextMenu) {
1153 defer l.ContextMenu.show(l.currentItem, -1, -1, setFocus)
1154 } else if len(l.items) == 0 {
1155 l.Unlock()
1156 return
1157 }
1158
1159 if event.Key() == tcell.KeyRune {
1160 ch := event.Rune()
1161 if ch != ' ' {
1162
1163 for index, item := range l.items {
1164 if !item.disabled && item.shortcut == ch {
1165
1166 l.currentItem = index
1167
1168 item := l.items[l.currentItem]
1169 if item.selected != nil {
1170 l.Unlock()
1171 item.selected()
1172 l.Lock()
1173 }
1174 if l.selected != nil {
1175 l.Unlock()
1176 l.selected(l.currentItem, item)
1177 l.Lock()
1178 }
1179
1180 l.Unlock()
1181 return
1182 }
1183 }
1184 }
1185 }
1186
1187 previousItem := l.currentItem
1188
1189 if HitShortcut(event, Keys.MoveFirst, Keys.MoveFirst2) {
1190 l.transform(TransformFirstItem)
1191 } else if HitShortcut(event, Keys.MoveLast, Keys.MoveLast2) {
1192 l.transform(TransformLastItem)
1193 } else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2) {
1194 l.transform(TransformPreviousItem)
1195 } else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2) {
1196 l.transform(TransformNextItem)
1197 } else if HitShortcut(event, Keys.MoveLeft, Keys.MoveLeft2) {
1198 l.columnOffset--
1199 l.updateOffset()
1200 } else if HitShortcut(event, Keys.MoveRight, Keys.MoveRight2) {
1201 l.columnOffset++
1202 l.updateOffset()
1203 } else if HitShortcut(event, Keys.MovePreviousPage) {
1204 l.transform(TransformPreviousPage)
1205 } else if HitShortcut(event, Keys.MoveNextPage) {
1206 l.transform(TransformNextPage)
1207 }
1208
1209 if l.currentItem != previousItem && l.currentItem < len(l.items) && l.changed != nil {
1210 item := l.items[l.currentItem]
1211 l.Unlock()
1212 l.changed(l.currentItem, item)
1213 } else {
1214 l.Unlock()
1215 }
1216 })
1217 }
1218
1219
1220
1221 func (l *List) indexAtY(y int) int {
1222 _, rectY, _, height := l.GetInnerRect()
1223 if y < rectY || y >= rectY+height {
1224 return -1
1225 }
1226
1227 index := y - rectY
1228 if l.showSecondaryText {
1229 index /= 2
1230 }
1231 index += l.itemOffset
1232
1233 if index >= len(l.items) {
1234 return -1
1235 }
1236 return index
1237 }
1238
1239
1240
1241 func (l *List) indexAtPoint(x, y int) int {
1242 rectX, rectY, width, height := l.GetInnerRect()
1243 if x < rectX || x >= rectX+width || y < rectY || y >= rectY+height {
1244 return -1
1245 }
1246
1247 index := y - rectY
1248 if l.showSecondaryText {
1249 index /= 2
1250 }
1251 index += l.itemOffset
1252
1253 if index >= len(l.items) {
1254 return -1
1255 }
1256 return index
1257 }
1258
1259
1260 func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
1261 return l.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
1262 l.Lock()
1263
1264
1265 if l.ContextMenuVisible() && l.ContextMenuList().InRect(event.Position()) {
1266 defer l.ContextMenuList().MouseHandler()(action, event, setFocus)
1267 consumed = true
1268 l.Unlock()
1269 return
1270 }
1271
1272 if !l.InRect(event.Position()) {
1273 l.Unlock()
1274 return false, nil
1275 }
1276
1277
1278 switch action {
1279 case MouseLeftClick:
1280 if l.ContextMenuVisible() {
1281 defer l.ContextMenu.hide(setFocus)
1282 consumed = true
1283 l.Unlock()
1284 return
1285 }
1286
1287 l.Unlock()
1288 setFocus(l)
1289 l.Lock()
1290
1291 index := l.indexAtPoint(event.Position())
1292 if index != -1 {
1293 item := l.items[index]
1294 if !item.disabled {
1295 l.currentItem = index
1296 if item.selected != nil {
1297 l.Unlock()
1298 item.selected()
1299 l.Lock()
1300 }
1301 if l.selected != nil {
1302 l.Unlock()
1303 l.selected(index, item)
1304 l.Lock()
1305 }
1306 if index != l.currentItem && l.changed != nil {
1307 l.Unlock()
1308 l.changed(index, item)
1309 l.Lock()
1310 }
1311 }
1312 }
1313 consumed = true
1314 case MouseMiddleClick:
1315 if l.ContextMenu.open {
1316 defer l.ContextMenu.hide(setFocus)
1317 consumed = true
1318 l.Unlock()
1319 return
1320 }
1321 case MouseRightDown:
1322 if len(l.ContextMenuList().items) == 0 {
1323 l.Unlock()
1324 return
1325 }
1326
1327 x, y := event.Position()
1328
1329 index := l.indexAtPoint(event.Position())
1330 if index != -1 {
1331 item := l.items[index]
1332 if !item.disabled {
1333 l.currentItem = index
1334 if index != l.currentItem && l.changed != nil {
1335 l.Unlock()
1336 l.changed(index, item)
1337 l.Lock()
1338 }
1339 }
1340 }
1341
1342 defer l.ContextMenu.show(l.currentItem, x, y, setFocus)
1343 l.ContextMenu.drag = true
1344 consumed = true
1345 case MouseMove:
1346 if l.hover {
1347 _, y := event.Position()
1348 index := l.indexAtY(y)
1349 if index >= 0 {
1350 item := l.items[index]
1351 if !item.disabled {
1352 l.currentItem = index
1353 }
1354 }
1355
1356 consumed = true
1357 }
1358 case MouseScrollUp:
1359 if l.itemOffset > 0 {
1360 l.itemOffset--
1361 }
1362 consumed = true
1363 case MouseScrollDown:
1364 lines := len(l.items) - l.itemOffset
1365 if l.showSecondaryText {
1366 lines *= 2
1367 }
1368 if _, _, _, height := l.GetInnerRect(); lines > height {
1369 l.itemOffset++
1370 }
1371 consumed = true
1372 }
1373
1374 l.Unlock()
1375 return
1376 })
1377 }
1378
View as plain text