From 2cef1c04ba634f95572d551824f63e0a013386ed Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 8 Aug 2021 00:25:53 +0200 Subject: [PATCH] Rewrote Table to accommodate virtual tables. --- demos/table/virtualtable/README.md | 1 + demos/table/virtualtable/main.go | 44 ++ demos/table/virtualtable/screenshot.png | Bin 0 -> 65213 bytes table.go | 685 ++++++++++++++++-------- 4 files changed, 518 insertions(+), 212 deletions(-) create mode 100644 demos/table/virtualtable/README.md create mode 100644 demos/table/virtualtable/main.go create mode 100644 demos/table/virtualtable/screenshot.png diff --git a/demos/table/virtualtable/README.md b/demos/table/virtualtable/README.md new file mode 100644 index 0000000..2158c36 --- /dev/null +++ b/demos/table/virtualtable/README.md @@ -0,0 +1 @@ +![Screenshot](screenshot.png) \ No newline at end of file diff --git a/demos/table/virtualtable/main.go b/demos/table/virtualtable/main.go new file mode 100644 index 0000000..25dac72 --- /dev/null +++ b/demos/table/virtualtable/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "math" + + "github.com/rivo/tview" +) + +type TableData struct { + tview.TableContentReadOnly +} + +func (d *TableData) GetCell(row, column int) *tview.TableCell { + letters := [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A' + byte(row%26)} // log(math.MaxInt64) / log(26) ~= 14 + start := len(letters) - 1 + row /= 26 + for row > 0 { + start-- + row-- + letters[start] = 'A' + byte(row%26) + row /= 26 + } + return tview.NewTableCell(fmt.Sprintf("[red]%s[green]%d", letters[start:], column)) +} + +func (d *TableData) GetRowCount() int { + return math.MaxInt64 +} + +func (d *TableData) GetColumnCount() int { + return math.MaxInt64 +} + +func main() { + data := &TableData{} + table := tview.NewTable(). + SetBorders(false). + SetSelectable(true, true). + SetContent(data) + if err := tview.NewApplication().SetRoot(table, true).EnableMouse(true).Run(); err != nil { + panic(err) + } +} diff --git a/demos/table/virtualtable/screenshot.png b/demos/table/virtualtable/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..edc6b89f200eccef71a36b48da8b6a062b4551f4 GIT binary patch literal 65213 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 yN>`K3(J)@*3_h-xm69_2LHMkp(== len(t.cells) { + t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...) + } + rowLen := len(t.cells[row]) + if column >= rowLen { + t.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...) + for c := rowLen; c < column; c++ { + t.cells[row][c] = &TableCell{} + } + } + t.cells[row][column] = cell + if column > t.lastColumn { + t.lastColumn = column + } +} + +// RemoveRow removes a row from the data. +func (t *tableDefaultContent) RemoveRow(row int) { + if row < 0 || row >= len(t.cells) { + return + } + t.cells = append(t.cells[:row], t.cells[row+1:]...) +} + +// RemoveColumn removes a column from the data. +func (t *tableDefaultContent) RemoveColumn(column int) { + for row := range t.cells { + if column < 0 || column >= len(t.cells[row]) { + continue + } + t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...) + } +} + +// InsertRow inserts a new row at the given position. +func (t *tableDefaultContent) InsertRow(row int) { + if row >= len(t.cells) { + return + } + t.cells = append(t.cells, nil) // Extend by one. + copy(t.cells[row+1:], t.cells[row:]) // Shift down. + t.cells[row] = nil // New row is uninitialized. +} + +// InsertColumn inserts a new column at the given position. +func (t *tableDefaultContent) InsertColumn(column int) { + for row := range t.cells { + if column >= len(t.cells[row]) { + continue + } + t.cells[row] = append(t.cells[row], nil) // Extend by one. + copy(t.cells[row][column+1:], t.cells[row][column:]) // Shift to the right. + t.cells[row][column] = &TableCell{} // New element is an uninitialized table cell. + } +} + +// GetCell returns the cell at the given position. +func (t *tableDefaultContent) GetCell(row, column int) *TableCell { + if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) { + return nil + } + return t.cells[row][column] +} + +// GetRowCount returns the number of rows in the data set. +func (t *tableDefaultContent) GetRowCount() int { + return len(t.cells) +} + +// GetColumnCount returns the number of columns in the data set. +func (t *tableDefaultContent) GetColumnCount() int { + if len(t.cells) == 0 { + return 0 + } + return t.lastColumn + 1 +} + // Table visualizes two-dimensional data consisting of rows and columns. Each // Table cell is defined via SetCell() by the TableCell type. They can be added // dynamically to the table and changed any time. @@ -248,11 +434,8 @@ type Table struct { // If there are no borders, the column separator. separator rune - // The cells of the table. Rows first, then columns. - cells [][]*TableCell - - // The rightmost column in the data set. - lastColumn int + // The table's data structure. + content TableContent // If true, when calculating the widths of the columns, all rows are evaluated // instead of only the visible ones. @@ -268,6 +451,10 @@ type Table struct { // The currently selected row and column. selectedRow, selectedColumn int + // A temporary flag which causes the next call to Draw() to force the + // current selection to remain visible. Set to false afterwards. + clampToSelection bool + // The number of rows/columns by which the table is scrolled down/to the // right. rowOffset, columnOffset int @@ -306,18 +493,36 @@ type Table struct { // NewTable returns a new table. func NewTable() *Table { - return &Table{ + t := &Table{ Box: NewBox(), bordersColor: Styles.GraphicsColor, separator: ' ', - lastColumn: -1, } + t.SetContent(nil) + return t +} + +// SetContent sets a new content type for this table. This allows you to back +// the table by a data structure of your own, for example one that cannot be +// fully held in memory. For details, see the TableContent interface +// documentation. +// +// A value of nil will return the table to its default implementation where all +// of its table cells are kept in memory. +func (t *Table) SetContent(content TableContent) *Table { + if content != nil { + t.content = content + } else { + t.content = &tableDefaultContent{ + lastColumn: -1, + } + } + return t } // Clear removes all table data. func (t *Table) Clear() *Table { - t.cells = nil - t.lastColumn = -1 + t.content.Clear() return t } @@ -427,6 +632,9 @@ func (t *Table) GetOffset() (row, column int) { // // Set this flag to true to avoid shifting column widths when the table is // scrolled. (May be slower for large tables.) +// +// Use with caution on very large tables, especially those not backed by the +// default TableContent data structure. func (t *Table) SetEvaluateAllRows(all bool) *Table { t.evaluateAllRows = all return t @@ -470,20 +678,7 @@ func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table { // // To avoid unnecessary garbage collection, fill columns from left to right. func (t *Table) SetCell(row, column int, cell *TableCell) *Table { - if row >= len(t.cells) { - t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...) - } - rowLen := len(t.cells[row]) - if column >= rowLen { - t.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...) - for c := rowLen; c < column; c++ { - t.cells[row][c] = &TableCell{} - } - } - t.cells[row][column] = cell - if column > t.lastColumn { - t.lastColumn = column - } + t.content.SetCell(row, column, cell) return t } @@ -499,34 +694,24 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table { // be inserted. Therefore, repeated calls to this function may return different // pointers for uninitialized cells. func (t *Table) GetCell(row, column int) *TableCell { - if row >= len(t.cells) || column >= len(t.cells[row]) { - return &TableCell{} + cell := t.content.GetCell(row, column) + if cell == nil { + cell = &TableCell{} } - return t.cells[row][column] + return cell } // RemoveRow removes the row at the given position from the table. If there is // no such row, this has no effect. func (t *Table) RemoveRow(row int) *Table { - if row < 0 || row >= len(t.cells) { - return t - } - - t.cells = append(t.cells[:row], t.cells[row+1:]...) - + t.content.RemoveRow(row) return t } // RemoveColumn removes the column at the given position from the table. If // there is no such column, this has no effect. func (t *Table) RemoveColumn(column int) *Table { - for row := range t.cells { - if column < 0 || column >= len(t.cells[row]) { - continue - } - t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...) - } - + t.content.RemoveColumn(column) return t } @@ -534,12 +719,7 @@ func (t *Table) RemoveColumn(column int) *Table { // given row and below will be shifted to the bottom by one row. If "row" is // equal or larger than the current number of rows, this function has no effect. func (t *Table) InsertRow(row int) *Table { - if row >= len(t.cells) { - return t - } - t.cells = append(t.cells, nil) // Extend by one. - copy(t.cells[row+1:], t.cells[row:]) // Shift down. - t.cells[row] = nil // New row is uninitialized. + t.content.InsertRow(row) return t } @@ -548,28 +728,18 @@ func (t *Table) InsertRow(row int) *Table { // column. Rows that have fewer initialized cells than "column" will remain // unchanged. func (t *Table) InsertColumn(column int) *Table { - for row := range t.cells { - if column >= len(t.cells[row]) { - continue - } - t.cells[row] = append(t.cells[row], nil) // Extend by one. - copy(t.cells[row][column+1:], t.cells[row][column:]) // Shift to the right. - t.cells[row][column] = &TableCell{} // New element is an uninitialized table cell. - } + t.content.InsertColumn(column) return t } // GetRowCount returns the number of rows in the table. func (t *Table) GetRowCount() int { - return len(t.cells) + return t.content.GetRowCount() } // GetColumnCount returns the (maximum) number of columns in the table. func (t *Table) GetColumnCount() int { - if len(t.cells) == 0 { - return 0 - } - return t.lastColumn + 1 + return t.content.GetColumnCount() } // cellAt returns the row and column located at the given screen coordinates. @@ -591,7 +761,7 @@ func (t *Table) cellAt(x, y int) (row, column int) { if row >= t.fixedRows { row += t.rowOffset } - if row >= len(t.cells) { + if row >= t.content.GetRowCount() { row = -1 } } @@ -632,7 +802,7 @@ func (t *Table) ScrollToBeginning() *Table { func (t *Table) ScrollToEnd() *Table { t.trackEnd = true t.columnOffset = 0 - t.rowOffset = len(t.cells) + t.rowOffset = t.content.GetRowCount() return t } @@ -643,21 +813,16 @@ func (t *Table) Draw(screen tcell.Screen) { // What's our available screen space? _, totalHeight := screen.Size() x, y, width, height := t.GetInnerRect() + netWidth := width if t.borders { t.visibleRows = height / 2 + netWidth -= 2 } else { t.visibleRows = height } - // Return the cell at the specified position (nil if it doesn't exist). - getCell := func(row, column int) *TableCell { - if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) { - return nil - } - return t.cells[row][column] - } - // If this cell is not selectable, find the next one. + rowCount, columnCount := t.content.GetRowCount(), t.content.GetColumnCount() if t.rowsSelectable || t.columnsSelectable { if t.selectedColumn < 0 { t.selectedColumn = 0 @@ -665,27 +830,30 @@ func (t *Table) Draw(screen tcell.Screen) { if t.selectedRow < 0 { t.selectedRow = 0 } - for t.selectedRow < len(t.cells) { - cell := getCell(t.selectedRow, t.selectedColumn) + for t.selectedRow < rowCount { + cell := t.content.GetCell(t.selectedRow, t.selectedColumn) if cell != nil && !cell.NotSelectable { break } t.selectedColumn++ - if t.selectedColumn > t.lastColumn { + if t.selectedColumn > columnCount-1 { t.selectedColumn = 0 t.selectedRow++ } } } - // Clamp row offsets. - if t.rowsSelectable { + // Clamp row offsets if requested. + defer func() { + t.clampToSelection = false // Only once. + }() + if t.clampToSelection && t.rowsSelectable { if t.selectedRow >= t.fixedRows && t.selectedRow < t.fixedRows+t.rowOffset { t.rowOffset = t.selectedRow - t.fixedRows t.trackEnd = false } if t.borders { - if 2*(t.selectedRow+1-t.rowOffset) >= height { + if t.selectedRow+1-t.rowOffset >= height/2 { t.rowOffset = t.selectedRow + 1 - height/2 t.trackEnd = false } @@ -696,52 +864,49 @@ func (t *Table) Draw(screen tcell.Screen) { } } } + if t.rowOffset < 0 { + t.rowOffset = 0 + } if t.borders { - if 2*(len(t.cells)-t.rowOffset) < height { + if rowCount-t.rowOffset < height/2 { t.trackEnd = true } } else { - if len(t.cells)-t.rowOffset < height { + if rowCount-t.rowOffset < height { t.trackEnd = true } } if t.trackEnd { if t.borders { - t.rowOffset = len(t.cells) - height/2 + t.rowOffset = rowCount - height/2 } else { - t.rowOffset = len(t.cells) - height + t.rowOffset = rowCount - height } } if t.rowOffset < 0 { t.rowOffset = 0 } - // Clamp column offset. (Only left side here. The right side is more - // difficult and we'll do it below.) - if t.columnsSelectable && t.selectedColumn >= t.fixedColumns && t.selectedColumn < t.fixedColumns+t.columnOffset { - t.columnOffset = t.selectedColumn - t.fixedColumns + // Avoid invalid column offsets. + if t.columnOffset >= columnCount-t.fixedColumns { + t.columnOffset = columnCount - t.fixedColumns - 1 } if t.columnOffset < 0 { t.columnOffset = 0 } - if t.selectedColumn < 0 { - t.selectedColumn = 0 - } - // Determine the indices and widths of the columns and rows which fit on the - // screen. + // Determine the indices of the rows which fit on the screen. var ( - columns, rows, allRows, widths []int - tableHeight, tableWidth int + rows, allRows []int + tableHeight int ) rowStep := 1 if t.borders { - rowStep = 2 // With borders, every table row takes two screen rows. - tableWidth = 1 // We start at the second character because of the left table border. + rowStep = 2 // With borders, every table row takes two screen rows. } if t.evaluateAllRows { - allRows = make([]int, len(t.cells)) - for row := range t.cells { + allRows = make([]int, rowCount) + for row := 0; row < rowCount; row++ { allRows[row] = row } } @@ -753,60 +918,39 @@ func (t *Table) Draw(screen tcell.Screen) { tableHeight += rowStep return true } - for row := 0; row < t.fixedRows && row < len(t.cells); row++ { // Do the fixed rows first. + for row := 0; row < t.fixedRows && row < rowCount; row++ { // Do the fixed rows first. if !indexRow(row) { break } } - for row := t.fixedRows + t.rowOffset; row < len(t.cells); row++ { // Then the remaining rows. + for row := t.fixedRows + t.rowOffset; row < rowCount; row++ { // Then the remaining rows. if !indexRow(row) { break } } + + // Determine the columns' indices, widths, and expansion values that fit on + // the screen. var ( - skipped, lastTableWidth, expansionTotal int - expansions []int + tableWidth, expansionTotal int + columns, widths, expansions []int ) -ColumnLoop: - for column := 0; ; column++ { - // If we've moved beyond the right border, we stop or skip a column. - for tableWidth-1 >= width { // -1 because we include one extra column if the separator falls on the right end of the box. - // We've moved beyond the available space. - if column < t.fixedColumns { - break ColumnLoop // We're in the fixed area. We're done. - } - if !t.columnsSelectable && skipped >= t.columnOffset { - break ColumnLoop // There is no selection and we've already reached the offset. - } - if t.columnsSelectable && t.selectedColumn-skipped == t.fixedColumns { - break ColumnLoop // The selected column reached the leftmost point before disappearing. - } - if t.columnsSelectable && skipped >= t.columnOffset && - (t.selectedColumn < column && lastTableWidth < width-1 && tableWidth < width-1 || t.selectedColumn < column-1) { - break ColumnLoop // We've skipped as many as requested and the selection is visible. - } - if len(columns) <= t.fixedColumns { - break // Nothing to skip. - } + includesSelection := !t.clampToSelection || !t.columnsSelectable - // We need to skip a column. - skipped++ - lastTableWidth -= widths[t.fixedColumns] + 1 - tableWidth -= widths[t.fixedColumns] + 1 - columns = append(columns[:t.fixedColumns], columns[t.fixedColumns+1:]...) - widths = append(widths[:t.fixedColumns], widths[t.fixedColumns+1:]...) - expansions = append(expansions[:t.fixedColumns], expansions[t.fixedColumns+1:]...) + // Helper function that evaluates one column. Returns true if the column + // didn't fit at all. + indexColumn := func(column int) bool { + if netWidth == 0 || tableWidth >= netWidth { + return true } - // What's this column's width (without expansion)? - maxWidth := -1 - expansion := 0 + var maxWidth, expansion int evaluationRows := rows if t.evaluateAllRows { evaluationRows = allRows } for _, row := range evaluationRows { - if cell := getCell(row, column); cell != nil { + if cell := t.content.GetCell(row, column); cell != nil { _, _, _, _, _, _, cellWidth := decomposeString(cell.Text, true, false) if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth { cellWidth = cell.MaxWidth @@ -819,23 +963,107 @@ ColumnLoop: } } } - if maxWidth < 0 { - break // No more cells found in this column. + clampedMaxWidth := maxWidth + if tableWidth+maxWidth > netWidth { + clampedMaxWidth = netWidth - tableWidth } - - // Store new column info at the end. columns = append(columns, column) - widths = append(widths, maxWidth) - lastTableWidth = tableWidth - tableWidth += maxWidth + 1 + widths = append(widths, clampedMaxWidth) expansions = append(expansions, expansion) + tableWidth += clampedMaxWidth + 1 expansionTotal += expansion + if t.columnsSelectable && t.clampToSelection && column == t.selectedColumn { + // We want selections to appear fully. + includesSelection = clampedMaxWidth == maxWidth + } + + return false + } + + // Helper function that evaluates multiple columns, starting at "start" and + // at most ending at "maxEnd". Returns first column not included anymore (or + // -1 if all are included). + indexColumns := func(start, maxEnd int) int { + if start == maxEnd { + return -1 + } + + if start < maxEnd { + // Forward-evaluate columns. + for column := start; column < maxEnd; column++ { + if indexColumn(column) { + return column + } + } + return -1 + } + + // Backward-evaluate columns. + startLen := len(columns) + defer func() { + // Becaue we went backwards, we must reverse the partial slices. + for i, j := startLen, len(columns)-1; i < j; i, j = i+1, j-1 { + columns[i], columns[j] = columns[j], columns[i] + widths[i], widths[j] = widths[j], widths[i] + expansions[i], expansions[j] = expansions[j], expansions[i] + } + }() + for column := start; column >= maxEnd; column-- { + if indexColumn(column) { + return column + } + } + return -1 + } + + // Reset the table to only its fixed columns. + var fixedTableWidth, fixedExpansionTotal int + resetColumns := func() { + tableWidth = fixedTableWidth + expansionTotal = fixedExpansionTotal + columns = columns[:t.fixedColumns] + widths = widths[:t.fixedColumns] + expansions = expansions[:t.fixedColumns] + } + + // Add fixed columns. + if indexColumns(0, t.fixedColumns) < 0 { + fixedTableWidth = tableWidth + fixedExpansionTotal = expansionTotal + + // Add unclamped columns. + if column := indexColumns(t.fixedColumns+t.columnOffset, columnCount); !includesSelection || column < 0 && t.columnOffset > 0 { + // Offset is not optimal. Try again. + if !includesSelection { + // Clamp to selection. + resetColumns() + if t.selectedColumn <= t.fixedColumns+t.columnOffset { + // It's on the left. Start with the selection. + t.columnOffset = t.selectedColumn - t.fixedColumns + indexColumns(t.fixedColumns+t.columnOffset, columnCount) + } else { + // It's on the right. End with the selection. + if column := indexColumns(t.selectedColumn, t.fixedColumns); column >= 0 { + t.columnOffset = column + 1 - t.fixedColumns + } else { + t.columnOffset = 0 + } + } + } else if tableWidth < netWidth { + // Don't waste space. Try to fit as much on screen as possible. + resetColumns() + if column := indexColumns(columnCount-1, t.fixedColumns); column >= 0 { + t.columnOffset = column + 1 - t.fixedColumns + } else { + t.columnOffset = 0 + } + } + } } - t.columnOffset = skipped // If we have space left, distribute it. - if tableWidth < width { - toDistribute := width - tableWidth + if tableWidth < netWidth { + toDistribute := netWidth - tableWidth for index, expansion := range expansions { if expansionTotal <= 0 { break @@ -855,8 +1083,8 @@ ColumnLoop: // Draw the cells (and borders). var columnX int - if !t.borders { - columnX-- + if t.borders { + columnX++ } for columnIndex, column := range columns { columnWidth := widths[columnIndex] @@ -864,79 +1092,97 @@ ColumnLoop: if t.borders { // Draw borders. rowY *= 2 - for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ { - drawBorder(columnX+pos+1, rowY, Borders.Horizontal) + for pos := 0; pos < columnWidth && columnX+pos < width; pos++ { + drawBorder(columnX+pos, rowY, Borders.Horizontal) } ch := Borders.Cross - if columnIndex == 0 { - if rowY == 0 { + if row == 0 { + if column == 0 { ch = Borders.TopLeft } else { - ch = Borders.LeftT + ch = Borders.TopT } - } else if rowY == 0 { - ch = Borders.TopT + } else if column == 0 { + ch = Borders.LeftT } - drawBorder(columnX, rowY, ch) + drawBorder(columnX-1, rowY, ch) rowY++ if rowY >= height || y+rowY >= totalHeight { break // No space for the text anymore. } - drawBorder(columnX, rowY, Borders.Vertical) - } else if columnIndex > 0 { + drawBorder(columnX-1, rowY, Borders.Vertical) + } else if column < columnCount-1 { // Draw separator. - drawBorder(columnX, rowY, t.separator) + drawBorder(columnX+columnWidth, rowY, t.separator) } // Get the cell. - cell := getCell(row, column) + cell := t.content.GetCell(row, column) if cell == nil { continue } // Draw text. finalWidth := columnWidth - if columnX+1+columnWidth >= width { - finalWidth = width - columnX - 1 + if columnX+columnWidth >= width { + finalWidth = width - columnX } - cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth - _, printed, _, _ := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, 0, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color).Attributes(cell.Attributes), true) + cell.x, cell.y, cell.width = x+columnX, y+rowY, finalWidth + _, printed, _, _ := printWithStyle(screen, cell.Text, x+columnX, y+rowY, 0, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color).Attributes(cell.Attributes), true) if TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 { - _, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY) - printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth, 0, y+rowY, 1, AlignLeft, style, false) + _, _, style, _ := screen.GetContent(x+columnX+finalWidth-1, y+rowY) + printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth-1, y+rowY, 0, 1, AlignLeft, style, false) } } // Draw bottom border. - if rowY := 2 * len(rows); t.borders && rowY < height { + if rowY := 2 * len(rows); t.borders && rowY > 0 && rowY < height { for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ { - drawBorder(columnX+pos+1, rowY, Borders.Horizontal) + drawBorder(columnX+pos, rowY, Borders.Horizontal) } - ch := Borders.BottomT - if columnIndex == 0 { + ch := Borders.Cross + if rows[len(rows)-1] == rowCount-1 { + if column == 0 { + ch = Borders.BottomLeft + } else { + ch = Borders.BottomT + } + } else if column == 0 { ch = Borders.BottomLeft } - drawBorder(columnX, rowY, ch) + drawBorder(columnX-1, rowY, ch) } columnX += columnWidth + 1 } // Draw right border. - if t.borders && len(t.cells) > 0 && columnX < width { + columnX-- + if t.borders && len(rows) > 0 && len(columns) > 0 && columnX < width { + lastColumn := columns[len(columns)-1] == columnCount-1 for rowY := range rows { rowY *= 2 if rowY+1 < height { drawBorder(columnX, rowY+1, Borders.Vertical) } - ch := Borders.RightT + ch := Borders.Cross if rowY == 0 { - ch = Borders.TopRight + if lastColumn { + ch = Borders.TopRight + } else { + ch = Borders.TopT + } + } else if lastColumn { + ch = Borders.RightT } drawBorder(columnX, rowY, ch) } if rowY := 2 * len(rows); rowY < height { - drawBorder(columnX, rowY, Borders.BottomRight) + ch := Borders.BottomT + if lastColumn { + ch = Borders.BottomRight + } + drawBorder(columnX, rowY, ch) } } @@ -984,7 +1230,7 @@ ColumnLoop: rowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow for columnIndex, column := range columns { columnWidth := widths[columnIndex] - cell := getCell(row, column) + cell := t.content.GetCell(row, column) if cell == nil { continue } @@ -1058,56 +1304,65 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi // Movement functions. previouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn + lastColumn := t.content.GetColumnCount() - 1 + rowCount := t.content.GetRowCount() var ( - getCell = func(row, column int) *TableCell { - if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) { - return nil - } - return t.cells[row][column] - } - previous = func() { - for t.selectedRow >= 0 { - cell := getCell(t.selectedRow, t.selectedColumn) + startRow := t.selectedRow + startColumn := t.selectedColumn + for { + cell := t.content.GetCell(t.selectedRow, t.selectedColumn) if cell != nil && !cell.NotSelectable { return } t.selectedColumn-- if t.selectedColumn < 0 { - t.selectedColumn = t.lastColumn t.selectedRow-- + if t.selectedRow < 0 { + t.selectedRow = rowCount - 1 + } + } + if t.selectedColumn == startColumn && t.selectedRow == startRow { + t.selectedColumn = -1 + t.selectedRow = -1 + return } } } next = func() { - if t.selectedColumn > t.lastColumn { - t.selectedColumn = 0 - t.selectedRow++ - if t.selectedRow >= len(t.cells) { - t.selectedRow = len(t.cells) - 1 - } - } - for t.selectedRow < len(t.cells) { - cell := getCell(t.selectedRow, t.selectedColumn) - if cell != nil && !cell.NotSelectable { - return + startRow := t.selectedRow + startColumn := t.selectedColumn + for { + if t.selectedColumn <= lastColumn { + cell := t.content.GetCell(t.selectedRow, t.selectedColumn) + if cell != nil && !cell.NotSelectable { + return + } } - t.selectedColumn++ - if t.selectedColumn > t.lastColumn { + if t.selectedColumn >= lastColumn { t.selectedColumn = 0 - t.selectedRow++ + if t.selectedRow >= rowCount-1 { + t.selectedRow = 0 + } else { + t.selectedRow++ + } + } else { + t.selectedColumn++ + } + if t.selectedColumn == startColumn && t.selectedRow == startRow { + t.selectedColumn = -1 + t.selectedRow = -1 + return } } - t.selectedColumn = t.lastColumn - t.selectedRow = len(t.cells) - 1 - previous() } home = func() { if t.rowsSelectable { t.selectedRow = 0 t.selectedColumn = 0 + t.clampToSelection = true next() } else { t.trackEnd = false @@ -1118,8 +1373,9 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi end = func() { if t.rowsSelectable { - t.selectedRow = len(t.cells) - 1 - t.selectedColumn = t.lastColumn + t.selectedRow = rowCount - 1 + t.selectedColumn = lastColumn + t.clampToSelection = true previous() } else { t.trackEnd = true @@ -1130,9 +1386,10 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi down = func() { if t.rowsSelectable { t.selectedRow++ - if t.selectedRow >= len(t.cells) { - t.selectedRow = len(t.cells) - 1 + if t.selectedRow >= rowCount { + t.selectedRow = rowCount - 1 } + t.clampToSelection = true next() } else { t.rowOffset++ @@ -1145,6 +1402,7 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi if t.selectedRow < 0 { t.selectedRow = 0 } + t.clampToSelection = true previous() } else { t.trackEnd = false @@ -1156,8 +1414,13 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi if t.columnsSelectable { t.selectedColumn-- if t.selectedColumn < 0 { - t.selectedColumn = 0 + t.selectedColumn = lastColumn + t.selectedRow-- + if t.selectedRow < 0 { + t.selectedRow = rowCount - 1 + } } + t.clampToSelection = true previous() } else { t.columnOffset-- @@ -1167,6 +1430,7 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi right = func() { if t.columnsSelectable { t.selectedColumn++ + t.clampToSelection = true next() } else { t.columnOffset++ @@ -1181,9 +1445,10 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi if t.rowsSelectable { t.selectedRow += offsetAmount - if t.selectedRow >= len(t.cells) { - t.selectedRow = len(t.cells) - 1 + if t.selectedRow >= rowCount { + t.selectedRow = rowCount - 1 } + t.clampToSelection = true next() } else { t.rowOffset += offsetAmount @@ -1201,6 +1466,7 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi if t.selectedRow < 0 { t.selectedRow = 0 } + t.clampToSelection = true previous() } else { t.trackEnd = false @@ -1268,15 +1534,10 @@ func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, case MouseLeftClick: selectEvent := true row, column := t.cellAt(x, y) - if row >= 0 && row < len(t.cells) && column >= 0 { - cells := t.cells[row] - if column < len(cells) { - cell := cells[column] - if cell != nil && cell.Clicked != nil { - if noSelect := cell.Clicked(); noSelect { - selectEvent = false - } - } + cell := t.content.GetCell(row, column) + if cell != nil && cell.Clicked != nil { + if noSelect := cell.Clicked(); noSelect { + selectEvent = false } } if selectEvent && (t.rowsSelectable || t.columnsSelectable) {