...

Source file src/code.rocket9labs.com/tslocum/etk/grid.go

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

View as plain text