table.go (52829B)
1 package tview 2 3 import ( 4 "sort" 5 6 "github.com/gdamore/tcell/v2" 7 colorful "github.com/lucasb-eyer/go-colorful" 8 ) 9 10 // TableCell represents one cell inside a Table. You can instantiate this type 11 // directly but all colors (background and text) will be set to their default 12 // which is black. 13 type TableCell struct { 14 // The reference object. 15 Reference interface{} 16 17 // The text to be displayed in the table cell. 18 Text string 19 20 // The alignment of the cell text. One of AlignLeft (default), AlignCenter, 21 // or AlignRight. 22 Align int 23 24 // The maximum width of the cell in screen space. This is used to give a 25 // column a maximum width. Any cell text whose screen width exceeds this width 26 // is cut off. Set to 0 if there is no maximum width. 27 MaxWidth int 28 29 // If the total table width is less than the available width, this value is 30 // used to add extra width to a column. See SetExpansion() for details. 31 Expansion int 32 33 // The color of the cell text. You should not use this anymore, it is only 34 // here for backwards compatibility. Use the Style field instead. 35 Color tcell.Color 36 37 // The background color of the cell. You should not use this anymore, it is 38 // only here for backwards compatibility. Use the Style field instead. 39 BackgroundColor tcell.Color 40 41 // The style attributes of the cell. You should not use this anymore, it is 42 // only here for backwards compatibility. Use the Style field instead. 43 Attributes tcell.AttrMask 44 45 // The style of the cell. If this is uninitialized (tcell.StyleDefault), the 46 // Color and BackgroundColor fields are used instead. 47 Style tcell.Style 48 49 // The style of the cell when it is selected. If this is uninitialized 50 // (tcell.StyleDefault), the table's selected style is used instead. If that 51 // is uninitialized as well, the cell's background and text color are 52 // swapped. 53 SelectedStyle tcell.Style 54 55 // If set to true, the BackgroundColor is not used and the cell will have 56 // the background color of the table. 57 Transparent bool 58 59 // If set to true, this cell cannot be selected. 60 NotSelectable bool 61 62 // An optional handler for mouse clicks. This also fires if the cell is not 63 // selectable. If true is returned, no additional "selected" event is fired 64 // on selectable cells. 65 Clicked func() bool 66 67 // The position and width of the cell the last time table was drawn. 68 x, y, width int 69 } 70 71 // NewTableCell returns a new table cell with sensible defaults. That is, left 72 // aligned text with the primary text color (see Styles) and a transparent 73 // background (using the background of the Table). 74 func NewTableCell(text string) *TableCell { 75 return &TableCell{ 76 Text: text, 77 Align: AlignLeft, 78 Style: tcell.StyleDefault.Foreground(Styles.PrimaryTextColor).Background(Styles.PrimitiveBackgroundColor), 79 Transparent: true, 80 } 81 } 82 83 // SetText sets the cell's text. 84 func (c *TableCell) SetText(text string) *TableCell { 85 c.Text = text 86 return c 87 } 88 89 // SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or 90 // AlignRight. 91 func (c *TableCell) SetAlign(align int) *TableCell { 92 c.Align = align 93 return c 94 } 95 96 // SetMaxWidth sets maximum width of the cell in screen space. This is used to 97 // give a column a maximum width. Any cell text whose screen width exceeds this 98 // width is cut off. Set to 0 if there is no maximum width. 99 func (c *TableCell) SetMaxWidth(maxWidth int) *TableCell { 100 c.MaxWidth = maxWidth 101 return c 102 } 103 104 // SetExpansion sets the value by which the column of this cell expands if the 105 // available width for the table is more than the table width (prior to applying 106 // this expansion value). This is a proportional value. The amount of unused 107 // horizontal space is divided into widths to be added to each column. How much 108 // extra width a column receives depends on the expansion value: A value of 0 109 // (the default) will not cause the column to increase in width. Other values 110 // are proportional, e.g. a value of 2 will cause a column to grow by twice 111 // the amount of a column with a value of 1. 112 // 113 // Since this value affects an entire column, the maximum over all visible cells 114 // in that column is used. 115 // 116 // This function panics if a negative value is provided. 117 func (c *TableCell) SetExpansion(expansion int) *TableCell { 118 if expansion < 0 { 119 panic("Table cell expansion values may not be negative") 120 } 121 c.Expansion = expansion 122 return c 123 } 124 125 // SetTextColor sets the cell's text color. 126 func (c *TableCell) SetTextColor(color tcell.Color) *TableCell { 127 if c.Style == tcell.StyleDefault { 128 c.Color = color 129 } else { 130 c.Style = c.Style.Foreground(color) 131 } 132 return c 133 } 134 135 // SetBackgroundColor sets the cell's background color. This will also cause the 136 // cell's Transparent flag to be set to "false". 137 func (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell { 138 if c.Style == tcell.StyleDefault { 139 c.BackgroundColor = color 140 } else { 141 c.Style = c.Style.Background(color) 142 } 143 c.Transparent = false 144 return c 145 } 146 147 // SetTransparency sets the background transparency of this cell. A value of 148 // "true" will cause the cell to use the table's background color. A value of 149 // "false" will cause it to use its own background color. 150 func (c *TableCell) SetTransparency(transparent bool) *TableCell { 151 c.Transparent = transparent 152 return c 153 } 154 155 // SetAttributes sets the cell's text attributes. You can combine different 156 // attributes using bitmask operations: 157 // 158 // cell.SetAttributes(tcell.AttrItalic | tcell.AttrBold) 159 func (c *TableCell) SetAttributes(attr tcell.AttrMask) *TableCell { 160 if c.Style == tcell.StyleDefault { 161 c.Attributes = attr 162 } else { 163 c.Style = c.Style.Attributes(attr) 164 } 165 return c 166 } 167 168 // SetStyle sets the cell's style (foreground color, background color, and 169 // attributes) all at once. 170 func (c *TableCell) SetStyle(style tcell.Style) *TableCell { 171 c.Style = style 172 return c 173 } 174 175 // SetSelectedStyle sets the cell's style when it is selected. If this is 176 // uninitialized (tcell.StyleDefault), the table's selected style is used 177 // instead. If that is uninitialized as well, the cell's background and text 178 // color are swapped. 179 func (c *TableCell) SetSelectedStyle(style tcell.Style) *TableCell { 180 c.SelectedStyle = style 181 return c 182 } 183 184 // SetSelectable sets whether or not this cell can be selected by the user. 185 func (c *TableCell) SetSelectable(selectable bool) *TableCell { 186 c.NotSelectable = !selectable 187 return c 188 } 189 190 // SetReference allows you to store a reference of any type in this cell. This 191 // will allow you to establish a mapping between the cell and your 192 // actual data. 193 func (c *TableCell) SetReference(reference interface{}) *TableCell { 194 c.Reference = reference 195 return c 196 } 197 198 // GetReference returns this cell's reference object. 199 func (c *TableCell) GetReference() interface{} { 200 return c.Reference 201 } 202 203 // GetLastPosition returns the position of the table cell the last time it was 204 // drawn on screen. If the cell is not on screen, the return values are 205 // undefined. 206 // 207 // Because the Table class will attempt to keep selected cells on screen, this 208 // function is most useful in response to a "selected" event (see 209 // SetSelectedFunc()) or a "selectionChanged" event (see 210 // SetSelectionChangedFunc()). 211 func (c *TableCell) GetLastPosition() (x, y, width int) { 212 return c.x, c.y, c.width 213 } 214 215 // SetClickedFunc sets a handler which fires when this cell is clicked. This is 216 // independent of whether the cell is selectable or not. But for selectable 217 // cells, if the function returns "true", the "selected" event is not fired. 218 func (c *TableCell) SetClickedFunc(clicked func() bool) *TableCell { 219 c.Clicked = clicked 220 return c 221 } 222 223 // TableContent defines a Table's data. You may replace a Table's default 224 // implementation with your own using the Table.SetContent() function. This will 225 // allow you to turn Table into a view of your own data structure. The 226 // Table.Draw() function, which is called when the screen is updated, will then 227 // use the (read-only) functions of this interface to update the table. The 228 // write functions are only called when the corresponding functions of Table are 229 // called. 230 // 231 // The interface's read-only functions are not called concurrently by the 232 // package (provided that users of the package don't call Table.Draw() in a 233 // separate goroutine, which would be uncommon and is not encouraged). 234 type TableContent interface { 235 // Return the cell at the given position or nil if there is no cell. The 236 // row and column arguments start at 0 and end at what GetRowCount() and 237 // GetColumnCount() return, minus 1. 238 GetCell(row, column int) *TableCell 239 240 // Return the total number of rows in the table. 241 GetRowCount() int 242 243 // Return the total number of columns in the table. 244 GetColumnCount() int 245 246 // The following functions are provided for completeness reasons as the 247 // original Table implementation was not read-only. If you do not wish to 248 // forward modifying operations to your data, you may opt to leave these 249 // functions empty. To make this easier, you can include the 250 // TableContentReadOnly type in your struct. See also the 251 // demos/table/virtualtable example. 252 253 // Set the cell at the given position to the provided cell. 254 SetCell(row, column int, cell *TableCell) 255 256 // Remove the row at the given position by shifting all following rows up 257 // by one. Out of range positions may be ignored. 258 RemoveRow(row int) 259 260 // Remove the column at the given position by shifting all following columns 261 // left by one. Out of range positions may be ignored. 262 RemoveColumn(column int) 263 264 // Insert a new empty row at the given position by shifting all rows at that 265 // position and below down by one. Implementers may decide what to do with 266 // out of range positions. 267 InsertRow(row int) 268 269 // Insert a new empty column at the given position by shifting all columns 270 // at that position and to the right by one to the right. Implementers may 271 // decide what to do with out of range positions. 272 InsertColumn(column int) 273 274 // Remove all table data. 275 Clear() 276 } 277 278 // TableContentReadOnly is an empty struct which implements the write operations 279 // of the TableContent interface. None of the implemented functions do anything. 280 // You can embed this struct into your own structs to free yourself from having 281 // to implement the empty write functions of TableContent. See 282 // demos/table/virtualtable for an example. 283 type TableContentReadOnly struct{} 284 285 // SetCell does not do anything. 286 func (t TableContentReadOnly) SetCell(row, column int, cell *TableCell) { 287 // nop. 288 } 289 290 // RemoveRow does not do anything. 291 func (t TableContentReadOnly) RemoveRow(row int) { 292 // nop. 293 } 294 295 // RemoveColumn does not do anything. 296 func (t TableContentReadOnly) RemoveColumn(column int) { 297 // nop. 298 } 299 300 // InsertRow does not do anything. 301 func (t TableContentReadOnly) InsertRow(row int) { 302 // nop. 303 } 304 305 // InsertColumn does not do anything. 306 func (t TableContentReadOnly) InsertColumn(column int) { 307 // nop. 308 } 309 310 // Clear does not do anything. 311 func (t TableContentReadOnly) Clear() { 312 // nop. 313 } 314 315 // tableDefaultContent implements the default TableContent interface for the 316 // Table class. 317 type tableDefaultContent struct { 318 // The cells of the table. Rows first, then columns. 319 cells [][]*TableCell 320 321 // The rightmost column in the data set. 322 lastColumn int 323 } 324 325 // Clear clears all data. 326 func (t *tableDefaultContent) Clear() { 327 t.cells = nil 328 t.lastColumn = -1 329 } 330 331 // SetCell sets a cell's content. 332 func (t *tableDefaultContent) SetCell(row, column int, cell *TableCell) { 333 if row >= len(t.cells) { 334 t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...) 335 } 336 rowLen := len(t.cells[row]) 337 if column >= rowLen { 338 t.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...) 339 for c := rowLen; c < column; c++ { 340 t.cells[row][c] = &TableCell{} 341 } 342 } 343 t.cells[row][column] = cell 344 if column > t.lastColumn { 345 t.lastColumn = column 346 } 347 } 348 349 // RemoveRow removes a row from the data. 350 func (t *tableDefaultContent) RemoveRow(row int) { 351 if row < 0 || row >= len(t.cells) { 352 return 353 } 354 t.cells = append(t.cells[:row], t.cells[row+1:]...) 355 } 356 357 // RemoveColumn removes a column from the data. 358 func (t *tableDefaultContent) RemoveColumn(column int) { 359 for row := range t.cells { 360 if column < 0 || column >= len(t.cells[row]) { 361 continue 362 } 363 t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...) 364 } 365 if column >= 0 && column <= t.lastColumn { 366 t.lastColumn-- 367 } 368 } 369 370 // InsertRow inserts a new row at the given position. 371 func (t *tableDefaultContent) InsertRow(row int) { 372 if row >= len(t.cells) { 373 return 374 } 375 t.cells = append(t.cells, nil) // Extend by one. 376 copy(t.cells[row+1:], t.cells[row:]) // Shift down. 377 t.cells[row] = nil // New row is uninitialized. 378 } 379 380 // InsertColumn inserts a new column at the given position. 381 func (t *tableDefaultContent) InsertColumn(column int) { 382 for row := range t.cells { 383 if column >= len(t.cells[row]) { 384 continue 385 } 386 t.cells[row] = append(t.cells[row], nil) // Extend by one. 387 copy(t.cells[row][column+1:], t.cells[row][column:]) // Shift to the right. 388 t.cells[row][column] = &TableCell{} // New element is an uninitialized table cell. 389 } 390 } 391 392 // GetCell returns the cell at the given position. 393 func (t *tableDefaultContent) GetCell(row, column int) *TableCell { 394 if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) { 395 return nil 396 } 397 return t.cells[row][column] 398 } 399 400 // GetRowCount returns the number of rows in the data set. 401 func (t *tableDefaultContent) GetRowCount() int { 402 return len(t.cells) 403 } 404 405 // GetColumnCount returns the number of columns in the data set. 406 func (t *tableDefaultContent) GetColumnCount() int { 407 if len(t.cells) == 0 { 408 return 0 409 } 410 return t.lastColumn + 1 411 } 412 413 // Table visualizes two-dimensional data consisting of rows and columns. Each 414 // Table cell is defined via [Table.SetCell] by the [TableCell] type. They can 415 // be added dynamically to the table and changed any time. 416 // 417 // The most compact display of a table is without borders. Each row will then 418 // occupy one row on screen and columns are separated by the rune defined via 419 // [Table.SetSeparator] (a space character by default). 420 // 421 // When borders are turned on (via [Table.SetBorders]), each table cell is 422 // surrounded by lines. Therefore one table row will require two rows on screen. 423 // 424 // Columns will use as much horizontal space as they need. You can constrain 425 // their size with the [TableCell.MaxWidth] parameter of the [TableCell] type. 426 // 427 // # Fixed Columns 428 // 429 // You can define fixed rows and rolumns via [Table.SetFixed]. They will always 430 // stay in their place, even when the table is scrolled. Fixed rows are always 431 // the top rows. Fixed columns are always the leftmost columns. 432 // 433 // # Selections 434 // 435 // You can call [Table.SetSelectable] to set columns and/or rows to 436 // "selectable". If the flag is set only for columns, entire columns can be 437 // selected by the user. If it is set only for rows, entire rows can be 438 // selected. If both flags are set, individual cells can be selected. The 439 // "selected" handler set via [Table.SetSelectedFunc] is invoked when the user 440 // presses Enter on a selection. 441 // 442 // # Navigation 443 // 444 // If the table extends beyond the available space, it can be navigated with 445 // key bindings similar to Vim: 446 // 447 // - h, left arrow: Move left by one column. 448 // - l, right arrow: Move right by one column. 449 // - j, down arrow: Move down by one row. 450 // - k, up arrow: Move up by one row. 451 // - g, home: Move to the top. 452 // - G, end: Move to the bottom. 453 // - Ctrl-F, page down: Move down by one page. 454 // - Ctrl-B, page up: Move up by one page. 455 // 456 // When there is no selection, this affects the entire table (except for fixed 457 // rows and columns). When there is a selection, the user moves the selection. 458 // The class will attempt to keep the selection from moving out of the screen. 459 // 460 // Use [Box.SetInputCapture] to override or modify keyboard input. 461 // 462 // See https://github.com/rivo/tview/wiki/Table for an example. 463 type Table struct { 464 *Box 465 466 // Whether or not this table has borders around each cell. 467 borders bool 468 469 // The color of the borders or the separator. 470 bordersColor tcell.Color 471 472 // If there are no borders, the column separator. 473 separator rune 474 475 // The table's data structure. 476 content TableContent 477 478 // If true, when calculating the widths of the columns, all rows are evaluated 479 // instead of only the visible ones. 480 evaluateAllRows bool 481 482 // The number of fixed rows / columns. 483 fixedRows, fixedColumns int 484 485 // Whether or not rows or columns can be selected. If both are set to true, 486 // cells can be selected. 487 rowsSelectable, columnsSelectable bool 488 489 // The currently selected row and column. 490 selectedRow, selectedColumn int 491 492 // A temporary flag which causes the next call to Draw() to force the 493 // current selection to remain visible. It is set to false afterwards. 494 clampToSelection bool 495 496 // If set to true, moving the selection will wrap around horizontally (last 497 // to first column and vice versa) or vertically (last to first row and vice 498 // versa). 499 wrapHorizontally, wrapVertically bool 500 501 // The number of rows/columns by which the table is scrolled down/to the 502 // right. 503 rowOffset, columnOffset int 504 505 // If set to true, the table's last row will always be visible. 506 trackEnd bool 507 508 // The number of visible rows the last time the table was drawn. 509 visibleRows int 510 511 // The indices of the visible columns as of the last time the table was drawn. 512 visibleColumnIndices []int 513 514 // The net widths of the visible columns as of the last time the table was 515 // drawn. 516 visibleColumnWidths []int 517 518 // The style of the selected rows. If this value is the empty struct, 519 // selected rows are simply inverted. 520 selectedStyle tcell.Style 521 522 // An optional function which gets called when the user presses Enter on a 523 // selected cell. If entire rows selected, the column value is undefined. 524 // Likewise for entire columns. 525 selected func(row, column int) 526 527 // An optional function which gets called when the user changes the selection. 528 // If entire rows selected, the column value is undefined. 529 // Likewise for entire columns. 530 selectionChanged func(row, column int) 531 532 // An optional function which gets called when the user presses Escape, Tab, 533 // or Backtab. Also when the user presses Enter if nothing is selectable. 534 done func(key tcell.Key) 535 } 536 537 // NewTable returns a new table. 538 func NewTable() *Table { 539 t := &Table{ 540 Box: NewBox(), 541 bordersColor: Styles.GraphicsColor, 542 separator: ' ', 543 } 544 t.SetContent(nil) 545 return t 546 } 547 548 // SetContent sets a new content type for this table. This allows you to back 549 // the table by a data structure of your own, for example one that cannot be 550 // fully held in memory. For details, see the TableContent interface 551 // documentation. 552 // 553 // A value of nil will return the table to its default implementation where all 554 // of its table cells are kept in memory. 555 func (t *Table) SetContent(content TableContent) *Table { 556 if content != nil { 557 t.content = content 558 } else { 559 t.content = &tableDefaultContent{ 560 lastColumn: -1, 561 } 562 } 563 return t 564 } 565 566 // Clear removes all table data. 567 func (t *Table) Clear() *Table { 568 t.content.Clear() 569 return t 570 } 571 572 // SetBorders sets whether or not each cell in the table is surrounded by a 573 // border. 574 func (t *Table) SetBorders(show bool) *Table { 575 t.borders = show 576 return t 577 } 578 579 // SetBordersColor sets the color of the cell borders. 580 func (t *Table) SetBordersColor(color tcell.Color) *Table { 581 t.bordersColor = color 582 return t 583 } 584 585 // SetSelectedStyle sets a specific style for selected cells. If no such style 586 // is set, the cell's background and text color are swapped. If a cell defines 587 // its own selected style, that will be used instead. 588 // 589 // To reset a previous setting to its default, make the following call: 590 // 591 // table.SetSelectedStyle(tcell.StyleDefault) 592 func (t *Table) SetSelectedStyle(style tcell.Style) *Table { 593 t.selectedStyle = style 594 return t 595 } 596 597 // SetSeparator sets the character used to fill the space between two 598 // neighboring cells. This is a space character ' ' per default but you may 599 // want to set it to Borders.Vertical (or any other rune) if the column 600 // separation should be more visible. If cell borders are activated, this is 601 // ignored. 602 // 603 // Separators have the same color as borders. 604 func (t *Table) SetSeparator(separator rune) *Table { 605 t.separator = separator 606 return t 607 } 608 609 // SetFixed sets the number of fixed rows and columns which are always visible 610 // even when the rest of the cells are scrolled out of view. Rows are always the 611 // top-most ones. Columns are always the left-most ones. 612 func (t *Table) SetFixed(rows, columns int) *Table { 613 t.fixedRows, t.fixedColumns = rows, columns 614 return t 615 } 616 617 // SetSelectable sets the flags which determine what can be selected in a table. 618 // There are three selection modi: 619 // 620 // - rows = false, columns = false: Nothing can be selected. 621 // - rows = true, columns = false: Rows can be selected. 622 // - rows = false, columns = true: Columns can be selected. 623 // - rows = true, columns = true: Individual cells can be selected. 624 func (t *Table) SetSelectable(rows, columns bool) *Table { 625 t.rowsSelectable, t.columnsSelectable = rows, columns 626 return t 627 } 628 629 // GetSelectable returns what can be selected in a table. Refer to 630 // SetSelectable() for details. 631 func (t *Table) GetSelectable() (rows, columns bool) { 632 return t.rowsSelectable, t.columnsSelectable 633 } 634 635 // GetSelection returns the position of the current selection. 636 // If entire rows are selected, the column index is undefined. 637 // Likewise for entire columns. 638 func (t *Table) GetSelection() (row, column int) { 639 return t.selectedRow, t.selectedColumn 640 } 641 642 // Select sets the selected cell. Depending on the selection settings 643 // specified via SetSelectable(), this may be an entire row or column, or even 644 // ignored completely. The "selection changed" event is fired if such a callback 645 // is available (even if the selection ends up being the same as before and even 646 // if cells are not selectable). 647 func (t *Table) Select(row, column int) *Table { 648 t.selectedRow, t.selectedColumn = row, column 649 t.clampToSelection = true 650 if t.selectionChanged != nil { 651 t.selectionChanged(row, column) 652 } 653 return t 654 } 655 656 // SetOffset sets how many rows and columns should be skipped when drawing the 657 // table. This is useful for large tables that do not fit on the screen. 658 // Navigating a selection can change these values. 659 // 660 // Fixed rows and columns are never skipped. 661 func (t *Table) SetOffset(row, column int) *Table { 662 t.rowOffset, t.columnOffset = row, column 663 t.trackEnd = false 664 return t 665 } 666 667 // GetOffset returns the current row and column offset. This indicates how many 668 // rows and columns the table is scrolled down and to the right. 669 func (t *Table) GetOffset() (row, column int) { 670 return t.rowOffset, t.columnOffset 671 } 672 673 // SetEvaluateAllRows sets a flag which determines the rows to be evaluated when 674 // calculating the widths of the table's columns. When false, only visible rows 675 // are evaluated. When true, all rows in the table are evaluated. 676 // 677 // Set this flag to true to avoid shifting column widths when the table is 678 // scrolled. (May come with a performance penalty for large tables.) 679 // 680 // Use with caution on very large tables, especially those not backed by the 681 // default TableContent data structure. 682 func (t *Table) SetEvaluateAllRows(all bool) *Table { 683 t.evaluateAllRows = all 684 return t 685 } 686 687 // SetSelectedFunc sets a handler which is called whenever the user presses the 688 // Enter key on a selected cell/row/column. The handler receives the position of 689 // the selection and its cell contents. If entire rows are selected, the column 690 // index is undefined. Likewise for entire columns. 691 func (t *Table) SetSelectedFunc(handler func(row, column int)) *Table { 692 t.selected = handler 693 return t 694 } 695 696 // SetSelectionChangedFunc sets a handler which is called whenever the current 697 // selection changes. The handler receives the position of the new selection. 698 // If entire rows are selected, the column index is undefined. Likewise for 699 // entire columns. 700 func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table { 701 t.selectionChanged = handler 702 return t 703 } 704 705 // SetDoneFunc sets a handler which is called whenever the user presses the 706 // Escape, Tab, or Backtab key. If nothing is selected, it is also called when 707 // user presses the Enter key (because pressing Enter on a selection triggers 708 // the "selected" handler set via SetSelectedFunc()). 709 func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table { 710 t.done = handler 711 return t 712 } 713 714 // SetCell sets the content of a cell the specified position. It is ok to 715 // directly instantiate a TableCell object. If the cell has content, at least 716 // the Text and Color fields should be set. 717 // 718 // Note that setting cells in previously unknown rows and columns will 719 // automatically extend the internal table representation with empty TableCell 720 // objects, e.g. starting with a row of 100,000 will immediately create 100,000 721 // empty rows. 722 // 723 // To avoid unnecessary garbage collection, fill columns from left to right. 724 func (t *Table) SetCell(row, column int, cell *TableCell) *Table { 725 t.content.SetCell(row, column, cell) 726 return t 727 } 728 729 // SetCellSimple calls SetCell() with the given text, left-aligned, in white. 730 func (t *Table) SetCellSimple(row, column int, text string) *Table { 731 t.SetCell(row, column, NewTableCell(text)) 732 return t 733 } 734 735 // GetCell returns the contents of the cell at the specified position. A valid 736 // TableCell object is always returned but it will be uninitialized if the cell 737 // was not previously set. Such an uninitialized object will not automatically 738 // be inserted. Therefore, repeated calls to this function may return different 739 // pointers for uninitialized cells. 740 func (t *Table) GetCell(row, column int) *TableCell { 741 cell := t.content.GetCell(row, column) 742 if cell == nil { 743 cell = &TableCell{} 744 } 745 return cell 746 } 747 748 // RemoveRow removes the row at the given position from the table. If there is 749 // no such row, this has no effect. 750 func (t *Table) RemoveRow(row int) *Table { 751 t.content.RemoveRow(row) 752 return t 753 } 754 755 // RemoveColumn removes the column at the given position from the table. If 756 // there is no such column, this has no effect. 757 func (t *Table) RemoveColumn(column int) *Table { 758 t.content.RemoveColumn(column) 759 return t 760 } 761 762 // InsertRow inserts a row before the row with the given index. Cells on the 763 // given row and below will be shifted to the bottom by one row. If "row" is 764 // equal or larger than the current number of rows, this function has no effect. 765 func (t *Table) InsertRow(row int) *Table { 766 t.content.InsertRow(row) 767 return t 768 } 769 770 // InsertColumn inserts a column before the column with the given index. Cells 771 // in the given column and to its right will be shifted to the right by one 772 // column. Rows that have fewer initialized cells than "column" will remain 773 // unchanged. 774 func (t *Table) InsertColumn(column int) *Table { 775 t.content.InsertColumn(column) 776 return t 777 } 778 779 // GetRowCount returns the number of rows in the table. 780 func (t *Table) GetRowCount() int { 781 return t.content.GetRowCount() 782 } 783 784 // GetColumnCount returns the (maximum) number of columns in the table. 785 func (t *Table) GetColumnCount() int { 786 return t.content.GetColumnCount() 787 } 788 789 // CellAt returns the row and column located at the given screen coordinates. 790 // Each returned value may be negative if there is no row and/or cell. This 791 // function will also process coordinates outside the table's inner rectangle so 792 // callers will need to check for bounds themselves. 793 // 794 // The layout of the table when it was last drawn is used so if anything has 795 // changed in the meantime, the results may not be reliable. 796 func (t *Table) CellAt(x, y int) (row, column int) { 797 rectX, rectY, _, _ := t.GetInnerRect() 798 799 // Determine row as seen on screen. 800 if t.borders { 801 row = (y - rectY - 1) / 2 802 } else { 803 row = y - rectY 804 } 805 806 // Respect fixed rows and row offset. 807 if row >= 0 { 808 if row >= t.fixedRows { 809 row += t.rowOffset 810 } 811 if row >= t.content.GetRowCount() { 812 row = -1 813 } 814 } 815 816 // Saerch for the clicked column. 817 column = -1 818 if x >= rectX { 819 columnX := rectX 820 if t.borders { 821 columnX++ 822 } 823 for index, width := range t.visibleColumnWidths { 824 columnX += width + 1 825 if x < columnX { 826 column = t.visibleColumnIndices[index] 827 break 828 } 829 } 830 } 831 832 return 833 } 834 835 // ScrollToBeginning scrolls the table to the beginning to that the top left 836 // corner of the table is shown. Note that this position may be corrected if 837 // there is a selection. 838 func (t *Table) ScrollToBeginning() *Table { 839 t.trackEnd = false 840 t.columnOffset = 0 841 t.rowOffset = 0 842 return t 843 } 844 845 // ScrollToEnd scrolls the table to the beginning to that the bottom left corner 846 // of the table is shown. Adding more rows to the table will cause it to 847 // automatically scroll with the new data. Note that this position may be 848 // corrected if there is a selection. 849 func (t *Table) ScrollToEnd() *Table { 850 t.trackEnd = true 851 t.columnOffset = 0 852 t.rowOffset = t.content.GetRowCount() 853 return t 854 } 855 856 // SetWrapSelection determines whether a selection wraps vertically or 857 // horizontally when moved. Vertically wrapping selections will jump from the 858 // last selectable row to the first selectable row and vice versa. Horizontally 859 // wrapping selections will jump from the last selectable column to the first 860 // selectable column (on the next selectable row) or from the first selectable 861 // column to the last selectable column (on the previous selectable row). If set 862 // to false, the selection is not moved when it is already on the first/last 863 // selectable row/column. 864 // 865 // The default is for both values to be false. 866 func (t *Table) SetWrapSelection(vertical, horizontal bool) *Table { 867 t.wrapHorizontally = horizontal 868 t.wrapVertically = vertical 869 return t 870 } 871 872 // Draw draws this primitive onto the screen. 873 func (t *Table) Draw(screen tcell.Screen) { 874 t.Box.DrawForSubclass(screen, t) 875 876 // What's our available screen space? 877 _, totalHeight := screen.Size() 878 x, y, width, height := t.GetInnerRect() 879 netWidth := width 880 if t.borders { 881 t.visibleRows = height / 2 882 netWidth -= 2 883 } else { 884 t.visibleRows = height 885 } 886 887 // If this cell is not selectable, find the next one. 888 rowCount, columnCount := t.content.GetRowCount(), t.content.GetColumnCount() 889 if t.rowsSelectable || t.columnsSelectable { 890 if t.selectedColumn < 0 { 891 t.selectedColumn = 0 892 } 893 if t.selectedRow < 0 { 894 t.selectedRow = 0 895 } 896 for t.selectedRow < rowCount { 897 cell := t.content.GetCell(t.selectedRow, t.selectedColumn) 898 if cell != nil && !cell.NotSelectable { 899 break 900 } 901 t.selectedColumn++ 902 if t.selectedColumn > columnCount-1 { 903 t.selectedColumn = 0 904 t.selectedRow++ 905 } 906 } 907 } 908 909 // Clamp row offsets if requested. 910 defer func() { 911 t.clampToSelection = false // Only once. 912 }() 913 if t.clampToSelection && t.rowsSelectable { 914 if t.selectedRow >= t.fixedRows && t.selectedRow < t.fixedRows+t.rowOffset { 915 t.rowOffset = t.selectedRow - t.fixedRows 916 t.trackEnd = false 917 } 918 if t.borders { 919 if t.selectedRow+1-t.rowOffset >= height/2 { 920 t.rowOffset = t.selectedRow + 1 - height/2 921 t.trackEnd = false 922 } 923 } else { 924 if t.selectedRow+1-t.rowOffset >= height { 925 t.rowOffset = t.selectedRow + 1 - height 926 t.trackEnd = false 927 } 928 } 929 } 930 if t.rowOffset < 0 { 931 t.rowOffset = 0 932 } 933 if t.borders { 934 if rowCount-t.rowOffset < height/2 { 935 t.trackEnd = true 936 } 937 } else { 938 if rowCount-t.rowOffset < height { 939 t.trackEnd = true 940 } 941 } 942 if t.trackEnd { 943 if t.borders { 944 t.rowOffset = rowCount - height/2 945 } else { 946 t.rowOffset = rowCount - height 947 } 948 } 949 if t.rowOffset < 0 { 950 t.rowOffset = 0 951 } 952 953 // Avoid invalid column offsets. 954 if t.columnOffset >= columnCount-t.fixedColumns { 955 t.columnOffset = columnCount - t.fixedColumns - 1 956 } 957 if t.columnOffset < 0 { 958 t.columnOffset = 0 959 } 960 961 // Determine the indices of the rows which fit on the screen. 962 var ( 963 rows, allRows []int 964 tableHeight int 965 ) 966 rowStep := 1 967 if t.borders { 968 rowStep = 2 // With borders, every table row takes two screen rows. 969 } 970 if t.evaluateAllRows { 971 allRows = make([]int, rowCount) 972 for row := 0; row < rowCount; row++ { 973 allRows[row] = row 974 } 975 } 976 indexRow := func(row int) bool { // Determine if this row is visible, store its index. 977 if tableHeight >= height { 978 return false 979 } 980 rows = append(rows, row) 981 tableHeight += rowStep 982 return true 983 } 984 for row := 0; row < t.fixedRows && row < rowCount; row++ { // Do the fixed rows first. 985 if !indexRow(row) { 986 break 987 } 988 } 989 for row := t.fixedRows + t.rowOffset; row < rowCount; row++ { // Then the remaining rows. 990 if !indexRow(row) { 991 break 992 } 993 } 994 995 // Determine the columns' indices, widths, and expansion values that fit on 996 // the screen. 997 var ( 998 tableWidth, expansionTotal int 999 columns, widths, expansions []int 1000 ) 1001 includesSelection := !t.clampToSelection || !t.columnsSelectable 1002 1003 // Helper function that evaluates one column. Returns true if the column 1004 // didn't fit at all. 1005 indexColumn := func(column int) bool { 1006 if netWidth == 0 || tableWidth >= netWidth { 1007 return true 1008 } 1009 1010 var maxWidth, expansion int 1011 evaluationRows := rows 1012 if t.evaluateAllRows { 1013 evaluationRows = allRows 1014 } 1015 for _, row := range evaluationRows { 1016 if cell := t.content.GetCell(row, column); cell != nil { 1017 cellWidth := TaggedStringWidth(cell.Text) 1018 if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth { 1019 cellWidth = cell.MaxWidth 1020 } 1021 if cellWidth > maxWidth { 1022 maxWidth = cellWidth 1023 } 1024 if cell.Expansion > expansion { 1025 expansion = cell.Expansion 1026 } 1027 } 1028 } 1029 clampedMaxWidth := maxWidth 1030 if tableWidth+maxWidth > netWidth { 1031 clampedMaxWidth = netWidth - tableWidth 1032 } 1033 columns = append(columns, column) 1034 widths = append(widths, clampedMaxWidth) 1035 expansions = append(expansions, expansion) 1036 tableWidth += clampedMaxWidth + 1 1037 expansionTotal += expansion 1038 if t.columnsSelectable && t.clampToSelection && column == t.selectedColumn { 1039 // We want selections to appear fully. 1040 includesSelection = clampedMaxWidth == maxWidth 1041 } 1042 1043 return false 1044 } 1045 1046 // Helper function that evaluates multiple columns, starting at "start" and 1047 // at most ending at "maxEnd". Returns first column not included anymore (or 1048 // -1 if all are included). 1049 indexColumns := func(start, maxEnd int) int { 1050 if start == maxEnd { 1051 return -1 1052 } 1053 1054 if start < maxEnd { 1055 // Forward-evaluate columns. 1056 for column := start; column < maxEnd; column++ { 1057 if indexColumn(column) { 1058 return column 1059 } 1060 } 1061 return -1 1062 } 1063 1064 // Backward-evaluate columns. 1065 startLen := len(columns) 1066 defer func() { 1067 // Because we went backwards, we must reverse the partial slices. 1068 for i, j := startLen, len(columns)-1; i < j; i, j = i+1, j-1 { 1069 columns[i], columns[j] = columns[j], columns[i] 1070 widths[i], widths[j] = widths[j], widths[i] 1071 expansions[i], expansions[j] = expansions[j], expansions[i] 1072 } 1073 }() 1074 for column := start; column >= maxEnd; column-- { 1075 if indexColumn(column) { 1076 return column 1077 } 1078 } 1079 return -1 1080 } 1081 1082 // Reset the table to only its fixed columns. 1083 var fixedTableWidth, fixedExpansionTotal int 1084 resetColumns := func() { 1085 tableWidth = fixedTableWidth 1086 expansionTotal = fixedExpansionTotal 1087 columns = columns[:t.fixedColumns] 1088 widths = widths[:t.fixedColumns] 1089 expansions = expansions[:t.fixedColumns] 1090 } 1091 1092 // Add fixed columns. 1093 if indexColumns(0, t.fixedColumns) < 0 { 1094 fixedTableWidth = tableWidth 1095 fixedExpansionTotal = expansionTotal 1096 1097 // Add unclamped columns. 1098 if column := indexColumns(t.fixedColumns+t.columnOffset, columnCount); !includesSelection || column < 0 && t.columnOffset > 0 { 1099 // Offset is not optimal. Try again. 1100 if !includesSelection { 1101 // Clamp to selection. 1102 resetColumns() 1103 if t.selectedColumn <= t.fixedColumns+t.columnOffset { 1104 // It's on the left. Start with the selection. 1105 t.columnOffset = t.selectedColumn - t.fixedColumns 1106 indexColumns(t.fixedColumns+t.columnOffset, columnCount) 1107 } else { 1108 // It's on the right. End with the selection. 1109 if column := indexColumns(t.selectedColumn, t.fixedColumns); column >= 0 { 1110 t.columnOffset = column + 1 - t.fixedColumns 1111 } else { 1112 t.columnOffset = 0 1113 } 1114 } 1115 } else if tableWidth < netWidth { 1116 // Don't waste space. Try to fit as much on screen as possible. 1117 resetColumns() 1118 if column := indexColumns(columnCount-1, t.fixedColumns); column >= 0 { 1119 t.columnOffset = column + 1 - t.fixedColumns 1120 } else { 1121 t.columnOffset = 0 1122 } 1123 } 1124 } 1125 } 1126 1127 // If we have space left, distribute it. 1128 if tableWidth < netWidth { 1129 toDistribute := netWidth - tableWidth 1130 for index, expansion := range expansions { 1131 if expansionTotal <= 0 { 1132 break 1133 } 1134 expWidth := toDistribute * expansion / expansionTotal 1135 widths[index] += expWidth 1136 toDistribute -= expWidth 1137 expansionTotal -= expansion 1138 } 1139 } 1140 1141 // Helper function which draws border runes. 1142 borderStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.bordersColor) 1143 drawBorder := func(colX, rowY int, ch rune) { 1144 screen.SetContent(x+colX, y+rowY, ch, nil, borderStyle) 1145 } 1146 1147 // Draw the cells (and borders). 1148 var columnX int 1149 if t.borders { 1150 columnX++ 1151 } 1152 for columnIndex, column := range columns { 1153 columnWidth := widths[columnIndex] 1154 for rowY, row := range rows { 1155 if t.borders { 1156 // Draw borders. 1157 rowY *= 2 1158 for pos := 0; pos < columnWidth && columnX+pos < width; pos++ { 1159 drawBorder(columnX+pos, rowY, Borders.Horizontal) 1160 } 1161 ch := Borders.Cross 1162 if row == 0 { 1163 if column == 0 { 1164 ch = Borders.TopLeft 1165 } else { 1166 ch = Borders.TopT 1167 } 1168 } else if column == 0 { 1169 ch = Borders.LeftT 1170 } 1171 drawBorder(columnX-1, rowY, ch) 1172 rowY++ 1173 if rowY >= height || y+rowY >= totalHeight { 1174 break // No space for the text anymore. 1175 } 1176 drawBorder(columnX-1, rowY, Borders.Vertical) 1177 } else if columnIndex < len(columns)-1 { 1178 // Draw separator. 1179 drawBorder(columnX+columnWidth, rowY, t.separator) 1180 } 1181 1182 // Get the cell. 1183 cell := t.content.GetCell(row, column) 1184 if cell == nil { 1185 continue 1186 } 1187 1188 // Draw text. 1189 finalWidth := columnWidth 1190 if columnX+columnWidth >= width { 1191 finalWidth = width - columnX 1192 } 1193 cell.x, cell.y, cell.width = x+columnX, y+rowY, finalWidth 1194 style := cell.Style 1195 if style == tcell.StyleDefault { 1196 style = tcell.StyleDefault.Background(cell.BackgroundColor).Foreground(cell.Color).Attributes(cell.Attributes) 1197 } 1198 start, end, _ := printWithStyle(screen, cell.Text, x+columnX, y+rowY, 0, finalWidth, cell.Align, style, true) 1199 printed := end - start 1200 if TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 { 1201 _, _, style, _ := screen.GetContent(x+columnX+finalWidth-1, y+rowY) 1202 printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth-1, y+rowY, 0, 1, AlignLeft, style, false) 1203 } 1204 } 1205 1206 // Draw bottom border. 1207 if rowY := 2 * len(rows); t.borders && rowY > 0 && rowY < height { 1208 for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ { 1209 drawBorder(columnX+pos, rowY, Borders.Horizontal) 1210 } 1211 ch := Borders.Cross 1212 if rows[len(rows)-1] == rowCount-1 { 1213 if column == 0 { 1214 ch = Borders.BottomLeft 1215 } else { 1216 ch = Borders.BottomT 1217 } 1218 } else if column == 0 { 1219 ch = Borders.BottomLeft 1220 } 1221 drawBorder(columnX-1, rowY, ch) 1222 } 1223 1224 columnX += columnWidth + 1 1225 } 1226 1227 // Draw right border. 1228 columnX-- 1229 if t.borders && len(rows) > 0 && len(columns) > 0 && columnX < width { 1230 lastColumn := columns[len(columns)-1] == columnCount-1 1231 for rowY := range rows { 1232 rowY *= 2 1233 if rowY+1 < height { 1234 drawBorder(columnX, rowY+1, Borders.Vertical) 1235 } 1236 ch := Borders.Cross 1237 if rowY == 0 { 1238 if lastColumn { 1239 ch = Borders.TopRight 1240 } else { 1241 ch = Borders.TopT 1242 } 1243 } else if lastColumn { 1244 ch = Borders.RightT 1245 } 1246 drawBorder(columnX, rowY, ch) 1247 } 1248 if rowY := 2 * len(rows); rowY < height { 1249 ch := Borders.BottomT 1250 if lastColumn { 1251 ch = Borders.BottomRight 1252 } 1253 drawBorder(columnX, rowY, ch) 1254 } 1255 } 1256 1257 // Helper function which colors the background of a box. 1258 // backgroundTransparent == true => Don't modify background color (when invert == false). 1259 // textTransparent == true => Don't modify text color (when invert == false). 1260 // attr == 0 => Don't change attributes. 1261 // invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor; 1262 // set background to textColor. 1263 colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, backgroundTransparent, textTransparent bool, attr tcell.AttrMask, invert bool) { 1264 for by := 0; by < h && fromY+by < y+height; by++ { 1265 for bx := 0; bx < w && fromX+bx < x+width; bx++ { 1266 m, c, style, _ := screen.GetContent(fromX+bx, fromY+by) 1267 fg, bg, a := style.Decompose() 1268 if invert { 1269 style = style.Background(textColor).Foreground(backgroundColor) 1270 } else { 1271 if !backgroundTransparent { 1272 bg = backgroundColor 1273 } 1274 if !textTransparent { 1275 fg = textColor 1276 } 1277 if attr != 0 { 1278 a = attr 1279 } 1280 style = style.Background(bg).Foreground(fg).Attributes(a) 1281 } 1282 screen.SetContent(fromX+bx, fromY+by, m, c, style) 1283 } 1284 } 1285 } 1286 1287 // Color the cell backgrounds. To avoid undesirable artefacts, we combine 1288 // the drawing of a cell by background color, selected cells last. 1289 type cellInfo struct { 1290 x, y, w, h int 1291 cell *TableCell 1292 selected bool 1293 } 1294 cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo) 1295 var backgroundColors []tcell.Color 1296 for rowY, row := range rows { 1297 columnX := 0 1298 rowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow 1299 for columnIndex, column := range columns { 1300 columnWidth := widths[columnIndex] 1301 cell := t.content.GetCell(row, column) 1302 if cell == nil { 1303 continue 1304 } 1305 bx, by, bw, bh := x+columnX, y+rowY, columnWidth+1, 1 1306 if t.borders { 1307 by = y + rowY*2 1308 bw++ 1309 bh = 3 1310 } 1311 columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn 1312 cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow) 1313 backgroundColor := cell.BackgroundColor 1314 if cell.Style != tcell.StyleDefault { 1315 _, backgroundColor, _ = cell.Style.Decompose() 1316 } 1317 entries, ok := cellsByBackgroundColor[backgroundColor] 1318 cellsByBackgroundColor[backgroundColor] = append(entries, &cellInfo{ 1319 x: bx, 1320 y: by, 1321 w: bw, 1322 h: bh, 1323 cell: cell, 1324 selected: cellSelected, 1325 }) 1326 if !ok { 1327 backgroundColors = append(backgroundColors, backgroundColor) 1328 } 1329 columnX += columnWidth + 1 1330 } 1331 } 1332 sort.Slice(backgroundColors, func(i int, j int) bool { 1333 // Draw brightest colors last (i.e. on top). 1334 r, g, b := backgroundColors[i].RGB() 1335 c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255} 1336 _, _, li := c.Hcl() 1337 r, g, b = backgroundColors[j].RGB() 1338 c = colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255} 1339 _, _, lj := c.Hcl() 1340 return li < lj 1341 }) 1342 for _, bgColor := range backgroundColors { 1343 entries := cellsByBackgroundColor[bgColor] 1344 for _, info := range entries { 1345 textColor := info.cell.Color 1346 if info.cell.Style != tcell.StyleDefault { 1347 textColor, _, _ = info.cell.Style.Decompose() 1348 } 1349 if info.selected { 1350 if info.cell.SelectedStyle != tcell.StyleDefault { 1351 selFg, selBg, selAttr := info.cell.SelectedStyle.Decompose() 1352 defer colorBackground(info.x, info.y, info.w, info.h, selBg, selFg, false, false, selAttr, false) 1353 } else if t.selectedStyle != tcell.StyleDefault { 1354 selFg, selBg, selAttr := t.selectedStyle.Decompose() 1355 defer colorBackground(info.x, info.y, info.w, info.h, selBg, selFg, false, false, selAttr, false) 1356 } else { 1357 defer colorBackground(info.x, info.y, info.w, info.h, bgColor, textColor, false, false, 0, true) 1358 } 1359 } else { 1360 colorBackground(info.x, info.y, info.w, info.h, bgColor, textColor, info.cell.Transparent, true, 0, false) 1361 } 1362 } 1363 } 1364 1365 // Remember column infos. 1366 t.visibleColumnIndices, t.visibleColumnWidths = columns, widths 1367 } 1368 1369 // InputHandler returns the handler for this primitive. 1370 func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { 1371 return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { 1372 key := event.Key() 1373 1374 if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) || 1375 key == tcell.KeyEscape || 1376 key == tcell.KeyTab || 1377 key == tcell.KeyBacktab { 1378 if t.done != nil { 1379 t.done(key) 1380 } 1381 return 1382 } 1383 1384 // Movement functions. 1385 previouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn 1386 lastColumn := t.content.GetColumnCount() - 1 1387 rowCount := t.content.GetRowCount() 1388 if rowCount == 0 { 1389 return // No movement on empty tables. 1390 } 1391 var ( 1392 // Move the selection forward, don't go beyond final cell, return 1393 // true if a selection was found. 1394 forward = func(finalRow, finalColumn int) bool { 1395 row, column := t.selectedRow, t.selectedColumn 1396 for { 1397 // Stop if the current selection is fine. 1398 cell := t.content.GetCell(row, column) 1399 if cell != nil && !cell.NotSelectable { 1400 t.selectedRow, t.selectedColumn = row, column 1401 return true 1402 } 1403 1404 // If we reached the final cell, stop. 1405 if row == finalRow && column == finalColumn { 1406 return false 1407 } 1408 1409 // Move forward. 1410 column++ 1411 if column > lastColumn { 1412 column = 0 1413 row++ 1414 if row >= rowCount { 1415 row = 0 1416 } 1417 } 1418 } 1419 } 1420 1421 // Move the selection backwards, don't go beyond final cell, return 1422 // true if a selection was found. 1423 backwards = func(finalRow, finalColumn int) bool { 1424 row, column := t.selectedRow, t.selectedColumn 1425 for { 1426 // Stop if the current selection is fine. 1427 cell := t.content.GetCell(row, column) 1428 if cell != nil && !cell.NotSelectable { 1429 t.selectedRow, t.selectedColumn = row, column 1430 return true 1431 } 1432 1433 // If we reached the final cell, stop. 1434 if row == finalRow && column == finalColumn { 1435 return false 1436 } 1437 1438 // Move backwards. 1439 column-- 1440 if column < 0 { 1441 column = lastColumn 1442 row-- 1443 if row < 0 { 1444 row = rowCount - 1 1445 } 1446 } 1447 } 1448 } 1449 1450 home = func() { 1451 if t.rowsSelectable { 1452 t.selectedRow = 0 1453 t.selectedColumn = 0 1454 forward(rowCount-1, lastColumn) 1455 t.clampToSelection = true 1456 } else { 1457 t.trackEnd = false 1458 t.rowOffset = 0 1459 t.columnOffset = 0 1460 } 1461 } 1462 1463 end = func() { 1464 if t.rowsSelectable { 1465 t.selectedRow = rowCount - 1 1466 t.selectedColumn = lastColumn 1467 backwards(0, 0) 1468 t.clampToSelection = true 1469 } else { 1470 t.trackEnd = true 1471 t.columnOffset = 0 1472 } 1473 } 1474 1475 down = func() { 1476 if t.rowsSelectable { 1477 row, column := t.selectedRow, t.selectedColumn 1478 t.selectedRow++ 1479 if t.selectedRow >= rowCount { 1480 if t.wrapVertically { 1481 t.selectedRow = 0 1482 } else { 1483 t.selectedRow = rowCount - 1 1484 } 1485 } 1486 finalRow, finalColumn := rowCount-1, lastColumn 1487 if t.wrapVertically { 1488 finalRow = row 1489 finalColumn = column 1490 } 1491 if !forward(finalRow, finalColumn) { 1492 backwards(row, column) 1493 } 1494 t.clampToSelection = true 1495 } else { 1496 t.rowOffset++ 1497 } 1498 } 1499 1500 up = func() { 1501 if t.rowsSelectable { 1502 row, column := t.selectedRow, t.selectedColumn 1503 t.selectedRow-- 1504 if t.selectedRow < 0 { 1505 if t.wrapVertically { 1506 t.selectedRow = rowCount - 1 1507 } else { 1508 t.selectedRow = 0 1509 } 1510 } 1511 finalRow, finalColumn := 0, 0 1512 if t.wrapVertically { 1513 finalRow = row 1514 finalColumn = column 1515 } 1516 if !backwards(finalRow, finalColumn) { 1517 forward(row, column) 1518 } 1519 t.clampToSelection = true 1520 } else { 1521 t.trackEnd = false 1522 t.rowOffset-- 1523 } 1524 } 1525 1526 left = func() { 1527 if t.columnsSelectable { 1528 row, column := t.selectedRow, t.selectedColumn 1529 t.selectedColumn-- 1530 if t.selectedColumn < 0 { 1531 if t.wrapHorizontally { 1532 t.selectedColumn = lastColumn 1533 t.selectedRow-- 1534 if t.selectedRow < 0 { 1535 if t.wrapVertically { 1536 t.selectedRow = rowCount - 1 1537 } else { 1538 t.selectedColumn = 0 1539 t.selectedRow = 0 1540 } 1541 } 1542 } else { 1543 t.selectedColumn = 0 1544 } 1545 } 1546 finalRow, finalColumn := row, column 1547 if !t.wrapHorizontally { 1548 finalColumn = 0 1549 } else if !t.wrapVertically { 1550 finalRow = 0 1551 finalColumn = 0 1552 } 1553 if !backwards(finalRow, finalColumn) { 1554 forward(row, column) 1555 } 1556 t.clampToSelection = true 1557 } else { 1558 t.columnOffset-- 1559 } 1560 } 1561 1562 right = func() { 1563 if t.columnsSelectable { 1564 row, column := t.selectedRow, t.selectedColumn 1565 t.selectedColumn++ 1566 if t.selectedColumn > lastColumn { 1567 if t.wrapHorizontally { 1568 t.selectedColumn = 0 1569 t.selectedRow++ 1570 if t.selectedRow >= rowCount { 1571 if t.wrapVertically { 1572 t.selectedRow = 0 1573 } else { 1574 t.selectedColumn = lastColumn 1575 t.selectedRow = rowCount - 1 1576 } 1577 } 1578 } else { 1579 t.selectedColumn = lastColumn 1580 } 1581 } 1582 finalRow, finalColumn := row, column 1583 if !t.wrapHorizontally { 1584 finalColumn = lastColumn 1585 } else if !t.wrapVertically { 1586 finalRow = rowCount - 1 1587 finalColumn = lastColumn 1588 } 1589 if !forward(finalRow, finalColumn) { 1590 backwards(row, column) 1591 } 1592 t.clampToSelection = true 1593 } else { 1594 t.columnOffset++ 1595 } 1596 } 1597 1598 pageDown = func() { 1599 offsetAmount := t.visibleRows - t.fixedRows 1600 if offsetAmount < 0 { 1601 offsetAmount = 0 1602 } 1603 if t.rowsSelectable { 1604 row, column := t.selectedRow, t.selectedColumn 1605 t.selectedRow += offsetAmount 1606 if t.selectedRow >= rowCount { 1607 t.selectedRow = rowCount - 1 1608 } 1609 finalRow, finalColumn := rowCount-1, lastColumn 1610 if !forward(finalRow, finalColumn) { 1611 backwards(row, column) 1612 } 1613 t.clampToSelection = true 1614 } else { 1615 t.rowOffset += offsetAmount 1616 } 1617 } 1618 1619 pageUp = func() { 1620 offsetAmount := t.visibleRows - t.fixedRows 1621 if offsetAmount < 0 { 1622 offsetAmount = 0 1623 } 1624 if t.rowsSelectable { 1625 row, column := t.selectedRow, t.selectedColumn 1626 t.selectedRow -= offsetAmount 1627 if t.selectedRow < 0 { 1628 t.selectedRow = 0 1629 } 1630 finalRow, finalColumn := 0, 0 1631 if !backwards(finalRow, finalColumn) { 1632 forward(row, column) 1633 } 1634 t.clampToSelection = true 1635 } else { 1636 t.trackEnd = false 1637 t.rowOffset -= offsetAmount 1638 } 1639 } 1640 ) 1641 1642 switch key { 1643 case tcell.KeyRune: 1644 switch event.Rune() { 1645 case 'g': 1646 home() 1647 case 'G': 1648 end() 1649 case 'j': 1650 down() 1651 case 'k': 1652 up() 1653 case 'h': 1654 left() 1655 case 'l': 1656 right() 1657 } 1658 case tcell.KeyHome: 1659 home() 1660 case tcell.KeyEnd: 1661 end() 1662 case tcell.KeyUp: 1663 up() 1664 case tcell.KeyDown: 1665 down() 1666 case tcell.KeyLeft: 1667 left() 1668 case tcell.KeyRight: 1669 right() 1670 case tcell.KeyPgDn, tcell.KeyCtrlF: 1671 pageDown() 1672 case tcell.KeyPgUp, tcell.KeyCtrlB: 1673 pageUp() 1674 case tcell.KeyEnter: 1675 if (t.rowsSelectable || t.columnsSelectable) && t.selected != nil { 1676 t.selected(t.selectedRow, t.selectedColumn) 1677 } 1678 } 1679 1680 // If the selection has changed, notify the handler. 1681 if t.selectionChanged != nil && 1682 (t.rowsSelectable && previouslySelectedRow != t.selectedRow || 1683 t.columnsSelectable && previouslySelectedColumn != t.selectedColumn) { 1684 t.selectionChanged(t.selectedRow, t.selectedColumn) 1685 } 1686 }) 1687 } 1688 1689 // MouseHandler returns the mouse handler for this primitive. 1690 func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) { 1691 return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) { 1692 x, y := event.Position() 1693 if !t.InRect(x, y) { 1694 return false, nil 1695 } 1696 1697 switch action { 1698 case MouseLeftDown: 1699 setFocus(t) 1700 consumed = true 1701 case MouseLeftClick: 1702 selectEvent := true 1703 row, column := t.CellAt(x, y) 1704 cell := t.content.GetCell(row, column) 1705 if cell != nil && cell.Clicked != nil { 1706 if noSelect := cell.Clicked(); noSelect { 1707 selectEvent = false 1708 } 1709 } 1710 if selectEvent && (t.rowsSelectable || t.columnsSelectable) { 1711 t.Select(row, column) 1712 } 1713 consumed = true 1714 case MouseScrollUp: 1715 t.trackEnd = false 1716 t.rowOffset-- 1717 consumed = true 1718 case MouseScrollDown: 1719 t.rowOffset++ 1720 consumed = true 1721 } 1722 1723 return 1724 }) 1725 }