table.go 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. package tview
  2. import (
  3. "sort"
  4. "github.com/gdamore/tcell"
  5. colorful "github.com/lucasb-eyer/go-colorful"
  6. )
  7. // TableCell represents one cell inside a Table. You can instantiate this type
  8. // directly but all colors (background and text) will be set to their default
  9. // which is black.
  10. type TableCell struct {
  11. // The reference object.
  12. Reference interface{}
  13. // The text to be displayed in the table cell.
  14. Text string
  15. // The alignment of the cell text. One of AlignLeft (default), AlignCenter,
  16. // or AlignRight.
  17. Align int
  18. // The maximum width of the cell in screen space. This is used to give a
  19. // column a maximum width. Any cell text whose screen width exceeds this width
  20. // is cut off. Set to 0 if there is no maximum width.
  21. MaxWidth int
  22. // If the total table width is less than the available width, this value is
  23. // used to add extra width to a column. See SetExpansion() for details.
  24. Expansion int
  25. // The color of the cell text.
  26. Color tcell.Color
  27. // The background color of the cell.
  28. BackgroundColor tcell.Color
  29. // The style attributes of the cell.
  30. Attributes tcell.AttrMask
  31. // If set to true, this cell cannot be selected.
  32. NotSelectable bool
  33. // The position and width of the cell the last time table was drawn.
  34. x, y, width int
  35. }
  36. // NewTableCell returns a new table cell with sensible defaults. That is, left
  37. // aligned text with the primary text color (see Styles) and a transparent
  38. // background (using the background of the Table).
  39. func NewTableCell(text string) *TableCell {
  40. return &TableCell{
  41. Text: text,
  42. Align: AlignLeft,
  43. Color: Styles.PrimaryTextColor,
  44. BackgroundColor: tcell.ColorDefault,
  45. }
  46. }
  47. // SetText sets the cell's text.
  48. func (c *TableCell) SetText(text string) *TableCell {
  49. c.Text = text
  50. return c
  51. }
  52. // SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or
  53. // AlignRight.
  54. func (c *TableCell) SetAlign(align int) *TableCell {
  55. c.Align = align
  56. return c
  57. }
  58. // SetMaxWidth sets maximum width of the cell in screen space. This is used to
  59. // give a column a maximum width. Any cell text whose screen width exceeds this
  60. // width is cut off. Set to 0 if there is no maximum width.
  61. func (c *TableCell) SetMaxWidth(maxWidth int) *TableCell {
  62. c.MaxWidth = maxWidth
  63. return c
  64. }
  65. // SetExpansion sets the value by which the column of this cell expands if the
  66. // available width for the table is more than the table width (prior to applying
  67. // this expansion value). This is a proportional value. The amount of unused
  68. // horizontal space is divided into widths to be added to each column. How much
  69. // extra width a column receives depends on the expansion value: A value of 0
  70. // (the default) will not cause the column to increase in width. Other values
  71. // are proportional, e.g. a value of 2 will cause a column to grow by twice
  72. // the amount of a column with a value of 1.
  73. //
  74. // Since this value affects an entire column, the maximum over all visible cells
  75. // in that column is used.
  76. //
  77. // This function panics if a negative value is provided.
  78. func (c *TableCell) SetExpansion(expansion int) *TableCell {
  79. if expansion < 0 {
  80. panic("Table cell expansion values may not be negative")
  81. }
  82. c.Expansion = expansion
  83. return c
  84. }
  85. // SetTextColor sets the cell's text color.
  86. func (c *TableCell) SetTextColor(color tcell.Color) *TableCell {
  87. c.Color = color
  88. return c
  89. }
  90. // SetBackgroundColor sets the cell's background color. Set to
  91. // tcell.ColorDefault to use the table's background color.
  92. func (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell {
  93. c.BackgroundColor = color
  94. return c
  95. }
  96. // SetAttributes sets the cell's text attributes. You can combine different
  97. // attributes using bitmask operations:
  98. //
  99. // cell.SetAttributes(tcell.AttrUnderline | tcell.AttrBold)
  100. func (c *TableCell) SetAttributes(attr tcell.AttrMask) *TableCell {
  101. c.Attributes = attr
  102. return c
  103. }
  104. // SetStyle sets the cell's style (foreground color, background color, and
  105. // attributes) all at once.
  106. func (c *TableCell) SetStyle(style tcell.Style) *TableCell {
  107. c.Color, c.BackgroundColor, c.Attributes = style.Decompose()
  108. return c
  109. }
  110. // SetSelectable sets whether or not this cell can be selected by the user.
  111. func (c *TableCell) SetSelectable(selectable bool) *TableCell {
  112. c.NotSelectable = !selectable
  113. return c
  114. }
  115. // SetReference allows you to store a reference of any type in this cell. This
  116. // will allow you to establish a mapping between the cell and your
  117. // actual data.
  118. func (c *TableCell) SetReference(reference interface{}) *TableCell {
  119. c.Reference = reference
  120. return c
  121. }
  122. // GetReference returns this cell's reference object.
  123. func (c *TableCell) GetReference() interface{} {
  124. return c.Reference
  125. }
  126. // GetLastPosition returns the position of the table cell the last time it was
  127. // drawn on screen. If the cell is not on screen, the return values are
  128. // undefined.
  129. //
  130. // Because the Table class will attempt to keep selected cells on screen, this
  131. // function is most useful in response to a "selected" event (see
  132. // SetSelectedFunc()) or a "selectionChanged" event (see
  133. // SetSelectionChangedFunc()).
  134. func (c *TableCell) GetLastPosition() (x, y, width int) {
  135. return c.x, c.y, c.width
  136. }
  137. // Table visualizes two-dimensional data consisting of rows and columns. Each
  138. // Table cell is defined via SetCell() by the TableCell type. They can be added
  139. // dynamically to the table and changed any time.
  140. //
  141. // The most compact display of a table is without borders. Each row will then
  142. // occupy one row on screen and columns are separated by the rune defined via
  143. // SetSeparator() (a space character by default).
  144. //
  145. // When borders are turned on (via SetBorders()), each table cell is surrounded
  146. // by lines. Therefore one table row will require two rows on screen.
  147. //
  148. // Columns will use as much horizontal space as they need. You can constrain
  149. // their size with the MaxWidth parameter of the TableCell type.
  150. //
  151. // Fixed Columns
  152. //
  153. // You can define fixed rows and rolumns via SetFixed(). They will always stay
  154. // in their place, even when the table is scrolled. Fixed rows are always the
  155. // top rows. Fixed columns are always the leftmost columns.
  156. //
  157. // Selections
  158. //
  159. // You can call SetSelectable() to set columns and/or rows to "selectable". If
  160. // the flag is set only for columns, entire columns can be selected by the user.
  161. // If it is set only for rows, entire rows can be selected. If both flags are
  162. // set, individual cells can be selected. The "selected" handler set via
  163. // SetSelectedFunc() is invoked when the user presses Enter on a selection.
  164. //
  165. // Navigation
  166. //
  167. // If the table extends beyond the available space, it can be navigated with
  168. // key bindings similar to Vim:
  169. //
  170. // - h, left arrow: Move left by one column.
  171. // - l, right arrow: Move right by one column.
  172. // - j, down arrow: Move down by one row.
  173. // - k, up arrow: Move up by one row.
  174. // - g, home: Move to the top.
  175. // - G, end: Move to the bottom.
  176. // - Ctrl-F, page down: Move down by one page.
  177. // - Ctrl-B, page up: Move up by one page.
  178. //
  179. // When there is no selection, this affects the entire table (except for fixed
  180. // rows and columns). When there is a selection, the user moves the selection.
  181. // The class will attempt to keep the selection from moving out of the screen.
  182. //
  183. // Use SetInputCapture() to override or modify keyboard input.
  184. //
  185. // See https://github.com/rivo/tview/wiki/Table for an example.
  186. type Table struct {
  187. *Box
  188. // Whether or not this table has borders around each cell.
  189. borders bool
  190. // The color of the borders or the separator.
  191. bordersColor tcell.Color
  192. // If there are no borders, the column separator.
  193. separator rune
  194. // The cells of the table. Rows first, then columns.
  195. cells [][]*TableCell
  196. // The rightmost column in the data set.
  197. lastColumn int
  198. // If true, when calculating the widths of the columns, all rows are evaluated
  199. // instead of only the visible ones.
  200. evaluateAllRows bool
  201. // The number of fixed rows / columns.
  202. fixedRows, fixedColumns int
  203. // Whether or not rows or columns can be selected. If both are set to true,
  204. // cells can be selected.
  205. rowsSelectable, columnsSelectable bool
  206. // The currently selected row and column.
  207. selectedRow, selectedColumn int
  208. // The number of rows/columns by which the table is scrolled down/to the
  209. // right.
  210. rowOffset, columnOffset int
  211. // If set to true, the table's last row will always be visible.
  212. trackEnd bool
  213. // The number of visible rows the last time the table was drawn.
  214. visibleRows int
  215. // The indices of the visible columns as of the last time the table was drawn.
  216. visibleColumnIndices []int
  217. // The net widths of the visible columns as of the last time the table was
  218. // drawn.
  219. visibleColumnWidths []int
  220. // The style of the selected rows. If this value is 0, selected rows are
  221. // simply inverted.
  222. selectedStyle tcell.Style
  223. // An optional function which gets called when the user presses Enter on a
  224. // selected cell. If entire rows selected, the column value is undefined.
  225. // Likewise for entire columns.
  226. selected func(row, column int)
  227. // An optional function which gets called when the user changes the selection.
  228. // If entire rows selected, the column value is undefined.
  229. // Likewise for entire columns.
  230. selectionChanged func(row, column int)
  231. // An optional function which gets called when the user presses Escape, Tab,
  232. // or Backtab. Also when the user presses Enter if nothing is selectable.
  233. done func(key tcell.Key)
  234. }
  235. // NewTable returns a new table.
  236. func NewTable() *Table {
  237. return &Table{
  238. Box: NewBox(),
  239. bordersColor: Styles.GraphicsColor,
  240. separator: ' ',
  241. lastColumn: -1,
  242. }
  243. }
  244. // Clear removes all table data.
  245. func (t *Table) Clear() *Table {
  246. t.cells = nil
  247. t.lastColumn = -1
  248. return t
  249. }
  250. // SetBorders sets whether or not each cell in the table is surrounded by a
  251. // border.
  252. func (t *Table) SetBorders(show bool) *Table {
  253. t.borders = show
  254. return t
  255. }
  256. // SetBordersColor sets the color of the cell borders.
  257. func (t *Table) SetBordersColor(color tcell.Color) *Table {
  258. t.bordersColor = color
  259. return t
  260. }
  261. // SetSelectedStyle sets a specific style for selected cells. If no such style
  262. // is set, per default, selected cells are inverted (i.e. their foreground and
  263. // background colors are swapped).
  264. //
  265. // To reset a previous setting to its default, make the following call:
  266. //
  267. // table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
  268. func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) *Table {
  269. t.selectedStyle = tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor) | tcell.Style(attributes)
  270. return t
  271. }
  272. // SetSeparator sets the character used to fill the space between two
  273. // neighboring cells. This is a space character ' ' per default but you may
  274. // want to set it to Borders.Vertical (or any other rune) if the column
  275. // separation should be more visible. If cell borders are activated, this is
  276. // ignored.
  277. //
  278. // Separators have the same color as borders.
  279. func (t *Table) SetSeparator(separator rune) *Table {
  280. t.separator = separator
  281. return t
  282. }
  283. // SetFixed sets the number of fixed rows and columns which are always visible
  284. // even when the rest of the cells are scrolled out of view. Rows are always the
  285. // top-most ones. Columns are always the left-most ones.
  286. func (t *Table) SetFixed(rows, columns int) *Table {
  287. t.fixedRows, t.fixedColumns = rows, columns
  288. return t
  289. }
  290. // SetSelectable sets the flags which determine what can be selected in a table.
  291. // There are three selection modi:
  292. //
  293. // - rows = false, columns = false: Nothing can be selected.
  294. // - rows = true, columns = false: Rows can be selected.
  295. // - rows = false, columns = true: Columns can be selected.
  296. // - rows = true, columns = true: Individual cells can be selected.
  297. func (t *Table) SetSelectable(rows, columns bool) *Table {
  298. t.rowsSelectable, t.columnsSelectable = rows, columns
  299. return t
  300. }
  301. // GetSelectable returns what can be selected in a table. Refer to
  302. // SetSelectable() for details.
  303. func (t *Table) GetSelectable() (rows, columns bool) {
  304. return t.rowsSelectable, t.columnsSelectable
  305. }
  306. // GetSelection returns the position of the current selection.
  307. // If entire rows are selected, the column index is undefined.
  308. // Likewise for entire columns.
  309. func (t *Table) GetSelection() (row, column int) {
  310. return t.selectedRow, t.selectedColumn
  311. }
  312. // Select sets the selected cell. Depending on the selection settings
  313. // specified via SetSelectable(), this may be an entire row or column, or even
  314. // ignored completely. The "selection changed" event is fired if such a callback
  315. // is available (even if the selection ends up being the same as before and even
  316. // if cells are not selectable).
  317. func (t *Table) Select(row, column int) *Table {
  318. t.selectedRow, t.selectedColumn = row, column
  319. if t.selectionChanged != nil {
  320. t.selectionChanged(row, column)
  321. }
  322. return t
  323. }
  324. // SetOffset sets how many rows and columns should be skipped when drawing the
  325. // table. This is useful for large tables that do not fit on the screen.
  326. // Navigating a selection can change these values.
  327. //
  328. // Fixed rows and columns are never skipped.
  329. func (t *Table) SetOffset(row, column int) *Table {
  330. t.rowOffset, t.columnOffset = row, column
  331. t.trackEnd = false
  332. return t
  333. }
  334. // GetOffset returns the current row and column offset. This indicates how many
  335. // rows and columns the table is scrolled down and to the right.
  336. func (t *Table) GetOffset() (row, column int) {
  337. return t.rowOffset, t.columnOffset
  338. }
  339. // SetEvaluateAllRows sets a flag which determines the rows to be evaluated when
  340. // calculating the widths of the table's columns. When false, only visible rows
  341. // are evaluated. When true, all rows in the table are evaluated.
  342. //
  343. // Set this flag to true to avoid shifting column widths when the table is
  344. // scrolled. (May be slower for large tables.)
  345. func (t *Table) SetEvaluateAllRows(all bool) *Table {
  346. t.evaluateAllRows = all
  347. return t
  348. }
  349. // SetSelectedFunc sets a handler which is called whenever the user presses the
  350. // Enter key on a selected cell/row/column. The handler receives the position of
  351. // the selection and its cell contents. If entire rows are selected, the column
  352. // index is undefined. Likewise for entire columns.
  353. func (t *Table) SetSelectedFunc(handler func(row, column int)) *Table {
  354. t.selected = handler
  355. return t
  356. }
  357. // SetSelectionChangedFunc sets a handler which is called whenever the current
  358. // selection changes. The handler receives the position of the new selection.
  359. // If entire rows are selected, the column index is undefined. Likewise for
  360. // entire columns.
  361. func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table {
  362. t.selectionChanged = handler
  363. return t
  364. }
  365. // SetDoneFunc sets a handler which is called whenever the user presses the
  366. // Escape, Tab, or Backtab key. If nothing is selected, it is also called when
  367. // user presses the Enter key (because pressing Enter on a selection triggers
  368. // the "selected" handler set via SetSelectedFunc()).
  369. func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
  370. t.done = handler
  371. return t
  372. }
  373. // SetCell sets the content of a cell the specified position. It is ok to
  374. // directly instantiate a TableCell object. If the cell has content, at least
  375. // the Text and Color fields should be set.
  376. //
  377. // Note that setting cells in previously unknown rows and columns will
  378. // automatically extend the internal table representation, e.g. starting with
  379. // a row of 100,000 will immediately create 100,000 empty rows.
  380. //
  381. // To avoid unnecessary garbage collection, fill columns from left to right.
  382. func (t *Table) SetCell(row, column int, cell *TableCell) *Table {
  383. if row >= len(t.cells) {
  384. t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...)
  385. }
  386. rowLen := len(t.cells[row])
  387. if column >= rowLen {
  388. t.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...)
  389. for c := rowLen; c < column; c++ {
  390. t.cells[row][c] = &TableCell{}
  391. }
  392. }
  393. t.cells[row][column] = cell
  394. if column > t.lastColumn {
  395. t.lastColumn = column
  396. }
  397. return t
  398. }
  399. // SetCellSimple calls SetCell() with the given text, left-aligned, in white.
  400. func (t *Table) SetCellSimple(row, column int, text string) *Table {
  401. t.SetCell(row, column, NewTableCell(text))
  402. return t
  403. }
  404. // GetCell returns the contents of the cell at the specified position. A valid
  405. // TableCell object is always returned but it will be uninitialized if the cell
  406. // was not previously set. Such an uninitialized object will not automatically
  407. // be inserted. Therefore, repeated calls to this function may return different
  408. // pointers for uninitialized cells.
  409. func (t *Table) GetCell(row, column int) *TableCell {
  410. if row >= len(t.cells) || column >= len(t.cells[row]) {
  411. return &TableCell{}
  412. }
  413. return t.cells[row][column]
  414. }
  415. // RemoveRow removes the row at the given position from the table. If there is
  416. // no such row, this has no effect.
  417. func (t *Table) RemoveRow(row int) *Table {
  418. if row < 0 || row >= len(t.cells) {
  419. return t
  420. }
  421. t.cells = append(t.cells[:row], t.cells[row+1:]...)
  422. return t
  423. }
  424. // RemoveColumn removes the column at the given position from the table. If
  425. // there is no such column, this has no effect.
  426. func (t *Table) RemoveColumn(column int) *Table {
  427. for row := range t.cells {
  428. if column < 0 || column >= len(t.cells[row]) {
  429. continue
  430. }
  431. t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
  432. }
  433. return t
  434. }
  435. // InsertRow inserts a row before the row with the given index. Cells on the
  436. // given row and below will be shifted to the bottom by one row. If "row" is
  437. // equal or larger than the current number of rows, this function has no effect.
  438. func (t *Table) InsertRow(row int) *Table {
  439. if row >= len(t.cells) {
  440. return t
  441. }
  442. t.cells = append(t.cells, nil) // Extend by one.
  443. copy(t.cells[row+1:], t.cells[row:]) // Shift down.
  444. t.cells[row] = nil // New row is uninitialized.
  445. return t
  446. }
  447. // InsertColumn inserts a column before the column with the given index. Cells
  448. // in the given column and to its right will be shifted to the right by one
  449. // column. Rows that have fewer initialized cells than "column" will remain
  450. // unchanged.
  451. func (t *Table) InsertColumn(column int) *Table {
  452. for row := range t.cells {
  453. if column >= len(t.cells[row]) {
  454. continue
  455. }
  456. t.cells[row] = append(t.cells[row], nil) // Extend by one.
  457. copy(t.cells[row][column+1:], t.cells[row][column:]) // Shift to the right.
  458. t.cells[row][column] = &TableCell{} // New element is an uninitialized table cell.
  459. }
  460. return t
  461. }
  462. // GetRowCount returns the number of rows in the table.
  463. func (t *Table) GetRowCount() int {
  464. return len(t.cells)
  465. }
  466. // GetColumnCount returns the (maximum) number of columns in the table.
  467. func (t *Table) GetColumnCount() int {
  468. if len(t.cells) == 0 {
  469. return 0
  470. }
  471. return t.lastColumn + 1
  472. }
  473. // cellAt returns the row and column located at the given screen coordinates.
  474. // Each returned value may be negative if there is no row and/or cell. This
  475. // function will also process coordinates outside the table's inner rectangle so
  476. // callers will need to check for bounds themselves.
  477. func (t *Table) cellAt(x, y int) (row, column int) {
  478. rectX, rectY, _, _ := t.GetInnerRect()
  479. // Determine row as seen on screen.
  480. if t.borders {
  481. row = (y - rectY - 1) / 2
  482. } else {
  483. row = y - rectY
  484. }
  485. // Respect fixed rows and row offset.
  486. if row >= 0 {
  487. if row >= t.fixedRows {
  488. row += t.rowOffset
  489. }
  490. if row >= len(t.cells) {
  491. row = -1
  492. }
  493. }
  494. // Saerch for the clicked column.
  495. column = -1
  496. if x >= rectX {
  497. columnX := rectX
  498. if t.borders {
  499. columnX++
  500. }
  501. for index, width := range t.visibleColumnWidths {
  502. columnX += width + 1
  503. if x < columnX {
  504. column = t.visibleColumnIndices[index]
  505. break
  506. }
  507. }
  508. }
  509. return
  510. }
  511. // ScrollToBeginning scrolls the table to the beginning to that the top left
  512. // corner of the table is shown. Note that this position may be corrected if
  513. // there is a selection.
  514. func (t *Table) ScrollToBeginning() *Table {
  515. t.trackEnd = false
  516. t.columnOffset = 0
  517. t.rowOffset = 0
  518. return t
  519. }
  520. // ScrollToEnd scrolls the table to the beginning to that the bottom left corner
  521. // of the table is shown. Adding more rows to the table will cause it to
  522. // automatically scroll with the new data. Note that this position may be
  523. // corrected if there is a selection.
  524. func (t *Table) ScrollToEnd() *Table {
  525. t.trackEnd = true
  526. t.columnOffset = 0
  527. t.rowOffset = len(t.cells)
  528. return t
  529. }
  530. // Draw draws this primitive onto the screen.
  531. func (t *Table) Draw(screen tcell.Screen) {
  532. t.Box.Draw(screen)
  533. // What's our available screen space?
  534. _, totalHeight := screen.Size()
  535. x, y, width, height := t.GetInnerRect()
  536. if t.borders {
  537. t.visibleRows = height / 2
  538. } else {
  539. t.visibleRows = height
  540. }
  541. // Return the cell at the specified position (nil if it doesn't exist).
  542. getCell := func(row, column int) *TableCell {
  543. if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) {
  544. return nil
  545. }
  546. return t.cells[row][column]
  547. }
  548. // If this cell is not selectable, find the next one.
  549. if t.rowsSelectable || t.columnsSelectable {
  550. if t.selectedColumn < 0 {
  551. t.selectedColumn = 0
  552. }
  553. if t.selectedRow < 0 {
  554. t.selectedRow = 0
  555. }
  556. for t.selectedRow < len(t.cells) {
  557. cell := getCell(t.selectedRow, t.selectedColumn)
  558. if cell == nil || !cell.NotSelectable {
  559. break
  560. }
  561. t.selectedColumn++
  562. if t.selectedColumn > t.lastColumn {
  563. t.selectedColumn = 0
  564. t.selectedRow++
  565. }
  566. }
  567. }
  568. // Clamp row offsets.
  569. if t.rowsSelectable {
  570. if t.selectedRow >= t.fixedRows && t.selectedRow < t.fixedRows+t.rowOffset {
  571. t.rowOffset = t.selectedRow - t.fixedRows
  572. t.trackEnd = false
  573. }
  574. if t.borders {
  575. if 2*(t.selectedRow+1-t.rowOffset) >= height {
  576. t.rowOffset = t.selectedRow + 1 - height/2
  577. t.trackEnd = false
  578. }
  579. } else {
  580. if t.selectedRow+1-t.rowOffset >= height {
  581. t.rowOffset = t.selectedRow + 1 - height
  582. t.trackEnd = false
  583. }
  584. }
  585. }
  586. if t.borders {
  587. if 2*(len(t.cells)-t.rowOffset) < height {
  588. t.trackEnd = true
  589. }
  590. } else {
  591. if len(t.cells)-t.rowOffset < height {
  592. t.trackEnd = true
  593. }
  594. }
  595. if t.trackEnd {
  596. if t.borders {
  597. t.rowOffset = len(t.cells) - height/2
  598. } else {
  599. t.rowOffset = len(t.cells) - height
  600. }
  601. }
  602. if t.rowOffset < 0 {
  603. t.rowOffset = 0
  604. }
  605. // Clamp column offset. (Only left side here. The right side is more
  606. // difficult and we'll do it below.)
  607. if t.columnsSelectable && t.selectedColumn >= t.fixedColumns && t.selectedColumn < t.fixedColumns+t.columnOffset {
  608. t.columnOffset = t.selectedColumn - t.fixedColumns
  609. }
  610. if t.columnOffset < 0 {
  611. t.columnOffset = 0
  612. }
  613. if t.selectedColumn < 0 {
  614. t.selectedColumn = 0
  615. }
  616. // Determine the indices and widths of the columns and rows which fit on the
  617. // screen.
  618. var (
  619. columns, rows, allRows, widths []int
  620. tableHeight, tableWidth int
  621. )
  622. rowStep := 1
  623. if t.borders {
  624. rowStep = 2 // With borders, every table row takes two screen rows.
  625. tableWidth = 1 // We start at the second character because of the left table border.
  626. }
  627. if t.evaluateAllRows {
  628. allRows = make([]int, len(t.cells))
  629. for row := range t.cells {
  630. allRows[row] = row
  631. }
  632. }
  633. indexRow := func(row int) bool { // Determine if this row is visible, store its index.
  634. if tableHeight >= height {
  635. return false
  636. }
  637. rows = append(rows, row)
  638. tableHeight += rowStep
  639. return true
  640. }
  641. for row := 0; row < t.fixedRows && row < len(t.cells); row++ { // Do the fixed rows first.
  642. if !indexRow(row) {
  643. break
  644. }
  645. }
  646. for row := t.fixedRows + t.rowOffset; row < len(t.cells); row++ { // Then the remaining rows.
  647. if !indexRow(row) {
  648. break
  649. }
  650. }
  651. var (
  652. skipped, lastTableWidth, expansionTotal int
  653. expansions []int
  654. )
  655. ColumnLoop:
  656. for column := 0; ; column++ {
  657. // If we've moved beyond the right border, we stop or skip a column.
  658. for tableWidth-1 >= width { // -1 because we include one extra column if the separator falls on the right end of the box.
  659. // We've moved beyond the available space.
  660. if column < t.fixedColumns {
  661. break ColumnLoop // We're in the fixed area. We're done.
  662. }
  663. if !t.columnsSelectable && skipped >= t.columnOffset {
  664. break ColumnLoop // There is no selection and we've already reached the offset.
  665. }
  666. if t.columnsSelectable && t.selectedColumn-skipped == t.fixedColumns {
  667. break ColumnLoop // The selected column reached the leftmost point before disappearing.
  668. }
  669. if t.columnsSelectable && skipped >= t.columnOffset &&
  670. (t.selectedColumn < column && lastTableWidth < width-1 && tableWidth < width-1 || t.selectedColumn < column-1) {
  671. break ColumnLoop // We've skipped as many as requested and the selection is visible.
  672. }
  673. if len(columns) <= t.fixedColumns {
  674. break // Nothing to skip.
  675. }
  676. // We need to skip a column.
  677. skipped++
  678. lastTableWidth -= widths[t.fixedColumns] + 1
  679. tableWidth -= widths[t.fixedColumns] + 1
  680. columns = append(columns[:t.fixedColumns], columns[t.fixedColumns+1:]...)
  681. widths = append(widths[:t.fixedColumns], widths[t.fixedColumns+1:]...)
  682. expansions = append(expansions[:t.fixedColumns], expansions[t.fixedColumns+1:]...)
  683. }
  684. // What's this column's width (without expansion)?
  685. maxWidth := -1
  686. expansion := 0
  687. evaluationRows := rows
  688. if t.evaluateAllRows {
  689. evaluationRows = allRows
  690. }
  691. for _, row := range evaluationRows {
  692. if cell := getCell(row, column); cell != nil {
  693. _, _, _, _, _, _, cellWidth := decomposeString(cell.Text, true, false)
  694. if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
  695. cellWidth = cell.MaxWidth
  696. }
  697. if cellWidth > maxWidth {
  698. maxWidth = cellWidth
  699. }
  700. if cell.Expansion > expansion {
  701. expansion = cell.Expansion
  702. }
  703. }
  704. }
  705. if maxWidth < 0 {
  706. break // No more cells found in this column.
  707. }
  708. // Store new column info at the end.
  709. columns = append(columns, column)
  710. widths = append(widths, maxWidth)
  711. lastTableWidth = tableWidth
  712. tableWidth += maxWidth + 1
  713. expansions = append(expansions, expansion)
  714. expansionTotal += expansion
  715. }
  716. t.columnOffset = skipped
  717. // If we have space left, distribute it.
  718. if tableWidth < width {
  719. toDistribute := width - tableWidth
  720. for index, expansion := range expansions {
  721. if expansionTotal <= 0 {
  722. break
  723. }
  724. expWidth := toDistribute * expansion / expansionTotal
  725. widths[index] += expWidth
  726. toDistribute -= expWidth
  727. expansionTotal -= expansion
  728. }
  729. }
  730. // Helper function which draws border runes.
  731. borderStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.bordersColor)
  732. drawBorder := func(colX, rowY int, ch rune) {
  733. screen.SetContent(x+colX, y+rowY, ch, nil, borderStyle)
  734. }
  735. // Draw the cells (and borders).
  736. var columnX int
  737. if !t.borders {
  738. columnX--
  739. }
  740. for columnIndex, column := range columns {
  741. columnWidth := widths[columnIndex]
  742. for rowY, row := range rows {
  743. if t.borders {
  744. // Draw borders.
  745. rowY *= 2
  746. for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
  747. drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
  748. }
  749. ch := Borders.Cross
  750. if columnIndex == 0 {
  751. if rowY == 0 {
  752. ch = Borders.TopLeft
  753. } else {
  754. ch = Borders.LeftT
  755. }
  756. } else if rowY == 0 {
  757. ch = Borders.TopT
  758. }
  759. drawBorder(columnX, rowY, ch)
  760. rowY++
  761. if rowY >= height || y+rowY >= totalHeight {
  762. break // No space for the text anymore.
  763. }
  764. drawBorder(columnX, rowY, Borders.Vertical)
  765. } else if columnIndex > 0 {
  766. // Draw separator.
  767. drawBorder(columnX, rowY, t.separator)
  768. }
  769. // Get the cell.
  770. cell := getCell(row, column)
  771. if cell == nil {
  772. continue
  773. }
  774. // Draw text.
  775. finalWidth := columnWidth
  776. if columnX+1+columnWidth >= width {
  777. finalWidth = width - columnX - 1
  778. }
  779. cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
  780. _, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color)|tcell.Style(cell.Attributes))
  781. if TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 {
  782. _, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY)
  783. printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
  784. }
  785. }
  786. // Draw bottom border.
  787. if rowY := 2 * len(rows); t.borders && rowY < height {
  788. for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
  789. drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
  790. }
  791. ch := Borders.BottomT
  792. if columnIndex == 0 {
  793. ch = Borders.BottomLeft
  794. }
  795. drawBorder(columnX, rowY, ch)
  796. }
  797. columnX += columnWidth + 1
  798. }
  799. // Draw right border.
  800. if t.borders && len(t.cells) > 0 && columnX < width {
  801. for rowY := range rows {
  802. rowY *= 2
  803. if rowY+1 < height {
  804. drawBorder(columnX, rowY+1, Borders.Vertical)
  805. }
  806. ch := Borders.RightT
  807. if rowY == 0 {
  808. ch = Borders.TopRight
  809. }
  810. drawBorder(columnX, rowY, ch)
  811. }
  812. if rowY := 2 * len(rows); rowY < height {
  813. drawBorder(columnX, rowY, Borders.BottomRight)
  814. }
  815. }
  816. // Helper function which colors the background of a box.
  817. // backgroundColor == tcell.ColorDefault => Don't color the background.
  818. // textColor == tcell.ColorDefault => Don't change the text color.
  819. // attr == 0 => Don't change attributes.
  820. // invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
  821. // set background to textColor.
  822. colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
  823. for by := 0; by < h && fromY+by < y+height; by++ {
  824. for bx := 0; bx < w && fromX+bx < x+width; bx++ {
  825. m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
  826. fg, bg, a := style.Decompose()
  827. if invert {
  828. if fg == textColor || fg == t.bordersColor {
  829. fg = backgroundColor
  830. }
  831. if fg == tcell.ColorDefault {
  832. fg = t.backgroundColor
  833. }
  834. style = style.Background(textColor).Foreground(fg)
  835. } else {
  836. if backgroundColor != tcell.ColorDefault {
  837. bg = backgroundColor
  838. }
  839. if textColor != tcell.ColorDefault {
  840. fg = textColor
  841. }
  842. if attr != 0 {
  843. a = attr
  844. }
  845. style = style.Background(bg).Foreground(fg) | tcell.Style(a)
  846. }
  847. screen.SetContent(fromX+bx, fromY+by, m, c, style)
  848. }
  849. }
  850. }
  851. // Color the cell backgrounds. To avoid undesirable artefacts, we combine
  852. // the drawing of a cell by background color, selected cells last.
  853. type cellInfo struct {
  854. x, y, w, h int
  855. text tcell.Color
  856. selected bool
  857. }
  858. cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
  859. var backgroundColors []tcell.Color
  860. for rowY, row := range rows {
  861. columnX := 0
  862. rowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow
  863. for columnIndex, column := range columns {
  864. columnWidth := widths[columnIndex]
  865. cell := getCell(row, column)
  866. if cell == nil {
  867. continue
  868. }
  869. bx, by, bw, bh := x+columnX, y+rowY, columnWidth+1, 1
  870. if t.borders {
  871. by = y + rowY*2
  872. bw++
  873. bh = 3
  874. }
  875. columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
  876. cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
  877. entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
  878. cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
  879. x: bx,
  880. y: by,
  881. w: bw,
  882. h: bh,
  883. text: cell.Color,
  884. selected: cellSelected,
  885. })
  886. if !ok {
  887. backgroundColors = append(backgroundColors, cell.BackgroundColor)
  888. }
  889. columnX += columnWidth + 1
  890. }
  891. }
  892. sort.Slice(backgroundColors, func(i int, j int) bool {
  893. // Draw brightest colors last (i.e. on top).
  894. r, g, b := backgroundColors[i].RGB()
  895. c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
  896. _, _, li := c.Hcl()
  897. r, g, b = backgroundColors[j].RGB()
  898. c = colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
  899. _, _, lj := c.Hcl()
  900. return li < lj
  901. })
  902. selFg, selBg, selAttr := t.selectedStyle.Decompose()
  903. for _, bgColor := range backgroundColors {
  904. entries := cellsByBackgroundColor[bgColor]
  905. for _, cell := range entries {
  906. if cell.selected {
  907. if t.selectedStyle != 0 {
  908. defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
  909. } else {
  910. defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, 0, true)
  911. }
  912. } else {
  913. colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
  914. }
  915. }
  916. }
  917. // Remember column infos.
  918. t.visibleColumnIndices, t.visibleColumnWidths = columns, widths
  919. }
  920. // InputHandler returns the handler for this primitive.
  921. func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  922. return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  923. key := event.Key()
  924. if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) ||
  925. key == tcell.KeyEscape ||
  926. key == tcell.KeyTab ||
  927. key == tcell.KeyBacktab {
  928. if t.done != nil {
  929. t.done(key)
  930. }
  931. return
  932. }
  933. // Movement functions.
  934. previouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn
  935. var (
  936. getCell = func(row, column int) *TableCell {
  937. if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) {
  938. return nil
  939. }
  940. return t.cells[row][column]
  941. }
  942. previous = func() {
  943. for t.selectedRow >= 0 {
  944. cell := getCell(t.selectedRow, t.selectedColumn)
  945. if cell == nil || !cell.NotSelectable {
  946. return
  947. }
  948. t.selectedColumn--
  949. if t.selectedColumn < 0 {
  950. t.selectedColumn = t.lastColumn
  951. t.selectedRow--
  952. }
  953. }
  954. }
  955. next = func() {
  956. if t.selectedColumn > t.lastColumn {
  957. t.selectedColumn = 0
  958. t.selectedRow++
  959. if t.selectedRow >= len(t.cells) {
  960. t.selectedRow = len(t.cells) - 1
  961. }
  962. }
  963. for t.selectedRow < len(t.cells) {
  964. cell := getCell(t.selectedRow, t.selectedColumn)
  965. if cell == nil || !cell.NotSelectable {
  966. return
  967. }
  968. t.selectedColumn++
  969. if t.selectedColumn > t.lastColumn {
  970. t.selectedColumn = 0
  971. t.selectedRow++
  972. }
  973. }
  974. t.selectedColumn = t.lastColumn
  975. t.selectedRow = len(t.cells) - 1
  976. previous()
  977. }
  978. home = func() {
  979. if t.rowsSelectable {
  980. t.selectedRow = 0
  981. t.selectedColumn = 0
  982. next()
  983. } else {
  984. t.trackEnd = false
  985. t.rowOffset = 0
  986. t.columnOffset = 0
  987. }
  988. }
  989. end = func() {
  990. if t.rowsSelectable {
  991. t.selectedRow = len(t.cells) - 1
  992. t.selectedColumn = t.lastColumn
  993. previous()
  994. } else {
  995. t.trackEnd = true
  996. t.columnOffset = 0
  997. }
  998. }
  999. down = func() {
  1000. if t.rowsSelectable {
  1001. t.selectedRow++
  1002. if t.selectedRow >= len(t.cells) {
  1003. t.selectedRow = len(t.cells) - 1
  1004. }
  1005. next()
  1006. } else {
  1007. t.rowOffset++
  1008. }
  1009. }
  1010. up = func() {
  1011. if t.rowsSelectable {
  1012. t.selectedRow--
  1013. if t.selectedRow < 0 {
  1014. t.selectedRow = 0
  1015. }
  1016. previous()
  1017. } else {
  1018. t.trackEnd = false
  1019. t.rowOffset--
  1020. }
  1021. }
  1022. left = func() {
  1023. if t.columnsSelectable {
  1024. t.selectedColumn--
  1025. if t.selectedColumn < 0 {
  1026. t.selectedColumn = 0
  1027. }
  1028. previous()
  1029. } else {
  1030. t.columnOffset--
  1031. }
  1032. }
  1033. right = func() {
  1034. if t.columnsSelectable {
  1035. t.selectedColumn++
  1036. if t.selectedColumn > t.lastColumn {
  1037. t.selectedColumn = t.lastColumn
  1038. }
  1039. next()
  1040. } else {
  1041. t.columnOffset++
  1042. }
  1043. }
  1044. pageDown = func() {
  1045. offsetAmount := t.visibleRows - t.fixedRows
  1046. if offsetAmount < 0 {
  1047. offsetAmount = 0
  1048. }
  1049. if t.rowsSelectable {
  1050. t.selectedRow += offsetAmount
  1051. if t.selectedRow >= len(t.cells) {
  1052. t.selectedRow = len(t.cells) - 1
  1053. }
  1054. next()
  1055. } else {
  1056. t.rowOffset += offsetAmount
  1057. }
  1058. }
  1059. pageUp = func() {
  1060. offsetAmount := t.visibleRows - t.fixedRows
  1061. if offsetAmount < 0 {
  1062. offsetAmount = 0
  1063. }
  1064. if t.rowsSelectable {
  1065. t.selectedRow -= offsetAmount
  1066. if t.selectedRow < 0 {
  1067. t.selectedRow = 0
  1068. }
  1069. previous()
  1070. } else {
  1071. t.trackEnd = false
  1072. t.rowOffset -= offsetAmount
  1073. }
  1074. }
  1075. )
  1076. switch key {
  1077. case tcell.KeyRune:
  1078. switch event.Rune() {
  1079. case 'g':
  1080. home()
  1081. case 'G':
  1082. end()
  1083. case 'j':
  1084. down()
  1085. case 'k':
  1086. up()
  1087. case 'h':
  1088. left()
  1089. case 'l':
  1090. right()
  1091. }
  1092. case tcell.KeyHome:
  1093. home()
  1094. case tcell.KeyEnd:
  1095. end()
  1096. case tcell.KeyUp:
  1097. up()
  1098. case tcell.KeyDown:
  1099. down()
  1100. case tcell.KeyLeft:
  1101. left()
  1102. case tcell.KeyRight:
  1103. right()
  1104. case tcell.KeyPgDn, tcell.KeyCtrlF:
  1105. pageDown()
  1106. case tcell.KeyPgUp, tcell.KeyCtrlB:
  1107. pageUp()
  1108. case tcell.KeyEnter:
  1109. if (t.rowsSelectable || t.columnsSelectable) && t.selected != nil {
  1110. t.selected(t.selectedRow, t.selectedColumn)
  1111. }
  1112. }
  1113. // If the selection has changed, notify the handler.
  1114. if t.selectionChanged != nil &&
  1115. (t.rowsSelectable && previouslySelectedRow != t.selectedRow ||
  1116. t.columnsSelectable && previouslySelectedColumn != t.selectedColumn) {
  1117. t.selectionChanged(t.selectedRow, t.selectedColumn)
  1118. }
  1119. })
  1120. }
  1121. // MouseHandler returns the mouse handler for this primitive.
  1122. func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  1123. return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  1124. x, y := event.Position()
  1125. if !t.InRect(x, y) {
  1126. return false, nil
  1127. }
  1128. switch action {
  1129. case MouseLeftClick:
  1130. if t.rowsSelectable || t.columnsSelectable {
  1131. t.Select(t.cellAt(x, y))
  1132. }
  1133. consumed = true
  1134. setFocus(t)
  1135. case MouseScrollUp:
  1136. t.trackEnd = false
  1137. t.rowOffset--
  1138. consumed = true
  1139. case MouseScrollDown:
  1140. t.rowOffset++
  1141. consumed = true
  1142. }
  1143. return
  1144. })
  1145. }