Finish Grid system
This commit is contained in:
parent
4a60506c99
commit
89f47edd59
33
README.md
33
README.md
@ -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)
|
||||
|
BIN
example/grid.gif
BIN
example/grid.gif
Binary file not shown.
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 782 KiB |
@ -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
251
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
|
||||
|
43
grid_test.go
43
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 ||
|
||||
|
22
render.go
22
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))
|
||||
|
Loading…
Reference in New Issue
Block a user