| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653 |
- package tview
- import (
- "sort"
- "github.com/gdamore/tcell/v2"
- 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 reference object.
- Reference interface{}
- // 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, the BackgroundColor is not used and the cell will have
- // the background color of the table.
- Transparent bool
- // The style attributes of the cell.
- Attributes tcell.AttrMask
- // If set to true, this cell cannot be selected.
- NotSelectable bool
- // An optional handler for mouse clicks. This also fires if the cell is not
- // selectable. If true is returned, no additional "selected" event is fired
- // on selectable cells.
- Clicked func() 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: Styles.PrimitiveBackgroundColor,
- Transparent: true,
- }
- }
- // 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. This will also cause the
- // cell's Transparent flag to be set to "false".
- func (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell {
- c.BackgroundColor = color
- c.Transparent = false
- return c
- }
- // SetTransparency sets the background transparency of this cell. A value of
- // "true" will cause the cell to use the table's background color. A value of
- // "false" will cause it to use its own background color.
- func (c *TableCell) SetTransparency(transparent bool) *TableCell {
- c.Transparent = transparent
- return c
- }
- // SetAttributes sets the cell's text attributes. You can combine different
- // attributes using bitmask operations:
- //
- // cell.SetAttributes(tcell.AttrUnderline | tcell.AttrBold)
- func (c *TableCell) SetAttributes(attr tcell.AttrMask) *TableCell {
- c.Attributes = attr
- return c
- }
- // SetStyle sets the cell's style (foreground color, background color, and
- // attributes) all at once.
- func (c *TableCell) SetStyle(style tcell.Style) *TableCell {
- c.Color, c.BackgroundColor, c.Attributes = style.Decompose()
- 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
- }
- // 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 (c *TableCell) SetReference(reference interface{}) *TableCell {
- c.Reference = reference
- return c
- }
- // GetReference returns this cell's reference object.
- func (c *TableCell) GetReference() interface{} {
- return c.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 (c *TableCell) GetLastPosition() (x, y, width int) {
- return c.x, c.y, c.width
- }
- // SetClickedFunc sets a handler which fires when this cell is clicked. This is
- // independent of whether the cell is selectable or not. But for selectable
- // cells, if the function returns "true", the "selected" event is not fired.
- func (c *TableCell) SetClickedFunc(clicked func() bool) *TableCell {
- c.Clicked = clicked
- return c
- }
- // TableContent defines a Table's data. You may replace a Table's default
- // implementation with your own using the Table.SetContent() function. This will
- // allow you to turn Table into a view of your own data structure. The
- // Table.Draw() function, which is called when the screen is updated, will then
- // use the (read-only) functions of this interface to update the table. The
- // write functions are only called when the corresponding functions of Table are
- // called.
- //
- // The interface's read-only functions are not called concurrently by the
- // package (provided that users of the package don't call Table.Draw() in a
- // separate goroutine, which would be uncommon and is not encouraged).
- type TableContent interface {
- // Return the cell at the given position or nil if there is no cell. The
- // row and column arguments start at 0 and end at what GetRowCount() and
- // GetColumnCount() return, minus 1.
- GetCell(row, column int) *TableCell
- // Return the total number of rows in the table.
- GetRowCount() int
- // Return the total number of columns in the table.
- GetColumnCount() int
- // The following functions are provided for completeness reasons as the
- // original Table implementation was not read-only. If you do not wish to
- // forward modifying operations to your data, you may opt to leave these
- // functions empty. To make this easier, you can include the
- // TableContentReadOnly type in your struct. See also the
- // demos/table/virtualtable example.
- // Set the cell at the given position to the provided cell.
- SetCell(row, column int, cell *TableCell)
- // Remove the row at the given position by shifting all following rows up
- // by one. Out of range positions may be ignored.
- RemoveRow(row int)
- // Remove the column at the given position by shifting all following columns
- // left by one. Out of range positions may be ignored.
- RemoveColumn(column int)
- // Insert a new empty row at the given position by shifting all rows at that
- // position and below down by one. Implementers may decide what to do with
- // out of range positions.
- InsertRow(row int)
- // Insert a new empty column at the given position by shifting all columns
- // at that position and to the right by one to the right. Implementers may
- // decide what to do with out of range positions.
- InsertColumn(column int)
- // Remove all table data.
- Clear()
- }
- // TableContentReadOnly is an empty struct which implements the write operations
- // of the TableContent interface. None of the implemented functions do anything.
- // You can embed this struct into your own structs to free yourself from having
- // to implement the empty write functions of TableContent. See
- // demos/table/virtualtable for an example.
- type TableContentReadOnly struct{}
- // SetCell does not do anything.
- func (t TableContentReadOnly) SetCell(row, column int, cell *TableCell) {
- // nop.
- }
- // RemoveRow does not do anything.
- func (t TableContentReadOnly) RemoveRow(row int) {
- // nop.
- }
- // RemoveColumn does not do anything.
- func (t TableContentReadOnly) RemoveColumn(column int) {
- // nop.
- }
- // InsertRow does not do anything.
- func (t TableContentReadOnly) InsertRow(row int) {
- // nop.
- }
- // InsertColumn does not do anything.
- func (t TableContentReadOnly) InsertColumn(column int) {
- // nop.
- }
- // Clear does not do anything.
- func (t TableContentReadOnly) Clear() {
- // nop.
- }
- // tableDefaultContent implements the default TableContent interface for the
- // Table class.
- type tableDefaultContent struct {
- // The cells of the table. Rows first, then columns.
- cells [][]*TableCell
- // The rightmost column in the data set.
- lastColumn int
- }
- // Clear clears all data.
- func (t *tableDefaultContent) Clear() {
- t.cells = nil
- t.lastColumn = -1
- }
- // SetCell sets a cell's content.
- func (t *tableDefaultContent) SetCell(row, column int, cell *TableCell) {
- 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
- }
- }
- // 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:]...)
- }
- if column >= 0 && column <= t.lastColumn {
- t.lastColumn--
- }
- }
- // 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.
- //
- // 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 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.
- 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
- // A temporary flag which causes the next call to Draw() to force the
- // current selection to remain visible. It is set to false afterwards.
- clampToSelection bool
- // If set to true, moving the selection will wrap around horizontally (last
- // to first column and vice versa) or vertically (last to first row and vice
- // versa).
- wrapHorizontally, wrapVertically bool
- // 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
- // 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
- // The style of the selected rows. If this value is the empty struct,
- // 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)
- }
- // NewTable returns a new table.
- func NewTable() *Table {
- t := &Table{
- Box: NewBox(),
- bordersColor: Styles.GraphicsColor,
- separator: ' ',
- }
- 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.content.Clear()
- 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
- }
- // 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.Style{})
- func (t *Table) SetSelectedStyle(style tcell.Style) *Table {
- t.selectedStyle = style
- 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 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 (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. 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 (t *Table) Select(row, column int) *Table {
- t.selectedRow, t.selectedColumn = row, column
- t.clampToSelection = true
- if t.selectionChanged != nil {
- t.selectionChanged(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
- t.trackEnd = false
- 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
- }
- // 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 come with a performance penalty 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
- }
- // 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 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 (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 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 with empty TableCell
- // objects, 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 {
- t.content.SetCell(row, column, cell)
- 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 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 (t *Table) GetCell(row, column int) *TableCell {
- cell := t.content.GetCell(row, column)
- if cell == nil {
- cell = &TableCell{}
- }
- 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 {
- 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 {
- t.content.RemoveColumn(column)
- return t
- }
- // 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 (t *Table) InsertRow(row int) *Table {
- t.content.InsertRow(row)
- return t
- }
- // 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 (t *Table) InsertColumn(column int) *Table {
- t.content.InsertColumn(column)
- return t
- }
- // GetRowCount returns the number of rows in the table.
- func (t *Table) GetRowCount() int {
- return t.content.GetRowCount()
- }
- // GetColumnCount returns the (maximum) number of columns in the table.
- func (t *Table) GetColumnCount() int {
- return t.content.GetColumnCount()
- }
- // 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 (t *Table) cellAt(x, y int) (row, column int) {
- rectX, rectY, _, _ := t.GetInnerRect()
- // Determine row as seen on screen.
- if t.borders {
- row = (y - rectY - 1) / 2
- } else {
- row = y - rectY
- }
- // Respect fixed rows and row offset.
- if row >= 0 {
- if row >= t.fixedRows {
- row += t.rowOffset
- }
- if row >= t.content.GetRowCount() {
- row = -1
- }
- }
- // Saerch for the clicked column.
- column = -1
- if x >= rectX {
- columnX := rectX
- if t.borders {
- columnX++
- }
- for index, width := range t.visibleColumnWidths {
- columnX += width + 1
- if x < columnX {
- column = t.visibleColumnIndices[index]
- 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 (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 = t.content.GetRowCount()
- return t
- }
- // SetWrapSelection determines whether a selection wraps vertically or
- // horizontally when moved. Vertically wrapping selections will jump from the
- // last selectable row to the first selectable row and vice versa. Horizontally
- // wrapping selections will jump from the last selectable column to the first
- // selectable column (on the next selectable row) or from the first selectable
- // column to the last selectable column (on the previous selectable row). If set
- // to false, the selection is not moved when it is already on the first/last
- // selectable row/column.
- //
- // The default is for both values to be false.
- func (t *Table) SetWrapSelection(vertical, horizontal bool) *Table {
- t.wrapHorizontally = horizontal
- t.wrapVertically = vertical
- return t
- }
- // Draw draws this primitive onto the screen.
- func (t *Table) Draw(screen tcell.Screen) {
- t.Box.DrawForSubclass(screen, t)
- // 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
- }
- // 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
- }
- if t.selectedRow < 0 {
- t.selectedRow = 0
- }
- for t.selectedRow < rowCount {
- cell := t.content.GetCell(t.selectedRow, t.selectedColumn)
- if cell != nil && !cell.NotSelectable {
- break
- }
- t.selectedColumn++
- if t.selectedColumn > columnCount-1 {
- t.selectedColumn = 0
- t.selectedRow++
- }
- }
- }
- // 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 t.selectedRow+1-t.rowOffset >= height/2 {
- 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.rowOffset < 0 {
- t.rowOffset = 0
- }
- if t.borders {
- if rowCount-t.rowOffset < height/2 {
- t.trackEnd = true
- }
- } else {
- if rowCount-t.rowOffset < height {
- t.trackEnd = true
- }
- }
- if t.trackEnd {
- if t.borders {
- t.rowOffset = rowCount - height/2
- } else {
- t.rowOffset = rowCount - height
- }
- }
- if t.rowOffset < 0 {
- t.rowOffset = 0
- }
- // Avoid invalid column offsets.
- if t.columnOffset >= columnCount-t.fixedColumns {
- t.columnOffset = columnCount - t.fixedColumns - 1
- }
- if t.columnOffset < 0 {
- t.columnOffset = 0
- }
- // Determine the indices of the rows which fit on the screen.
- var (
- rows, allRows []int
- tableHeight int
- )
- rowStep := 1
- if t.borders {
- rowStep = 2 // With borders, every table row takes two screen rows.
- }
- if t.evaluateAllRows {
- allRows = make([]int, rowCount)
- for row := 0; row < rowCount; row++ {
- allRows[row] = row
- }
- }
- 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 < rowCount; row++ { // Do the fixed rows first.
- if !indexRow(row) {
- break
- }
- }
- 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 (
- tableWidth, expansionTotal int
- columns, widths, expansions []int
- )
- includesSelection := !t.clampToSelection || !t.columnsSelectable
- // 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
- }
- var maxWidth, expansion int
- evaluationRows := rows
- if t.evaluateAllRows {
- evaluationRows = allRows
- }
- for _, row := range evaluationRows {
- 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
- }
- if cellWidth > maxWidth {
- maxWidth = cellWidth
- }
- if cell.Expansion > expansion {
- expansion = cell.Expansion
- }
- }
- }
- clampedMaxWidth := maxWidth
- if tableWidth+maxWidth > netWidth {
- clampedMaxWidth = netWidth - tableWidth
- }
- columns = append(columns, column)
- 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
- }
- }
- }
- }
- // If we have space left, distribute it.
- if tableWidth < netWidth {
- toDistribute := netWidth - tableWidth
- for index, expansion := range expansions {
- if expansionTotal <= 0 {
- break
- }
- expWidth := toDistribute * expansion / expansionTotal
- widths[index] += 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+pos < width; pos++ {
- drawBorder(columnX+pos, rowY, Borders.Horizontal)
- }
- ch := Borders.Cross
- if row == 0 {
- if column == 0 {
- ch = Borders.TopLeft
- } else {
- ch = Borders.TopT
- }
- } else if column == 0 {
- ch = Borders.LeftT
- }
- drawBorder(columnX-1, rowY, ch)
- rowY++
- if rowY >= height || y+rowY >= totalHeight {
- break // No space for the text anymore.
- }
- drawBorder(columnX-1, rowY, Borders.Vertical)
- } else if columnIndex < len(columns)-1 {
- // Draw separator.
- drawBorder(columnX+columnWidth, rowY, t.separator)
- }
- // Get the cell.
- cell := t.content.GetCell(row, column)
- if cell == nil {
- continue
- }
- // Draw text.
- finalWidth := columnWidth
- if columnX+columnWidth >= width {
- finalWidth = width - columnX
- }
- 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-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 > 0 && rowY < height {
- for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
- drawBorder(columnX+pos, rowY, Borders.Horizontal)
- }
- 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-1, rowY, ch)
- }
- columnX += columnWidth + 1
- }
- // Draw right border.
- 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.Cross
- if rowY == 0 {
- 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 {
- ch := Borders.BottomT
- if lastColumn {
- ch = Borders.BottomRight
- }
- drawBorder(columnX, rowY, ch)
- }
- }
- // Helper function which colors the background of a box.
- // backgroundTransparent == true => Don't modify background color (when invert == false).
- // textTransparent == true => Don't modify text color (when invert == false).
- // attr == 0 => Don't change attributes.
- // invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
- // set background to textColor.
- colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, backgroundTransparent, textTransparent bool, attr tcell.AttrMask, invert 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)
- fg, bg, a := style.Decompose()
- if invert {
- style = style.Background(textColor).Foreground(backgroundColor)
- } else {
- if !backgroundTransparent {
- bg = backgroundColor
- }
- if !textTransparent {
- fg = textColor
- }
- if attr != 0 {
- a = attr
- }
- style = style.Background(bg).Foreground(fg).Attributes(a)
- }
- 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.
- type cellInfo struct {
- x, y, w, h int
- cell *TableCell
- selected bool
- }
- cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
- 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 := t.content.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, &cellInfo{
- x: bx,
- y: by,
- w: bw,
- h: bh,
- cell: cell,
- 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
- })
- selFg, selBg, selAttr := t.selectedStyle.Decompose()
- for _, bgColor := range backgroundColors {
- entries := cellsByBackgroundColor[bgColor]
- for _, info := range entries {
- if info.selected {
- if t.selectedStyle != (tcell.Style{}) {
- defer colorBackground(info.x, info.y, info.w, info.h, selBg, selFg, false, false, selAttr, false)
- } else {
- defer colorBackground(info.x, info.y, info.w, info.h, bgColor, info.cell.Color, false, false, 0, true)
- }
- } else {
- colorBackground(info.x, info.y, info.w, info.h, bgColor, info.cell.Color, info.cell.Transparent, true, 0, false)
- }
- }
- }
- // Remember column infos.
- t.visibleColumnIndices, t.visibleColumnWidths = columns, widths
- }
- // 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
- lastColumn := t.content.GetColumnCount() - 1
- rowCount := t.content.GetRowCount()
- if rowCount == 0 {
- return // No movement on empty tables.
- }
- var (
- previous = func() {
- startRow, previousRow := t.selectedRow, t.selectedRow
- startColumn, previousColumn := t.selectedColumn, t.selectedColumn
- for {
- cell := t.content.GetCell(t.selectedRow, t.selectedColumn)
- if cell != nil && !cell.NotSelectable {
- return
- }
- t.selectedColumn--
- if t.selectedColumn < 0 {
- if t.wrapHorizontally {
- t.selectedColumn = lastColumn
- t.selectedRow--
- if t.selectedRow < 0 {
- if t.wrapVertically {
- t.selectedRow = rowCount - 1
- } else {
- t.selectedRow = 0
- }
- }
- } else {
- t.selectedColumn = 0
- }
- }
- if t.selectedColumn == startColumn && t.selectedRow == startRow {
- t.selectedColumn = 0
- t.selectedRow = 0
- return
- }
- if t.selectedColumn == previousColumn && t.selectedRow == previousRow {
- return
- }
- previousRow, previousColumn = t.selectedRow, t.selectedColumn
- }
- }
- next = func() {
- startRow, previousRow := t.selectedRow, t.selectedRow
- startColumn, previousColumn := t.selectedColumn, t.selectedColumn
- for {
- if t.selectedColumn <= lastColumn {
- cell := t.content.GetCell(t.selectedRow, t.selectedColumn)
- if cell != nil && !cell.NotSelectable {
- return
- }
- }
- if t.selectedColumn < lastColumn {
- t.selectedColumn++
- } else {
- if t.wrapHorizontally {
- t.selectedColumn = 0
- if t.selectedRow >= rowCount-1 {
- if t.wrapVertically {
- t.selectedRow = 0
- } else {
- t.selectedRow = rowCount - 1
- }
- } else {
- t.selectedRow++
- }
- } else {
- t.selectedColumn = lastColumn
- }
- }
- if t.selectedColumn == startColumn && t.selectedRow == startRow {
- t.selectedColumn = 0
- t.selectedRow = 0
- return
- }
- if t.selectedColumn == previousColumn && t.selectedRow == previousRow {
- return
- }
- previousRow, previousColumn = t.selectedRow, t.selectedColumn
- }
- }
- home = func() {
- if t.rowsSelectable {
- t.selectedRow = 0
- t.selectedColumn = 0
- t.clampToSelection = true
- next()
- } else {
- t.trackEnd = false
- t.rowOffset = 0
- t.columnOffset = 0
- }
- }
- end = func() {
- if t.rowsSelectable {
- t.selectedRow = rowCount - 1
- t.selectedColumn = lastColumn
- t.clampToSelection = true
- previous()
- } else {
- t.trackEnd = true
- t.columnOffset = 0
- }
- }
- down = func() {
- if t.rowsSelectable {
- startRow := t.selectedRow
- t.selectedRow++
- if t.selectedRow >= rowCount {
- t.selectedRow = 0
- }
- t.clampToSelection = true
- next()
- if !t.wrapVertically && t.selectedRow < startRow {
- t.selectedRow = rowCount - 1
- t.selectedColumn = lastColumn
- previous()
- }
- } else {
- t.rowOffset++
- }
- }
- up = func() {
- if t.rowsSelectable {
- startRow := t.selectedRow
- t.selectedRow--
- if t.selectedRow < 0 {
- t.selectedRow = rowCount - 1
- }
- t.clampToSelection = true
- previous()
- if !t.wrapVertically && t.selectedRow > startRow {
- t.selectedRow = 0
- t.selectedColumn = 0
- next()
- }
- } else {
- t.trackEnd = false
- t.rowOffset--
- }
- }
- left = func() {
- if t.columnsSelectable {
- startRow := t.selectedRow
- startColumn := t.selectedColumn
- t.selectedColumn--
- if t.selectedColumn < 0 {
- t.selectedColumn = lastColumn
- t.selectedRow--
- if t.selectedRow < 0 {
- t.selectedRow = rowCount - 1
- }
- }
- t.clampToSelection = true
- previous()
- if !t.wrapHorizontally && (t.selectedRow != startRow || t.selectedColumn > startColumn) ||
- !t.wrapVertically && t.selectedRow > startRow {
- t.selectedRow = startRow
- t.selectedColumn = startColumn
- }
- } else {
- t.columnOffset--
- }
- }
- right = func() {
- if t.columnsSelectable {
- startRow := t.selectedRow
- startColumn := t.selectedColumn
- t.selectedColumn++
- t.clampToSelection = true
- next()
- if !t.wrapHorizontally && (t.selectedRow != startRow || t.selectedColumn < startColumn) ||
- !t.wrapVertically && t.selectedRow < startRow {
- t.selectedRow = startRow
- t.selectedColumn = startColumn
- }
- } else {
- t.columnOffset++
- }
- }
- pageDown = func() {
- offsetAmount := t.visibleRows - t.fixedRows
- if offsetAmount < 0 {
- offsetAmount = 0
- }
- if t.rowsSelectable {
- startRow := t.selectedRow
- t.selectedRow += offsetAmount
- if t.selectedRow >= rowCount {
- t.selectedRow = rowCount - 1
- }
- t.clampToSelection = true
- next()
- if !t.wrapVertically && t.selectedRow < startRow {
- t.selectedRow = rowCount - 1
- t.selectedColumn = lastColumn
- previous()
- }
- } else {
- t.rowOffset += offsetAmount
- }
- }
- pageUp = func() {
- offsetAmount := t.visibleRows - t.fixedRows
- if offsetAmount < 0 {
- offsetAmount = 0
- }
- if t.rowsSelectable {
- startRow := t.selectedRow
- t.selectedRow -= offsetAmount
- if t.selectedRow < 0 {
- t.selectedRow = 0
- }
- t.clampToSelection = true
- previous()
- if !t.wrapVertically && t.selectedRow > startRow {
- t.selectedRow = 0
- t.selectedColumn = 0
- next()
- }
- } else {
- t.trackEnd = false
- t.rowOffset -= offsetAmount
- }
- }
- )
- 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)
- }
- })
- }
- // MouseHandler returns the mouse handler for this primitive.
- func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
- return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
- x, y := event.Position()
- if !t.InRect(x, y) {
- return false, nil
- }
- switch action {
- case MouseLeftDown:
- setFocus(t)
- consumed = true
- case MouseLeftClick:
- selectEvent := true
- row, column := t.cellAt(x, y)
- 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) {
- t.Select(row, column)
- }
- consumed = true
- case MouseScrollUp:
- t.trackEnd = false
- t.rowOffset--
- consumed = true
- case MouseScrollDown:
- t.rowOffset++
- consumed = true
- }
- return
- })
- }
|