gridlayout.go 7.9 KB

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