hboxlayout.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. /*
  6. HBoxLayout implements a panel layout which arranges the panel children horizontally.
  7. The children can be separated by a space in pixels set by SetSpacing().
  8. The whole group of children can be aligned horizontally by SetAlignH() which can
  9. accept the following types of alignment:
  10. AlignLeft: Try to align the group of children to the left if the panel width is
  11. greater the the sum of the children widths + spacing.
  12. AlignRight: Try to align the group of children to the right if the panel width is
  13. greater the the sum of the children widths + spacing.
  14. AlignCenter: Try to align the group of children in the center if the panel width is
  15. greater the the sum of the children widths + spacing.
  16. AlignWidth - Try to align the individual children with the same same space between each other.
  17. Each individual child can be aligned vertically by SetLayoutParameters()
  18. If the layout method SetMinHeight(true) is called, the panel minimum height will be the
  19. height of the child with the largest height.
  20. */
  21. type HBoxLayout struct {
  22. pan IPanel
  23. spacing float32
  24. alignH Align
  25. minHeight bool
  26. minWidth bool
  27. }
  28. // HBoxLayoutParameters specify the vertical alignment of each individual child.
  29. type HBoxLayoutParams struct {
  30. Expand float32 // item expand horizontally factor (0 - no expand)
  31. AlignV Align // item vertical alignment
  32. }
  33. // NewHBoxLayout creates and returns a pointer to a new horizontal box layout
  34. func NewHBoxLayout() *HBoxLayout {
  35. bl := new(HBoxLayout)
  36. bl.spacing = 0
  37. bl.alignH = AlignLeft
  38. return bl
  39. }
  40. // SetSpacing sets the horizontal spacing between the items in pixels
  41. // and updates the layout if possible
  42. func (bl *HBoxLayout) SetSpacing(spacing float32) {
  43. bl.spacing = spacing
  44. bl.Recalc(bl.pan)
  45. }
  46. // SetAlignH sets the horizontal alignment of the whole group of items
  47. // inside the parent panel and updates the layout if possible.
  48. // This only has any effect if there are no expanded items.
  49. func (bl *HBoxLayout) SetAlignH(align Align) {
  50. bl.alignH = align
  51. bl.Recalc(bl.pan)
  52. }
  53. // SetMinHeight sets if the panel minimum height should be the height of
  54. // the largest of its children's height.
  55. func (bl *HBoxLayout) SetMinHeight(state bool) {
  56. bl.minHeight = state
  57. bl.Recalc(bl.pan)
  58. }
  59. // SetMinWidth sets if the panel minimum width should be sum of its
  60. // children's width plus the spacing
  61. func (bl *HBoxLayout) SetMinWidth(state bool) {
  62. bl.minWidth = state
  63. bl.Recalc(bl.pan)
  64. }
  65. // Recalc recalculates and sets the position and sizes of all children
  66. func (bl *HBoxLayout) Recalc(ipan IPanel) {
  67. // Saves the received panel
  68. bl.pan = ipan
  69. if bl.pan == nil {
  70. return
  71. }
  72. parent := ipan.GetPanel()
  73. if len(parent.Children()) == 0 {
  74. return
  75. }
  76. // If minHeight set, sets the panel content height to the height of the heighest child.
  77. newHeight := float32(0)
  78. if bl.minHeight {
  79. for _, ichild := range parent.Children() {
  80. child := ichild.(IPanel).GetPanel()
  81. if child.Height() > newHeight {
  82. newHeight = child.Height()
  83. }
  84. }
  85. if parent.ContentHeight() < newHeight {
  86. parent.SetContentHeight(newHeight)
  87. }
  88. }
  89. // If minWidth set, sets the panel content width to the sum of children widths plus spacing
  90. newWidth := float32(0)
  91. if bl.minWidth {
  92. for _, ichild := range parent.Children() {
  93. child := ichild.(IPanel).GetPanel()
  94. newWidth += child.Width()
  95. }
  96. // Adds spacing
  97. newWidth += bl.spacing * float32(len(parent.Children())-1)
  98. if parent.ContentWidth() < newWidth {
  99. parent.SetContentWidth(newWidth)
  100. }
  101. }
  102. // Calculates the total width, expanded width, fixed width and
  103. // the sum of the expand factor for all items.
  104. var twidth float32 = 0
  105. var ewidth float32 = 0
  106. var fwidth float32 = 0
  107. var texpand float32 = 0
  108. ecount := 0
  109. paramsDef := HBoxLayoutParams{Expand: 0, AlignV: AlignTop}
  110. for pos, obj := range parent.Children() {
  111. pan := obj.(IPanel).GetPanel()
  112. // Get item layout parameters or use default
  113. params := paramsDef
  114. if pan.layoutParams != nil {
  115. params = *pan.layoutParams.(*HBoxLayoutParams)
  116. }
  117. // Calculate total width
  118. twidth += pan.Width()
  119. if pos > 0 {
  120. twidth += bl.spacing
  121. }
  122. // Calculate width of expanded items
  123. if params.Expand > 0 {
  124. texpand += params.Expand
  125. ewidth += pan.Width()
  126. if pos > 0 {
  127. ewidth += bl.spacing
  128. }
  129. ecount++
  130. // Calculate width of fixed items
  131. } else {
  132. fwidth += pan.Width()
  133. if pos > 0 {
  134. fwidth += bl.spacing
  135. }
  136. }
  137. }
  138. // If there is at least on expanded item, all free space will be occupied
  139. spaceMiddle := bl.spacing
  140. var posX float32 = 0
  141. if texpand > 0 {
  142. // If there is free space, distribute space between expanded items
  143. totalSpace := parent.ContentWidth() - twidth
  144. if totalSpace > 0 {
  145. for _, obj := range parent.Children() {
  146. pan := obj.(IPanel).GetPanel()
  147. // Get item layout parameters or use default
  148. params := paramsDef
  149. if pan.layoutParams != nil {
  150. params = *pan.layoutParams.(*HBoxLayoutParams)
  151. }
  152. if params.Expand > 0 {
  153. iwidth := totalSpace * params.Expand / texpand
  154. pan.SetWidth(pan.Width() + iwidth)
  155. }
  156. }
  157. // No free space: distribute expanded items widths
  158. } else {
  159. for _, obj := range parent.Children() {
  160. pan := obj.(IPanel).GetPanel()
  161. // Get item layout parameters or use default
  162. params := paramsDef
  163. if pan.layoutParams != nil {
  164. params = *pan.layoutParams.(*HBoxLayoutParams)
  165. }
  166. if params.Expand > 0 {
  167. spacing := bl.spacing * (float32(ecount) - 1)
  168. iwidth := (parent.ContentWidth() - spacing - fwidth - bl.spacing) * params.Expand / texpand
  169. pan.SetWidth(iwidth)
  170. }
  171. }
  172. }
  173. // No expanded items: checks block horizontal alignment
  174. } else {
  175. // Calculates initial x position which depends
  176. // on the current horizontal alignment.
  177. switch bl.alignH {
  178. case AlignLeft:
  179. posX = 0
  180. case AlignCenter:
  181. posX = (parent.ContentWidth() - twidth) / 2
  182. case AlignRight:
  183. posX = parent.ContentWidth() - twidth
  184. case AlignWidth:
  185. space := parent.ContentWidth() - twidth + bl.spacing*float32(len(parent.Children())-1)
  186. if space < 0 {
  187. space = bl.spacing * float32(len(parent.Children())-1)
  188. }
  189. spaceMiddle = space / float32(len(parent.Children())+1)
  190. posX = spaceMiddle
  191. default:
  192. log.Fatal("HBoxLayout: invalid global horizontal alignment")
  193. }
  194. }
  195. // Calculates the Y position of each item considering its vertical alignment
  196. var posY float32
  197. height := parent.ContentHeight()
  198. for pos, obj := range parent.Children() {
  199. pan := obj.(IPanel).GetPanel()
  200. // Get item layout parameters or use default
  201. params := paramsDef
  202. if pan.layoutParams != nil {
  203. params = *pan.layoutParams.(*HBoxLayoutParams)
  204. }
  205. cheight := pan.Height()
  206. switch params.AlignV {
  207. case AlignTop:
  208. posY = 0
  209. case AlignCenter:
  210. posY = (height - cheight) / 2
  211. case AlignBottom:
  212. posY = height - cheight
  213. case AlignHeight:
  214. posY = 0
  215. pan.SetHeight(height)
  216. default:
  217. log.Fatal("HBoxLayout: invalid item vertical alignment")
  218. }
  219. // Sets the child position
  220. pan.SetPosition(posX, posY)
  221. // Calculates next position
  222. posX += pan.Width()
  223. if pos < len(parent.Children())-1 {
  224. posX += spaceMiddle
  225. }
  226. }
  227. }