gemini-browser

A text-based gemini browser
git clone git://git.laack.co/gemini-browser.git
Log | Files | Refs | README

grid.go (20831B)


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