grid.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. package tview
  2. import (
  3. "math"
  4. "github.com/gdamore/tcell/v2"
  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. // [Grid.SetRows] / [Grid.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 be transparent so that any grid
  51. // areas not covered by any primitives will leave their background unchanged. To
  52. // clear a Grid's background before any items are drawn, reset its Box to one
  53. // with the desired color:
  54. //
  55. // grid.Box = NewBox()
  56. func NewGrid() *Grid {
  57. g := &Grid{
  58. bordersColor: Styles.GraphicsColor,
  59. }
  60. g.Box = NewBox()
  61. g.Box.dontClear = true
  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 [Grid.SetColumns], see there
  99. // for a 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 [Grid.SetRows] and [Grid.SetColumns] where
  108. // all row and column values are set to the given size values. See
  109. // [Grid.SetColumns] for details on sizes.
  110. func (g *Grid) SetSize(numRows, numColumns, rowSize, columnSize int) *Grid {
  111. g.rows = make([]int, numRows)
  112. for index := range g.rows {
  113. g.rows[index] = rowSize
  114. }
  115. g.columns = make([]int, numColumns)
  116. for index := range g.columns {
  117. g.columns[index] = columnSize
  118. }
  119. return g
  120. }
  121. // SetMinSize sets an absolute minimum width for rows and an absolute minimum
  122. // height for columns. Panics if negative values are provided.
  123. func (g *Grid) SetMinSize(row, column int) *Grid {
  124. if row < 0 || column < 0 {
  125. panic("Invalid minimum row/column size")
  126. }
  127. g.minHeight, g.minWidth = row, column
  128. return g
  129. }
  130. // SetGap sets the size of the gaps between neighboring primitives on the grid.
  131. // If borders are drawn (see SetBorders()), these values are ignored and a gap
  132. // of 1 is assumed. Panics if negative values are provided.
  133. func (g *Grid) SetGap(row, column int) *Grid {
  134. if row < 0 || column < 0 {
  135. panic("Invalid gap size")
  136. }
  137. g.gapRows, g.gapColumns = row, column
  138. return g
  139. }
  140. // SetBorders sets whether or not borders are drawn around grid items. Setting
  141. // this value to true will cause the gap values (see SetGap()) to be ignored and
  142. // automatically assumed to be 1 where the border graphics are drawn.
  143. func (g *Grid) SetBorders(borders bool) *Grid {
  144. g.borders = borders
  145. return g
  146. }
  147. // SetBordersColor sets the color of the item borders.
  148. func (g *Grid) SetBordersColor(color tcell.Color) *Grid {
  149. g.bordersColor = color
  150. return g
  151. }
  152. // AddItem adds a primitive and its position to the grid. The top-left corner
  153. // of the primitive will be located in the top-left corner of the grid cell at
  154. // the given row and column and will span "rowSpan" rows and "colSpan" columns.
  155. // For example, for a primitive to occupy rows 2, 3, and 4 and columns 5 and 6:
  156. //
  157. // grid.AddItem(p, 2, 5, 3, 2, 0, 0, true)
  158. //
  159. // If rowSpan or colSpan is 0, the primitive will not be drawn.
  160. //
  161. // You can add the same primitive multiple times with different grid positions.
  162. // The minGridWidth and minGridHeight values will then determine which of those
  163. // positions will be used. This is similar to CSS media queries. These minimum
  164. // values refer to the overall size of the grid. If multiple items for the same
  165. // primitive apply, the one that has at least one highest minimum value will be
  166. // used, or the primitive added last if those values are the same. Example:
  167. //
  168. // grid.AddItem(p, 0, 0, 0, 0, 0, 0, true). // Hide in small grids.
  169. // AddItem(p, 0, 0, 1, 2, 100, 0, true). // One-column layout for medium grids.
  170. // AddItem(p, 1, 1, 3, 2, 300, 0, true) // Multi-column layout for large grids.
  171. //
  172. // To use the same grid layout for all sizes, simply set minGridWidth and
  173. // minGridHeight to 0.
  174. //
  175. // If the item's focus is set to true, it will receive focus when the grid
  176. // receives focus. If there are multiple items with a true focus flag, the last
  177. // visible one that was added will receive focus.
  178. func (g *Grid) AddItem(p Primitive, row, column, rowSpan, colSpan, minGridHeight, minGridWidth int, focus bool) *Grid {
  179. g.items = append(g.items, &gridItem{
  180. Item: p,
  181. Row: row,
  182. Column: column,
  183. Height: rowSpan,
  184. Width: colSpan,
  185. MinGridHeight: minGridHeight,
  186. MinGridWidth: minGridWidth,
  187. Focus: focus,
  188. })
  189. return g
  190. }
  191. // RemoveItem removes all items for the given primitive from the grid, keeping
  192. // the order of the remaining items intact.
  193. func (g *Grid) RemoveItem(p Primitive) *Grid {
  194. for index := len(g.items) - 1; index >= 0; index-- {
  195. if g.items[index].Item == p {
  196. g.items = append(g.items[:index], g.items[index+1:]...)
  197. }
  198. }
  199. return g
  200. }
  201. // Clear removes all items from the grid.
  202. func (g *Grid) Clear() *Grid {
  203. g.items = nil
  204. return g
  205. }
  206. // SetOffset sets the number of rows and columns which are skipped before
  207. // drawing the first grid cell in the top-left corner. As the grid will never
  208. // completely move off the screen, these values may be adjusted the next time
  209. // the grid is drawn. The actual position of the grid may also be adjusted such
  210. // that contained primitives that have focus remain visible.
  211. func (g *Grid) SetOffset(rows, columns int) *Grid {
  212. g.rowOffset, g.columnOffset = rows, columns
  213. return g
  214. }
  215. // GetOffset returns the current row and column offset (see SetOffset() for
  216. // details).
  217. func (g *Grid) GetOffset() (rows, columns int) {
  218. return g.rowOffset, g.columnOffset
  219. }
  220. // Focus is called when this primitive receives focus.
  221. func (g *Grid) Focus(delegate func(p Primitive)) {
  222. for _, item := range g.items {
  223. if item.Focus {
  224. delegate(item.Item)
  225. return
  226. }
  227. }
  228. g.Box.Focus(delegate)
  229. }
  230. // HasFocus returns whether or not this primitive has focus.
  231. func (g *Grid) HasFocus() bool {
  232. for _, item := range g.items {
  233. if item.visible && item.Item.HasFocus() {
  234. return true
  235. }
  236. }
  237. return g.Box.HasFocus()
  238. }
  239. // InputHandler returns the handler for this primitive.
  240. func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  241. return g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  242. if !g.hasFocus {
  243. // Pass event on to child primitive.
  244. for _, item := range g.items {
  245. if item != nil && item.Item.HasFocus() {
  246. if handler := item.Item.InputHandler(); handler != nil {
  247. handler(event, setFocus)
  248. return
  249. }
  250. }
  251. }
  252. return
  253. }
  254. // Process our own key events if we have direct focus.
  255. switch event.Key() {
  256. case tcell.KeyRune:
  257. switch event.Rune() {
  258. case 'g':
  259. g.rowOffset, g.columnOffset = 0, 0
  260. case 'G':
  261. g.rowOffset = math.MaxInt32
  262. case 'j':
  263. g.rowOffset++
  264. case 'k':
  265. g.rowOffset--
  266. case 'h':
  267. g.columnOffset--
  268. case 'l':
  269. g.columnOffset++
  270. }
  271. case tcell.KeyHome:
  272. g.rowOffset, g.columnOffset = 0, 0
  273. case tcell.KeyEnd:
  274. g.rowOffset = math.MaxInt32
  275. case tcell.KeyUp:
  276. g.rowOffset--
  277. case tcell.KeyDown:
  278. g.rowOffset++
  279. case tcell.KeyLeft:
  280. g.columnOffset--
  281. case tcell.KeyRight:
  282. g.columnOffset++
  283. }
  284. })
  285. }
  286. // Draw draws this primitive onto the screen.
  287. func (g *Grid) Draw(screen tcell.Screen) {
  288. g.Box.DrawForSubclass(screen, g)
  289. x, y, width, height := g.GetInnerRect()
  290. screenWidth, screenHeight := screen.Size()
  291. // Make a list of items which apply.
  292. items := make(map[Primitive]*gridItem)
  293. for _, item := range g.items {
  294. item.visible = false
  295. if item.Width <= 0 || item.Height <= 0 || width < item.MinGridWidth || height < item.MinGridHeight {
  296. continue
  297. }
  298. previousItem, ok := items[item.Item]
  299. if ok && item.MinGridWidth < previousItem.MinGridWidth && item.MinGridHeight < previousItem.MinGridHeight {
  300. continue
  301. }
  302. items[item.Item] = item
  303. }
  304. // How many rows and columns do we have?
  305. rows := len(g.rows)
  306. columns := len(g.columns)
  307. for _, item := range items {
  308. rowEnd := item.Row + item.Height
  309. if rowEnd > rows {
  310. rows = rowEnd
  311. }
  312. columnEnd := item.Column + item.Width
  313. if columnEnd > columns {
  314. columns = columnEnd
  315. }
  316. }
  317. if rows == 0 || columns == 0 {
  318. return // No content.
  319. }
  320. // Where are they located?
  321. rowPos := make([]int, rows)
  322. rowHeight := make([]int, rows)
  323. columnPos := make([]int, columns)
  324. columnWidth := make([]int, columns)
  325. // How much space do we distribute?
  326. remainingWidth := width
  327. remainingHeight := height
  328. proportionalWidth := 0
  329. proportionalHeight := 0
  330. for index, row := range g.rows {
  331. if row > 0 {
  332. if row < g.minHeight {
  333. row = g.minHeight
  334. }
  335. remainingHeight -= row
  336. rowHeight[index] = row
  337. } else if row == 0 {
  338. proportionalHeight++
  339. } else {
  340. proportionalHeight += -row
  341. }
  342. }
  343. for index, column := range g.columns {
  344. if column > 0 {
  345. if column < g.minWidth {
  346. column = g.minWidth
  347. }
  348. remainingWidth -= column
  349. columnWidth[index] = column
  350. } else if column == 0 {
  351. proportionalWidth++
  352. } else {
  353. proportionalWidth += -column
  354. }
  355. }
  356. if g.borders {
  357. remainingHeight -= rows + 1
  358. remainingWidth -= columns + 1
  359. } else {
  360. remainingHeight -= (rows - 1) * g.gapRows
  361. remainingWidth -= (columns - 1) * g.gapColumns
  362. }
  363. if rows > len(g.rows) {
  364. proportionalHeight += rows - len(g.rows)
  365. }
  366. if columns > len(g.columns) {
  367. proportionalWidth += columns - len(g.columns)
  368. }
  369. // Distribute proportional rows/columns.
  370. for index := 0; index < rows; index++ {
  371. row := 0
  372. if index < len(g.rows) {
  373. row = g.rows[index]
  374. }
  375. if row > 0 {
  376. continue // Not proportional. We already know the width.
  377. } else if row == 0 {
  378. row = 1
  379. } else {
  380. row = -row
  381. }
  382. rowAbs := row * remainingHeight / proportionalHeight
  383. remainingHeight -= rowAbs
  384. proportionalHeight -= row
  385. if rowAbs < g.minHeight {
  386. rowAbs = g.minHeight
  387. }
  388. rowHeight[index] = rowAbs
  389. }
  390. for index := 0; index < columns; index++ {
  391. column := 0
  392. if index < len(g.columns) {
  393. column = g.columns[index]
  394. }
  395. if column > 0 {
  396. continue // Not proportional. We already know the height.
  397. } else if column == 0 {
  398. column = 1
  399. } else {
  400. column = -column
  401. }
  402. columnAbs := column * remainingWidth / proportionalWidth
  403. remainingWidth -= columnAbs
  404. proportionalWidth -= column
  405. if columnAbs < g.minWidth {
  406. columnAbs = g.minWidth
  407. }
  408. columnWidth[index] = columnAbs
  409. }
  410. // Calculate row/column positions.
  411. var columnX, rowY int
  412. if g.borders {
  413. columnX++
  414. rowY++
  415. }
  416. for index, row := range rowHeight {
  417. rowPos[index] = rowY
  418. gap := g.gapRows
  419. if g.borders {
  420. gap = 1
  421. }
  422. rowY += row + gap
  423. }
  424. for index, column := range columnWidth {
  425. columnPos[index] = columnX
  426. gap := g.gapColumns
  427. if g.borders {
  428. gap = 1
  429. }
  430. columnX += column + gap
  431. }
  432. // Calculate primitive positions.
  433. var focus *gridItem // The item which has focus.
  434. for primitive, item := range items {
  435. px := columnPos[item.Column]
  436. py := rowPos[item.Row]
  437. var pw, ph int
  438. for index := 0; index < item.Height; index++ {
  439. ph += rowHeight[item.Row+index]
  440. }
  441. for index := 0; index < item.Width; index++ {
  442. pw += columnWidth[item.Column+index]
  443. }
  444. if g.borders {
  445. pw += item.Width - 1
  446. ph += item.Height - 1
  447. } else {
  448. pw += (item.Width - 1) * g.gapColumns
  449. ph += (item.Height - 1) * g.gapRows
  450. }
  451. item.x, item.y, item.w, item.h = px, py, pw, ph
  452. item.visible = true
  453. if primitive.HasFocus() {
  454. focus = item
  455. }
  456. }
  457. // Calculate screen offsets.
  458. var offsetX, offsetY int
  459. add := 1
  460. if !g.borders {
  461. add = g.gapRows
  462. }
  463. for index, height := range rowHeight {
  464. if index >= g.rowOffset {
  465. break
  466. }
  467. offsetY += height + add
  468. }
  469. if !g.borders {
  470. add = g.gapColumns
  471. }
  472. for index, width := range columnWidth {
  473. if index >= g.columnOffset {
  474. break
  475. }
  476. offsetX += width + add
  477. }
  478. // Line up the last row/column with the end of the available area.
  479. var border int
  480. if g.borders {
  481. border = 1
  482. }
  483. last := len(rowPos) - 1
  484. if rowPos[last]+rowHeight[last]+border-offsetY < height {
  485. offsetY = rowPos[last] - height + rowHeight[last] + border
  486. }
  487. last = len(columnPos) - 1
  488. if columnPos[last]+columnWidth[last]+border-offsetX < width {
  489. offsetX = columnPos[last] - width + columnWidth[last] + border
  490. }
  491. // The focused item must be within the visible area.
  492. if focus != nil {
  493. if focus.y+focus.h-offsetY >= height {
  494. offsetY = focus.y - height + focus.h
  495. }
  496. if focus.y-offsetY < 0 {
  497. offsetY = focus.y
  498. }
  499. if focus.x+focus.w-offsetX >= width {
  500. offsetX = focus.x - width + focus.w
  501. }
  502. if focus.x-offsetX < 0 {
  503. offsetX = focus.x
  504. }
  505. }
  506. // Adjust row/column offsets based on this value.
  507. var from, to int
  508. for index, pos := range rowPos {
  509. if pos-offsetY < 0 {
  510. from = index + 1
  511. }
  512. if pos-offsetY < height {
  513. to = index
  514. }
  515. }
  516. if g.rowOffset < from {
  517. g.rowOffset = from
  518. }
  519. if g.rowOffset > to {
  520. g.rowOffset = to
  521. }
  522. from, to = 0, 0
  523. for index, pos := range columnPos {
  524. if pos-offsetX < 0 {
  525. from = index + 1
  526. }
  527. if pos-offsetX < width {
  528. to = index
  529. }
  530. }
  531. if g.columnOffset < from {
  532. g.columnOffset = from
  533. }
  534. if g.columnOffset > to {
  535. g.columnOffset = to
  536. }
  537. // Draw primitives and borders.
  538. borderStyle := tcell.StyleDefault.Background(g.backgroundColor).Foreground(g.bordersColor)
  539. for primitive, item := range items {
  540. // Final primitive position.
  541. if !item.visible {
  542. continue
  543. }
  544. item.x -= offsetX
  545. item.y -= offsetY
  546. if item.x >= width || item.x+item.w <= 0 || item.y >= height || item.y+item.h <= 0 {
  547. item.visible = false
  548. continue
  549. }
  550. if item.x+item.w > width {
  551. item.w = width - item.x
  552. }
  553. if item.y+item.h > height {
  554. item.h = height - item.y
  555. }
  556. if item.x < 0 {
  557. item.w += item.x
  558. item.x = 0
  559. }
  560. if item.y < 0 {
  561. item.h += item.y
  562. item.y = 0
  563. }
  564. if item.w <= 0 || item.h <= 0 {
  565. item.visible = false
  566. continue
  567. }
  568. item.x += x
  569. item.y += y
  570. primitive.SetRect(item.x, item.y, item.w, item.h)
  571. // Draw primitive.
  572. if item == focus {
  573. defer primitive.Draw(screen)
  574. } else {
  575. primitive.Draw(screen)
  576. }
  577. // Draw border around primitive.
  578. if g.borders {
  579. for bx := item.x; bx < item.x+item.w; bx++ { // Top/bottom lines.
  580. if bx < 0 || bx >= screenWidth {
  581. continue
  582. }
  583. by := item.y - 1
  584. if by >= 0 && by < screenHeight {
  585. PrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, borderStyle)
  586. }
  587. by = item.y + item.h
  588. if by >= 0 && by < screenHeight {
  589. PrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, borderStyle)
  590. }
  591. }
  592. for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
  593. if by < 0 || by >= screenHeight {
  594. continue
  595. }
  596. bx := item.x - 1
  597. if bx >= 0 && bx < screenWidth {
  598. PrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, borderStyle)
  599. }
  600. bx = item.x + item.w
  601. if bx >= 0 && bx < screenWidth {
  602. PrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, borderStyle)
  603. }
  604. }
  605. bx, by := item.x-1, item.y-1 // Top-left corner.
  606. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  607. PrintJoinedSemigraphics(screen, bx, by, Borders.TopLeft, borderStyle)
  608. }
  609. bx, by = item.x+item.w, item.y-1 // Top-right corner.
  610. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  611. PrintJoinedSemigraphics(screen, bx, by, Borders.TopRight, borderStyle)
  612. }
  613. bx, by = item.x-1, item.y+item.h // Bottom-left corner.
  614. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  615. PrintJoinedSemigraphics(screen, bx, by, Borders.BottomLeft, borderStyle)
  616. }
  617. bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
  618. if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
  619. PrintJoinedSemigraphics(screen, bx, by, Borders.BottomRight, borderStyle)
  620. }
  621. }
  622. }
  623. }
  624. // MouseHandler returns the mouse handler for this primitive.
  625. func (g *Grid) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  626. return g.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  627. if !g.InRect(event.Position()) {
  628. return false, nil
  629. }
  630. // Pass mouse events along to the first child item that takes it.
  631. for _, item := range g.items {
  632. if item.Item == nil {
  633. continue
  634. }
  635. consumed, capture = item.Item.MouseHandler()(action, event, setFocus)
  636. if consumed {
  637. return
  638. }
  639. }
  640. return
  641. })
  642. }