1 package cview
2
3 import (
4 "bytes"
5 "sort"
6 "sync"
7
8 "github.com/gdamore/tcell/v2"
9 colorful "github.com/lucasb-eyer/go-colorful"
10 )
11
12
13
14
15 type TableCell struct {
16
17 Reference interface{}
18
19
20 Text []byte
21
22
23
24 Align int
25
26
27
28
29 MaxWidth int
30
31
32
33 Expansion int
34
35
36 Color tcell.Color
37
38
39 BackgroundColor tcell.Color
40
41
42 Attributes tcell.AttrMask
43
44
45 NotSelectable bool
46
47
48 x, y, width int
49
50 sync.RWMutex
51 }
52
53
54
55
56 func NewTableCell(text string) *TableCell {
57 return &TableCell{
58 Text: []byte(text),
59 Align: AlignLeft,
60 Color: Styles.PrimaryTextColor,
61 BackgroundColor: tcell.ColorDefault,
62 }
63 }
64
65
66 func (c *TableCell) SetBytes(text []byte) {
67 c.Lock()
68 defer c.Unlock()
69
70 c.Text = text
71 }
72
73
74 func (c *TableCell) SetText(text string) {
75 c.SetBytes([]byte(text))
76 }
77
78
79 func (c *TableCell) GetBytes() []byte {
80 c.RLock()
81 defer c.RUnlock()
82
83 return c.Text
84 }
85
86
87 func (c *TableCell) GetText() string {
88 return string(c.GetBytes())
89 }
90
91
92
93 func (c *TableCell) SetAlign(align int) {
94 c.Lock()
95 defer c.Unlock()
96
97 c.Align = align
98 }
99
100
101
102
103 func (c *TableCell) SetMaxWidth(maxWidth int) {
104 c.Lock()
105 defer c.Unlock()
106
107 c.MaxWidth = maxWidth
108 }
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 func (c *TableCell) SetExpansion(expansion int) {
124 c.Lock()
125 defer c.Unlock()
126
127 if expansion < 0 {
128 panic("Table cell expansion values may not be negative")
129 }
130 c.Expansion = expansion
131 }
132
133
134 func (c *TableCell) SetTextColor(color tcell.Color) {
135 c.Lock()
136 defer c.Unlock()
137
138 c.Color = color
139 }
140
141
142
143 func (c *TableCell) SetBackgroundColor(color tcell.Color) {
144 c.Lock()
145 defer c.Unlock()
146
147 c.BackgroundColor = color
148 }
149
150
151
152
153
154 func (c *TableCell) SetAttributes(attr tcell.AttrMask) {
155 c.Lock()
156 defer c.Unlock()
157
158 c.Attributes = attr
159 }
160
161
162
163 func (c *TableCell) SetStyle(style tcell.Style) {
164 c.Lock()
165 defer c.Unlock()
166
167 c.Color, c.BackgroundColor, c.Attributes = style.Decompose()
168 }
169
170
171 func (c *TableCell) SetSelectable(selectable bool) {
172 c.Lock()
173 defer c.Unlock()
174
175 c.NotSelectable = !selectable
176 }
177
178
179
180
181 func (c *TableCell) SetReference(reference interface{}) {
182 c.Lock()
183 defer c.Unlock()
184
185 c.Reference = reference
186 }
187
188
189 func (c *TableCell) GetReference() interface{} {
190 c.RLock()
191 defer c.RUnlock()
192
193 return c.Reference
194 }
195
196
197
198
199
200
201
202
203
204 func (c *TableCell) GetLastPosition() (x, y, width int) {
205 c.RLock()
206 defer c.RUnlock()
207
208 return c.x, c.y, c.width
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 type Table struct {
263 *Box
264
265
266 borders bool
267
268
269 bordersColor tcell.Color
270
271
272 separator rune
273
274
275 cells [][]*TableCell
276
277
278 lastColumn int
279
280
281
282 evaluateAllRows bool
283
284
285 fixedRows, fixedColumns int
286
287
288
289 rowsSelectable, columnsSelectable bool
290
291
292 selectedRow, selectedColumn int
293
294
295
296 rowOffset, columnOffset int
297
298
299 trackEnd bool
300
301
302 sortFunc func(column, i, j int) bool
303
304
305 sortClicked bool
306
307
308 sortClickedDescending bool
309
310
311 sortClickedColumn int
312
313
314 visibleRows int
315
316
317 visibleColumnIndices []int
318
319
320
321 visibleColumnWidths []int
322
323
324 scrollBarVisibility ScrollBarVisibility
325
326
327 scrollBarColor tcell.Color
328
329
330
331 selectedStyle tcell.Style
332
333
334
335
336 selected func(row, column int)
337
338
339
340
341 selectionChanged func(row, column int)
342
343
344
345 done func(key tcell.Key)
346
347 sync.RWMutex
348 }
349
350
351 func NewTable() *Table {
352 return &Table{
353 Box: NewBox(),
354 scrollBarVisibility: ScrollBarAuto,
355 scrollBarColor: Styles.ScrollBarColor,
356 bordersColor: Styles.GraphicsColor,
357 separator: ' ',
358 sortClicked: true,
359 lastColumn: -1,
360 }
361 }
362
363
364 func (t *Table) Clear() {
365 t.Lock()
366 defer t.Unlock()
367
368 t.cells = nil
369 t.lastColumn = -1
370 }
371
372
373
374 func (t *Table) SetBorders(show bool) {
375 t.Lock()
376 defer t.Unlock()
377
378 t.borders = show
379 }
380
381
382 func (t *Table) SetBordersColor(color tcell.Color) {
383 t.Lock()
384 defer t.Unlock()
385
386 t.bordersColor = color
387 }
388
389
390 func (t *Table) SetScrollBarVisibility(visibility ScrollBarVisibility) {
391 t.Lock()
392 defer t.Unlock()
393
394 t.scrollBarVisibility = visibility
395 }
396
397
398 func (t *Table) SetScrollBarColor(color tcell.Color) {
399 t.Lock()
400 defer t.Unlock()
401
402 t.scrollBarColor = color
403 }
404
405
406
407
408
409
410
411
412 func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) {
413 t.Lock()
414 defer t.Unlock()
415
416 t.selectedStyle = SetAttributes(tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor), attributes)
417 }
418
419
420
421
422
423
424
425
426 func (t *Table) SetSeparator(separator rune) {
427 t.Lock()
428 defer t.Unlock()
429
430 t.separator = separator
431 }
432
433
434
435
436 func (t *Table) SetFixed(rows, columns int) {
437 t.Lock()
438 defer t.Unlock()
439
440 t.fixedRows, t.fixedColumns = rows, columns
441 }
442
443
444
445
446
447
448
449
450 func (t *Table) SetSelectable(rows, columns bool) {
451 t.Lock()
452 defer t.Unlock()
453
454 t.rowsSelectable, t.columnsSelectable = rows, columns
455 }
456
457
458
459 func (t *Table) GetSelectable() (rows, columns bool) {
460 t.RLock()
461 defer t.RUnlock()
462
463 return t.rowsSelectable, t.columnsSelectable
464 }
465
466
467
468
469 func (t *Table) GetSelection() (row, column int) {
470 t.RLock()
471 defer t.RUnlock()
472
473 return t.selectedRow, t.selectedColumn
474 }
475
476
477
478
479
480
481 func (t *Table) Select(row, column int) {
482 t.Lock()
483 defer t.Unlock()
484
485 t.selectedRow, t.selectedColumn = row, column
486 if t.selectionChanged != nil {
487 t.Unlock()
488 t.selectionChanged(row, column)
489 t.Lock()
490 }
491 }
492
493
494
495
496
497
498 func (t *Table) SetOffset(row, column int) {
499 t.Lock()
500 defer t.Unlock()
501
502 t.rowOffset, t.columnOffset = row, column
503 t.trackEnd = false
504 }
505
506
507
508 func (t *Table) GetOffset() (row, column int) {
509 t.RLock()
510 defer t.RUnlock()
511
512 return t.rowOffset, t.columnOffset
513 }
514
515
516
517
518
519
520
521 func (t *Table) SetEvaluateAllRows(all bool) {
522 t.Lock()
523 defer t.Unlock()
524
525 t.evaluateAllRows = all
526 }
527
528
529
530
531
532 func (t *Table) SetSelectedFunc(handler func(row, column int)) {
533 t.Lock()
534 defer t.Unlock()
535
536 t.selected = handler
537 }
538
539
540
541
542
543 func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) {
544 t.Lock()
545 defer t.Unlock()
546
547 t.selectionChanged = handler
548 }
549
550
551
552
553
554 func (t *Table) SetDoneFunc(handler func(key tcell.Key)) {
555 t.Lock()
556 defer t.Unlock()
557
558 t.done = handler
559 }
560
561
562
563
564
565
566
567
568
569
570 func (t *Table) SetCell(row, column int, cell *TableCell) {
571 t.Lock()
572 defer t.Unlock()
573
574 if row >= len(t.cells) {
575 t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...)
576 }
577 rowLen := len(t.cells[row])
578 if column >= rowLen {
579 t.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...)
580 for c := rowLen; c < column; c++ {
581 t.cells[row][c] = &TableCell{}
582 }
583 }
584 t.cells[row][column] = cell
585 if column > t.lastColumn {
586 t.lastColumn = column
587 }
588 }
589
590
591 func (t *Table) SetCellSimple(row, column int, text string) {
592 t.SetCell(row, column, NewTableCell(text))
593 }
594
595
596
597
598
599
600 func (t *Table) GetCell(row, column int) *TableCell {
601 t.RLock()
602 defer t.RUnlock()
603
604 if row >= len(t.cells) || column >= len(t.cells[row]) {
605 return &TableCell{}
606 }
607 return t.cells[row][column]
608 }
609
610
611
612 func (t *Table) RemoveRow(row int) {
613 t.Lock()
614 defer t.Unlock()
615
616 if row < 0 || row >= len(t.cells) {
617 return
618 }
619
620 t.cells = append(t.cells[:row], t.cells[row+1:]...)
621 }
622
623
624
625 func (t *Table) RemoveColumn(column int) {
626 t.Lock()
627 defer t.Unlock()
628
629 for row := range t.cells {
630 if column < 0 || column >= len(t.cells[row]) {
631 continue
632 }
633 t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
634 }
635 }
636
637
638
639
640 func (t *Table) InsertRow(row int) {
641 t.Lock()
642 defer t.Unlock()
643
644 if row >= len(t.cells) {
645 return
646 }
647 t.cells = append(t.cells, nil)
648 copy(t.cells[row+1:], t.cells[row:])
649 t.cells[row] = nil
650 }
651
652
653
654
655
656 func (t *Table) InsertColumn(column int) {
657 t.Lock()
658 defer t.Unlock()
659
660 for row := range t.cells {
661 if column >= len(t.cells[row]) {
662 continue
663 }
664 t.cells[row] = append(t.cells[row], nil)
665 copy(t.cells[row][column+1:], t.cells[row][column:])
666 t.cells[row][column] = &TableCell{}
667 }
668 }
669
670
671 func (t *Table) GetRowCount() int {
672 t.RLock()
673 defer t.RUnlock()
674
675 return len(t.cells)
676 }
677
678
679 func (t *Table) GetColumnCount() int {
680 t.RLock()
681 defer t.RUnlock()
682
683 if len(t.cells) == 0 {
684 return 0
685 }
686 return t.lastColumn + 1
687 }
688
689
690
691
692
693 func (t *Table) cellAt(x, y int) (row, column int) {
694 rectX, rectY, _, _ := t.GetInnerRect()
695
696
697 if t.borders {
698 row = (y - rectY - 1) / 2
699 } else {
700 row = y - rectY
701 }
702
703
704 if row >= 0 {
705 if row >= t.fixedRows {
706 row += t.rowOffset
707 }
708 if row >= len(t.cells) {
709 row = -1
710 }
711 }
712
713
714 column = -1
715 if x >= rectX {
716 columnX := rectX
717 if t.borders {
718 columnX++
719 }
720 for index, width := range t.visibleColumnWidths {
721 columnX += width + 1
722 if x < columnX {
723 column = t.visibleColumnIndices[index]
724 break
725 }
726 }
727 }
728
729 return
730 }
731
732
733
734
735 func (t *Table) ScrollToBeginning() {
736 t.Lock()
737 defer t.Unlock()
738
739 t.trackEnd = false
740 t.columnOffset = 0
741 t.rowOffset = 0
742 }
743
744
745
746
747
748 func (t *Table) ScrollToEnd() {
749 t.Lock()
750 defer t.Unlock()
751
752 t.trackEnd = true
753 t.columnOffset = 0
754 t.rowOffset = len(t.cells)
755 }
756
757
758
759 func (t *Table) SetSortClicked(sortClicked bool) {
760 t.Lock()
761 defer t.Unlock()
762
763 t.sortClicked = sortClicked
764 }
765
766
767
768 func (t *Table) SetSortFunc(sortFunc func(column, i, j int) bool) {
769 t.Lock()
770 defer t.Unlock()
771
772 t.sortFunc = sortFunc
773 }
774
775
776
777 func (t *Table) Sort(column int, descending bool) {
778 t.Lock()
779 defer t.Unlock()
780
781 if len(t.cells) == 0 || column < 0 || column >= len(t.cells[0]) {
782 return
783 }
784
785 if t.sortFunc == nil {
786 t.sortFunc = func(column, i, j int) bool {
787 return bytes.Compare(t.cells[i][column].Text, t.cells[j][column].Text) == -1
788 }
789 }
790
791 sort.SliceStable(t.cells, func(i, j int) bool {
792 if i < t.fixedRows {
793 return i < j
794 } else if j < t.fixedRows {
795 return j > i
796 }
797
798 if !descending {
799 return t.sortFunc(column, i, j)
800 }
801 return t.sortFunc(column, j, i)
802 })
803 }
804
805
806 func (t *Table) Draw(screen tcell.Screen) {
807 if !t.GetVisible() {
808 return
809 }
810
811 t.Box.Draw(screen)
812
813 t.Lock()
814 defer t.Unlock()
815
816
817 x, y, width, height := t.GetInnerRect()
818 if t.borders {
819 t.visibleRows = height / 2
820 } else {
821 t.visibleRows = height
822 }
823
824 showVerticalScrollBar := t.scrollBarVisibility == ScrollBarAlways || (t.scrollBarVisibility == ScrollBarAuto && len(t.cells) > t.visibleRows-t.fixedRows)
825 if showVerticalScrollBar {
826 width--
827 }
828
829
830 getCell := func(row, column int) *TableCell {
831 if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) {
832 return nil
833 }
834 return t.cells[row][column]
835 }
836
837
838 if t.rowsSelectable || t.columnsSelectable {
839 if t.selectedColumn < 0 {
840 t.selectedColumn = 0
841 }
842 if t.selectedRow < 0 {
843 t.selectedRow = 0
844 }
845 for t.selectedRow < len(t.cells) {
846 cell := getCell(t.selectedRow, t.selectedColumn)
847 if cell == nil || !cell.NotSelectable {
848 break
849 }
850 t.selectedColumn++
851 if t.selectedColumn > t.lastColumn {
852 t.selectedColumn = 0
853 t.selectedRow++
854 }
855 }
856 }
857
858
859 if t.rowsSelectable {
860 if t.selectedRow >= t.fixedRows && t.selectedRow < t.fixedRows+t.rowOffset {
861 t.rowOffset = t.selectedRow - t.fixedRows
862 t.trackEnd = false
863 }
864 if t.borders {
865 if 2*(t.selectedRow+1-t.rowOffset) >= height {
866 t.rowOffset = t.selectedRow + 1 - height/2
867 t.trackEnd = false
868 }
869 } else {
870 if t.selectedRow+1-t.rowOffset >= height {
871 t.rowOffset = t.selectedRow + 1 - height
872 t.trackEnd = false
873 }
874 }
875 }
876 if t.borders {
877 if 2*(len(t.cells)-t.rowOffset) < height {
878 t.trackEnd = true
879 }
880 } else {
881 if len(t.cells)-t.rowOffset < height {
882 t.trackEnd = true
883 }
884 }
885 if t.trackEnd {
886 if t.borders {
887 t.rowOffset = len(t.cells) - height/2
888 } else {
889 t.rowOffset = len(t.cells) - height
890 }
891 }
892 if t.rowOffset < 0 {
893 t.rowOffset = 0
894 }
895
896
897
898 if t.columnsSelectable && t.selectedColumn >= t.fixedColumns && t.selectedColumn < t.fixedColumns+t.columnOffset {
899 t.columnOffset = t.selectedColumn - t.fixedColumns
900 }
901 if t.columnOffset < 0 {
902 t.columnOffset = 0
903 }
904 if t.selectedColumn < 0 {
905 t.selectedColumn = 0
906 }
907
908
909
910 var (
911 columns, rows, allRows, widths []int
912 tableHeight, tableWidth int
913 )
914 rowStep := 1
915 if t.borders {
916 rowStep = 2
917 tableWidth = 1
918 }
919 if t.evaluateAllRows {
920 allRows = make([]int, len(t.cells))
921 for row := range t.cells {
922 allRows[row] = row
923 }
924 }
925 indexRow := func(row int) bool {
926 if tableHeight >= height {
927 return false
928 }
929 rows = append(rows, row)
930 tableHeight += rowStep
931 return true
932 }
933 for row := 0; row < t.fixedRows && row < len(t.cells); row++ {
934 if !indexRow(row) {
935 break
936 }
937 }
938 for row := t.fixedRows + t.rowOffset; row < len(t.cells); row++ {
939 if !indexRow(row) {
940 break
941 }
942 }
943 var (
944 skipped, lastTableWidth, expansionTotal int
945 expansions []int
946 )
947 ColumnLoop:
948 for column := 0; ; column++ {
949
950 for tableWidth-1 >= width {
951
952 if column < t.fixedColumns {
953 break ColumnLoop
954 }
955 if !t.columnsSelectable && skipped >= t.columnOffset {
956 break ColumnLoop
957 }
958 if t.columnsSelectable && t.selectedColumn-skipped == t.fixedColumns {
959 break ColumnLoop
960 }
961 if t.columnsSelectable && skipped >= t.columnOffset &&
962 (t.selectedColumn < column && lastTableWidth < width-1 && tableWidth < width-1 || t.selectedColumn < column-1) {
963 break ColumnLoop
964 }
965 if len(columns) <= t.fixedColumns {
966 break
967 }
968
969
970 skipped++
971 lastTableWidth -= widths[t.fixedColumns] + 1
972 tableWidth -= widths[t.fixedColumns] + 1
973 columns = append(columns[:t.fixedColumns], columns[t.fixedColumns+1:]...)
974 widths = append(widths[:t.fixedColumns], widths[t.fixedColumns+1:]...)
975 expansions = append(expansions[:t.fixedColumns], expansions[t.fixedColumns+1:]...)
976 }
977
978
979 maxWidth := -1
980 expansion := 0
981 evaluationRows := rows
982 if t.evaluateAllRows {
983 evaluationRows = allRows
984 }
985 for _, row := range evaluationRows {
986 if cell := getCell(row, column); cell != nil {
987 _, _, _, _, _, _, cellWidth := decomposeText(cell.Text, true, false)
988 if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
989 cellWidth = cell.MaxWidth
990 }
991 if cellWidth > maxWidth {
992 maxWidth = cellWidth
993 }
994 if cell.Expansion > expansion {
995 expansion = cell.Expansion
996 }
997 }
998 }
999 if maxWidth < 0 {
1000 break
1001 }
1002
1003
1004 columns = append(columns, column)
1005 widths = append(widths, maxWidth)
1006 lastTableWidth = tableWidth
1007 tableWidth += maxWidth + 1
1008 expansions = append(expansions, expansion)
1009 expansionTotal += expansion
1010 }
1011 t.columnOffset = skipped
1012
1013
1014 if tableWidth < width {
1015 toDistribute := width - tableWidth
1016 for index, expansion := range expansions {
1017 if expansionTotal <= 0 {
1018 break
1019 }
1020 expWidth := toDistribute * expansion / expansionTotal
1021 widths[index] += expWidth
1022 toDistribute -= expWidth
1023 expansionTotal -= expansion
1024 }
1025 tableWidth = width - toDistribute
1026 }
1027
1028
1029 borderStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.bordersColor)
1030 drawBorder := func(colX, rowY int, ch rune) {
1031 screen.SetContent(x+colX, y+rowY, ch, nil, borderStyle)
1032 }
1033
1034
1035 var columnX int
1036 if !t.borders {
1037 columnX--
1038 }
1039 for columnIndex, column := range columns {
1040 columnWidth := widths[columnIndex]
1041 for rowY, row := range rows {
1042 if t.borders {
1043
1044 rowY *= 2
1045 for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
1046 drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
1047 }
1048 ch := Borders.Cross
1049 if columnIndex == 0 {
1050 if rowY == 0 {
1051 ch = Borders.TopLeft
1052 } else {
1053 ch = Borders.LeftT
1054 }
1055 } else if rowY == 0 {
1056 ch = Borders.TopT
1057 }
1058 drawBorder(columnX, rowY, ch)
1059 rowY++
1060 if rowY >= height {
1061 break
1062 }
1063 drawBorder(columnX, rowY, Borders.Vertical)
1064 } else if columnIndex > 0 {
1065
1066 drawBorder(columnX, rowY, t.separator)
1067 }
1068
1069
1070 cell := getCell(row, column)
1071 if cell == nil {
1072 continue
1073 }
1074
1075
1076 finalWidth := columnWidth
1077 if columnX+1+columnWidth >= width {
1078 finalWidth = width - columnX - 1
1079 }
1080 cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
1081 _, printed := PrintStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, SetAttributes(tcell.StyleDefault.Foreground(cell.Color), cell.Attributes))
1082 if TaggedTextWidth(cell.Text)-printed > 0 && printed > 0 {
1083 _, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY)
1084 PrintStyle(screen, []byte(string(SemigraphicsHorizontalEllipsis)), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
1085 }
1086 }
1087
1088
1089 if rowY := 2 * len(rows); t.borders && rowY < height {
1090 for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
1091 drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
1092 }
1093 ch := Borders.BottomT
1094 if columnIndex == 0 {
1095 ch = Borders.BottomLeft
1096 }
1097 drawBorder(columnX, rowY, ch)
1098 }
1099
1100 columnX += columnWidth + 1
1101 }
1102
1103
1104 if t.borders && len(t.cells) > 0 && columnX < width {
1105 for rowY := range rows {
1106 rowY *= 2
1107 if rowY+1 < height {
1108 drawBorder(columnX, rowY+1, Borders.Vertical)
1109 }
1110 ch := Borders.RightT
1111 if rowY == 0 {
1112 ch = Borders.TopRight
1113 }
1114 drawBorder(columnX, rowY, ch)
1115 }
1116 if rowY := 2 * len(rows); rowY < height {
1117 drawBorder(columnX, rowY, Borders.BottomRight)
1118 }
1119 }
1120
1121 if showVerticalScrollBar {
1122
1123 rows := len(t.cells)
1124
1125 scrollBarItems := rows - t.fixedRows
1126 scrollBarHeight := t.visibleRows - t.fixedRows
1127
1128 scrollBarX := x + width
1129 scrollBarY := y + t.fixedRows
1130 if scrollBarX > x+tableWidth {
1131 scrollBarX = x + tableWidth
1132 }
1133
1134 padTotalOffset := 1
1135 if t.borders {
1136 padTotalOffset = 2
1137
1138 scrollBarItems *= 2
1139 scrollBarHeight = (scrollBarHeight * 2) - 1
1140
1141 scrollBarY += t.fixedRows + 1
1142 }
1143
1144
1145 cursor := int(float64(scrollBarItems) * (float64(t.rowOffset) / float64(((rows-t.fixedRows)-t.visibleRows)+padTotalOffset)))
1146 for printed := 0; printed < scrollBarHeight; printed++ {
1147 RenderScrollBar(screen, t.scrollBarVisibility, scrollBarX, scrollBarY+printed, scrollBarHeight, scrollBarItems, cursor, printed, t.hasFocus, t.scrollBarColor)
1148 }
1149 }
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159 colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
1160 for by := 0; by < h && fromY+by < y+height; by++ {
1161 for bx := 0; bx < w && fromX+bx < x+width; bx++ {
1162 m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
1163 fg, bg, a := style.Decompose()
1164 if invert {
1165 if fg == textColor || fg == t.bordersColor {
1166 fg = backgroundColor
1167 }
1168 if fg == tcell.ColorDefault {
1169 fg = t.backgroundColor
1170 }
1171 style = style.Background(textColor).Foreground(fg)
1172 } else {
1173 if backgroundColor != tcell.ColorDefault {
1174 bg = backgroundColor
1175 }
1176 if textColor != tcell.ColorDefault {
1177 fg = textColor
1178 }
1179 if attr != 0 {
1180 a = attr
1181 }
1182 style = SetAttributes(style.Background(bg).Foreground(fg), a)
1183 }
1184 screen.SetContent(fromX+bx, fromY+by, m, c, style)
1185 }
1186 }
1187 }
1188
1189
1190
1191 type cellInfo struct {
1192 x, y, w, h int
1193 color tcell.Color
1194 selected bool
1195 }
1196 cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
1197 var backgroundColors []tcell.Color
1198 for rowY, row := range rows {
1199 columnX := 0
1200 rowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow
1201 for columnIndex, column := range columns {
1202 columnWidth := widths[columnIndex]
1203 cell := getCell(row, column)
1204 if cell == nil {
1205 continue
1206 }
1207 bx, by, bw, bh := x+columnX, y+rowY, columnWidth+1, 1
1208 if t.borders {
1209 by = y + rowY*2
1210 bw++
1211 bh = 3
1212 }
1213 columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
1214 cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
1215 entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
1216 cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
1217 x: bx,
1218 y: by,
1219 w: bw,
1220 h: bh,
1221 color: cell.Color,
1222 selected: cellSelected,
1223 })
1224 if !ok {
1225 backgroundColors = append(backgroundColors, cell.BackgroundColor)
1226 }
1227 columnX += columnWidth + 1
1228 }
1229 }
1230 sort.Slice(backgroundColors, func(i int, j int) bool {
1231
1232 r, g, b := backgroundColors[i].RGB()
1233 c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
1234 _, _, li := c.Hcl()
1235 r, g, b = backgroundColors[j].RGB()
1236 c = colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
1237 _, _, lj := c.Hcl()
1238 return li < lj
1239 })
1240 selFg, selBg, selAttr := t.selectedStyle.Decompose()
1241 for _, bgColor := range backgroundColors {
1242 entries := cellsByBackgroundColor[bgColor]
1243 for _, cell := range entries {
1244 if cell.selected {
1245 if t.selectedStyle != tcell.StyleDefault {
1246 defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
1247 } else {
1248 defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.color, 0, true)
1249 }
1250 } else {
1251 colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
1252 }
1253 }
1254 }
1255
1256
1257 t.visibleColumnIndices, t.visibleColumnWidths = columns, widths
1258 }
1259
1260
1261 func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
1262 return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
1263 t.Lock()
1264 defer t.Unlock()
1265
1266 key := event.Key()
1267
1268 if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) ||
1269 key == tcell.KeyEscape ||
1270 key == tcell.KeyTab ||
1271 key == tcell.KeyBacktab {
1272 if t.done != nil {
1273 t.Unlock()
1274 t.done(key)
1275 t.Lock()
1276 }
1277 return
1278 }
1279
1280
1281 previouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn
1282 var (
1283 validSelection = func(row, column int) bool {
1284 if row < t.fixedRows || row >= len(t.cells) || column < t.fixedColumns || column > t.lastColumn {
1285 return false
1286 }
1287 cell := t.cells[row][column]
1288 return cell == nil || !cell.NotSelectable
1289 }
1290
1291 home = func() {
1292 if t.rowsSelectable {
1293 t.selectedRow = 0
1294 t.selectedColumn = 0
1295 } else {
1296 t.trackEnd = false
1297 t.rowOffset = 0
1298 t.columnOffset = 0
1299 }
1300 }
1301
1302 end = func() {
1303 if t.rowsSelectable {
1304 t.selectedRow = len(t.cells) - 1
1305 t.selectedColumn = t.lastColumn
1306 } else {
1307 t.trackEnd = true
1308 t.columnOffset = 0
1309 }
1310 }
1311
1312 down = func() {
1313 if t.rowsSelectable {
1314 if validSelection(t.selectedRow+1, t.selectedColumn) {
1315 t.selectedRow++
1316 }
1317 } else {
1318 t.rowOffset++
1319 }
1320 }
1321
1322 up = func() {
1323 if t.rowsSelectable {
1324 if validSelection(t.selectedRow-1, t.selectedColumn) {
1325 t.selectedRow--
1326 }
1327 } else {
1328 t.trackEnd = false
1329 t.rowOffset--
1330 }
1331 }
1332
1333 left = func() {
1334 if t.columnsSelectable {
1335 if validSelection(t.selectedRow, t.selectedColumn-1) {
1336 t.selectedColumn--
1337 }
1338 } else {
1339 t.columnOffset--
1340 }
1341 }
1342
1343 right = func() {
1344 if t.columnsSelectable {
1345 if validSelection(t.selectedRow, t.selectedColumn+1) {
1346 t.selectedColumn++
1347 }
1348 } else {
1349 t.columnOffset++
1350 }
1351 }
1352
1353 pageDown = func() {
1354 offsetAmount := t.visibleRows - t.fixedRows
1355 if offsetAmount < 0 {
1356 offsetAmount = 0
1357 }
1358
1359 if t.rowsSelectable {
1360 t.selectedRow += offsetAmount
1361 if t.selectedRow >= len(t.cells) {
1362 t.selectedRow = len(t.cells) - 1
1363 }
1364 } else {
1365 t.rowOffset += offsetAmount
1366 }
1367 }
1368
1369 pageUp = func() {
1370 offsetAmount := t.visibleRows - t.fixedRows
1371 if offsetAmount < 0 {
1372 offsetAmount = 0
1373 }
1374
1375 if t.rowsSelectable {
1376 t.selectedRow -= offsetAmount
1377 if t.selectedRow < 0 {
1378 t.selectedRow = 0
1379 }
1380 } else {
1381 t.trackEnd = false
1382 t.rowOffset -= offsetAmount
1383 }
1384 }
1385 )
1386
1387 if HitShortcut(event, Keys.MoveFirst, Keys.MoveFirst2) {
1388 home()
1389 } else if HitShortcut(event, Keys.MoveLast, Keys.MoveLast2) {
1390 end()
1391 } else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2) {
1392 up()
1393 } else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2) {
1394 down()
1395 } else if HitShortcut(event, Keys.MoveLeft, Keys.MoveLeft2) {
1396 left()
1397 } else if HitShortcut(event, Keys.MoveRight, Keys.MoveRight2) {
1398 right()
1399 } else if HitShortcut(event, Keys.MovePreviousPage) {
1400 pageUp()
1401 } else if HitShortcut(event, Keys.MoveNextPage) {
1402 pageDown()
1403 } else if HitShortcut(event, Keys.Select, Keys.Select2) {
1404 if (t.rowsSelectable || t.columnsSelectable) && t.selected != nil {
1405 t.Unlock()
1406 t.selected(t.selectedRow, t.selectedColumn)
1407 t.Lock()
1408 }
1409 }
1410
1411
1412 if t.selectionChanged != nil && ((t.rowsSelectable && previouslySelectedRow != t.selectedRow) || (t.columnsSelectable && previouslySelectedColumn != t.selectedColumn)) {
1413 t.Unlock()
1414 t.selectionChanged(t.selectedRow, t.selectedColumn)
1415 t.Lock()
1416 }
1417 })
1418 }
1419
1420
1421 func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
1422 return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
1423 x, y := event.Position()
1424 if !t.InRect(x, y) {
1425 return false, nil
1426 }
1427
1428 switch action {
1429 case MouseLeftClick:
1430 _, tableY, _, _ := t.GetInnerRect()
1431 mul := 1
1432 maxY := tableY
1433 if t.borders {
1434 mul = 2
1435 maxY = tableY + 1
1436 }
1437
1438 if t.sortClicked && t.fixedRows > 0 && (y >= tableY && y < maxY+(t.fixedRows*mul)) {
1439 _, column := t.cellAt(x, y)
1440 if t.sortClickedColumn != column {
1441 t.sortClickedColumn = column
1442 t.sortClickedDescending = false
1443 } else {
1444 t.sortClickedDescending = !t.sortClickedDescending
1445 }
1446 t.Sort(column, t.sortClickedDescending)
1447
1448 if t.columnsSelectable {
1449 t.selectedColumn = column
1450 }
1451 } else if t.rowsSelectable || t.columnsSelectable {
1452 t.Select(t.cellAt(x, y))
1453 }
1454
1455 consumed = true
1456 setFocus(t)
1457 case MouseScrollUp:
1458 t.trackEnd = false
1459 t.rowOffset--
1460 consumed = true
1461 case MouseScrollDown:
1462 t.rowOffset++
1463 consumed = true
1464 }
1465
1466 return
1467 })
1468 }
1469
View as plain text