aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/gizak/termui/grid.go
blob: a9502322535a0bc9b69d38c67bc3b706295a440b (plain) (tree)
1
                                                                       
































































































































































                                                                              

                               






                                                                
                                               




                                          
                                                


























































































                                                                             


                               
                                  
                                     
         
                  

 
              
// 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