forking tview to fork tcell to increase terminal input buffer length for password/key pasting
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

grid.go 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. package tview
  2. import (
  3. "math"
  4. "github.com/gdamore/tcell"
  5. )
  6. // gridItem represents one primitive and its possible position on a grid.
  7. type gridItem struct {
  8. Item Primitive // The item to be positioned. May be nil for an empty item.
  9. Row, Column int // The top-left grid cell where the item is placed.
  10. Width, Height int // The number of rows and columns the item occupies.
  11. MinGridWidth, MinGridHeight int // The minimum grid width/height for which this item is visible.
  12. Focus bool // Whether or not this item attracts the layout's focus.
  13. visible bool // Whether or not this item was visible the last time the grid was drawn.
  14. x, y, w, h int // The last position of the item relative to the top-left corner of the grid. Undefined if visible is false.
  15. }
  16. // Grid is an implementation of a grid-based layout. It works by defining the
  17. // size of the rows and columns, then placing primitives into the grid.
  18. //
  19. // Some settings can lead to the grid exceeding its available space. SetOffset()
  20. // can then be used to scroll in steps of rows and columns. These offset values
  21. // can also be controlled with the arrow keys (or the "g","G", "j", "k", "h",
  22. // and "l" keys) while the grid has focus and none of its contained primitives
  23. // do.
  24. //
  25. // See https://github.com/rivo/tview/wiki/Grid for an example.
  26. type Grid struct {
  27. *Box
  28. // The items to be positioned.
  29. items []*gridItem
  30. // The definition of the rows and columns of the grid. See
  31. // SetRows()/SetColumns() for details.
  32. rows, columns []int
  33. // The minimum sizes for rows and columns.
  34. minWidth, minHeight int
  35. // The size of the gaps between neighboring primitives. This is automatically
  36. // set to 1 if borders is true.
  37. gapRows, gapColumns int
  38. // The number of rows and columns skipped before drawing the top-left corner
  39. // of the grid.
  40. rowOffset, columnOffset int
  41. // Whether or not borders are drawn around grid items. If this is set to true,
  42. // a gap size of 1 is automatically assumed (which is filled with the border
  43. // graphics).
  44. borders bool
  45. // The color of the borders around grid items.
  46. bordersColor tcell.Color
  47. }
  48. // NewGrid returns a new grid-based layout container with no initial primitives.
  49. //
  50. // Note that Box, the superclass of Grid, will have its background color set to
  51. // transparent so that any grid areas not covered by any primitives will leave
  52. // their background unchanged. To clear a Grid's background before any items are
  53. // drawn, set it to the desired color:
  54. //
  55. // grid.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
  56. func NewGrid() *Grid {
  57. g := &Grid{
  58. Box: NewBox().SetBackgroundColor(tcell.ColorDefault),
  59. bordersColor: Styles.GraphicsColor,
  60. }
  61. g.focus = g
  62. return g
  63. }
  64. // SetColumns defines how the columns of the grid are distributed. Each value
  65. // defines the size of one column, starting with the leftmost column. Values
  66. // greater 0 represent absolute column widths (gaps not included). Values less
  67. // or equal 0 represent proportional column widths or fractions of the remaining
  68. // free space, where 0 is treated the same as -1. That is, a column with a value
  69. // of -3 will have three times the width of a column with a value of -1 (or 0).
  70. // The minimum width set with SetMinSize() is always observed.
  71. //
  72. // Primitives may extend beyond the columns defined explicitly with this
  73. // function. A value of 0 is assumed for any undefined column. In fact, if you
  74. // never call this function, all columns occupied by primitives will have the
  75. // same width. On the other hand, unoccupied columns defined with this function
  76. // will always take their place.
  77. //
  78. // Assuming a total width of the grid of 100 cells and a minimum width of 0, the
  79. // following call will result in columns with widths of 30, 10, 15, 15, and 30
  80. // cells:
  81. //
  82. // grid.Setcolumns(30, 10, -1, -1, -2)
  83. //
  84. // If a primitive were then placed in the 6th and 7th column, the resulting
  85. // widths would be: 30, 10, 10, 10, 20, 10, and 10 cells.
  86. //
  87. // If you then called SetMinSize() as follows:
  88. //
  89. // grid.SetMinSize(15, 20)
  90. //
  91. // The resulting widths would be: 30, 15, 15, 15, 20, 15, and 15 cells, a total
  92. // of 125 cells, 25 cells wider than the available grid width.
  93. func (g *Grid) SetColumns(columns ...int) *Grid {
  94. g.columns = columns
  95. return g
  96. }
  97. // SetRows defines how the rows of the grid are distributed. These values behave
  98. // the same as the column values provided with SetColumns(), see there for a
  99. // definition and examples.
  100. //
  101. // The provided values correspond to row heights, the first value defining
  102. // the height of the topmost row.
  103. func (g *Grid) SetRows(rows ...int) *Grid {
  104. g.rows = rows
  105. return g
  106. }
  107. // SetSize is a shortcut for SetRows() and SetColumns() where all row and column
  108. // values are set to the given size values. See SetRows() for details on sizes.
  109. func (g *Grid) SetSize(numRows, numColumns, rowSize, columnSize int) *Grid {
  110. g.rows = make([]int, numRows)
  111. for index := range g.rows {
  112. g.rows[index] = rowSize
  113. }
  114. g.columns = make([]int, numColumns)
  115. for index := range g.columns {
  116. g.columns[index] = columnSize
  117. }
  118. return g
  119. }
  120. // SetMinSize sets an absolute minimum width for rows and an absolute minimum
  121. // height for columns. Panics if negative values are provided.
  122. func (g *Grid) SetMinSize(row, column int) *Grid {
  123. if row < 0 || column < 0 {
  124. panic("Invalid minimum row/column size")
  125. }
  126. g.minHeight, g.minWidth = row, column
  127. return g
  128. }
  129. // SetGap sets the size of the gaps between neighboring primitives on the grid.
  130. // If borders are drawn (see SetBorders()), these values are ignored and a gap
  131. // of 1 is assumed. Panics if negative values are provided.
  132. func (g *Grid) SetGap(row, column int) *Grid {
  133. if row < 0 || column < 0 {
  134. panic("Invalid gap size")
  135. }
  136. g.gapRows, g.gapColumns = row, column
  137. return g
  138. }
  139. // SetBorders sets whether or not borders are drawn around grid items. Setting
  140. // this value to true will cause the gap values (see SetGap()) to be ignored and
  141. // automatically assumed to be 1 where the border graphics are drawn.
  142. func (g *Grid) SetBorders(borders bool) *Grid {
  143. g.borders = borders
  144. return g
  145. }
  146. // SetBordersColor sets the color of the item borders.
  147. func (g *Grid) SetBordersColor(color tcell.Color) *Grid {
  148. g.bordersColor = color
  149. return g
  150. }
  151. // AddItem adds a primitive and its position to the grid. The top-left corner
  152. // of the primitive will be located in the top-left corner of the grid cell at
  153. // the given row and column and will span "rowSpan" rows and "colSpan" columns.
  154. // For example, for a primitive to occupy rows 2, 3, and 4 and columns 5 and 6:
  155. //
  156. // grid.AddItem(p, 2, 5, 3, 2, true)
  157. //
  158. // If rowSpan or colSpan is 0, the primitive will not be drawn.
  159. //
  160. // You can add the same primitive multiple times with different grid positions.
  161. // The minGridWidth and minGridHeight values will then determine which of those
  162. // positions will be used. This is similar to CSS media queries. These minimum
  163. // values refer to the overall size of the grid. If multiple items for the same
  164. // primitive apply, the one that has at least one highest minimum value will be
  165. // used, or the primitive added last if those values are the same. Example:
  166. //
  167. // grid.AddItem(p, 0, 0, 0, 0, 0, 0, true). // Hide in small grids.
  168. // AddItem(p, 0, 0, 1, 2, 100, 0, true). // One-column layout for medium grids.
  169. // AddItem(p, 1, 1, 3, 2, 300, 0, true) // Multi-column layout for large grids.
  170. //
  171. // To use the same grid layout for all sizes, simply set minGridWidth and
  172. // minGridHeight to 0.
  173. //
  174. // If the item's focus is set to true, it will receive focus when the grid
  175. // receives focus. If there are multiple items with a true focus flag, the last
  176. // visible one that was added will receive focus.
  177. func (g *Grid) AddItem(p Primitive, row, column, rowSpan, colSpan, minGridHeight, minGridWidth int, focus bool) *Grid {
  178. g.items = append(g.items, &gridItem{
  179. Item: p,
  180. Row: row,
  181. Column: column,
  182. Height: rowSpan,
  183. Width: colSpan,
  184. MinGridHeight: minGridHeight,
  185. MinGridWidth: minGridWidth,
  186. Focus: focus,
  187. })
  188. return g
  189. }
  190. // RemoveItem removes all items for the given primitive from the grid, keeping
  191. // the order of the remaining items intact.
  192. func (g *Grid) RemoveItem(p Primitive) *Grid {
  193. for index := len(g.items) - 1; index >= 0; index-- {
  194. if g.items[index].Item == p {
  195. g.items = append(g.items[:index], g.items[index+1:]...)
  196. }
  197. }
  198. return g
  199. }
  200. // Clear removes all items from the grid.
  201. func (g *Grid) Clear() *Grid {
  202. g.items = nil
  203. return g
  204. }
  205. // SetOffset sets the number of rows and columns which are skipped before
  206. // drawing the first grid cell in the top-left corner. As the grid will never
  207. // completely move off the screen, these values may be adjusted the next time
  208. // the grid is drawn. The actual position of the grid may also be adjusted such
  209. // that contained primitives that have focus are visible.
  210. func (g *Grid) SetOffset(rows, columns int) *Grid {
  211. g.rowOffset, g.columnOffset = rows, columns
  212. return g
  213. }
  214. // GetOffset returns the current row and column offset (see SetOffset() for
  215. // details).
  216. func (g *Grid) GetOffset() (rows, columns int) {
  217. return g.rowOffset, g.columnOffset
  218. }
  219. // Focus is called when this primitive receives focus.
  220. func (g *Grid) Focus(delegate func(p Primitive)) {
  221. for _, item := range g.items {
  222. if item.Focus {
  223. delegate(item.Item)
  224. return
  225. }
  226. }
  227. g.hasFocus = true
  228. }
  229. // Blur is called when this primitive loses focus.
  230. func (g *Grid) Blur() {
  231. g.hasFocus = false
  232. }
  233. // HasFocus returns whether or not this primitive has focus.
  234. func (g *Grid) HasFocus() bool {
  235. for _, item := range g.items {
  236. if item.visible && item.Item.GetFocusable().HasFocus() {
  237. return true
  238. }
  239. }
  240. return g.hasFocus
  241. }
  242. // InputHandler returns the handler for this primitive.
  243. func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  244. return g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  245. switch event.Key() {
  246. case tcell.KeyRune:
  247. switch event.Rune() {
  248. case 'g':
  249. g.rowOffset, g.columnOffset = 0, 0
  250. case 'G':
  251. g.rowOffset = math.MaxInt32
  252. case 'j':
  253. g.rowOffset++
  254. case 'k':
  255. g.rowOffset--
  256. case 'h':
  257. g.columnOffset--
  258. case 'l':
  259. g.columnOffset++
  260. }
  261. case tcell.KeyHome:
  262. g.rowOffset, g.columnOffset = 0, 0
  263. case tcell.KeyEnd:
  264. g.rowOffset = math.MaxInt32
  265. case tcell.KeyUp:
  266. g.rowOffset--
  267. case tcell.KeyDown:
  268. g.rowOffset++
  269. case tcell.KeyLeft:
  270. g.columnOffset--
  271. case tcell.KeyRight:
  272. g.columnOffset++
  273. }
  274. })
  275. }
  276. // Draw draws this primitive onto the screen.
  277. func (g *Grid) Draw(screen tcell.Screen) {
  278. g.Box.Draw(screen)
  279. x, y, width, height := g.GetInnerRect()
  280. screenWidth, screenHeight := screen.Size()
  281. // Make a list of items which apply.
  282. items := make(map[Primitive]*gridItem)
  283. for _, item := range g.items {
  284. item.visible = false
  285. if item.Width <= 0 || item.Height <= 0 || width < item.MinGridWidth || height < item.MinGridHeight {
  286. continue
  287. }
  288. previousItem, ok := items[item.Item]
  289. if ok && item.Width < previousItem.Width && item.Height < previousItem.Height {
  290. continue
  291. }
  292. items[item.Item] = item
  293. }
  294. // How many rows and columns do we have?
  295. rows := len(g.rows)
  296. columns := len(g.columns)
  297. for _, item := range items {
  298. rowEnd := item.Row + item.Height
  299. if rowEnd > rows {
  300. rows = rowEnd
  301. }
  302. columnEnd := item.Column + item.Width
  303. if columnEnd > columns {
  304. columns = columnEnd
  305. }
  306. }
  307. if rows == 0 || columns == 0 {
  308. return // No content.
  309. }
  310. // Where are they located?
  311. rowPos := make([]int, rows)
  312. rowHeight := make([]int, rows)
  313. columnPos := make([]int, columns)
  314. columnWidth := make([]int, columns)
  315. // How much space do we distribute?
  316. remainingWidth := width
  317. remainingHeight := height
  318. proportionalWidth := 0
  319. proportionalHeight := 0
  320. for index, row := range g.rows {
  321. if row > 0 {
  322. if row < g.minHeight {
  323. row = g.minHeight
  324. }
  325. remainingHeight -= row
  326. rowHeight[index] = row
  327. } else if row == 0 {
  328. proportionalHeight++
  329. } else {
  330. proportionalHeight += -row
  331. }
  332. }
  333. for index, column := range g.columns {
  334. if column > 0 {
  335. if column < g.minWidth {
  336. column = g.minWidth
  337. }
  338. remainingWidth -= column
  339. columnWidth[index] = column
  340. } else if column == 0 {
  341. proportionalWidth++
  342. } else {
  343. proportionalWidth += -column
  344. }
  345. }
  346. if g.borders {
  347. remainingHeight -= rows + 1
  348. remainingWidth -= columns + 1
  349. } else {
  350. remainingHeight -= (rows - 1) * g.gapRows
  351. remainingWidth -= (columns - 1) * g.gapColumns
  352. }
  353. if rows > len(g.rows) {
  354. proportionalHeight += rows - len(g.rows)
  355. }
  356. if columns > len(g.columns) {
  357. proportionalWidth += columns - len(g.columns)
  358. }
  359. // Distribute proportional rows/columns.
  360. gridWidth := 0
  361. gridHeight := 0
  362. for index := 0; index < rows; index++ {
  363. row := 0
  364. if index < len(g.rows) {
  365. row = g.rows[index]
  366. }
  367. if row > 0 {
  368. if row < g.minHeight {
  369. row = g.minHeight
  370. }
  371. gridHeight += row
  372. continue // Not proportional. We already know the width.
  373. } else if row == 0 {
  374. row = 1
  375. } else {
  376. row = -row
  377. }
  378. rowAbs := row * remainingHeight / proportionalHeight
  379. remainingHeight -= rowAbs
  380. proportionalHeight -= row
  381. if rowAbs < g.minHeight {
  382. rowAbs = g.minHeight
  383. }
  384. rowHeight[index] = rowAbs
  385. gridHeight += rowAbs
  386. }
  387. for index := 0; index < columns; index++ {
  388. column := 0
  389. if index < len(g.columns) {
  390. column = g.columns[index]
  391. }
  392. if column > 0 {
  393. if column < g.minWidth {
  394. column = g.minWidth
  395. }
  396. gridWidth += column
  397. continue // Not proportional. We already know the height.
  398. } else if column == 0 {
  399. column = 1
  400. } else {
  401. column = -column
  402. }
  403. columnAbs := column * remainingWidth / proportionalWidth
  404. remainingWidth -= columnAbs
  405. proportionalWidth -= column
  406. if columnAbs < g.minWidth {
  407. columnAbs = g.minWidth
  408. }
  409. columnWidth[index] = columnAbs
  410. gridWidth += columnAbs
  411. }
  412. if g.borders {
  413. gridHeight += rows + 1
  414. gridWidth += columns + 1
  415. } else {
  416. gridHeight += (rows - 1) * g.gapRows
  417. gridWidth += (columns - 1) * g.gapColumns
  418. }
  419. // Calculate row/column positions.
  420. columnX, rowY := x, y
  421. if g.borders {
  422. columnX++
  423. rowY++
  424. }
  425. for index, row := range rowHeight {
  426. rowPos[index] = rowY
  427. gap := g.gapRows
  428. if g.borders {
  429. gap = 1
  430. }
  431. rowY += row + gap
  432. }
  433. for index, column := range columnWidth {
  434. columnPos[index] = columnX
  435. gap := g.gapColumns
  436. if g.borders {
  437. gap = 1
  438. }
  439. columnX += column + gap
  440. }
  441. // Calculate primitive positions.
  442. var focus *gridItem // The item which has focus.
  443. for primitive, item := range items {
  444. px := columnPos[item.Column]
  445. py := rowPos[item.Row]
  446. var pw, ph int
  447. for index := 0; index < item.Height; index++ {
  448. ph += rowHeight[item.Row+index]
  449. }
  450. for index := 0; index < item.Width; index++ {
  451. pw += columnWidth[item.Column+index]
  452. }
  453. if g.borders {
  454. pw += item.Width - 1
  455. ph += item.Height - 1
  456. } else {
  457. pw += (item.Width - 1) * g.gapColumns
  458. ph += (item.Height - 1) * g.gapRows
  459. }
  460. item.x, item.y, item.w, item.h = px, py, pw, ph
  461. item.visible = true
  462. if primitive.GetFocusable().HasFocus() {
  463. focus = item
  464. }
  465. }
  466. // Calculate screen offsets.
  467. var offsetX, offsetY, add int
  468. if g.rowOffset < 0 {
  469. g.rowOffset = 0
  470. }
  471. if g.columnOffset < 0 {
  472. g.columnOffset = 0
  473. }
  474. if g.borders {
  475. add = 1
  476. }
  477. for row := 0; row < rows-1; row++ {
  478. remainingHeight := gridHeight - offsetY
  479. if focus != nil && focus.y-add <= offsetY || // Don't let the focused item move out of screen.
  480. row >= g.rowOffset && (focus == nil || focus != nil && focus.y-offsetY < height) || // We've reached the requested offset.
  481. remainingHeight <= height { // We have enough space to show the rest.
  482. if row > 0 {
  483. if focus != nil && focus.y+focus.h+add-offsetY > height {
  484. offsetY += focus.y + focus.h + add - offsetY - height
  485. }
  486. if remainingHeight < height {
  487. offsetY = gridHeight - height
  488. }
  489. }
  490. g.rowOffset = row
  491. break
  492. }
  493. offsetY = rowPos[row+1] - add
  494. }
  495. for column := 0; column < columns-1; column++ {
  496. remainingWidth := gridWidth - offsetX
  497. if focus != nil && focus.x-add <= offsetX || // Don't let the focused item move out of screen.
  498. column >= g.columnOffset && (focus == nil || focus != nil && focus.x-offsetX < width) || // We've reached the requested offset.
  499. remainingWidth <= width { // We have enough space to show the rest.
  500. if column > 0 {
  501. if focus != nil && focus.x+focus.w+add-offsetX > width {
  502. offsetX += focus.x + focus.w + add - offsetX - width
  503. } else if remainingWidth < width {
  504. offsetX = gridWidth - width
  505. }
  506. }
  507. g.columnOffset = column
  508. break
  509. }
  510. offsetX = columnPos[column+1] - add
  511. }
  512. // Draw primitives and borders.
  513. for primitive, item := range items {
  514. // Final primitive position.
  515. if !item.visible {
  516. continue
  517. }
  518. item.x -= offsetX
  519. item.y -= offsetY
  520. if item.x+item.w > x+width {
  521. item.w = width - item.x
  522. }
  523. if item.y+item.h > y+height {
  524. item.h = height - item.y
  525. }
  526. if item.x < 0 {
  527. item.w += item.x
  528. item.x = 0
  529. }
  530. if item.y < 0 {
  531. item.h += item.y
  532. item.y = 0
  533. }
  534. if item.w <= 0 || item.h <= 0 {
  535. item.visible = false
  536. continue
  537. }
  538. primitive.SetRect(item.x, item.y, item.w, item.h)
  539. // Draw primitive.
  540. if item == focus {
  541. defer primitive.Draw(screen)
  542. } else {
  543. primitive.Draw(screen)
  544. }
  545. // Draw border around primitive.
  546. if g.borders {
  547. for bx := item.x; bx < item.x+item.w; bx++ { // Top/bottom lines.
  548. if bx < 0 || bx >= screenWidth {
  549. continue
  550. }
  551. by := item.y - 1
  552. if by >= 0 && by < screenHeight {
  553. PrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, g.bordersColor)
  554. }
  555. by = item.y + item.h
  556. if by >= 0 && by < screenHeight {
  557. PrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, g.bordersColor)
  558. }
  559. }
  560. for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
  561. if by < 0 || by >= screenHeight {
  562. continue
  563. }
  564. bx := item.x - 1
  565. if bx >= 0 && bx < screenWidth {
  566. PrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, g.bordersColor)
  567. }
  568. bx = item.x + item.w
  569. if bx >= 0 && bx < screenWidth {
  570. PrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, g.bordersColor)
  571. }
  572. }
  573. bx, by := item.x-1, item.y-1 // Top-left corner.
  574. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  575. PrintJoinedSemigraphics(screen, bx, by, Borders.TopLeft, g.bordersColor)
  576. }
  577. bx, by = item.x+item.w, item.y-1 // Top-right corner.
  578. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  579. PrintJoinedSemigraphics(screen, bx, by, Borders.TopRight, g.bordersColor)
  580. }
  581. bx, by = item.x-1, item.y+item.h // Bottom-left corner.
  582. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  583. PrintJoinedSemigraphics(screen, bx, by, Borders.BottomLeft, g.bordersColor)
  584. }
  585. bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
  586. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  587. PrintJoinedSemigraphics(screen, bx, by, Borders.BottomRight, g.bordersColor)
  588. }
  589. }
  590. }
  591. }