...

Source file src/github.com/rivo/tview/grid.go

Documentation: github.com/rivo/tview

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

View as plain text