From 64fa922ec013079f8f0c90fc9e93c56db3611d30 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 22 Apr 2018 21:25:06 +0300 Subject: Switch to dep --- vendor/maunium.net/go/tview/table.go | 1025 ++++++++++++++++++++++++++++++++++ 1 file changed, 1025 insertions(+) create mode 100644 vendor/maunium.net/go/tview/table.go (limited to 'vendor/maunium.net/go/tview/table.go') diff --git a/vendor/maunium.net/go/tview/table.go b/vendor/maunium.net/go/tview/table.go new file mode 100644 index 0000000..4f2336f --- /dev/null +++ b/vendor/maunium.net/go/tview/table.go @@ -0,0 +1,1025 @@ +package tview + +import ( + "sort" + + "maunium.net/go/tcell" + colorful "github.com/lucasb-eyer/go-colorful" +) + +// TableCell represents one cell inside a Table. You can instantiate this type +// directly but all colors (background and text) will be set to their default +// which is black. +type TableCell struct { + // The text to be displayed in the table cell. + Text string + + // The alignment of the cell text. One of AlignLeft (default), AlignCenter, + // or AlignRight. + Align int + + // The maximum width of the cell in screen space. This is used to give a + // column a maximum width. Any cell text whose screen width exceeds this width + // is cut off. Set to 0 if there is no maximum width. + MaxWidth int + + // If the total table width is less than the available width, this value is + // used to add extra width to a column. See SetExpansion() for details. + Expansion int + + // The color of the cell text. + Color tcell.Color + + // The background color of the cell. + BackgroundColor tcell.Color + + // If set to true, this cell cannot be selected. + NotSelectable bool + + // The position and width of the cell the last time table was drawn. + x, y, width int +} + +// NewTableCell returns a new table cell with sensible defaults. That is, left +// aligned text with the primary text color (see Styles) and a transparent +// background (using the background of the Table). +func NewTableCell(text string) *TableCell { + return &TableCell{ + Text: text, + Align: AlignLeft, + Color: Styles.PrimaryTextColor, + BackgroundColor: tcell.ColorDefault, + } +} + +// SetText sets the cell's text. +func (c *TableCell) SetText(text string) *TableCell { + c.Text = text + return c +} + +// SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or +// AlignRight. +func (c *TableCell) SetAlign(align int) *TableCell { + c.Align = align + return c +} + +// SetMaxWidth sets maximum width of the cell in screen space. This is used to +// give a column a maximum width. Any cell text whose screen width exceeds this +// width is cut off. Set to 0 if there is no maximum width. +func (c *TableCell) SetMaxWidth(maxWidth int) *TableCell { + c.MaxWidth = maxWidth + return c +} + +// SetExpansion sets the value by which the column of this cell expands if the +// available width for the table is more than the table width (prior to applying +// this expansion value). This is a proportional value. The amount of unused +// horizontal space is divided into widths to be added to each column. How much +// extra width a column receives depends on the expansion value: A value of 0 +// (the default) will not cause the column to increase in width. Other values +// are proportional, e.g. a value of 2 will cause a column to grow by twice +// the amount of a column with a value of 1. +// +// Since this value affects an entire column, the maximum over all visible cells +// in that column is used. +// +// This function panics if a negative value is provided. +func (c *TableCell) SetExpansion(expansion int) *TableCell { + if expansion < 0 { + panic("Table cell expansion values may not be negative") + } + c.Expansion = expansion + return c +} + +// SetTextColor sets the cell's text color. +func (c *TableCell) SetTextColor(color tcell.Color) *TableCell { + c.Color = color + return c +} + +// SetBackgroundColor sets the cell's background color. Set to +// tcell.ColorDefault to use the table's background color. +func (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell { + c.BackgroundColor = color + return c +} + +// SetSelectable sets whether or not this cell can be selected by the user. +func (c *TableCell) SetSelectable(selectable bool) *TableCell { + c.NotSelectable = !selectable + return c +} + +// GetLastPosition returns the position of the table cell the last time it was +// drawn on screen. If the cell is not on screen, the return values are +// undefined. +// +// Because the Table class will attempt to keep selected cells on screen, this +// function is most useful in response to a "selected" event (see +// SetSelectedFunc()) or a "selectionChanged" event (see +// SetSelectionChangedFunc()). +func (c *TableCell) GetLastPosition() (x, y, width int) { + return c.x, c.y, c.width +} + +// 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. +// +// The most compact display of a table is without borders. Each row will then +// occupy one row on screen and columns are separated by the rune defined via +// SetSeparator() (a space character by default). +// +// When borders are turned on (via SetBorders()), each table cell is surrounded +// by lines. Therefore one table row will require two rows on screen. +// +// Columns will use as much horizontal space as they need. You can constrain +// their size with the MaxWidth parameter of the TableCell type. +// +// Fixed Columns +// +// You can define fixed rows and rolumns via SetFixed(). They will always stay +// in their place, even when the table is scrolled. Fixed rows are always the +// top rows. Fixed columns are always the leftmost columns. +// +// Selections +// +// You can call SetSelectable() to set columns and/or rows to "selectable". If +// the flag is set only for columns, entire columns can be selected by the user. +// If it is set only for rows, entire rows can be selected. If both flags are +// set, individual cells can be selected. The "selected" handler set via +// SetSelectedFunc() is invoked when the user presses Enter on a selection. +// +// Navigation +// +// If the table extends beyond the available space, it can be navigated with +// key bindings similar to Vim: +// +// - h, left arrow: Move left by one column. +// - l, right arrow: Move right by one column. +// - j, down arrow: Move down by one row. +// - k, up arrow: Move up by one row. +// - g, home: Move to the top. +// - G, end: Move to the bottom. +// - Ctrl-F, page down: Move down by one page. +// - Ctrl-B, page up: Move up by one page. +// +// When there is no selection, this affects the entire table (except for fixed +// rows and columns). When there is a selection, the user moves the selection. +// The class will attempt to keep the selection from moving out of the screen. +// +// Use SetInputCapture() to override or modify keyboard input. +// +// See https://github.com/rivo/tview/wiki/Table for an example. +type Table struct { + *Box + + // Whether or not this table has borders around each cell. + borders bool + + // The color of the borders or the separator. + bordersColor tcell.Color + + // 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 number of fixed rows / columns. + fixedRows, fixedColumns int + + // Whether or not rows or columns can be selected. If both are set to true, + // cells can be selected. + rowsSelectable, columnsSelectable bool + + // The currently selected row and column. + selectedRow, selectedColumn int + + // The number of rows/columns by which the table is scrolled down/to the + // right. + rowOffset, columnOffset int + + // If set to true, the table's last row will always be visible. + trackEnd bool + + // The number of visible rows the last time the table was drawn. + visibleRows int + + // An optional function which gets called when the user presses Enter on a + // selected cell. If entire rows selected, the column value is undefined. + // Likewise for entire columns. + selected func(row, column int) + + // An optional function which gets called when the user changes the selection. + // If entire rows selected, the column value is undefined. + // Likewise for entire columns. + selectionChanged func(row, column int) + + // An optional function which gets called when the user presses Escape, Tab, + // or Backtab. Also when the user presses Enter if nothing is selectable. + done func(key tcell.Key) +} + +// NewTable returns a new table. +func NewTable() *Table { + return &Table{ + Box: NewBox(), + bordersColor: Styles.GraphicsColor, + separator: ' ', + lastColumn: -1, + } +} + +// Clear removes all table data. +func (t *Table) Clear() *Table { + t.cells = nil + t.lastColumn = -1 + return t +} + +// SetBorders sets whether or not each cell in the table is surrounded by a +// border. +func (t *Table) SetBorders(show bool) *Table { + t.borders = show + return t +} + +// SetBordersColor sets the color of the cell borders. +func (t *Table) SetBordersColor(color tcell.Color) *Table { + t.bordersColor = color + return t +} + +// SetSeparator sets the character used to fill the space between two +// neighboring cells. This is a space character ' ' per default but you may +// want to set it to GraphicsVertBar (or any other rune) if the column +// separation should be more visible. If cell borders are activated, this is +// ignored. +// +// Separators have the same color as borders. +func (t *Table) SetSeparator(separator rune) *Table { + t.separator = separator + return t +} + +// SetFixed sets the number of fixed rows and columns which are always visible +// even when the rest of the cells are scrolled out of view. Rows are always the +// top-most ones. Columns are always the left-most ones. +func (t *Table) SetFixed(rows, columns int) *Table { + t.fixedRows, t.fixedColumns = rows, columns + return t +} + +// SetSelectable sets the flags which determine what can be selected in a table. +// There are three selection modi: +// +// - rows = false, columns = false: Nothing can be selected. +// - rows = true, columns = false: Rows can be selected. +// - rows = false, columns = true: Columns can be selected. +// - rows = true, columns = true: Individual cells can be selected. +func (t *Table) SetSelectable(rows, columns bool) *Table { + t.rowsSelectable, t.columnsSelectable = rows, columns + return t +} + +// GetSelectable returns what can be selected in a table. Refer to +// SetSelectable() for details. +func (t *Table) GetSelectable() (rows, columns bool) { + return t.rowsSelectable, t.columnsSelectable +} + +// GetSelection returns the position of the current selection. +// If entire rows are selected, the column index is undefined. +// Likewise for entire columns. +func (t *Table) GetSelection() (row, column int) { + return t.selectedRow, t.selectedColumn +} + +// Select sets the selected cell. Depending on the selection settings +// specified via SetSelectable(), this may be an entire row or column, or even +// ignored completely. +func (t *Table) Select(row, column int) *Table { + t.selectedRow, t.selectedColumn = row, column + return t +} + +// SetOffset sets how many rows and columns should be skipped when drawing the +// table. This is useful for large tables that do not fit on the screen. +// Navigating a selection can change these values. +// +// Fixed rows and columns are never skipped. +func (t *Table) SetOffset(row, column int) *Table { + t.rowOffset, t.columnOffset = row, column + return t +} + +// GetOffset returns the current row and column offset. This indicates how many +// rows and columns the table is scrolled down and to the right. +func (t *Table) GetOffset() (row, column int) { + return t.rowOffset, t.columnOffset +} + +// SetSelectedFunc sets a handler which is called whenever the user presses the +// Enter key on a selected cell/row/column. The handler receives the position of +// the selection and its cell contents. If entire rows are selected, the column +// index is undefined. Likewise for entire columns. +func (t *Table) SetSelectedFunc(handler func(row, column int)) *Table { + t.selected = handler + return t +} + +// SetSelectionChangedFunc sets a handler which is called whenever the user +// navigates to a new selection. The handler receives the position of the new +// selection. If entire rows are selected, the column index is undefined. +// Likewise for entire columns. +func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table { + t.selectionChanged = handler + return t +} + +// SetDoneFunc sets a handler which is called whenever the user presses the +// Escape, Tab, or Backtab key. If nothing is selected, it is also called when +// user presses the Enter key (because pressing Enter on a selection triggers +// the "selected" handler set via SetSelectedFunc()). +func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table { + t.done = handler + return t +} + +// SetCell sets the content of a cell the specified position. It is ok to +// directly instantiate a TableCell object. If the cell has contain, at least +// the Text and Color fields should be set. +// +// Note that setting cells in previously unknown rows and columns will +// automatically extend the internal table representation, e.g. starting with +// a row of 100,000 will immediately create 100,000 empty rows. +// +// 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 + } + return t +} + +// SetCellSimple calls SetCell() with the given text, left-aligned, in white. +func (t *Table) SetCellSimple(row, column int, text string) *Table { + t.SetCell(row, column, NewTableCell(text)) + return t +} + +// GetCell returns the contents of the cell at the specified position. A valid +// TableCell object is always returns but it will be uninitialized if the cell +// was not previously set. +func (t *Table) GetCell(row, column int) *TableCell { + if row >= len(t.cells) || column >= len(t.cells[row]) { + return &TableCell{} + } + return t.cells[row][column] +} + +// GetRowCount returns the number of rows in the table. +func (t *Table) GetRowCount() int { + return len(t.cells) +} + +// 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 +} + +// ScrollToBeginning scrolls the table to the beginning to that the top left +// corner of the table is shown. Note that this position may be corrected if +// there is a selection. +func (t *Table) ScrollToBeginning() *Table { + t.trackEnd = false + t.columnOffset = 0 + t.rowOffset = 0 + return t +} + +// ScrollToEnd scrolls the table to the beginning to that the bottom left corner +// of the table is shown. Adding more rows to the table will cause it to +// automatically scroll with the new data. Note that this position may be +// corrected if there is a selection. +func (t *Table) ScrollToEnd() *Table { + t.trackEnd = true + t.columnOffset = 0 + t.rowOffset = len(t.cells) + return t +} + +// Draw draws this primitive onto the screen. +func (t *Table) Draw(screen tcell.Screen) { + t.Box.Draw(screen) + + // What's our available screen space? + x, y, width, height := t.GetInnerRect() + if t.borders { + t.visibleRows = height / 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. + if t.rowsSelectable || t.columnsSelectable { + if t.selectedColumn < 0 { + t.selectedColumn = 0 + } + if t.selectedRow < 0 { + t.selectedRow = 0 + } + for t.selectedRow < len(t.cells) { + cell := getCell(t.selectedRow, t.selectedColumn) + if cell == nil || !cell.NotSelectable { + break + } + t.selectedColumn++ + if t.selectedColumn > t.lastColumn { + t.selectedColumn = 0 + t.selectedRow++ + } + } + } + + // Clamp row offsets. + if 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 { + t.rowOffset = t.selectedRow + 1 - height/2 + t.trackEnd = false + } + } else { + if t.selectedRow+1-t.rowOffset >= height { + t.rowOffset = t.selectedRow + 1 - height + t.trackEnd = false + } + } + } + if t.borders { + if 2*(len(t.cells)-t.rowOffset) < height { + t.trackEnd = true + } + } else { + if len(t.cells)-t.rowOffset < height { + t.trackEnd = true + } + } + if t.trackEnd { + if t.borders { + t.rowOffset = len(t.cells) - height/2 + } else { + t.rowOffset = len(t.cells) - 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 + } + 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. + var ( + columns, rows, widths []int + tableHeight, tableWidth 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. + } + indexRow := func(row int) bool { // Determine if this row is visible, store its index. + if tableHeight >= height { + return false + } + rows = append(rows, row) + tableHeight += rowStep + return true + } + for row := 0; row < t.fixedRows && row < len(t.cells); 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. + if !indexRow(row) { + break + } + } + var ( + skipped, lastTableWidth, expansionTotal int + 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. + } + + // 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:]...) + } + + // What's this column's width (without expansion)? + maxWidth := -1 + expansion := 0 + for _, row := range rows { + if cell := getCell(row, column); cell != nil { + cellWidth := StringWidth(cell.Text) + if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth { + cellWidth = cell.MaxWidth + } + if cellWidth > maxWidth { + maxWidth = cellWidth + } + if cell.Expansion > expansion { + expansion = cell.Expansion + } + } + } + if maxWidth < 0 { + break // No more cells found in this column. + } + + // Store new column info at the end. + columns = append(columns, column) + widths = append(widths, maxWidth) + lastTableWidth = tableWidth + tableWidth += maxWidth + 1 + expansions = append(expansions, expansion) + expansionTotal += expansion + } + t.columnOffset = skipped + + // If we have space left, distribute it. + if tableWidth < width { + toDistribute := width - tableWidth + for index, expansion := range expansions { + if expansionTotal <= 0 { + break + } + expWidth := toDistribute * expansion / expansionTotal + widths[index] += expWidth + tableWidth += expWidth + toDistribute -= expWidth + expansionTotal -= expansion + } + } + + // Helper function which draws border runes. + borderStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.bordersColor) + drawBorder := func(colX, rowY int, ch rune) { + screen.SetContent(x+colX, y+rowY, ch, nil, borderStyle) + } + + // Draw the cells (and borders). + var columnX int + if !t.borders { + columnX-- + } + for columnIndex, column := range columns { + columnWidth := widths[columnIndex] + for rowY, row := range rows { + if t.borders { + // Draw borders. + rowY *= 2 + for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ { + drawBorder(columnX+pos+1, rowY, GraphicsHoriBar) + } + ch := GraphicsCross + if columnIndex == 0 { + if rowY == 0 { + ch = GraphicsTopLeftCorner + } else { + ch = GraphicsLeftT + } + } else if rowY == 0 { + ch = GraphicsTopT + } + drawBorder(columnX, rowY, ch) + rowY++ + if rowY >= height { + break // No space for the text anymore. + } + drawBorder(columnX, rowY, GraphicsVertBar) + } else if columnIndex > 0 { + // Draw separator. + drawBorder(columnX, rowY, t.separator) + } + + // Get the cell. + cell := getCell(row, column) + if cell == nil { + continue + } + + // Draw text. + finalWidth := columnWidth + if columnX+1+columnWidth >= width { + finalWidth = width - columnX - 1 + } + cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth + _, printed := Print(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, cell.Color) + if StringWidth(cell.Text)-printed > 0 && printed > 0 { + _, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY) + fg, _, _ := style.Decompose() + Print(screen, string(GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, fg) + } + } + + // Draw bottom border. + if rowY := 2 * len(rows); t.borders && rowY < height { + for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ { + drawBorder(columnX+pos+1, rowY, GraphicsHoriBar) + } + ch := GraphicsBottomT + if columnIndex == 0 { + ch = GraphicsBottomLeftCorner + } + drawBorder(columnX, rowY, ch) + } + + columnX += columnWidth + 1 + } + + // Draw right border. + if t.borders && len(t.cells) > 0 && columnX < width { + for rowY := range rows { + rowY *= 2 + if rowY+1 < height { + drawBorder(columnX, rowY+1, GraphicsVertBar) + } + ch := GraphicsRightT + if rowY == 0 { + ch = GraphicsTopRightCorner + } + drawBorder(columnX, rowY, ch) + } + if rowY := 2 * len(rows); rowY < height { + drawBorder(columnX, rowY, GraphicsBottomRightCorner) + } + } + + // Helper function which colors the background of a box. + colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, selected bool) { + for by := 0; by < h && fromY+by < y+height; by++ { + for bx := 0; bx < w && fromX+bx < x+width; bx++ { + m, c, style, _ := screen.GetContent(fromX+bx, fromY+by) + if selected { + fg, _, _ := style.Decompose() + if fg == textColor || fg == t.bordersColor { + fg = backgroundColor + } + if fg == tcell.ColorDefault { + fg = t.backgroundColor + } + style = style.Background(textColor).Foreground(fg) + } else { + if backgroundColor == tcell.ColorDefault { + continue + } + style = style.Background(backgroundColor) + } + screen.SetContent(fromX+bx, fromY+by, m, c, style) + } + } + } + + // Color the cell backgrounds. To avoid undesirable artefacts, we combine + // the drawing of a cell by background color, selected cells last. + cellsByBackgroundColor := make(map[tcell.Color][]*struct { + x, y, w, h int + text tcell.Color + selected bool + }) + var backgroundColors []tcell.Color + for rowY, row := range rows { + columnX := 0 + rowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow + for columnIndex, column := range columns { + columnWidth := widths[columnIndex] + cell := getCell(row, column) + if cell == nil { + continue + } + bx, by, bw, bh := x+columnX, y+rowY, columnWidth+1, 1 + if t.borders { + by = y + rowY*2 + bw++ + bh = 3 + } + columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn + cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow) + entries, ok := cellsByBackgroundColor[cell.BackgroundColor] + cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &struct { + x, y, w, h int + text tcell.Color + selected bool + }{ + x: bx, + y: by, + w: bw, + h: bh, + text: cell.Color, + selected: cellSelected, + }) + if !ok { + backgroundColors = append(backgroundColors, cell.BackgroundColor) + } + columnX += columnWidth + 1 + } + } + sort.Slice(backgroundColors, func(i int, j int) bool { + // Draw brightest colors last (i.e. on top). + r, g, b := backgroundColors[i].RGB() + c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255} + _, _, li := c.Hcl() + r, g, b = backgroundColors[j].RGB() + c = colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255} + _, _, lj := c.Hcl() + return li < lj + }) + for _, bgColor := range backgroundColors { + entries := cellsByBackgroundColor[bgColor] + for _, cell := range entries { + if cell.selected { + defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, true) + } else { + colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, false) + } + } + } +} + +// InputHandler returns the handler for this primitive. +func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { + key := event.Key() + + if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) || + key == tcell.KeyEscape || + key == tcell.KeyTab || + key == tcell.KeyBacktab { + if t.done != nil { + t.done(key) + } + return + } + + // Movement functions. + previouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn + 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) + if cell == nil || !cell.NotSelectable { + return + } + t.selectedColumn-- + if t.selectedColumn < 0 { + t.selectedColumn = t.lastColumn + t.selectedRow-- + } + } + } + + 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 + } + t.selectedColumn++ + if t.selectedColumn > t.lastColumn { + t.selectedColumn = 0 + t.selectedRow++ + } + } + t.selectedColumn = t.lastColumn + t.selectedRow = len(t.cells) - 1 + previous() + } + + home = func() { + if t.rowsSelectable { + t.selectedRow = 0 + t.selectedColumn = 0 + next() + } else { + t.trackEnd = false + t.rowOffset = 0 + t.columnOffset = 0 + } + } + + end = func() { + if t.rowsSelectable { + t.selectedRow = len(t.cells) - 1 + t.selectedColumn = t.lastColumn + previous() + } else { + t.trackEnd = true + t.columnOffset = 0 + } + } + + down = func() { + if t.rowsSelectable { + t.selectedRow++ + if t.selectedRow >= len(t.cells) { + t.selectedRow = len(t.cells) - 1 + } + next() + } else { + t.rowOffset++ + } + } + + up = func() { + if t.rowsSelectable { + t.selectedRow-- + if t.selectedRow < 0 { + t.selectedRow = 0 + } + previous() + } else { + t.trackEnd = false + t.rowOffset-- + } + } + + left = func() { + if t.columnsSelectable { + t.selectedColumn-- + if t.selectedColumn < 0 { + t.selectedColumn = 0 + } + previous() + } else { + t.columnOffset-- + } + } + + right = func() { + if t.columnsSelectable { + t.selectedColumn++ + if t.selectedColumn > t.lastColumn { + t.selectedColumn = t.lastColumn + } + next() + } else { + t.columnOffset++ + } + } + + pageDown = func() { + if t.rowsSelectable { + t.selectedRow += t.visibleRows + if t.selectedRow >= len(t.cells) { + t.selectedRow = len(t.cells) - 1 + } + next() + } else { + t.rowOffset += t.visibleRows + } + } + + pageUp = func() { + if t.rowsSelectable { + t.selectedRow -= t.visibleRows + if t.selectedRow < 0 { + t.selectedRow = 0 + } + previous() + } else { + t.trackEnd = false + t.rowOffset -= t.visibleRows + } + } + ) + + switch key { + case tcell.KeyRune: + switch event.Rune() { + case 'g': + home() + case 'G': + end() + case 'j': + down() + case 'k': + up() + case 'h': + left() + case 'l': + right() + } + case tcell.KeyHome: + home() + case tcell.KeyEnd: + end() + case tcell.KeyUp: + up() + case tcell.KeyDown: + down() + case tcell.KeyLeft: + left() + case tcell.KeyRight: + right() + case tcell.KeyPgDn, tcell.KeyCtrlF: + pageDown() + case tcell.KeyPgUp, tcell.KeyCtrlB: + pageUp() + case tcell.KeyEnter: + if (t.rowsSelectable || t.columnsSelectable) && t.selected != nil { + t.selected(t.selectedRow, t.selectedColumn) + } + } + + // If the selection has changed, notify the handler. + if t.selectionChanged != nil && + (t.rowsSelectable && previouslySelectedRow != t.selectedRow || + t.columnsSelectable && previouslySelectedColumn != t.selectedColumn) { + t.selectionChanged(t.selectedRow, t.selectedColumn) + } + }) +} -- cgit v1.2.3