gridlayout.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. spanWidths := make([]float32, len(g.columns))
  148. for i := 0; i < len(rows); i++ {
  149. r := &rows[i]
  150. r.height = 0
  151. for ci, cell := range r.cells {
  152. if cell == nil {
  153. continue
  154. }
  155. if cell.panel.Height() > r.height {
  156. r.height = cell.panel.Height()
  157. }
  158. // If this cell span columns compare with other span cell widths
  159. if cell.params.ColSpan > 0 {
  160. if cell.panel.Width() > spanWidths[ci] {
  161. spanWidths[ci] = cell.panel.Width()
  162. }
  163. } else {
  164. if cell.panel.Width() > colWidths[ci] {
  165. colWidths[ci] = cell.panel.Width()
  166. }
  167. }
  168. }
  169. }
  170. // The final width for each column is the maximum no span column width
  171. // but if it is zero, is the maximum span column width
  172. for i := 0; i < len(colWidths); i++ {
  173. if colWidths[i] == 0 {
  174. colWidths[i] = spanWidths[i]
  175. }
  176. }
  177. // If expand horizontally set, distribute available space between all columns
  178. if g.expandh {
  179. var twidth float32
  180. for i := 0; i < len(colWidths); i++ {
  181. twidth += colWidths[i]
  182. }
  183. space := pan.ContentWidth() - twidth
  184. if space > 0 {
  185. colspace := space / float32(len(colWidths))
  186. for i := 0; i < len(colWidths); i++ {
  187. colWidths[i] += colspace
  188. }
  189. }
  190. }
  191. // If expand vertically set, distribute available space between all rows
  192. if g.expandv {
  193. // Calculates the sum of all row heights
  194. var theight float32
  195. for _, r := range rows {
  196. theight += r.height
  197. }
  198. // If space available distribute between all rows
  199. space := pan.ContentHeight() - theight
  200. if space > 0 {
  201. rowspace := space / float32(len(rows))
  202. for i := 0; i < len(rows); i++ {
  203. rows[i].height += rowspace
  204. }
  205. }
  206. }
  207. // Position each child panel in the parent panel
  208. var celly float32
  209. for _, r := range rows {
  210. var cellx float32
  211. for ci, cell := range r.cells {
  212. if cell == nil {
  213. continue
  214. }
  215. colspan := 0
  216. // Default grid cell alignment
  217. alignv := g.alignv
  218. alignh := g.alignh
  219. // If column has alignment, use them
  220. if g.columns[ci].alignv != nil {
  221. alignv = *g.columns[ci].alignv
  222. }
  223. if g.columns[ci].alignh != nil {
  224. alignh = *g.columns[ci].alignh
  225. }
  226. // If cell has layout parameters, use them
  227. if !cell.paramsDef {
  228. alignh = cell.params.AlignH
  229. alignv = cell.params.AlignV
  230. colspan = cell.params.ColSpan
  231. }
  232. // Calculates the available width for the cell considering colspan
  233. var cellWidth float32
  234. for i := ci; i < ci+colspan+1; i++ {
  235. if i >= len(colWidths) {
  236. break
  237. }
  238. cellWidth += colWidths[i]
  239. }
  240. // Determines child panel horizontal position
  241. px := cellx
  242. switch alignh {
  243. case AlignNone:
  244. case AlignLeft:
  245. case AlignRight:
  246. space := cellWidth - cell.panel.Width()
  247. if space > 0 {
  248. px += space
  249. }
  250. case AlignCenter:
  251. space := (cellWidth - cell.panel.Width()) / 2
  252. if space > 0 {
  253. px += space
  254. }
  255. default:
  256. panic("Invalid horizontal alignment")
  257. }
  258. // Determines child panel vertical position
  259. py := celly
  260. switch alignv {
  261. case AlignNone:
  262. case AlignTop:
  263. case AlignBottom:
  264. space := r.height - cell.panel.Height()
  265. if space > 0 {
  266. py += space
  267. }
  268. case AlignCenter:
  269. space := (r.height - cell.panel.Height()) / 2
  270. if space > 0 {
  271. py += space
  272. }
  273. default:
  274. panic("Invalid vertical alignment")
  275. }
  276. // Sets child panel position
  277. cell.panel.SetPosition(px, py)
  278. // Advances to next row cell considering colspan
  279. cellx += cellWidth
  280. }
  281. celly += r.height
  282. }
  283. }