123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- // Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
- // Use of this source code is governed by a MIT license that can
- // be found in the LICENSE file.
- package termui
- // GridBufferer introduces a Bufferer that can be manipulated by Grid.
- type GridBufferer interface {
- Bufferer
- GetHeight() int
- SetWidth(int)
- SetX(int)
- SetY(int)
- }
- // Row builds a layout tree
- type Row struct {
- Cols []*Row //children
- Widget GridBufferer // root
- X int
- Y int
- Width int
- Height int
- Span int
- Offset int
- }
- // calculate and set the underlying layout tree's x, y, height and width.
- func (r *Row) calcLayout() {
- r.assignWidth(r.Width)
- r.Height = r.solveHeight()
- r.assignX(r.X)
- r.assignY(r.Y)
- }
- // tell if the node is leaf in the tree.
- func (r *Row) isLeaf() bool {
- return r.Cols == nil || len(r.Cols) == 0
- }
- func (r *Row) isRenderableLeaf() bool {
- return r.isLeaf() && r.Widget != nil
- }
- // assign widgets' (and their parent rows') width recursively.
- func (r *Row) assignWidth(w int) {
- r.SetWidth(w)
- accW := 0 // acc span and offset
- calcW := make([]int, len(r.Cols)) // calculated width
- calcOftX := make([]int, len(r.Cols)) // computated start position of x
- for i, c := range r.Cols {
- accW += c.Span + c.Offset
- cw := int(float64(c.Span*r.Width) / 12.0)
- if i >= 1 {
- calcOftX[i] = calcOftX[i-1] +
- calcW[i-1] +
- int(float64(r.Cols[i-1].Offset*r.Width)/12.0)
- }
- // use up the space if it is the last col
- if i == len(r.Cols)-1 && accW == 12 {
- cw = r.Width - calcOftX[i]
- }
- calcW[i] = cw
- r.Cols[i].assignWidth(cw)
- }
- }
- // bottom up calc and set rows' (and their widgets') height,
- // return r's total height.
- func (r *Row) solveHeight() int {
- if r.isRenderableLeaf() {
- r.Height = r.Widget.GetHeight()
- return r.Widget.GetHeight()
- }
- maxh := 0
- if !r.isLeaf() {
- for _, c := range r.Cols {
- nh := c.solveHeight()
- // when embed rows in Cols, row widgets stack up
- if r.Widget != nil {
- nh += r.Widget.GetHeight()
- }
- if nh > maxh {
- maxh = nh
- }
- }
- }
- r.Height = maxh
- return maxh
- }
- // recursively assign x position for r tree.
- func (r *Row) assignX(x int) {
- r.SetX(x)
- if !r.isLeaf() {
- acc := 0
- for i, c := range r.Cols {
- if c.Offset != 0 {
- acc += int(float64(c.Offset*r.Width) / 12.0)
- }
- r.Cols[i].assignX(x + acc)
- acc += c.Width
- }
- }
- }
- // recursively assign y position to r.
- func (r *Row) assignY(y int) {
- r.SetY(y)
- if r.isLeaf() {
- return
- }
- for i := range r.Cols {
- acc := 0
- if r.Widget != nil {
- acc = r.Widget.GetHeight()
- }
- r.Cols[i].assignY(y + acc)
- }
- }
- // GetHeight implements GridBufferer interface.
- func (r Row) GetHeight() int {
- return r.Height
- }
- // SetX implements GridBufferer interface.
- func (r *Row) SetX(x int) {
- r.X = x
- if r.Widget != nil {
- r.Widget.SetX(x)
- }
- }
- // SetY implements GridBufferer interface.
- func (r *Row) SetY(y int) {
- r.Y = y
- if r.Widget != nil {
- r.Widget.SetY(y)
- }
- }
- // SetWidth implements GridBufferer interface.
- func (r *Row) SetWidth(w int) {
- r.Width = w
- if r.Widget != nil {
- r.Widget.SetWidth(w)
- }
- }
- // Buffer implements Bufferer interface,
- // recursively merge all widgets buffer
- func (r *Row) Buffer() Buffer {
- merged := NewBuffer()
- if r.isRenderableLeaf() {
- return r.Widget.Buffer()
- }
- // for those are not leaves but have a renderable widget
- if r.Widget != nil {
- merged.Merge(r.Widget.Buffer())
- }
- // collect buffer from children
- if !r.isLeaf() {
- for _, c := range r.Cols {
- merged.Merge(c.Buffer())
- }
- }
- return merged
- }
- // Grid implements 12 columns system.
- // A simple example:
- /*
- import ui "github.com/gizak/termui"
- // init and create widgets...
- // build
- ui.Body.AddRows(
- ui.NewRow(
- ui.NewCol(6, 0, widget0),
- ui.NewCol(6, 0, widget1)),
- ui.NewRow(
- ui.NewCol(3, 0, widget2),
- ui.NewCol(3, 0, widget30, widget31, widget32),
- ui.NewCol(6, 0, widget4)))
- // calculate layout
- ui.Body.Align()
- ui.Render(ui.Body)
- */
- type Grid struct {
- Rows []*Row
- Width int
- X int
- Y int
- BgColor Attribute
- }
- // NewGrid returns *Grid with given rows.
- func NewGrid(rows ...*Row) *Grid {
- return &Grid{Rows: rows}
- }
- // AddRows appends given rows to Grid.
- func (g *Grid) AddRows(rs ...*Row) {
- g.Rows = append(g.Rows, rs...)
- }
- // NewRow creates a new row out of given columns.
- func NewRow(cols ...*Row) *Row {
- rs := &Row{Span: 12, Cols: cols}
- return rs
- }
- // NewCol accepts: widgets are LayoutBufferer or widgets is A NewRow.
- // Note that if multiple widgets are provided, they will stack up in the col.
- func NewCol(span, offset int, widgets ...GridBufferer) *Row {
- r := &Row{Span: span, Offset: offset}
- if widgets != nil && len(widgets) == 1 {
- wgt := widgets[0]
- nw, isRow := wgt.(*Row)
- if isRow {
- r.Cols = nw.Cols
- } else {
- r.Widget = wgt
- }
- return r
- }
- r.Cols = []*Row{}
- ir := r
- for _, w := range widgets {
- nr := &Row{Span: 12, Widget: w}
- ir.Cols = []*Row{nr}
- ir = nr
- }
- return r
- }
- // Align calculate each rows' layout.
- func (g *Grid) Align() {
- h := 0
- for _, r := range g.Rows {
- r.SetWidth(g.Width)
- r.SetX(g.X)
- r.SetY(g.Y + h)
- r.calcLayout()
- h += r.GetHeight()
- }
- }
- // Buffer implments Bufferer interface.
- func (g Grid) Buffer() Buffer {
- buf := NewBuffer()
- for _, r := range g.Rows {
- buf.Merge(r.Buffer())
- }
- return buf
- }
- var Body *Grid
|