diff --git a/README.md b/README.md
index 1160705..0bb0c97 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,31 @@ __Demo:__
-__Grid layout:(incomplete)__
+__Grid layout:__
-
+Expressive syntax, using [12 columns grid system](http://www.w3schools.com/bootstrap/bootstrap_grid_system.asp)
+```go
+ 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)
+```
+[demo code:](https://github.com/gizak/termui/blob/master/example/grid.go)
+
+
## Installation
@@ -58,7 +80,7 @@ Note that components can be overlapped (I'd rather call this a feature...), `Ren
## Themes
-_All_ colors in _all_ components _can_ be changed at _any_ time, while there provides some predefined color schemes:
+_All_ colors in _all_ components can be changed at _any_ time, while there provides some predefined color schemes:
```go
// for now there are only two themes: default and helloworld
@@ -114,8 +136,11 @@ The `helloworld` color scheme drops in some colors!
## TODO
-- [ ] Grid layout
+- [x] Grid layout
- [ ] Event system
+- [ ] Canvas widget
+- [ ] Refine APIs
+- [ ] Focusable widgets
## License
This library is under the [MIT License](http://opensource.org/licenses/MIT)
diff --git a/example/grid.gif b/example/grid.gif
index 52ee501..7490043 100644
Binary files a/example/grid.gif and b/example/grid.gif differ
diff --git a/example/grid.go b/example/grid.go
index a40f767..41f5bc9 100644
--- a/example/grid.go
+++ b/example/grid.go
@@ -1,6 +1,5 @@
package main
-/*
import ui "github.com/gizak/termui"
import tm "github.com/nsf/termbox-go"
import "math"
@@ -50,10 +49,43 @@ func main() {
lc.AxesColor = ui.ColorWhite
lc.LineColor = ui.ColorYellow | ui.AttrBold
- ui.Body.Rows = []ui.Row{
+ gs := make([]*ui.Gauge, 3)
+ for i := range gs {
+ gs[i] = ui.NewGauge()
+ gs[i].Height = 2
+ gs[i].HasBorder = false
+ gs[i].Percent = i * 10
+ gs[i].PaddingBottom = 1
+ gs[i].BarColor = ui.ColorRed
+ }
+
+ ls := ui.NewList()
+ ls.HasBorder = false
+ ls.Items = []string{
+ "[1] Downloading File 1",
+ "", // == \newline
+ "[2] Downloading File 2",
+ "",
+ "[3] Uploading File 3",
+ }
+ ls.Height = 5
+
+ par := ui.NewPar("<> This row has 3 columns\n<- Widgets can be stacked up like left side\n<- Stacked widgets are treated as a single widget")
+ par.Height = 5
+ par.Border.Label = "Demonstration"
+
+ // build layout
+ ui.Body.AddRows(
ui.NewRow(
- ui.NewCol(sp, 6, 0, true),
- ui.NewCol(lc, 6, 0, true))}
+ ui.NewCol(6, 0, sp),
+ ui.NewCol(6, 0, lc)),
+ ui.NewRow(
+ ui.NewCol(3, 0, ls),
+ ui.NewCol(3, 0, gs[0], gs[1], gs[2]),
+ ui.NewCol(6, 0, par)))
+
+ // calculate layout
+ ui.Body.Align()
draw := func(t int) {
sp.Lines[0].Data = spdata[t:]
@@ -75,7 +107,15 @@ func main() {
if e.Type == tm.EventKey && e.Ch == 'q' {
return
}
+ if e.Type == tm.EventResize {
+ ui.Body.Width = ui.TermWidth()
+ ui.Body.Align()
+ }
default:
+ for _, g := range gs {
+ g.Percent = (g.Percent + 3) % 100
+ }
+
draw(i)
i++
if i == 102 {
@@ -85,4 +125,3 @@ func main() {
}
}
}
-*/
diff --git a/grid.go b/grid.go
index 8108906..e4f8e3a 100644
--- a/grid.go
+++ b/grid.go
@@ -1,125 +1,6 @@
package termui
-import tm "github.com/nsf/termbox-go"
-
-/*
-type container struct {
- height int
- width int
- BgColor Attribute
- Rows []Row
-}
-
-type Row []Col
-
-type Col struct {
- Blocks []ColumnBufferer
- Offset int // 0 ~ 11
- Span int // 1 ~ 12
-}
-
-type ColumnBufferer interface {
- Bufferer
- GetHeight() int
- SetWidth(int)
- SetX(int)
- SetY(int)
-}
-
-func NewRow(cols ...Col) Row {
- return cols
-}
-
-func NewCol(span, offset int, blocks ...ColumnBufferer) Col {
- return Col{Blocks: blocks, Span: span, Offset: offset}
-}
-
-// Highest col is the height of a Row
-func (r Row) GetHeight() int {
- h := 0
- for _, v := range r {
- if nh := v.GetHeight(); nh > h {
- h = nh
- }
- }
- return h
-}
-
-// Set width according to its span
-func (r Row) SetWidth(w int) {
- for _, c := range r {
- c.SetWidth(int(float64(w*c.Span) / 12.0))
- }
-}
-
-// Set x y
-func (r Row) SetX(x int) {
- for i := range r {
- r[i].SetX(x)
- }
-}
-
-func (r Row) SetY(y int) {
- for i := range r {
- r[i].SetY(y)
- }
-}
-
-// GetHeight recursively retrieves height of each children, then add them up.
-func (c Col) GetHeight() int {
- h := 0
- for _, v := range c.Blocks {
- h += c.GetHeight()
- }
- return h
-}
-
-func (c Col) GetWidth() int {
- w := 0
- for _, v := range c.Blocks {
- if nw := v.GetWidth(); nw > w {
- w = nw
- }
- }
- return w
-}
-
-func (c Col) SetWidth(w int) {
- for i := range c.Blocks {
- c.SetWidth(w)
- }
-}
-
-func (c container) Buffer() []Point {
- ps := []Point{}
- maxw, _ := tm.Size()
-
- y := 0
- for _, row := range c.Rows {
- x := 0
- maxHeight := 0
-
- for _, col := range row {
- if h := col.GetHeight(); h > maxHeight {
- maxHeight = h
- }
-
- w := int(float64(maxw*(col.Span+col.Offset)) / 12.0)
- if col.GetWidth() > w {
- col.SetWidth(w)
- }
-
- col.SetY(y)
- col.SetX(x)
- ps = append(ps, col.Buffer()...)
- x += w + int(float64(maxw*col.Offset)/12)
- }
- y += maxHeight
- }
- return ps
-}
-*/
-
+// Bufferers that can be manipulated by Grid
type LayoutBufferer interface {
Bufferer
GetHeight() int
@@ -130,8 +11,8 @@ type LayoutBufferer interface {
// build a layout tree
type row struct {
- Cols []*row
- Widget LayoutBufferer // only leaves hold this
+ Cols []*row //children
+ Widget LayoutBufferer // root
X int
Y int
Width int
@@ -140,15 +21,9 @@ type row struct {
Offset int
}
-func newContainer() *row {
- w, _ := tm.Size()
- r := &row{Width: w, Span: 12, X: 0, Y: 0, Cols: []*row{}}
- return r
-}
-
-func (r *row) layout() {
+func (r *row) calcLayout() {
r.assignWidth(r.Width)
- r.solveHeight()
+ r.Height = r.solveHeight()
r.assignX(r.X)
r.assignY(r.Y)
}
@@ -163,14 +38,14 @@ func (r *row) isRenderableLeaf() bool {
func (r *row) assignWidth(w int) {
cw := int(float64(w*r.Span) / 12)
- r.Width = cw
+ r.SetWidth(cw)
for i, _ := range r.Cols {
r.Cols[i].assignWidth(cw)
}
}
-// bottom up
+// bottom up, return r's total height
func (r *row) solveHeight() int {
if r.isRenderableLeaf() {
r.Height = r.Widget.GetHeight()
@@ -196,9 +71,7 @@ func (r *row) solveHeight() int {
}
func (r *row) assignX(x int) {
- if r.isRenderableLeaf() {
- r.Widget.SetX(x)
- }
+ r.SetX(x)
if !r.isLeaf() {
acc := 0
@@ -210,14 +83,12 @@ func (r *row) assignX(x int) {
}
}
}
- r.X = x
}
func (r *row) assignY(y int) {
- r.Y = y
+ r.SetY(y)
- if r.isRenderableLeaf() {
- r.Widget.SetY(y)
+ if r.isLeaf() {
return
}
@@ -231,6 +102,31 @@ func (r *row) assignY(y int) {
}
+func (r row) GetHeight() int {
+ return r.Height
+}
+
+func (r *row) SetX(x int) {
+ r.X = x
+ if r.Widget != nil {
+ r.Widget.SetX(x)
+ }
+}
+
+func (r *row) SetY(y int) {
+ r.Y = y
+ if r.Widget != nil {
+ r.Widget.SetY(y)
+ }
+}
+
+func (r *row) SetWidth(w int) {
+ r.Width = w
+ if r.Widget != nil {
+ r.Widget.SetWidth(w)
+ }
+}
+
// recursively merge all widgets buffer
func (r *row) Buffer() []Point {
merged := []Point{}
@@ -239,6 +135,12 @@ func (r *row) Buffer() []Point {
return r.Widget.Buffer()
}
+ // for those are not leaves but have a renderable widget
+ if r.Widget != nil {
+ merged = append(merged, r.Widget.Buffer()...)
+ }
+
+ // collect buffer from children
if !r.isLeaf() {
for _, c := range r.Cols {
merged = append(merged, c.Buffer()...)
@@ -248,4 +150,73 @@ func (r *row) Buffer() []Point {
return merged
}
-//var Body container
+type Grid struct {
+ Rows []*row
+ Width int
+ X int
+ Y int
+ BgColor Attribute
+}
+
+func NewGrid(rows ...*row) *Grid {
+ return &Grid{Rows: rows}
+}
+
+func (g *Grid) AddRows(rs ...*row) {
+ g.Rows = append(g.Rows, rs...)
+}
+
+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 ...LayoutBufferer) *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
+}
+
+// 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()
+ }
+}
+
+func (g Grid) Buffer() []Point {
+ ps := []Point{}
+ for _, r := range g.Rows {
+ ps = append(ps, r.Buffer()...)
+ }
+ return ps
+}
+
+var Body *Grid
diff --git a/grid_test.go b/grid_test.go
index cd906bb..7712f3f 100644
--- a/grid_test.go
+++ b/grid_test.go
@@ -30,22 +30,32 @@ func TestRowWidth(t *testing.T) {
/
1100:w
*/
- r = &row{
- Span: 12,
- Cols: []*row{
- &row{Widget: p0, Span: 6},
- &row{
- Span: 6,
- Cols: []*row{
- &row{Widget: p1, Span: 6},
- &row{
- Span: 6,
- Cols: []*row{
- &row{
- Span: 12,
- Widget: p2,
- Cols: []*row{
- &row{Span: 12, Widget: p3}}}}}}}}}
+ /*
+ r = &row{
+ Span: 12,
+ Cols: []*row{
+ &row{Widget: p0, Span: 6},
+ &row{
+ Span: 6,
+ Cols: []*row{
+ &row{Widget: p1, Span: 6},
+ &row{
+ Span: 6,
+ Cols: []*row{
+ &row{
+ Span: 12,
+ Widget: p2,
+ Cols: []*row{
+ &row{Span: 12, Widget: p3}}}}}}}}}
+ */
+
+ r = NewRow(
+ NewCol(6, 0, p0),
+ NewCol(6, 0,
+ NewRow(
+ NewCol(6, 0, p1),
+ NewCol(6, 0, p2, p3))))
+
r.assignWidth(100)
if r.Width != 100 ||
(r.Cols[0].Width) != 50 ||
@@ -60,6 +70,7 @@ func TestRowWidth(t *testing.T) {
func TestRowHeight(t *testing.T) {
spew.Dump()
+
if (r.solveHeight()) != 2 ||
(r.Cols[1].Cols[1].Height) != 2 ||
(r.Cols[1].Cols[1].Cols[0].Height) != 2 ||
diff --git a/render.go b/render.go
index c701f0b..c0f3100 100644
--- a/render.go
+++ b/render.go
@@ -8,10 +8,14 @@ type Bufferer interface {
}
func Init() error {
- // Body = container{
- // BgColor: theme.BodyBg,
- // Rows: []Row{},
- // }
+ Body = NewGrid()
+ Body.X = 0
+ Body.Y = 0
+ Body.BgColor = theme.BodyBg
+ defer (func() {
+ w, _ := tm.Size()
+ Body.Width = w
+ })()
return tm.Init()
}
@@ -19,6 +23,16 @@ func Close() {
tm.Close()
}
+func TermWidth() int {
+ w, _ := tm.Size()
+ return w
+}
+
+func TermHeight() int {
+ _, h := tm.Size()
+ return h
+}
+
// render all from left to right, right could overlap on left ones
func Render(rs ...Bufferer) {
tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg))