grid.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
  2. // Use of this source code is governed by a MIT license that can
  3. // be found in the LICENSE file.
  4. package termui
  5. // GridBufferer introduces a Bufferer that can be manipulated by Grid.
  6. type GridBufferer interface {
  7. Bufferer
  8. GetHeight() int
  9. SetWidth(int)
  10. SetX(int)
  11. SetY(int)
  12. }
  13. // Row builds a layout tree
  14. type Row struct {
  15. Cols []*Row //children
  16. Widget GridBufferer // root
  17. X int
  18. Y int
  19. Width int
  20. Height int
  21. Span int
  22. Offset int
  23. }
  24. // calculate and set the underlying layout tree's x, y, height and width.
  25. func (r *Row) calcLayout() {
  26. r.assignWidth(r.Width)
  27. r.Height = r.solveHeight()
  28. r.assignX(r.X)
  29. r.assignY(r.Y)
  30. }
  31. // tell if the node is leaf in the tree.
  32. func (r *Row) isLeaf() bool {
  33. return r.Cols == nil || len(r.Cols) == 0
  34. }
  35. func (r *Row) isRenderableLeaf() bool {
  36. return r.isLeaf() && r.Widget != nil
  37. }
  38. // assign widgets' (and their parent rows') width recursively.
  39. func (r *Row) assignWidth(w int) {
  40. r.SetWidth(w)
  41. accW := 0 // acc span and offset
  42. calcW := make([]int, len(r.Cols)) // calculated width
  43. calcOftX := make([]int, len(r.Cols)) // computed start position of x
  44. for i, c := range r.Cols {
  45. accW += c.Span + c.Offset
  46. cw := int(float64(c.Span*r.Width) / 12.0)
  47. if i >= 1 {
  48. calcOftX[i] = calcOftX[i-1] +
  49. calcW[i-1] +
  50. int(float64(r.Cols[i-1].Offset*r.Width)/12.0)
  51. }
  52. // use up the space if it is the last col
  53. if i == len(r.Cols)-1 && accW == 12 {
  54. cw = r.Width - calcOftX[i]
  55. }
  56. calcW[i] = cw
  57. r.Cols[i].assignWidth(cw)
  58. }
  59. }
  60. // bottom up calc and set rows' (and their widgets') height,
  61. // return r's total height.
  62. func (r *Row) solveHeight() int {
  63. if r.isRenderableLeaf() {
  64. r.Height = r.Widget.GetHeight()
  65. return r.Widget.GetHeight()
  66. }
  67. maxh := 0
  68. if !r.isLeaf() {
  69. for _, c := range r.Cols {
  70. nh := c.solveHeight()
  71. // when embed rows in Cols, row widgets stack up
  72. if r.Widget != nil {
  73. nh += r.Widget.GetHeight()
  74. }
  75. if nh > maxh {
  76. maxh = nh
  77. }
  78. }
  79. }
  80. r.Height = maxh
  81. return maxh
  82. }
  83. // recursively assign x position for r tree.
  84. func (r *Row) assignX(x int) {
  85. r.SetX(x)
  86. if !r.isLeaf() {
  87. acc := 0
  88. for i, c := range r.Cols {
  89. if c.Offset != 0 {
  90. acc += int(float64(c.Offset*r.Width) / 12.0)
  91. }
  92. r.Cols[i].assignX(x + acc)
  93. acc += c.Width
  94. }
  95. }
  96. }
  97. // recursively assign y position to r.
  98. func (r *Row) assignY(y int) {
  99. r.SetY(y)
  100. if r.isLeaf() {
  101. return
  102. }
  103. for i := range r.Cols {
  104. acc := 0
  105. if r.Widget != nil {
  106. acc = r.Widget.GetHeight()
  107. }
  108. r.Cols[i].assignY(y + acc)
  109. }
  110. }
  111. // GetHeight implements GridBufferer interface.
  112. func (r Row) GetHeight() int {
  113. return r.Height
  114. }
  115. // SetX implements GridBufferer interface.
  116. func (r *Row) SetX(x int) {
  117. r.X = x
  118. if r.Widget != nil {
  119. r.Widget.SetX(x)
  120. }
  121. }
  122. // SetY implements GridBufferer interface.
  123. func (r *Row) SetY(y int) {
  124. r.Y = y
  125. if r.Widget != nil {
  126. r.Widget.SetY(y)
  127. }
  128. }
  129. // SetWidth implements GridBufferer interface.
  130. func (r *Row) SetWidth(w int) {
  131. r.Width = w
  132. if r.Widget != nil {
  133. r.Widget.SetWidth(w)
  134. }
  135. }
  136. // Buffer implements Bufferer interface,
  137. // recursively merge all widgets buffer
  138. func (r *Row) Buffer() Buffer {
  139. merged := NewBuffer()
  140. if r.isRenderableLeaf() {
  141. return r.Widget.Buffer()
  142. }
  143. // for those are not leaves but have a renderable widget
  144. if r.Widget != nil {
  145. merged.Merge(r.Widget.Buffer())
  146. }
  147. // collect buffer from children
  148. if !r.isLeaf() {
  149. for _, c := range r.Cols {
  150. merged.Merge(c.Buffer())
  151. }
  152. }
  153. return merged
  154. }
  155. // Grid implements 12 columns system.
  156. // A simple example:
  157. /*
  158. import ui "notabug.org/themusicgod1/termui"
  159. // init and create widgets...
  160. // build
  161. ui.Body.AddRows(
  162. ui.NewRow(
  163. ui.NewCol(6, 0, widget0),
  164. ui.NewCol(6, 0, widget1)),
  165. ui.NewRow(
  166. ui.NewCol(3, 0, widget2),
  167. ui.NewCol(3, 0, widget30, widget31, widget32),
  168. ui.NewCol(6, 0, widget4)))
  169. // calculate layout
  170. ui.Body.Align()
  171. ui.Render(ui.Body)
  172. */
  173. type Grid struct {
  174. Rows []*Row
  175. Width int
  176. X int
  177. Y int
  178. BgColor Attribute
  179. }
  180. // NewGrid returns *Grid with given rows.
  181. func NewGrid(rows ...*Row) *Grid {
  182. return &Grid{Rows: rows}
  183. }
  184. // AddRows appends given rows to Grid.
  185. func (g *Grid) AddRows(rs ...*Row) {
  186. g.Rows = append(g.Rows, rs...)
  187. }
  188. // NewRow creates a new row out of given columns.
  189. func NewRow(cols ...*Row) *Row {
  190. rs := &Row{Span: 12, Cols: cols}
  191. return rs
  192. }
  193. // NewCol accepts: widgets are LayoutBufferer or widgets is A NewRow.
  194. // Note that if multiple widgets are provided, they will stack up in the col.
  195. func NewCol(span, offset int, widgets ...GridBufferer) *Row {
  196. r := &Row{Span: span, Offset: offset}
  197. if widgets != nil && len(widgets) == 1 {
  198. wgt := widgets[0]
  199. nw, isRow := wgt.(*Row)
  200. if isRow {
  201. r.Cols = nw.Cols
  202. } else {
  203. r.Widget = wgt
  204. }
  205. return r
  206. }
  207. r.Cols = []*Row{}
  208. ir := r
  209. for _, w := range widgets {
  210. nr := &Row{Span: 12, Widget: w}
  211. ir.Cols = []*Row{nr}
  212. ir = nr
  213. }
  214. return r
  215. }
  216. // Align calculate each rows' layout.
  217. func (g *Grid) Align() {
  218. h := 0
  219. for _, r := range g.Rows {
  220. r.SetWidth(g.Width)
  221. r.SetX(g.X)
  222. r.SetY(g.Y + h)
  223. r.calcLayout()
  224. h += r.GetHeight()
  225. }
  226. }
  227. // Buffer implements Bufferer interface.
  228. func (g Grid) Buffer() Buffer {
  229. buf := NewBuffer()
  230. for _, r := range g.Rows {
  231. buf.Merge(r.Buffer())
  232. }
  233. return buf
  234. }
  235. var Body *Grid