package cviewimport ()// gridItem represents one primitive and its possible position on a grid.type gridItem struct { Item Primitive// The item to be positioned. May be nil for an empty item. Row, Column int// The top-left grid cell where the item is placed. Width, Height int// The number of rows and columns the item occupies. MinGridWidth, MinGridHeight int// The minimum grid width/height for which this item is visible. Focus bool// Whether or not this item attracts the layout's focus. visible bool// Whether or not this item was visible the last time the grid was drawn. 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.}// Grid is an implementation of a grid-based layout. It works by defining the// size of the rows and columns, then placing primitives into the grid.//// Some settings can lead to the grid exceeding its available space. SetOffset()// can then be used to scroll in steps of rows and columns. These offset values// can also be controlled with the arrow keys (or the "g","G", "j", "k", "h",// and "l" keys) while the grid has focus and none of its contained primitives// do.typeGridstruct { *Box// The items to be positioned. items []*gridItem// The definition of the rows and columns of the grid. See // SetRows()/SetColumns() for details. rows, columns []int// The minimum sizes for rows and columns. minWidth, minHeight int// The size of the gaps between neighboring primitives. This is automatically // set to 1 if borders is true. gapRows, gapColumns int// The number of rows and columns skipped before drawing the top-left corner // of the grid. rowOffset, columnOffset int// Whether or not borders are drawn around grid items. If this is set to true, // a gap size of 1 is automatically assumed (which is filled with the border // graphics). borders bool// The color of the borders around grid items. bordersColor tcell.Colorsync.RWMutex}// NewGrid returns a new grid-based layout container with no initial primitives.//// Note that Grid will have a transparent background by default so that any// areas not covered by any primitives will show primitives behind the Grid.// To disable this transparency://// grid.SetBackgroundTransparent(false)func () *Grid { := &Grid{Box: NewBox(),bordersColor: Styles.GraphicsColor, } .SetBackgroundTransparent(true) .focus = return}// SetColumns defines how the columns of the grid are distributed. Each value// defines the size of one column, starting with the leftmost column. Values// greater 0 represent absolute column widths (gaps not included). Values less// or equal 0 represent proportional column widths or fractions of the remaining// free space, where 0 is treated the same as -1. That is, a column with a value// of -3 will have three times the width of a column with a value of -1 (or 0).// The minimum width set with SetMinSize() is always observed.//// Primitives may extend beyond the columns defined explicitly with this// function. A value of 0 is assumed for any undefined column. In fact, if you// never call this function, all columns occupied by primitives will have the// same width. On the other hand, unoccupied columns defined with this function// will always take their place.//// Assuming a total width of the grid of 100 cells and a minimum width of 0, the// following call will result in columns with widths of 30, 10, 15, 15, and 30// cells://// grid.SetColumns(30, 10, -1, -1, -2)//// If a primitive were then placed in the 6th and 7th column, the resulting// widths would be: 30, 10, 10, 10, 20, 10, and 10 cells.//// If you then called SetMinSize() as follows://// grid.SetMinSize(15, 20)//// The resulting widths would be: 30, 15, 15, 15, 20, 15, and 15 cells, a total// of 125 cells, 25 cells wider than the available grid width.func ( *Grid) ( ...int) { .Lock()defer .Unlock() .columns = }// SetRows defines how the rows of the grid are distributed. These values behave// the same as the column values provided with SetColumns(), see there for a// definition and examples.//// The provided values correspond to row heights, the first value defining// the height of the topmost row.func ( *Grid) ( ...int) { .Lock()defer .Unlock() .rows = }// SetSize is a shortcut for SetRows() and SetColumns() where all row and column// values are set to the given size values. See SetColumns() for details on sizes.func ( *Grid) (, , , int) { .Lock()defer .Unlock() .rows = make([]int, )for := range .rows { .rows[] = } .columns = make([]int, )for := range .columns { .columns[] = }}// SetMinSize sets an absolute minimum width for rows and an absolute minimum// height for columns. Panics if negative values are provided.func ( *Grid) (, int) { .Lock()defer .Unlock()if < 0 || < 0 {panic("Invalid minimum row/column size") } .minHeight, .minWidth = , }// SetGap sets the size of the gaps between neighboring primitives on the grid.// If borders are drawn (see SetBorders()), these values are ignored and a gap// of 1 is assumed. Panics if negative values are provided.func ( *Grid) (, int) { .Lock()defer .Unlock()if < 0 || < 0 {panic("Invalid gap size") } .gapRows, .gapColumns = , }// SetBorders sets whether or not borders are drawn around grid items. Setting// this value to true will cause the gap values (see SetGap()) to be ignored and// automatically assumed to be 1 where the border graphics are drawn.func ( *Grid) ( bool) { .Lock()defer .Unlock() .borders = }// SetBordersColor sets the color of the item borders.func ( *Grid) ( tcell.Color) { .Lock()defer .Unlock() .bordersColor = }// AddItem adds a primitive and its position to the grid. The top-left corner// of the primitive will be located in the top-left corner of the grid cell at// the given row and column and will span "rowSpan" rows and "colSpan" columns.// For example, for a primitive to occupy rows 2, 3, and 4 and columns 5 and 6://// grid.AddItem(p, 2, 5, 3, 2, 0, 0, true)//// If rowSpan or colSpan is 0, the primitive will not be drawn.//// You can add the same primitive multiple times with different grid positions.// The minGridWidth and minGridHeight values will then determine which of those// positions will be used. This is similar to CSS media queries. These minimum// values refer to the overall size of the grid. If multiple items for the same// primitive apply, the one that has at least one highest minimum value will be// used, or the primitive added last if those values are the same. Example://// grid.AddItem(p, 0, 0, 0, 0, 0, 0, true). // Hide in small grids.// AddItem(p, 0, 0, 1, 2, 100, 0, true). // One-column layout for medium grids.// AddItem(p, 1, 1, 3, 2, 300, 0, true) // Multi-column layout for large grids.//// To use the same grid layout for all sizes, simply set minGridWidth and// minGridHeight to 0.//// If the item's focus is set to true, it will receive focus when the grid// receives focus. If there are multiple items with a true focus flag, the last// visible one that was added will receive focus.func ( *Grid) ( Primitive, , , , , , int, bool) { .Lock()defer .Unlock() .items = append(.items, &gridItem{Item: ,Row: ,Column: ,Height: ,Width: ,MinGridHeight: ,MinGridWidth: ,Focus: , })}func ( *Grid) ( Primitive) bool {for , := range .items {if .Item == {returntrue } }returnfalse}func ( *Grid) ( Primitive, , , , , , int, bool) { .Lock()defer .Unlock()for , := range .items {if .Item != {continue } .Row = .Column = .Height = .Width = .MinGridHeight = .MinGridWidth = .Focus = break }}// RemoveItem removes all items for the given primitive from the grid, keeping// the order of the remaining items intact.func ( *Grid) ( Primitive) { .Lock()defer .Unlock()for := len(.items) - 1; >= 0; -- {if .items[].Item == { .items = append(.items[:], .items[+1:]...) } }}// Clear removes all items from the grid.func ( *Grid) () { .Lock()defer .Unlock() .items = nil}// SetOffset sets the number of rows and columns which are skipped before// drawing the first grid cell in the top-left corner. As the grid will never// completely move off the screen, these values may be adjusted the next time// the grid is drawn. The actual position of the grid may also be adjusted such// that contained primitives that have focus remain visible.func ( *Grid) (, int) { .Lock()defer .Unlock() .rowOffset, .columnOffset = , }// GetOffset returns the current row and column offset (see SetOffset() for// details).func ( *Grid) () (, int) { .RLock()defer .RUnlock()return .rowOffset, .columnOffset}// Focus is called when this primitive receives focus.func ( *Grid) ( func( Primitive)) { .Lock() := .items .Unlock()for , := range {if .Focus { (.Item)return } } .Lock() .hasFocus = true .Unlock()}// Blur is called when this primitive loses focus.func ( *Grid) () { .Lock()defer .Unlock() .hasFocus = false}// HasFocus returns whether or not this primitive has focus.func ( *Grid) () bool { .RLock()defer .RUnlock()for , := range .items {if .visible && .Item.GetFocusable().HasFocus() {returntrue } }return .hasFocus}// InputHandler returns the handler for this primitive.func ( *Grid) () func( *tcell.EventKey, func( Primitive)) {return .WrapInputHandler(func( *tcell.EventKey, func( Primitive)) { .Lock()defer .Unlock()ifHitShortcut(, Keys.MoveFirst, Keys.MoveFirst2) { .rowOffset, .columnOffset = 0, 0 } elseifHitShortcut(, Keys.MoveLast, Keys.MoveLast2) { .rowOffset = math.MaxInt32 } elseifHitShortcut(, Keys.MoveUp, Keys.MoveUp2, Keys.MovePreviousField) { .rowOffset-- } elseifHitShortcut(, Keys.MoveDown, Keys.MoveDown2, Keys.MoveNextField) { .rowOffset++ } elseifHitShortcut(, Keys.MoveLeft, Keys.MoveLeft2) { .columnOffset-- } elseifHitShortcut(, Keys.MoveRight, Keys.MoveRight2) { .columnOffset++ } })}// Draw draws this primitive onto the screen.func ( *Grid) ( tcell.Screen) {if !.GetVisible() {return } .Box.Draw() .Lock()defer .Unlock() , , , := .GetInnerRect() , := .Size()// Make a list of items which apply. := make(map[Primitive]*gridItem)for , := range .items { .visible = falseif .Width <= 0 || .Height <= 0 || < .MinGridWidth || < .MinGridHeight {continue } , := [.Item]if && .MinGridWidth < .MinGridWidth && .MinGridHeight < .MinGridHeight {continue } [.Item] = }// How many rows and columns do we have? := len(.rows) := len(.columns)for , := range { := .Row + .Heightif > { = } := .Column + .Widthif > { = } }if == 0 || == 0 {return// No content. }// Where are they located? := make([]int, ) := make([]int, ) := make([]int, ) := make([]int, )// How much space do we distribute? := := := 0 := 0for , := range .rows {if > 0 {if < .minHeight { = .minHeight } -= [] = } elseif == 0 { ++ } else { += - } }for , := range .columns {if > 0 {if < .minWidth { = .minWidth } -= [] = } elseif == 0 { ++ } else { += - } }if .borders { -= + 1 -= + 1 } else { -= ( - 1) * .gapRows -= ( - 1) * .gapColumns }if > len(.rows) { += - len(.rows) }if > len(.columns) { += - len(.columns) }// Distribute proportional rows/columns.for := 0; < ; ++ { := 0if < len(.rows) { = .rows[] }if > 0 {if < .minHeight { = .minHeight }continue// Not proportional. We already know the width. } elseif == 0 { = 1 } else { = - } := * / -= -= if < .minHeight { = .minHeight } [] = }for := 0; < ; ++ { := 0if < len(.columns) { = .columns[] }if > 0 {if < .minWidth { = .minWidth }continue// Not proportional. We already know the height. } elseif == 0 { = 1 } else { = - } := * / -= -= if < .minWidth { = .minWidth } [] = }// Calculate row/column positions.var , intif .borders { ++ ++ }for , := range { [] = := .gapRowsif .borders { = 1 } += + }for , := range { [] = := .gapColumnsif .borders { = 1 } += + }// Calculate primitive positions.var *gridItem// The item which has focus.for , := range { := [.Column] := [.Row]var , intfor := 0; < .Height; ++ { += [.Row+] }for := 0; < .Width; ++ { += [.Column+] }if .borders { += .Width - 1 += .Height - 1 } else { += (.Width - 1) * .gapColumns += (.Height - 1) * .gapRows } .x, .y, .w, .h = , , , .visible = trueif .GetFocusable().HasFocus() { = } }// Calculate screen offsets.var , int := 1if !.borders { = .gapRows }for , := range {if >= .rowOffset {break } += + }if !.borders { = .gapColumns }for , := range {if >= .columnOffset {break } += + }// Line up the last row/column with the end of the available area.varintif .borders { = 1 } := len() - 1if []+[]+- < { = [] - + [] + } = len() - 1if []+[]+- < { = [] - + [] + }// The focused item must be within the visible area.if != nil {if .y+.h- >= { = .y - + .h }if .y- < 0 { = .y }if .x+.w- >= { = .x - + .w }if .x- < 0 { = .x } }// Adjust row/column offsets based on this value.var , intfor , := range {if - < 0 { = + 1 }if - < { = } }if .rowOffset < { .rowOffset = }if .rowOffset > { .rowOffset = } , = 0, 0for , := range {if - < 0 { = + 1 }if - < { = } }if .columnOffset < { .columnOffset = }if .columnOffset > { .columnOffset = }// Draw primitives and borders.for , := range {// Final primitive position.if !.visible {continue } .x -= .y -= if .x >= || .x+.w <= 0 || .y >= || .y+.h <= 0 { .visible = falsecontinue }if .x+.w > { .w = - .x }if .y+.h > { .h = - .y }if .x < 0 { .w += .x .x = 0 }if .y < 0 { .h += .y .y = 0 }if .w <= 0 || .h <= 0 { .visible = falsecontinue } .x += .y += .SetRect(.x, .y, .w, .h)// Draw primitive.if == {defer .Draw() } else { .Draw() }// Draw border around primitive.if .borders {for := .x; < .x+.w; ++ { // Top/bottom lines.if < 0 || >= {continue } := .y - 1if >= 0 && < {PrintJoinedSemigraphics(, , , Borders.Horizontal, .bordersColor) } = .y + .hif >= 0 && < {PrintJoinedSemigraphics(, , , Borders.Horizontal, .bordersColor) } }for := .y; < .y+.h; ++ { // Left/right lines.if < 0 || >= {continue } := .x - 1if >= 0 && < {PrintJoinedSemigraphics(, , , Borders.Vertical, .bordersColor) } = .x + .wif >= 0 && < {PrintJoinedSemigraphics(, , , Borders.Vertical, .bordersColor) } } , := .x-1, .y-1// Top-left corner.if >= 0 && < && >= 0 && < {PrintJoinedSemigraphics(, , , Borders.TopLeft, .bordersColor) } , = .x+.w, .y-1// Top-right corner.if >= 0 && < && >= 0 && < {PrintJoinedSemigraphics(, , , Borders.TopRight, .bordersColor) } , = .x-1, .y+.h// Bottom-left corner.if >= 0 && < && >= 0 && < {PrintJoinedSemigraphics(, , , Borders.BottomLeft, .bordersColor) } , = .x+.w, .y+.h// Bottom-right corner.if >= 0 && < && >= 0 && < {PrintJoinedSemigraphics(, , , Borders.BottomRight, .bordersColor) } } }}// MouseHandler returns the mouse handler for this primitive.func ( *Grid) () func( MouseAction, *tcell.EventMouse, func( Primitive)) ( bool, Primitive) {return .WrapMouseHandler(func( MouseAction, *tcell.EventMouse, func( Primitive)) ( bool, Primitive) {if !.InRect(.Position()) {returnfalse, nil }// Pass mouse events along to the first child item under the mouse that consumes it. , := .Position()for , := range .items { , , , := .Item.GetRect() := >= && < + && >= && < +if ! {continue } , = .Item.MouseHandler()(, , )if {return } }return })}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.