Finish Grid system

This commit is contained in:
gizak 2015-03-20 08:24:48 -04:00
parent 4a60506c99
commit 89f47edd59
6 changed files with 229 additions and 169 deletions

View File

@ -7,9 +7,31 @@ __Demo:__
<img src="./example/screencast.gif" alt="demo" width="600">
__Grid layout:(incomplete)__
__Grid layout:__
<img src="./example/grid.gif" alt="grid" width="400">
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)
<img src="./example/grid.gif" alt="grid" width="500">
## 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 782 KiB

View File

@ -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() {
}
}
}
*/

251
grid.go
View File

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

View File

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

View File

@ -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))