280 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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
 |