gridlayout.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. padWidth := float32(g.Cols-1) * theme.Padding()
  65. padHeight := float32(rows-1) * theme.Padding()
  66. cellWidth := float64(size.Width-padWidth) / float64(g.Cols)
  67. cellHeight := float64(size.Height-padHeight) / float64(rows)
  68. if !g.horizontal() {
  69. padWidth, padHeight = padHeight, padWidth
  70. cellWidth = float64(size.Width-padWidth) / float64(rows)
  71. cellHeight = float64(size.Height-padHeight) / float64(g.Cols)
  72. }
  73. row, col := 0, 0
  74. i := 0
  75. for _, child := range objects {
  76. if !child.Visible() {
  77. continue
  78. }
  79. x1 := getLeading(cellWidth, col)
  80. y1 := getLeading(cellHeight, row)
  81. x2 := getTrailing(cellWidth, col)
  82. y2 := getTrailing(cellHeight, row)
  83. child.Move(fyne.NewPos(x1, y1))
  84. child.Resize(fyne.NewSize(x2-x1, y2-y1))
  85. if g.horizontal() {
  86. if (i+1)%g.Cols == 0 {
  87. row++
  88. col = 0
  89. } else {
  90. col++
  91. }
  92. } else {
  93. if (i+1)%g.Cols == 0 {
  94. col++
  95. row = 0
  96. } else {
  97. row++
  98. }
  99. }
  100. i++
  101. }
  102. }
  103. // MinSize finds the smallest size that satisfies all the child objects.
  104. // For a GridLayout this is the size of the largest child object multiplied by
  105. // the required number of columns and rows, with appropriate padding between
  106. // children.
  107. func (g *gridLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
  108. rows := g.countRows(objects)
  109. minSize := fyne.NewSize(0, 0)
  110. for _, child := range objects {
  111. if !child.Visible() {
  112. continue
  113. }
  114. minSize = minSize.Max(child.MinSize())
  115. }
  116. if g.horizontal() {
  117. minContentSize := fyne.NewSize(minSize.Width*float32(g.Cols), minSize.Height*float32(rows))
  118. return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(g.Cols-1), 0), theme.Padding()*fyne.Max(float32(rows-1), 0)))
  119. }
  120. minContentSize := fyne.NewSize(minSize.Width*float32(rows), minSize.Height*float32(g.Cols))
  121. return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(rows-1), 0), theme.Padding()*fyne.Max(float32(g.Cols-1), 0)))
  122. }