formlayout.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package layout
  2. import (
  3. "fyne.io/fyne/v2"
  4. "fyne.io/fyne/v2/canvas"
  5. "fyne.io/fyne/v2/theme"
  6. )
  7. const formLayoutCols = 2
  8. // Declare conformity with Layout interface
  9. var _ fyne.Layout = (*formLayout)(nil)
  10. // formLayout is two column grid where each row has a label and a widget.
  11. type formLayout struct {
  12. }
  13. func (f *formLayout) countRows(objects []fyne.CanvasObject) int {
  14. count := 0
  15. for i := 0; i < len(objects); i += formLayoutCols {
  16. if !objects[i].Visible() && !objects[i+1].Visible() {
  17. continue
  18. }
  19. count++
  20. }
  21. return count
  22. }
  23. // tableCellsSize defines the size for all the cells of the form table.
  24. // The height of each row will be set as the max value between the label and content cell heights.
  25. // The width of the label column will be set as the max width value between all the label cells.
  26. // The width of the content column will be set as the max width value between all the content cells
  27. // or the remaining space of the bounding containerWidth, if it is larger.
  28. func (f *formLayout) tableCellsSize(objects []fyne.CanvasObject, containerWidth float32) [][2]fyne.Size {
  29. rows := f.countRows(objects)
  30. table := make([][2]fyne.Size, rows)
  31. if (len(objects))%formLayoutCols != 0 {
  32. return table
  33. }
  34. padding := theme.Padding()
  35. lowBound := 0
  36. highBound := 2
  37. labelCellMaxWidth := float32(0)
  38. contentCellMaxWidth := float32(0)
  39. for row := 0; row < rows; {
  40. currentRow := objects[lowBound:highBound]
  41. lowBound = highBound
  42. highBound += formLayoutCols
  43. if !currentRow[0].Visible() && !currentRow[1].Visible() {
  44. continue
  45. }
  46. labelCell := currentRow[0].MinSize()
  47. if _, ok := currentRow[0].(*canvas.Text); ok {
  48. labelCell.Width += padding * 4
  49. }
  50. labelCellMaxWidth = fyne.Max(labelCellMaxWidth, labelCell.Width)
  51. contentCell := currentRow[1].MinSize()
  52. contentCellMaxWidth = fyne.Max(contentCellMaxWidth, contentCell.Width)
  53. rowHeight := fyne.Max(labelCell.Height, contentCell.Height)
  54. labelCell.Height = rowHeight
  55. contentCell.Height = rowHeight
  56. table[row][0] = labelCell
  57. table[row][1] = contentCell
  58. row++
  59. }
  60. contentWidth := fyne.Max(contentCellMaxWidth, containerWidth-labelCellMaxWidth-padding)
  61. for row := 0; row < rows; row++ {
  62. table[row][0].Width = labelCellMaxWidth
  63. table[row][1].Width = contentWidth
  64. }
  65. return table
  66. }
  67. // Layout is called to pack all child objects into a table format with two columns.
  68. func (f *formLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
  69. table := f.tableCellsSize(objects, size.Width)
  70. padding := theme.Padding()
  71. innerPadding := theme.InnerPadding()
  72. row := 0
  73. y := float32(0)
  74. for i := 0; i < len(objects); i += formLayoutCols {
  75. if !objects[i].Visible() && (i+1 < len(objects) && !objects[i+1].Visible()) {
  76. continue
  77. }
  78. if row > 0 {
  79. y += table[row-1][0].Height + padding
  80. }
  81. tableRow := table[row]
  82. if _, ok := objects[i].(*canvas.Text); ok {
  83. objects[i].Move(fyne.NewPos(innerPadding, y+innerPadding))
  84. objects[i].Resize(fyne.NewSize(tableRow[0].Width-innerPadding*2, objects[i].MinSize().Height))
  85. } else {
  86. objects[i].Move(fyne.NewPos(0, y))
  87. objects[i].Resize(fyne.NewSize(tableRow[0].Width, tableRow[0].Height))
  88. }
  89. if i+1 < len(objects) {
  90. objects[i+1].Move(fyne.NewPos(padding+tableRow[0].Width, y))
  91. objects[i+1].Resize(fyne.NewSize(tableRow[1].Width, tableRow[0].Height))
  92. }
  93. row++
  94. }
  95. }
  96. // MinSize finds the smallest size that satisfies all the child objects.
  97. // For a FormLayout this is the width of the widest label and content items and the height is
  98. // the sum of all column children combined with padding between each.
  99. func (f *formLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
  100. table := f.tableCellsSize(objects, 0)
  101. padding := theme.Padding()
  102. minSize := fyne.NewSize(0, 0)
  103. if len(table) == 0 {
  104. return minSize
  105. }
  106. added := false
  107. minSize.Width = table[0][0].Width + table[0][1].Width + padding
  108. for row := 0; row < len(table); row++ {
  109. minSize.Height += table[row][0].Height
  110. if added {
  111. minSize.Height += padding
  112. }
  113. added = true
  114. }
  115. return minSize
  116. }
  117. // NewFormLayout returns a new FormLayout instance
  118. func NewFormLayout() fyne.Layout {
  119. return &formLayout{}
  120. }