...

Source file src/codeberg.org/tslocum/etk/grid.go

Documentation: codeberg.org/tslocum/etk

     1  package etk
     2  
     3  import (
     4  	"image"
     5  
     6  	"github.com/hajimehoshi/ebiten/v2"
     7  )
     8  
     9  // Grid is a highly customizable cell-based layout. Widgets added to the Grid
    10  // may span multiple cells.
    11  type Grid struct {
    12  	*Box
    13  
    14  	columnSizes []int
    15  	rowSizes    []int
    16  
    17  	columnPadding int
    18  	rowPadding    int
    19  
    20  	cellPositions [][2]int
    21  	cellSpans     [][2]int
    22  
    23  	numColumns int
    24  	numRows    int
    25  
    26  	updated bool
    27  }
    28  
    29  // NewGrid returns a new Grid widget.
    30  func NewGrid() *Grid {
    31  	return &Grid{
    32  		Box: NewBox(),
    33  	}
    34  }
    35  
    36  // SetRect sets the position and size of the widget.
    37  func (g *Grid) SetRect(r image.Rectangle) {
    38  	g.Lock()
    39  	defer g.Unlock()
    40  
    41  	g.Box.rect = r
    42  	g.updated = true
    43  }
    44  
    45  // SetColumnSizes sets the size of each column. A size of -1 represents an equal
    46  // proportion of the available space.
    47  func (g *Grid) SetColumnSizes(size ...int) {
    48  	g.Lock()
    49  	defer g.Unlock()
    50  
    51  	g.columnSizes = size
    52  	g.updated = true
    53  }
    54  
    55  // SetColumnPadding sets the amount of padding between each column.
    56  func (g *Grid) SetColumnPadding(padding int) {
    57  	g.Lock()
    58  	defer g.Unlock()
    59  
    60  	g.columnPadding = padding
    61  	g.updated = true
    62  }
    63  
    64  // SetRowSizes sets the size of each row. A size of -1 represents an equal
    65  // proportion of the available space.
    66  func (g *Grid) SetRowSizes(size ...int) {
    67  	g.Lock()
    68  	defer g.Unlock()
    69  
    70  	g.rowSizes = size
    71  	g.updated = true
    72  }
    73  
    74  // SetRowPadding sets the amount of padding between each row.
    75  func (g *Grid) SetRowPadding(padding int) {
    76  	g.Lock()
    77  	defer g.Unlock()
    78  
    79  	g.rowPadding = padding
    80  	g.updated = true
    81  }
    82  
    83  // Columns returns the number of columns in the grid, including empty columns.
    84  func (g *Grid) Columns() int {
    85  	g.Lock()
    86  	defer g.Unlock()
    87  
    88  	if g.updated {
    89  		g.reposition()
    90  		g.updated = false
    91  	}
    92  	return g.numColumns
    93  }
    94  
    95  // Rows returns the number of rows in the grid, including empty rows.
    96  func (g *Grid) Rows() int {
    97  	g.Lock()
    98  	defer g.Unlock()
    99  
   100  	if g.updated {
   101  		g.reposition()
   102  		g.updated = false
   103  	}
   104  	return g.numRows
   105  }
   106  
   107  // AddChild adds a widget to the Grid at 0,0. To add widgets to a Grid, you
   108  // should use AddChildAt instead.
   109  func (g *Grid) AddChild(wgt ...Widget) {
   110  	g.Box.AddChild(wgt...)
   111  
   112  	for i := 0; i < len(wgt); i++ {
   113  		g.cellPositions = append(g.cellPositions, [2]int{0, 0})
   114  		g.cellSpans = append(g.cellSpans, [2]int{1, 1})
   115  	}
   116  
   117  	g.updated = true
   118  }
   119  
   120  // AddChildAt adds a widget to the Grid at the specified position. Each widget
   121  // added to the grid may span multiple cells.
   122  func (g *Grid) AddChildAt(wgt Widget, x int, y int, columns int, rows int) {
   123  	g.Box.AddChild(wgt)
   124  
   125  	g.cellPositions = append(g.cellPositions, [2]int{x, y})
   126  	g.cellSpans = append(g.cellSpans, [2]int{columns, rows})
   127  
   128  	g.updated = true
   129  }
   130  
   131  // Clear removes all children from the Grid.
   132  func (g *Grid) Clear() {
   133  	g.Lock()
   134  	defer g.Unlock()
   135  
   136  	g.children = g.children[:0]
   137  	g.cellPositions = g.cellPositions[:0]
   138  	g.cellSpans = g.cellSpans[:0]
   139  	g.updated = true
   140  }
   141  
   142  // HandleKeyboard is called when a keyboard event occurs.
   143  func (g *Grid) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
   144  	if g.updated {
   145  		g.reposition()
   146  		g.updated = false
   147  	}
   148  
   149  	return false, nil
   150  }
   151  
   152  // HandleMouse is called when a mouse event occurs.
   153  func (g *Grid) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   154  	if g.updated {
   155  		g.reposition()
   156  		g.updated = false
   157  	}
   158  
   159  	return false, nil
   160  }
   161  
   162  // Draw draws the widget on the screen.
   163  func (g *Grid) Draw(screen *ebiten.Image) error {
   164  	g.Lock()
   165  	defer g.Unlock()
   166  
   167  	if g.updated {
   168  		g.reposition()
   169  		g.updated = false
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func (g *Grid) reposition() {
   176  	if g.rect.Min.X == 0 && g.rect.Min.Y == 0 && g.rect.Max.X == 0 && g.rect.Max.Y == 0 {
   177  		return
   178  	}
   179  
   180  	gridX, gridY := g.rect.Min.X, g.rect.Min.Y
   181  	gridW, gridH := g.rect.Dx(), g.rect.Dy()
   182  
   183  	// Determine max column and row sizes and proportions.
   184  	var (
   185  		numColumns int
   186  		numRows    int
   187  
   188  		maxColumnProportion = 1
   189  		maxRowProportion    = 1
   190  
   191  		numColumnProportions = make(map[int]int)
   192  		numRowProportions    = make(map[int]int)
   193  	)
   194  	for i := range g.children {
   195  		position := g.cellPositions[i]
   196  		x, y := position[0], position[1]
   197  
   198  		span := g.cellSpans[i]
   199  		w, h := span[0], span[1]
   200  
   201  		if x+w > numColumns {
   202  			numColumns = x + w
   203  		}
   204  		if y+h > numRows {
   205  			numRows = y + h
   206  		}
   207  
   208  		if -w > maxColumnProportion {
   209  			maxColumnProportion = -w
   210  		}
   211  		if -h > maxRowProportion {
   212  			maxRowProportion = -h
   213  		}
   214  	}
   215  
   216  	// Determine actual column and row sizes and proportions.
   217  	numColumnSizes := len(g.columnSizes)
   218  	numRowSizes := len(g.rowSizes)
   219  
   220  	columnWidths := make([]int, numColumns)
   221  	var usedWidth int
   222  	for i := 0; i < numColumns; i++ {
   223  		if i >= numColumnSizes {
   224  			columnWidths[i] = -1
   225  		} else {
   226  			columnWidths[i] = g.columnSizes[i]
   227  
   228  			if g.columnSizes[i] > 0 {
   229  				usedWidth += g.columnSizes[i]
   230  			}
   231  		}
   232  
   233  		if columnWidths[i] < 0 {
   234  			numColumnProportions[-columnWidths[i]]++
   235  		}
   236  	}
   237  	remainingWidth := gridW - usedWidth - (g.columnPadding * (numColumns + 1))
   238  	columnProportions := make([]int, maxColumnProportion)
   239  	for i := 0; i < maxColumnProportion; i++ {
   240  		columnProportions[i] = remainingWidth / (i + 1)
   241  	}
   242  	for i := 0; i < numColumns; i++ {
   243  		if columnWidths[i] < 0 {
   244  			columnWidths[i] = columnProportions[-columnWidths[i]-1] / numColumnProportions[-columnWidths[i]]
   245  		}
   246  	}
   247  
   248  	rowHeights := make([]int, numRows)
   249  	var usedHeight int
   250  	for i := 0; i < numRows; i++ {
   251  		if i >= numRowSizes {
   252  			rowHeights[i] = -1
   253  		} else {
   254  			rowHeights[i] = g.rowSizes[i]
   255  
   256  			if g.rowSizes[i] > 0 {
   257  				usedHeight += g.rowSizes[i]
   258  			}
   259  		}
   260  
   261  		if rowHeights[i] < 0 {
   262  			numRowProportions[-rowHeights[i]]++
   263  		}
   264  	}
   265  	remainingHeight := gridH - usedHeight - (g.rowPadding * (numRows + 1))
   266  	rowProportions := make([]int, maxRowProportion)
   267  	for i := 0; i < maxRowProportion; i++ {
   268  		rowProportions[i] = remainingHeight / (i + 1)
   269  	}
   270  	for i := 0; i < numRows; i++ {
   271  		if rowHeights[i] < 0 {
   272  			rowHeights[i] = rowProportions[-rowHeights[i]-1] / numRowProportions[-rowHeights[i]]
   273  		}
   274  	}
   275  
   276  	columnPositions := make([]int, numColumns)
   277  	{
   278  		x := g.columnPadding
   279  		for i := 0; i < numColumns; i++ {
   280  			columnPositions[i] = x
   281  			x += columnWidths[i] + g.columnPadding
   282  		}
   283  	}
   284  
   285  	rowPositions := make([]int, numRows)
   286  	{
   287  		y := g.rowPadding
   288  		for i := 0; i < numRows; i++ {
   289  			rowPositions[i] = y
   290  			y += rowHeights[i] + g.rowPadding
   291  		}
   292  	}
   293  
   294  	// Cache column and row count.
   295  	g.numColumns, g.numRows = numColumns, numRows
   296  
   297  	// Reposition and resize all children.
   298  	for i, child := range g.children {
   299  		position := g.cellPositions[i]
   300  		span := g.cellSpans[i]
   301  
   302  		x := columnPositions[position[0]]
   303  		y := rowPositions[position[1]]
   304  
   305  		var w, h int
   306  		for j := 0; j < span[0]; j++ {
   307  			if j > 0 {
   308  				w += g.columnPadding
   309  			}
   310  			w += columnWidths[position[0]+j]
   311  		}
   312  		for j := 0; j < span[1]; j++ {
   313  			if j > 0 {
   314  				h += g.rowPadding
   315  			}
   316  			h += rowHeights[position[1]+j]
   317  		}
   318  
   319  		child.SetRect(image.Rect(gridX+x, gridY+y, gridX+x+w, gridY+y+h))
   320  	}
   321  }
   322  

View as plain text