gridlayout.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gui
  5. // GridLayout is a panel layout which arranges its children in a rectangular grid.
  6. // It is necessary to set the number of columns of the grid when the layout is created.
  7. // The panel's child elements are positioned in the grid cells accordingly to the
  8. // order they were added to the panel.
  9. // The height of each row is determined by the height of the heightest child in the row.
  10. // The width of each column is determined by the width of the widest child in the column
  11. type GridLayout struct {
  12. pan IPanel // parent panel
  13. columns []colInfo // columns alignment info
  14. alignh Align // global cell horizontal alignment
  15. alignv Align // global cell vertical alignment
  16. expandh bool // expand horizontally flag
  17. expandv bool // expand vertically flag
  18. }
  19. // GridLayoutParams describes layout parameter for an specific child
  20. type GridLayoutParams struct {
  21. ColSpan int // Number of additional columns to ocuppy to the right
  22. AlignH Align // Vertical alignment
  23. AlignV Align // Horizontal alignment
  24. }
  25. // colInfo keeps information about each grid column
  26. type colInfo struct {
  27. alignh *Align // optional column horizontal alignment
  28. alignv *Align // optional column vertical alignment
  29. }
  30. // NewGridLayout creates and returns a pointer of a new grid layout
  31. func NewGridLayout(ncols int) *GridLayout {
  32. if ncols <= 0 {
  33. panic("Invalid number of columns")
  34. }
  35. gl := new(GridLayout)
  36. gl.columns = make([]colInfo, ncols)
  37. return gl
  38. }
  39. // SetAlignV sets the vertical alignment for all the grid cells
  40. // The alignment of an individual cell can be set by settings its layout parameters.
  41. func (g *GridLayout) SetAlignV(align Align) {
  42. g.alignv = align
  43. g.Recalc(g.pan)
  44. }
  45. // SetAlignH sets the horizontal alignment for all the grid cells
  46. // The alignment of an individual cell can be set by settings its layout parameters.
  47. func (g *GridLayout) SetAlignH(align Align) {
  48. g.alignh = align
  49. g.Recalc(g.pan)
  50. }
  51. // SetExpandH sets it the columns should expand horizontally if possible
  52. func (g *GridLayout) SetExpandH(expand bool) {
  53. g.expandh = expand
  54. g.Recalc(g.pan)
  55. }
  56. // SetExpandV sets it the rowss should expand vertically if possible
  57. func (g *GridLayout) SetExpandV(expand bool) {
  58. g.expandv = expand
  59. g.Recalc(g.pan)
  60. }
  61. // SetColAlignV sets the vertical alignment for all the cells of
  62. // the specified column. The function panics if the supplied column is invalid
  63. func (g *GridLayout) SetColAlignV(col int, align Align) {
  64. if col < 0 || col >= len(g.columns) {
  65. panic("Invalid column")
  66. }
  67. if g.columns[col].alignv == nil {
  68. g.columns[col].alignv = new(Align)
  69. }
  70. *g.columns[col].alignv = align
  71. g.Recalc(g.pan)
  72. }
  73. // SetColAlignH sets the horizontal alignment for all the cells of
  74. // the specified column. The function panics if the supplied column is invalid.
  75. func (g *GridLayout) SetColAlignH(col int, align Align) {
  76. if col < 0 || col >= len(g.columns) {
  77. panic("Invalid column")
  78. }
  79. if g.columns[col].alignh == nil {
  80. g.columns[col].alignh = new(Align)
  81. }
  82. *g.columns[col].alignh = align
  83. g.Recalc(g.pan)
  84. }
  85. // Recalc sets the position and sizes of all of the panel's children.
  86. // It is normally called by the parent panel when its size changes or
  87. // a child is added or removed.
  88. func (g *GridLayout) Recalc(ipan IPanel) {
  89. type cell struct {
  90. panel *Panel // pointer to cell panel
  91. params GridLayoutParams // copy of params or default
  92. paramsDef bool // true if parameters are default
  93. }
  94. type row struct {
  95. cells []*cell // array of row cells
  96. height float32 // row height
  97. }
  98. // Saves the received panel
  99. g.pan = ipan
  100. if g.pan == nil {
  101. return
  102. }
  103. // Builds array of child rows
  104. pan := ipan.GetPanel()
  105. var irow int
  106. var icol int
  107. rows := []row{}
  108. for _, node := range pan.Children() {
  109. // Ignore invisible child
  110. child := node.(IPanel).GetPanel()
  111. if !child.Visible() {
  112. continue
  113. }
  114. // Checks child layout params, if supplied
  115. ip := child.layoutParams
  116. var params *GridLayoutParams
  117. var ok bool
  118. var paramsDef bool
  119. if ip != nil {
  120. params, ok = child.layoutParams.(*GridLayoutParams)
  121. if !ok {
  122. panic("layoutParams is not GridLayoutParams")
  123. }
  124. paramsDef = false
  125. } else {
  126. params = &GridLayoutParams{}
  127. paramsDef = true
  128. }
  129. // If first column, creates row and appends to rows
  130. if icol == 0 {
  131. var r row
  132. r.cells = make([]*cell, len(g.columns))
  133. rows = append(rows, r)
  134. }
  135. // Set current child panel to current cells
  136. rows[irow].cells[icol] = &cell{child, *params, paramsDef}
  137. // Updates next cell column and row
  138. icol += 1 + params.ColSpan
  139. if icol >= len(g.columns) {
  140. irow++
  141. icol = 0
  142. }
  143. }
  144. // Sets the height of each row to the height of the heightest child
  145. // Sets the width of each column to the width of the widest column
  146. colWidths := make([]float32, len(g.columns))
  147. for i := 0; i < len(rows); i++ {
  148. r := &rows[i]
  149. r.height = 0
  150. for ci, cell := range r.cells {
  151. if cell == nil {
  152. continue
  153. }
  154. if cell.panel.Height() > r.height {
  155. r.height = cell.panel.Height()
  156. }
  157. // If cell span columns, ignore this cell when computing column width
  158. if cell.params.ColSpan > 0 {
  159. continue
  160. }
  161. if cell.panel.Width() > colWidths[ci] {
  162. colWidths[ci] = cell.panel.Width()
  163. }
  164. }
  165. }
  166. // If expand horizontally set, distribute available space between all columns
  167. if g.expandh {
  168. var twidth float32
  169. for i := 0; i < len(colWidths); i++ {
  170. twidth += colWidths[i]
  171. }
  172. space := pan.ContentWidth() - twidth
  173. if space > 0 {
  174. colspace := space / float32(len(colWidths))
  175. for i := 0; i < len(colWidths); i++ {
  176. colWidths[i] += colspace
  177. }
  178. }
  179. }
  180. // If expand vertically set, distribute available space between all rows
  181. if g.expandv {
  182. // Calculates the sum of all row heights
  183. var theight float32
  184. for _, r := range rows {
  185. theight += r.height
  186. }
  187. // If space available distribute between all rows
  188. space := pan.ContentHeight() - theight
  189. if space > 0 {
  190. rowspace := space / float32(len(rows))
  191. for i := 0; i < len(rows); i++ {
  192. rows[i].height += rowspace
  193. }
  194. }
  195. }
  196. // Position each child panel in the parent panel
  197. var celly float32
  198. for _, r := range rows {
  199. var cellx float32
  200. for ci, cell := range r.cells {
  201. if cell == nil {
  202. continue
  203. }
  204. colspan := 0
  205. // Default grid cell alignment
  206. alignv := g.alignv
  207. alignh := g.alignh
  208. // If column has alignment, use them
  209. if g.columns[ci].alignv != nil {
  210. alignv = *g.columns[ci].alignv
  211. }
  212. if g.columns[ci].alignh != nil {
  213. alignh = *g.columns[ci].alignh
  214. }
  215. // If cell has layout parameters, use them
  216. if !cell.paramsDef {
  217. alignh = cell.params.AlignH
  218. alignv = cell.params.AlignV
  219. colspan = cell.params.ColSpan
  220. }
  221. // Calculates the available width for the cell considering colspan
  222. var cellWidth float32
  223. for i := ci; i < ci+colspan+1; i++ {
  224. if i >= len(colWidths) {
  225. break
  226. }
  227. cellWidth += colWidths[i]
  228. }
  229. // Determines child panel horizontal position
  230. px := cellx
  231. switch alignh {
  232. case AlignNone:
  233. case AlignLeft:
  234. case AlignRight:
  235. space := cellWidth - cell.panel.Width()
  236. if space > 0 {
  237. px += space
  238. }
  239. case AlignCenter:
  240. space := (cellWidth - cell.panel.Width()) / 2
  241. if space > 0 {
  242. px += space
  243. }
  244. default:
  245. panic("Invalid horizontal alignment")
  246. }
  247. // Determines child panel vertical position
  248. py := celly
  249. switch alignv {
  250. case AlignNone:
  251. case AlignTop:
  252. case AlignBottom:
  253. space := r.height - cell.panel.Height()
  254. if space > 0 {
  255. py += space
  256. }
  257. case AlignCenter:
  258. space := (r.height - cell.panel.Height()) / 2
  259. if space > 0 {
  260. py += space
  261. }
  262. default:
  263. panic("Invalid vertical alignment")
  264. }
  265. // Sets child panel position
  266. cell.panel.SetPosition(px, py)
  267. // Advances to next row cell considering colspan
  268. cellx += cellWidth
  269. }
  270. celly += r.height
  271. }
  272. }