termui/grid.go

263 lines
4.9 KiB
Go
Raw Normal View History

2015-03-20 14:21:50 -06:00
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
2015-03-15 13:56:38 -06:00
package termui
2015-03-24 15:16:43 -06:00
// GridBufferer introduces a Bufferer that can be manipulated by Grid.
type GridBufferer interface {
2015-03-19 10:00:38 -06:00
Bufferer
GetHeight() int
SetWidth(int)
SetX(int)
SetY(int)
}
2015-03-24 15:16:43 -06:00
// row builds a layout tree
type Row struct {
Cols []*Row //children
2015-03-24 15:16:43 -06:00
Widget GridBufferer // root
2015-03-19 10:00:38 -06:00
X int
Y int
Width int
Height int
Span int
Offset int
}
2015-03-24 15:16:43 -06:00
// calculate and set the underlying layout tree's x, y, height and width.
func (r *Row) calcLayout() {
2015-03-19 10:00:38 -06:00
r.assignWidth(r.Width)
2015-03-20 06:24:48 -06:00
r.Height = r.solveHeight()
2015-03-19 10:00:38 -06:00
r.assignX(r.X)
r.assignY(r.Y)
}
2015-03-24 15:16:43 -06:00
// tell if the node is leaf in the tree.
func (r *Row) isLeaf() bool {
2015-03-19 10:00:38 -06:00
return r.Cols == nil || len(r.Cols) == 0
}
func (r *Row) isRenderableLeaf() bool {
2015-03-19 10:00:38 -06:00
return r.isLeaf() && r.Widget != nil
}
2015-03-24 15:16:43 -06:00
// assign widgets' (and their parent rows') width recursively.
func (r *Row) assignWidth(w int) {
2015-03-19 10:00:38 -06:00
cw := int(float64(w*r.Span) / 12)
2015-03-20 06:24:48 -06:00
r.SetWidth(cw)
2015-03-19 10:00:38 -06:00
2015-03-24 15:16:43 -06:00
for i := range r.Cols {
2015-03-19 10:00:38 -06:00
r.Cols[i].assignWidth(cw)
}
}
2015-03-24 15:16:43 -06:00
// bottom up calc and set rows' (and their widgets') height,
// return r's total height.
func (r *Row) solveHeight() int {
2015-03-19 10:00:38 -06:00
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
}
2015-03-24 15:16:43 -06:00
// recursively assign x position for r tree.
func (r *Row) assignX(x int) {
2015-03-20 06:24:48 -06:00
r.SetX(x)
2015-03-19 10:00:38 -06:00
if !r.isLeaf() {
acc := 0
for i, c := range r.Cols {
r.Cols[i].assignX(x + acc)
acc += c.Width
if c.Offset != 0 {
acc += int(float64(c.Offset*c.Width) / float64(12*c.Span))
}
}
}
}
2015-03-24 15:16:43 -06:00
// recursively assign y position to r.
func (r *Row) assignY(y int) {
2015-03-20 06:24:48 -06:00
r.SetY(y)
2015-03-19 10:00:38 -06:00
2015-03-20 06:24:48 -06:00
if r.isLeaf() {
2015-03-19 10:00:38 -06:00
return
}
for i := range r.Cols {
acc := 0
if r.Widget != nil {
acc = r.Widget.GetHeight()
}
r.Cols[i].assignY(y + acc)
}
}
2015-03-24 15:16:43 -06:00
// GetHeight implements GridBufferer interface.
func (r Row) GetHeight() int {
2015-03-20 06:24:48 -06:00
return r.Height
}
2015-03-24 15:16:43 -06:00
// SetX implements GridBufferer interface.
func (r *Row) SetX(x int) {
2015-03-20 06:24:48 -06:00
r.X = x
if r.Widget != nil {
r.Widget.SetX(x)
}
}
2015-03-24 15:16:43 -06:00
// SetY implements GridBufferer interface.
func (r *Row) SetY(y int) {
2015-03-20 06:24:48 -06:00
r.Y = y
if r.Widget != nil {
r.Widget.SetY(y)
}
}
2015-03-24 15:16:43 -06:00
// SetWidth implements GridBufferer interface.
func (r *Row) SetWidth(w int) {
2015-03-20 06:24:48 -06:00
r.Width = w
if r.Widget != nil {
r.Widget.SetWidth(w)
}
}
2015-03-24 15:16:43 -06:00
// Buffer implements Bufferer interface,
2015-03-19 10:00:38 -06:00
// recursively merge all widgets buffer
func (r *Row) Buffer() []Point {
2015-03-19 10:00:38 -06:00
merged := []Point{}
if r.isRenderableLeaf() {
return r.Widget.Buffer()
}
2015-03-20 06:24:48 -06:00
// for those are not leaves but have a renderable widget
if r.Widget != nil {
merged = append(merged, r.Widget.Buffer()...)
}
// collect buffer from children
2015-03-19 10:00:38 -06:00
if !r.isLeaf() {
for _, c := range r.Cols {
merged = append(merged, c.Buffer()...)
}
}
return merged
}
2015-03-15 13:56:38 -06:00
2015-03-24 15:16:43 -06:00
// 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)
*/
2015-03-20 06:24:48 -06:00
type Grid struct {
Rows []*Row
2015-03-20 06:24:48 -06:00
Width int
X int
Y int
BgColor Attribute
}
2015-03-24 15:16:43 -06:00
// NewGrid returns *Grid with given rows.
func NewGrid(rows ...*Row) *Grid {
2015-03-20 06:24:48 -06:00
return &Grid{Rows: rows}
}
2015-03-24 15:16:43 -06:00
// AddRows appends given rows to Grid.
func (g *Grid) AddRows(rs ...*Row) {
2015-03-20 06:24:48 -06:00
g.Rows = append(g.Rows, rs...)
}
2015-03-24 15:16:43 -06:00
// NewRow creates a new row out of given columns.
func NewRow(cols ...*Row) *Row {
rs := &Row{Span: 12, Cols: cols}
2015-03-20 06:24:48 -06:00
return rs
}
2015-03-24 15:16:43 -06:00
// 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}
2015-03-20 06:24:48 -06:00
if widgets != nil && len(widgets) == 1 {
wgt := widgets[0]
nw, isRow := wgt.(*Row)
2015-03-20 06:24:48 -06:00
if isRow {
r.Cols = nw.Cols
} else {
r.Widget = wgt
}
return r
}
r.Cols = []*Row{}
2015-03-20 06:24:48 -06:00
ir := r
for _, w := range widgets {
nr := &Row{Span: 12, Widget: w}
ir.Cols = []*Row{nr}
2015-03-20 06:24:48 -06:00
ir = nr
}
return r
}
2015-03-24 15:16:43 -06:00
// Align calculate each rows' layout.
2015-03-20 06:24:48 -06:00
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()
}
}
2015-03-24 15:16:43 -06:00
// Buffer implments Bufferer interface.
2015-03-20 06:24:48 -06:00
func (g Grid) Buffer() []Point {
ps := []Point{}
for _, r := range g.Rows {
ps = append(ps, r.Buffer()...)
}
return ps
}
2015-03-24 15:16:43 -06:00
// Body corresponds to the entire terminal display region.
2015-03-20 06:24:48 -06:00
var Body *Grid