package cviewimport (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.typeTableCellstruct {// The reference object. Reference interface{}// The text to be displayed in the table cell. Text []byte// 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// The style attributes of the cell. Attributes tcell.AttrMask// 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 intsync.RWMutex}// 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 ( string) *TableCell {return &TableCell{Text: []byte(),Align: AlignLeft,Color: Styles.PrimaryTextColor,BackgroundColor: tcell.ColorDefault, }}// SetBytes sets the cell's text.func ( *TableCell) ( []byte) { .Lock()defer .Unlock() .Text = }// SetText sets the cell's text.func ( *TableCell) ( string) { .SetBytes([]byte())}// GetBytes returns the cell's text.func ( *TableCell) () []byte { .RLock()defer .RUnlock()return .Text}// GetText returns the cell's text.func ( *TableCell) () string {returnstring(.GetBytes())}// SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or// AlignRight.func ( *TableCell) ( int) { .Lock()defer .Unlock() .Align = }// 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 ( *TableCell) ( int) { .Lock()defer .Unlock() .MaxWidth = }// 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 ( *TableCell) ( int) { .Lock()defer .Unlock()if < 0 {panic("Table cell expansion values may not be negative") } .Expansion = }// SetTextColor sets the cell's text color.func ( *TableCell) ( tcell.Color) { .Lock()defer .Unlock() .Color = }// SetBackgroundColor sets the cell's background color. Set to// tcell.ColorDefault to use the table's background color.func ( *TableCell) ( tcell.Color) { .Lock()defer .Unlock() .BackgroundColor = }// SetAttributes sets the cell's text attributes. You can combine different// attributes using bitmask operations://// cell.SetAttributes(tcell.AttrUnderline | tcell.AttrBold)func ( *TableCell) ( tcell.AttrMask) { .Lock()defer .Unlock() .Attributes = }// SetStyle sets the cell's style (foreground color, background color, and// attributes) all at once.func ( *TableCell) ( tcell.Style) { .Lock()defer .Unlock() .Color, .BackgroundColor, .Attributes = .Decompose()}// SetSelectable sets whether or not this cell can be selected by the user.func ( *TableCell) ( bool) { .Lock()defer .Unlock() .NotSelectable = !}// SetReference allows you to store a reference of any type in this cell. This// will allow you to establish a mapping between the cell and your// actual data.func ( *TableCell) ( interface{}) { .Lock()defer .Unlock() .Reference = }// GetReference returns this cell's reference object.func ( *TableCell) () interface{} { .RLock()defer .RUnlock()return .Reference}// 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 ( *TableCell) () (, , int) { .RLock()defer .RUnlock()return .x, .y, .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.//// Each row of the table must have the same number of columns when it is drawn// or navigated. This isn't strictly enforced, however you may encounter issues// when navigating a table with rows of varied column sizes.//// 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.typeTablestruct { *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// If true, when calculating the widths of the columns, all rows are evaluated // instead of only the visible ones. evaluateAllRows bool// 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 sort function of the table. Defaults to a case-sensitive comparison. sortFunc func(column, i, j int) bool// Whether or not the table should be sorted when a fixed row is clicked. sortClicked bool// The last direction the table was sorted by when clicked. sortClickedDescending bool// The last column the table was sorted by when clicked. sortClickedColumn int// The number of visible rows the last time the table was drawn. visibleRows int// The indices of the visible columns as of the last time the table was drawn. visibleColumnIndices []int// The net widths of the visible columns as of the last time the table was // drawn. visibleColumnWidths []int// Visibility of the scroll bar. scrollBarVisibility ScrollBarVisibility// The scroll bar color. scrollBarColor tcell.Color// The style of the selected rows. If this value is StyleDefault, selected rows // are simply inverted. selectedStyle tcell.Style// 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)sync.RWMutex}// NewTable returns a new table.func () *Table {return &Table{Box: NewBox(),scrollBarVisibility: ScrollBarAuto,scrollBarColor: Styles.ScrollBarColor,bordersColor: Styles.GraphicsColor,separator: ' ',sortClicked: true,lastColumn: -1, }}// Clear removes all table data.func ( *Table) () { .Lock()defer .Unlock() .cells = nil .lastColumn = -1}// SetBorders sets whether or not each cell in the table is surrounded by a// border.func ( *Table) ( bool) { .Lock()defer .Unlock() .borders = }// SetBordersColor sets the color of the cell borders.func ( *Table) ( tcell.Color) { .Lock()defer .Unlock() .bordersColor = }// SetScrollBarVisibility specifies the display of the scroll bar.func ( *Table) ( ScrollBarVisibility) { .Lock()defer .Unlock() .scrollBarVisibility = }// SetScrollBarColor sets the color of the scroll bar.func ( *Table) ( tcell.Color) { .Lock()defer .Unlock() .scrollBarColor = }// SetSelectedStyle sets a specific style for selected cells. If no such style// is set, per default, selected cells are inverted (i.e. their foreground and// background colors are swapped).//// To reset a previous setting to its default, make the following call://// table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)func ( *Table) (, tcell.Color, tcell.AttrMask) { .Lock()defer .Unlock() .selectedStyle = SetAttributes(tcell.StyleDefault.Foreground().Background(), )}// 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 Borders.Vertical (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 ( *Table) ( rune) { .Lock()defer .Unlock() .separator = }// 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 ( *Table) (, int) { .Lock()defer .Unlock() .fixedRows, .fixedColumns = , }// 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 ( *Table) (, bool) { .Lock()defer .Unlock() .rowsSelectable, .columnsSelectable = , }// GetSelectable returns what can be selected in a table. Refer to// SetSelectable() for details.func ( *Table) () (, bool) { .RLock()defer .RUnlock()return .rowsSelectable, .columnsSelectable}// GetSelection returns the position of the current selection.// If entire rows are selected, the column index is undefined.// Likewise for entire columns.func ( *Table) () (, int) { .RLock()defer .RUnlock()return .selectedRow, .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. The "selection changed" event is fired if such a callback// is available (even if the selection ends up being the same as before and even// if cells are not selectable).func ( *Table) (, int) { .Lock()defer .Unlock() .selectedRow, .selectedColumn = , if .selectionChanged != nil { .Unlock() .selectionChanged(, ) .Lock() }}// 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 ( *Table) (, int) { .Lock()defer .Unlock() .rowOffset, .columnOffset = , .trackEnd = false}// 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 ( *Table) () (, int) { .RLock()defer .RUnlock()return .rowOffset, .columnOffset}// SetEvaluateAllRows sets a flag which determines the rows to be evaluated when// calculating the widths of the table's columns. When false, only visible rows// are evaluated. When true, all rows in the table are evaluated.//// Set this flag to true to avoid shifting column widths when the table is// scrolled. (May be slower for large tables.)func ( *Table) ( bool) { .Lock()defer .Unlock() .evaluateAllRows = }// 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 ( *Table) ( func(, int)) { .Lock()defer .Unlock() .selected = }// SetSelectionChangedFunc sets a handler which is called whenever the current// selection changes. The handler receives the position of the new selection.// If entire rows are selected, the column index is undefined. Likewise for// entire columns.func ( *Table) ( func(, int)) { .Lock()defer .Unlock() .selectionChanged = }// 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 ( *Table) ( func( tcell.Key)) { .Lock()defer .Unlock() .done = }// SetCell sets the content of a cell the specified position. It is ok to// directly instantiate a TableCell object. If the cell has content, 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 ( *Table) (, int, *TableCell) { .Lock()defer .Unlock()if >= len(.cells) { .cells = append(.cells, make([][]*TableCell, -len(.cells)+1)...) } := len(.cells[])if >= { .cells[] = append(.cells[], make([]*TableCell, -+1)...)for := ; < ; ++ { .cells[][] = &TableCell{} } } .cells[][] = if > .lastColumn { .lastColumn = }}// SetCellSimple calls SetCell() with the given text, left-aligned, in white.func ( *Table) (, int, string) { .SetCell(, , NewTableCell())}// GetCell returns the contents of the cell at the specified position. A valid// TableCell object is always returned but it will be uninitialized if the cell// was not previously set. Such an uninitialized object will not automatically// be inserted. Therefore, repeated calls to this function may return different// pointers for uninitialized cells.func ( *Table) (, int) *TableCell { .RLock()defer .RUnlock()if >= len(.cells) || >= len(.cells[]) {return &TableCell{} }return .cells[][]}// RemoveRow removes the row at the given position from the table. If there is// no such row, this has no effect.func ( *Table) ( int) { .Lock()defer .Unlock()if < 0 || >= len(.cells) {return } .cells = append(.cells[:], .cells[+1:]...)}// RemoveColumn removes the column at the given position from the table. If// there is no such column, this has no effect.func ( *Table) ( int) { .Lock()defer .Unlock()for := range .cells {if < 0 || >= len(.cells[]) {continue } .cells[] = append(.cells[][:], .cells[][+1:]...) }}// InsertRow inserts a row before the row with the given index. Cells on the// 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 ( *Table) ( int) { .Lock()defer .Unlock()if >= len(.cells) {return } .cells = append(.cells, nil) // Extend by one.copy(.cells[+1:], .cells[:]) // Shift down. .cells[] = nil// New row is uninitialized.}// InsertColumn inserts a column before the column with the given index. Cells// in the given column and to its right will be shifted to the right by one// column. Rows that have fewer initialized cells than "column" will remain// unchanged.func ( *Table) ( int) { .Lock()defer .Unlock()for := range .cells {if >= len(.cells[]) {continue } .cells[] = append(.cells[], nil) // Extend by one.copy(.cells[][+1:], .cells[][:]) // Shift to the right. .cells[][] = &TableCell{} // New element is an uninitialized table cell. }}// GetRowCount returns the number of rows in the table.func ( *Table) () int { .RLock()defer .RUnlock()returnlen(.cells)}// GetColumnCount returns the (maximum) number of columns in the table.func ( *Table) () int { .RLock()defer .RUnlock()iflen(.cells) == 0 {return0 }return .lastColumn + 1}// cellAt returns the row and column located at the given screen coordinates.// Each returned value may be negative if there is no row and/or cell. This// function will also process coordinates outside the table's inner rectangle so// callers will need to check for bounds themselves.func ( *Table) (, int) (, int) { , , , := .GetInnerRect()// Determine row as seen on screen.if .borders { = ( - - 1) / 2 } else { = - }// Respect fixed rows and row offset.if >= 0 {if >= .fixedRows { += .rowOffset }if >= len(.cells) { = -1 } }// Search for the clicked column. = -1if >= { := if .borders { ++ }for , := range .visibleColumnWidths { += + 1if < { = .visibleColumnIndices[]break } } }return}// 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 ( *Table) () { .Lock()defer .Unlock() .trackEnd = false .columnOffset = 0 .rowOffset = 0}// 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 ( *Table) () { .Lock()defer .Unlock() .trackEnd = true .columnOffset = 0 .rowOffset = len(.cells)}// SetSortClicked sets a flag which determines whether the table is sorted when// a fixed row is clicked. This flag is enabled by default.func ( *Table) ( bool) { .Lock()defer .Unlock() .sortClicked = }// SetSortFunc sets the sorting function used for the table. When unset, a// case-sensitive string comparison is used.func ( *Table) ( func(, , int) bool) { .Lock()defer .Unlock() .sortFunc = }// Sort sorts the table by the column at the given index. You may set a custom// sorting function with SetSortFunc.func ( *Table) ( int, bool) { .Lock()defer .Unlock()iflen(.cells) == 0 || < 0 || >= len(.cells[0]) {return }if .sortFunc == nil { .sortFunc = func(, , int) bool {returnbytes.Compare(.cells[][].Text, .cells[][].Text) == -1 } }sort.SliceStable(.cells, func(, int) bool {if < .fixedRows {return < } elseif < .fixedRows {return > }if ! {return .sortFunc(, , ) }return .sortFunc(, , ) })}// Draw draws this primitive onto the screen.func ( *Table) ( tcell.Screen) {if !.GetVisible() {return } .Box.Draw() .Lock()defer .Unlock()// What's our available screen space? , , , := .GetInnerRect()if .borders { .visibleRows = / 2 } else { .visibleRows = } := .scrollBarVisibility == ScrollBarAlways || (.scrollBarVisibility == ScrollBarAuto && len(.cells) > .visibleRows-.fixedRows)if { -- // Subtract space for scroll bar. }// TODO horizontal scrollbar// Return the cell at the specified position (nil if it doesn't exist). := func(, int) *TableCell {if < 0 || < 0 || >= len(.cells) || >= len(.cells[]) {returnnil }return .cells[][] }// If this cell is not selectable, find the next one.if .rowsSelectable || .columnsSelectable {if .selectedColumn < 0 { .selectedColumn = 0 }if .selectedRow < 0 { .selectedRow = 0 }for .selectedRow < len(.cells) { := (.selectedRow, .selectedColumn)if == nil {break } .RLock()if !.NotSelectable { .RUnlock()break } .RUnlock() .selectedColumn++if .selectedColumn > .lastColumn { .selectedColumn = 0 .selectedRow++ } } }// Clamp row offsets.if .rowsSelectable {if .selectedRow >= .fixedRows && .selectedRow < .fixedRows+.rowOffset { .rowOffset = .selectedRow - .fixedRows .trackEnd = false }if .borders {if2*(.selectedRow+1-.rowOffset) >= { .rowOffset = .selectedRow + 1 - /2 .trackEnd = false } } else {if .selectedRow+1-.rowOffset >= { .rowOffset = .selectedRow + 1 - .trackEnd = false } } }if .borders {if2*(len(.cells)-.rowOffset) < { .trackEnd = true } } else {iflen(.cells)-.rowOffset < { .trackEnd = true } }if .trackEnd {if .borders { .rowOffset = len(.cells) - /2 } else { .rowOffset = len(.cells) - } }if .rowOffset < 0 { .rowOffset = 0 }// Clamp column offset. (Only left side here. The right side is more // difficult and we'll do it below.)if .columnsSelectable && .selectedColumn >= .fixedColumns && .selectedColumn < .fixedColumns+.columnOffset { .columnOffset = .selectedColumn - .fixedColumns }if .columnOffset < 0 { .columnOffset = 0 }if .selectedColumn < 0 { .selectedColumn = 0 }// Determine the indices and widths of the columns and rows which fit on the // screen.var ( , , , []int , int ) := 1if .borders { = 2// With borders, every table row takes two screen rows. = 1// We start at the second character because of the left table border. }if .evaluateAllRows { = make([]int, len(.cells))for := range .cells { [] = } } := func( int) bool { // Determine if this row is visible, store its index.if >= {returnfalse } = append(, ) += returntrue }for := 0; < .fixedRows && < len(.cells); ++ { // Do the fixed rows first.if !() {break } }for := .fixedRows + .rowOffset; < len(.cells); ++ { // Then the remaining rows.if !() {break } }var ( , , int []int ):for := 0; ; ++ {// If we've moved beyond the right border, we stop or skip a column.for -1 >= { // -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 < .fixedColumns {break// We're in the fixed area. We're done. }if !.columnsSelectable && >= .columnOffset {break// There is no selection and we've already reached the offset. }if .columnsSelectable && .selectedColumn- == .fixedColumns {break// The selected column reached the leftmost point before disappearing. }if .columnsSelectable && >= .columnOffset && (.selectedColumn < && < -1 && < -1 || .selectedColumn < -1) {break// We've skipped as many as requested and the selection is visible. }iflen() <= .fixedColumns {break// Nothing to skip. }// We need to skip a column. ++ -= [.fixedColumns] + 1 -= [.fixedColumns] + 1 = append([:.fixedColumns], [.fixedColumns+1:]...) = append([:.fixedColumns], [.fixedColumns+1:]...) = append([:.fixedColumns], [.fixedColumns+1:]...) }// What's this column's width (without expansion)? := -1 := 0 := if .evaluateAllRows { = }for , := range {if := (, ); != nil { .RLock() , , , , , , := decomposeText(.Text, true, false)if .MaxWidth > 0 && .MaxWidth < { = .MaxWidth }if > { = }if .Expansion > { = .Expansion } .RUnlock() } }if < 0 {break// No more cells found in this column. }// Store new column info at the end. = append(, ) = append(, ) = += + 1 = append(, ) += } .columnOffset = // If we have space left, distribute it.if < { := - for , := range {if <= 0 {break } := * / [] += -= -= } = - }// Helper function which draws border runes. := tcell.StyleDefault.Background(.backgroundColor).Foreground(.bordersColor) := func(, int, rune) { .SetContent(+, +, , nil, ) }// Draw the cells (and borders).varintif !.borders { -- }for , := range { := []for , := range {if .borders {// Draw borders. *= 2for := 0; < && +1+ < ; ++ { (++1, , Borders.Horizontal) } := Borders.Crossif == 0 {if == 0 { = Borders.TopLeft } else { = Borders.LeftT } } elseif == 0 { = Borders.TopT } (, , ) ++if >= {break// No space for the text anymore. } (, , Borders.Vertical) } elseif > 0 {// Draw separator. (, , .separator) }// Get the cell. := (, )if == nil {continue } .Lock()// Draw text. := if +1+ >= { = - - 1 } .x, .y, .width = ++1, +, , := PrintStyle(, .Text, ++1, +, , .Align, SetAttributes(tcell.StyleDefault.Foreground(.Color), .Attributes))ifTaggedTextWidth(.Text)- > 0 && > 0 { , , , := .GetContent(++, +)PrintStyle(, []byte(string(SemigraphicsHorizontalEllipsis)), ++, +, 1, AlignLeft, ) } .Unlock() }// Draw bottom border.if := 2 * len(); .borders && < {for := 0; < && +1+ < ; ++ { (++1, , Borders.Horizontal) } := Borders.BottomTif == 0 { = Borders.BottomLeft } (, , ) } += + 1 }// Draw right border.if .borders && len(.cells) > 0 && < {for := range { *= 2if +1 < { (, +1, Borders.Vertical) } := Borders.RightTif == 0 { = Borders.TopRight } (, , ) }if := 2 * len(); < { (, , Borders.BottomRight) } }if {// Calculate scroll bar position and dimensions. := len(.cells) := - .fixedRows := .visibleRows - .fixedRows := + := + .fixedRowsif > + { = + } := 1if .borders { = 2 *= 2 = ( * 2) - 1 += .fixedRows + 1 }// Draw scroll bar. := int(float64() * (float64(.rowOffset) / float64(((-.fixedRows)-.visibleRows)+)))for := 0; < ; ++ {RenderScrollBar(, .scrollBarVisibility, , +, , , , , .hasFocus, .scrollBarColor) } }// TODO Draw horizontal scroll bar// Helper function which colors the background of a box. // backgroundColor == tcell.ColorDefault => Don't color the background. // textColor == tcell.ColorDefault => Don't change the text color. // attr == 0 => Don't change attributes. // invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor; // set background to textColor. := func(, , , int, , tcell.Color, tcell.AttrMask, bool) {for := 0; < && + < +; ++ {for := 0; < && + < +; ++ { , , , := .GetContent(+, +) , , := .Decompose()if {if == || == .bordersColor { = }if == tcell.ColorDefault { = .backgroundColor } = .Background().Foreground() } else {if != tcell.ColorDefault { = }if != tcell.ColorDefault { = }if != 0 { = } = SetAttributes(.Background().Foreground(), ) } .SetContent(+, +, , , ) } } }// Color the cell backgrounds. To avoid undesirable artefacts, we combine // the drawing of a cell by background color, selected cells last.typestruct { , , , inttcell.Colorbool } := make(map[tcell.Color][]*)var []tcell.Colorfor , := range { := 0 := .rowsSelectable && !.columnsSelectable && == .selectedRowfor , := range { := [] := (, )if == nil {continue } , , , := +, +, +1, 1if .borders { = + *2 ++ = 3 } := .columnsSelectable && !.rowsSelectable && == .selectedColumn := !.NotSelectable && ( || || .rowsSelectable && .columnsSelectable && == .selectedColumn && == .selectedRow) , := [.BackgroundColor] [.BackgroundColor] = append(, &{ : , : , : , : , : .Color, : , })if ! { = append(, .BackgroundColor) } += + 1 } }sort.Slice(, func( int, int) bool {// Draw brightest colors last (i.e. on top). , , := [].RGB() := colorful.Color{R: float64() / 255, G: float64() / 255, B: float64() / 255} , , := .Hcl() , , = [].RGB() = colorful.Color{R: float64() / 255, G: float64() / 255, B: float64() / 255} , , := .Hcl()return < }) , , := .selectedStyle.Decompose()for , := range { := []for , := range {if . && .hasFocus {if .selectedStyle != tcell.StyleDefault {defer (., ., ., ., , , , false) } else {defer (., ., ., ., , ., 0, true) } } else { (., ., ., ., , tcell.ColorDefault, 0, false) } } }// Remember column infos. .visibleColumnIndices, .visibleColumnWidths = , }// InputHandler returns the handler for this primitive.func ( *Table) () func( *tcell.EventKey, func( Primitive)) {return .WrapInputHandler(func( *tcell.EventKey, func( Primitive)) { .Lock()defer .Unlock() := .Key()if (!.rowsSelectable && !.columnsSelectable && == tcell.KeyEnter) || == tcell.KeyEscape || == tcell.KeyTab || == tcell.KeyBacktab {if .done != nil { .Unlock() .done() .Lock() }return }// Movement functions. , := .selectedRow, .selectedColumnvar ( = func(, int) bool {if < .fixedRows || >= len(.cells) || < .fixedColumns || > .lastColumn {returnfalse } := .cells[][]return == nil || !.NotSelectable } = func() {if .rowsSelectable { .selectedRow = 0 .selectedColumn = 0 } else { .trackEnd = false .rowOffset = 0 .columnOffset = 0 } } = func() {if .rowsSelectable { .selectedRow = len(.cells) - 1 .selectedColumn = .lastColumn } else { .trackEnd = true .columnOffset = 0 } } = func() {if .rowsSelectable {if (.selectedRow+1, .selectedColumn) { .selectedRow++ } } else { .rowOffset++ } } = func() {if .rowsSelectable {if (.selectedRow-1, .selectedColumn) { .selectedRow-- } } else { .trackEnd = false .rowOffset-- } } = func() {if .columnsSelectable {for := .selectedColumn - 1; >= 0; -- {if (.selectedRow, ) { .selectedColumn = break } } } else { .columnOffset-- } } = func() {if .columnsSelectable {for := .selectedColumn + 1; <= .lastColumn; ++ {if (.selectedRow, ) { .selectedColumn = break } } } else { .columnOffset++ } } = func() { := .visibleRows - .fixedRowsif < 0 { = 0 }if .rowsSelectable { .selectedRow += if .selectedRow >= len(.cells) { .selectedRow = len(.cells) - 1 } } else { .rowOffset += } } = func() { := .visibleRows - .fixedRowsif < 0 { = 0 }if .rowsSelectable { .selectedRow -= if .selectedRow < 0 { .selectedRow = 0 } } else { .trackEnd = false .rowOffset -= } } )ifHitShortcut(, Keys.MoveFirst, Keys.MoveFirst2) { () } elseifHitShortcut(, Keys.MoveLast, Keys.MoveLast2) { () } elseifHitShortcut(, Keys.MoveUp, Keys.MoveUp2) { () } elseifHitShortcut(, Keys.MoveDown, Keys.MoveDown2) { () } elseifHitShortcut(, Keys.MoveLeft, Keys.MoveLeft2) { () } elseifHitShortcut(, Keys.MoveRight, Keys.MoveRight2) { () } elseifHitShortcut(, Keys.MovePreviousPage) { () } elseifHitShortcut(, Keys.MoveNextPage) { () } elseifHitShortcut(, Keys.Select, Keys.Select2) {if (.rowsSelectable || .columnsSelectable) && .selected != nil { .Unlock() .selected(.selectedRow, .selectedColumn) .Lock() } }// If the selection has changed, notify the handler.if .selectionChanged != nil && ((.rowsSelectable && != .selectedRow) || (.columnsSelectable && != .selectedColumn)) { .Unlock() .selectionChanged(.selectedRow, .selectedColumn) .Lock() } })}// MouseHandler returns the mouse handler for this primitive.func ( *Table) () func( MouseAction, *tcell.EventMouse, func( Primitive)) ( bool, Primitive) {return .WrapMouseHandler(func( MouseAction, *tcell.EventMouse, func( Primitive)) ( bool, Primitive) { , := .Position()if !.InRect(, ) {returnfalse, nil }switch {caseMouseLeftClick: , , , := .GetInnerRect() := 1 := if .borders { = 2 = + 1 }if .sortClicked && .fixedRows > 0 && ( >= && < +(.fixedRows*)) { , := .cellAt(, )if .sortClickedColumn != { .sortClickedColumn = .sortClickedDescending = false } else { .sortClickedDescending = !.sortClickedDescending } .Sort(, .sortClickedDescending)if .columnsSelectable { .selectedColumn = } } elseif .rowsSelectable || .columnsSelectable { .Select(.cellAt(, ))// mouse always selectsif .selected != nil { .selected(.selectedRow, .selectedColumn) } } = true ()caseMouseScrollUp: .trackEnd = false .rowOffset-- = truecaseMouseScrollDown: .rowOffset++ = true// TODO test horizontal scrollingcaseMouseScrollLeft: .columnOffset-- = truecaseMouseScrollRight: .columnOffset++ = true }return })}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.