gridlayout.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package layout
  2. import (
  3. "math"
  4. "fyne.io/fyne/v2"
  5. "fyne.io/fyne/v2/theme"
  6. )
  7. // Declare conformity with Layout interface
  8. var _ fyne.Layout = (*gridLayout)(nil)
  9. type gridLayout struct {
  10. Cols int
  11. vertical, adapt bool
  12. }
  13. // NewAdaptiveGridLayout returns a new grid layout which uses columns when horizontal but rows when vertical.
  14. func NewAdaptiveGridLayout(rowcols int) fyne.Layout {
  15. return &gridLayout{Cols: rowcols, adapt: true}
  16. }
  17. // NewGridLayout returns a grid layout arranged in a specified number of columns.
  18. // The number of rows will depend on how many children are in the container that uses this layout.
  19. func NewGridLayout(cols int) fyne.Layout {
  20. return NewGridLayoutWithColumns(cols)
  21. }
  22. // NewGridLayoutWithColumns returns a new grid layout that specifies a column count and wrap to new rows when needed.
  23. func NewGridLayoutWithColumns(cols int) fyne.Layout {
  24. return &gridLayout{Cols: cols}
  25. }
  26. // NewGridLayoutWithRows returns a new grid layout that specifies a row count that creates new rows as required.
  27. func NewGridLayoutWithRows(rows int) fyne.Layout {
  28. return &gridLayout{Cols: rows, vertical: true}
  29. }
  30. func (g *gridLayout) horizontal() bool {
  31. if g.adapt {
  32. return fyne.IsHorizontal(fyne.CurrentDevice().Orientation())
  33. }
  34. return !g.vertical
  35. }
  36. func (g *gridLayout) countRows(objects []fyne.CanvasObject) int {
  37. if g.Cols < 1 {
  38. g.Cols = 1
  39. }
  40. count := 0
  41. for _, child := range objects {
  42. if child.Visible() {
  43. count++
  44. }
  45. }
  46. return int(math.Ceil(float64(count) / float64(g.Cols)))
  47. }
  48. // Get the leading (top or left) edge of a grid cell.
  49. // size is the ideal cell size and the offset is which col or row its on.
  50. func getLeading(size float64, offset int) float32 {
  51. ret := (size + float64(theme.Padding())) * float64(offset)
  52. return float32(ret)
  53. }
  54. // Get the trailing (bottom or right) edge of a grid cell.
  55. // size is the ideal cell size and the offset is which col or row its on.
  56. func getTrailing(size float64, offset int) float32 {
  57. return getLeading(size, offset+1) - theme.Padding()
  58. }
  59. // Layout is called to pack all child objects into a specified size.
  60. // For a GridLayout this will pack objects into a table format with the number
  61. // of columns specified in our constructor.
  62. func (g *gridLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
  63. rows := g.countRows(objects)
  64. padding := theme.Padding()
  65. padWidth := float32(g.Cols-1) * padding
  66. padHeight := float32(rows-1) * padding
  67. cellWidth := float64(size.Width-padWidth) / float64(g.Cols)
  68. cellHeight := float64(size.Height-padHeight) / float64(rows)
  69. if !g.horizontal() {
  70. padWidth, padHeight = padHeight, padWidth
  71. cellWidth = float64(size.Width-padWidth) / float64(rows)
  72. cellHeight = float64(size.Height-padHeight) / float64(g.Cols)
  73. }
  74. row, col := 0, 0
  75. i := 0
  76. for _, child := range objects {
  77. if !child.Visible() {
  78. continue
  79. }
  80. x1 := getLeading(cellWidth, col)
  81. y1 := getLeading(cellHeight, row)
  82. x2 := getTrailing(cellWidth, col)
  83. y2 := getTrailing(cellHeight, row)
  84. child.Move(fyne.NewPos(x1, y1))
  85. child.Resize(fyne.NewSize(x2-x1, y2-y1))
  86. if g.horizontal() {
  87. if (i+1)%g.Cols == 0 {
  88. row++
  89. col = 0
  90. } else {
  91. col++
  92. }
  93. } else {
  94. if (i+1)%g.Cols == 0 {
  95. col++
  96. row = 0
  97. } else {
  98. row++
  99. }
  100. }
  101. i++
  102. }
  103. }
  104. // MinSize finds the smallest size that satisfies all the child objects.
  105. // For a GridLayout this is the size of the largest child object multiplied by
  106. // the required number of columns and rows, with appropriate padding between
  107. // children.
  108. func (g *gridLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
  109. rows := g.countRows(objects)
  110. minSize := fyne.NewSize(0, 0)
  111. for _, child := range objects {
  112. if !child.Visible() {
  113. continue
  114. }
  115. minSize = minSize.Max(child.MinSize())
  116. }
  117. if g.horizontal() {
  118. minContentSize := fyne.NewSize(minSize.Width*float32(g.Cols), minSize.Height*float32(rows))
  119. return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(g.Cols-1), 0), theme.Padding()*fyne.Max(float32(rows-1), 0)))
  120. }
  121. minContentSize := fyne.NewSize(minSize.Width*float32(rows), minSize.Height*float32(g.Cols))
  122. return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(rows-1), 0), theme.Padding()*fyne.Max(float32(g.Cols-1), 0)))
  123. }