WIP use Buffer instead of []Point in Bufferer

Merge https://github.com/Matt3o12/termui.git colored-list
Merge https://github.com/funkygao/termui.git master
Add subdir widget
Use image Rectangle represent buffer area
This commit is contained in:
gizak 2015-04-21 09:56:10 -04:00
parent 0a29dad7e7
commit 7aed750f64
20 changed files with 428 additions and 298 deletions

124
block.go
View File

@ -4,22 +4,22 @@
package termui
import "image"
// Block is a base struct for all other upper level widgets,
// consider it as css: display:block.
// Normally you do not need to create it manually.
type Block struct {
Area image.Rectangle
innerArea image.Rectangle
X int
Y int
Border labeledBorder
Border LabeledBorder
IsDisplay bool
HasBorder bool
BgColor Attribute
Bg Attribute
Width int
Height int
innerWidth int
innerHeight int
innerX int
innerY int
PaddingTop int
PaddingBottom int
PaddingLeft int
@ -31,75 +31,81 @@ func NewBlock() *Block {
d := Block{}
d.IsDisplay = true
d.HasBorder = theme.HasBorder
d.Border.BgColor = theme.BorderBg
d.Border.FgColor = theme.BorderFg
d.Border.LabelBgColor = theme.BorderLabelTextBg
d.Border.LabelFgColor = theme.BorderLabelTextFg
d.BgColor = theme.BlockBg
d.Border.Left = true
d.Border.Right = true
d.Border.Top = true
d.Border.Bottom = true
d.Border.Bg = theme.BorderBg
d.Border.Fg = theme.BorderFg
d.Border.LabelBgClr = theme.BorderLabelTextBg
d.Border.LabelFgClr = theme.BorderLabelTextFg
d.Bg = theme.BlockBg
d.Width = 2
d.Height = 2
return &d
}
// compute box model
func (d *Block) align() {
d.innerWidth = d.Width - d.PaddingLeft - d.PaddingRight
d.innerHeight = d.Height - d.PaddingTop - d.PaddingBottom
d.innerX = d.X + d.PaddingLeft
d.innerY = d.Y + d.PaddingTop
// Align computes box model
func (d *Block) Align() {
d.Area.Min.X = d.X
d.Area.Min.Y = d.Y
d.Area.Max.X = d.X + d.Width - 1
d.Area.Max.Y = d.Y + d.Height - 1
d.innerArea.Min.X = d.X + d.PaddingLeft
d.innerArea.Min.Y = d.Y + d.PaddingTop
d.innerArea.Max.X = d.Area.Max.X - d.PaddingRight
d.innerArea.Max.Y = d.Area.Max.Y - d.PaddingBottom
d.Border.Area = d.Area
if d.HasBorder {
d.innerHeight -= 2
d.innerWidth -= 2
d.Border.X = d.X
d.Border.Y = d.Y
d.Border.Width = d.Width
d.Border.Height = d.Height
d.innerX++
d.innerY++
switch {
case d.Border.Left:
d.innerArea.Min.X++
fallthrough
case d.Border.Right:
d.innerArea.Max.X--
fallthrough
case d.Border.Top:
d.innerArea.Min.Y++
fallthrough
case d.Border.Bottom:
d.innerArea.Max.Y--
}
}
if d.innerHeight < 0 {
d.innerHeight = 0
}
if d.innerWidth < 0 {
d.innerWidth = 0
}
}
// InnerBounds returns the internal bounds of the block after aligning and
// calculating the padding and border, if any.
func (d *Block) InnerBounds() (x, y, width, height int) {
d.align()
return d.innerX, d.innerY, d.innerWidth, d.innerHeight
func (d *Block) InnerBounds() image.Rectangle {
d.Align()
return d.innerArea
}
// Buffer implements Bufferer interface.
// Draw background and border (if any).
func (d *Block) Buffer() []Point {
d.align()
func (d *Block) Buffer() Buffer {
d.Align()
ps := []Point{}
buf := NewBuffer()
buf.Area = d.Area
if !d.IsDisplay {
return ps
return buf
}
// render border
if d.HasBorder {
ps = d.Border.Buffer()
buf.Union(d.Border.Buffer())
}
for i := 0; i < d.innerWidth; i++ {
for j := 0; j < d.innerHeight; j++ {
p := Point{}
p.X = d.X + 1 + i
p.Y = d.Y + 1 + j
p.Ch = ' '
p.Bg = d.BgColor
ps = append(ps, p)
// render background
for p := range buf.CellMap {
if p.In(d.innerArea) {
buf.CellMap[p] = Cell{' ', ColorDefault, d.Bg}
}
}
return ps
return buf
}
// GetHeight implements GridBufferer.
@ -122,21 +128,3 @@ func (d *Block) SetY(y int) {
func (d *Block) SetWidth(w int) {
d.Width = w
}
// chop the overflow parts
func (d *Block) chopOverflow(ps []Point) []Point {
nps := make([]Point, 0, len(ps))
x := d.X
y := d.Y
w := d.Width
h := d.Height
for _, v := range ps {
if v.X >= x &&
v.X < x+w &&
v.Y >= y &&
v.Y < y+h {
nps = append(nps, v)
}
}
return nps
}

198
box.go
View File

@ -4,114 +4,120 @@
package termui
type border struct {
X int
Y int
Width int
Height int
FgColor Attribute
BgColor Attribute
import "image"
type Border struct {
Area image.Rectangle
Left bool
Top bool
Right bool
Bottom bool
Fg Attribute
Bg Attribute
}
type hline struct {
X int
Y int
Length int
FgColor Attribute
BgColor Attribute
type Hline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
type vline struct {
X int
Y int
Length int
FgColor Attribute
BgColor Attribute
type Vline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
// Draw a horizontal line.
func (l hline) Buffer() []Point {
pts := make([]Point, l.Length)
for i := 0; i < l.Length; i++ {
pts[i].X = l.X + i
pts[i].Y = l.Y
pts[i].Ch = HORIZONTAL_LINE
pts[i].Bg = l.BgColor
pts[i].Fg = l.FgColor
// Buffer draws a horizontal line.
func (l Hline) Buffer() Buffer {
buf := NewBuffer()
for i := 0; i < l.Len; i++ {
buf.Set(l.X+i, l.Y, Cell{HORIZONTAL_LINE, l.Fg, l.Bg})
}
return pts
buf.Align()
return buf
}
// Draw a vertical line.
func (l vline) Buffer() []Point {
pts := make([]Point, l.Length)
for i := 0; i < l.Length; i++ {
pts[i].X = l.X
pts[i].Y = l.Y + i
pts[i].Ch = VERTICAL_LINE
pts[i].Bg = l.BgColor
pts[i].Fg = l.FgColor
// Buffer draws a vertical line.
func (l Vline) Buffer() Buffer {
buf := NewBuffer()
for i := 0; i < l.Len; i++ {
buf.Set(l.X, l.Y+i, Cell{VERTICAL_LINE, l.Fg, l.Bg})
}
return pts
buf.Align()
return buf
}
// Draw a box border.
func (b border) Buffer() []Point {
if b.Width < 2 || b.Height < 2 {
return nil
}
pts := make([]Point, 2*b.Width+2*b.Height-4)
pts[0].X = b.X
pts[0].Y = b.Y
pts[0].Fg = b.FgColor
pts[0].Bg = b.BgColor
pts[0].Ch = TOP_LEFT
pts[1].X = b.X + b.Width - 1
pts[1].Y = b.Y
pts[1].Fg = b.FgColor
pts[1].Bg = b.BgColor
pts[1].Ch = TOP_RIGHT
pts[2].X = b.X
pts[2].Y = b.Y + b.Height - 1
pts[2].Fg = b.FgColor
pts[2].Bg = b.BgColor
pts[2].Ch = BOTTOM_LEFT
pts[3].X = b.X + b.Width - 1
pts[3].Y = b.Y + b.Height - 1
pts[3].Fg = b.FgColor
pts[3].Bg = b.BgColor
pts[3].Ch = BOTTOM_RIGHT
copy(pts[4:], (hline{b.X + 1, b.Y, b.Width - 2, b.FgColor, b.BgColor}).Buffer())
copy(pts[4+b.Width-2:], (hline{b.X + 1, b.Y + b.Height - 1, b.Width - 2, b.FgColor, b.BgColor}).Buffer())
copy(pts[4+2*b.Width-4:], (vline{b.X, b.Y + 1, b.Height - 2, b.FgColor, b.BgColor}).Buffer())
copy(pts[4+2*b.Width-4+b.Height-2:], (vline{b.X + b.Width - 1, b.Y + 1, b.Height - 2, b.FgColor, b.BgColor}).Buffer())
return pts
}
type labeledBorder struct {
border
Label string
LabelFgColor Attribute
LabelBgColor Attribute
}
// Draw a box border with label.
func (lb labeledBorder) Buffer() []Point {
ps := lb.border.Buffer()
maxTxtW := lb.Width - 2
rs := trimStr2Runes(lb.Label, maxTxtW)
for i, j, w := 0, 0, 0; i < len(rs); i++ {
w = charWidth(rs[i])
ps = append(ps, newPointWithAttrs(rs[i], lb.X+1+j, lb.Y, lb.LabelFgColor, lb.LabelBgColor))
j += w
// Buffer draws a box border.
func (b Border) Buffer() Buffer {
buf := NewBuffer()
if b.Area.Size().X < 2 || b.Area.Size().Y < 2 {
return buf
}
return ps
min := b.Area.Min
max := b.Area.Max
x0 := min.X
y0 := min.Y
x1 := max.X
y1 := max.Y
// draw lines
switch {
case b.Top:
buf.Union(Hline{x0, y0, x1 - x0, b.Fg, b.Bg}.Buffer())
fallthrough
case b.Bottom:
buf.Union(Hline{x0, y1, x1 - x0, b.Fg, b.Bg}.Buffer())
fallthrough
case b.Left:
buf.Union(Vline{x0, y0, y1 - y0, b.Fg, b.Bg}.Buffer())
fallthrough
case b.Right:
buf.Union(Vline{x1, y0, y1 - y0, b.Fg, b.Bg}.Buffer())
}
// draw corners
switch {
case b.Top && b.Left:
buf.Set(x0, y0, Cell{TOP_LEFT, b.Fg, b.Bg})
fallthrough
case b.Top && b.Right:
buf.Set(x1, y0, Cell{TOP_RIGHT, b.Fg, b.Bg})
fallthrough
case b.Bottom && b.Left:
buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.Fg, b.Bg})
fallthrough
case b.Bottom && b.Right:
buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.Fg, b.Bg})
}
return buf
}
// LabeledBorder defined label upon Border
type LabeledBorder struct {
Border
Label string
LabelFgClr Attribute
LabelBgClr Attribute
}
// Buffer draw a box border with label.
func (lb LabeledBorder) Buffer() Buffer {
border := lb.Border.Buffer()
maxTxtW := lb.Area.Dx() + 1 - 2
tx := DTrimTxCls(TextCells(lb.Label, lb.LabelFgClr, lb.LabelBgClr), maxTxtW)
for i, w := 0, 0; i < len(tx); i++ {
border.Set(border.Area.Min.X+1+w, border.Area.Min.Y, tx[i])
w += tx[i].Width()
}
return border
}

89
buffer.go Normal file
View File

@ -0,0 +1,89 @@
// 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.
package termui
import "image"
// Cell is a rune with assigned Fg and Bg
type Cell struct {
Ch rune
Fg Attribute
Bg Attribute
}
// Buffer is a renderable rectangle cell data container.
type Buffer struct {
Area image.Rectangle // selected drawing area
CellMap map[image.Point]Cell
}
// At returns the cell at (x,y).
func (b Buffer) At(x, y int) Cell {
return b.CellMap[image.Pt(x, y)]
}
// Set assigns a char to (x,y)
func (b Buffer) Set(x, y int, c Cell) {
b.CellMap[image.Pt(x, y)] = c
}
// Bounds returns the domain for which At can return non-zero color.
func (b Buffer) Bounds() image.Rectangle {
x0, y0, x1, y1 := 0, 0, 0, 0
for p := range b.CellMap {
switch {
case p.X > x1:
x1 = p.X
case p.X < x0:
x0 = p.X
case p.Y > y1:
y1 = p.Y
case p.Y < y0:
y0 = p.Y
}
}
return image.Rect(x0, y0, x1, y1)
}
// Align sets drawing area to the buffer's bound
func (b *Buffer) Align() {
b.Area = b.Bounds()
}
// NewCell returns a new cell
func NewCell(ch rune, fg, bg Attribute) Cell {
return Cell{ch, fg, bg}
}
// Union squeezes buf into b
func (b Buffer) Union(buf Buffer) {
for p, c := range buf.CellMap {
b.Set(p.X, p.Y, c)
}
}
// Union returns a new Buffer formed by squeezing bufs into one Buffer
func Union(bufs ...Buffer) Buffer {
buf := NewBuffer()
for _, b := range bufs {
buf.Union(b)
}
buf.Align()
return buf
}
// Point for adapting use, will be removed after resolving bridging.
type Point struct {
X int
Y int
Ch rune
Fg Attribute
Bg Attribute
}
// NewBuffer returns a new Buffer
func NewBuffer() Buffer {
return Buffer{CellMap: make(map[image.Point]Cell)}
}

View File

@ -7,6 +7,7 @@
package main
import "github.com/gizak/termui"
import "github.com/gizak/termui/widget"
func main() {
err := termui.Init()
@ -17,16 +18,23 @@ func main() {
termui.UseTheme("helloworld")
g0 := termui.NewGauge()
g0 := widget.NewGauge()
g0.Percent = 40
g0.Width = 50
g0.Height = 3
g0.Border.Label = "Slim Gauge"
g0.BarColor = termui.ColorRed
g0.Border.FgColor = termui.ColorWhite
g0.Border.LabelFgColor = termui.ColorCyan
g0.Border.Fg = termui.ColorWhite
g0.Border.LabelFgClr = termui.ColorCyan
g2 := termui.NewGauge()
gg := termui.NewBlock()
gg.Width = 50
gg.Height = 5
gg.Y = 12
gg.Border.Label = "TEST"
gg.Align()
g2 := widget.NewGauge()
g2.Percent = 60
g2.Width = 50
g2.Height = 3
@ -34,9 +42,9 @@ func main() {
g2.Y = 3
g2.Border.Label = "Slim Gauge"
g2.BarColor = termui.ColorYellow
g2.Border.FgColor = termui.ColorWhite
g2.Border.Fg = termui.ColorWhite
g1 := termui.NewGauge()
g1 := widget.NewGauge()
g1.Percent = 30
g1.Width = 50
g1.Height = 5
@ -44,10 +52,10 @@ func main() {
g1.Border.Label = "Big Gauge"
g1.PercentColor = termui.ColorYellow
g1.BarColor = termui.ColorGreen
g1.Border.FgColor = termui.ColorWhite
g1.Border.LabelFgColor = termui.ColorMagenta
g1.Border.Fg = termui.ColorWhite
g1.Border.LabelFgClr = termui.ColorMagenta
termui.Render(g0, g1, g2)
termui.Render(g0, g1, g2, gg)
<-termui.EventCh()
}

View File

@ -1,83 +0,0 @@
// 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.
package termui
import "strconv"
// Gauge is a progress bar like widget.
// A simple example:
/*
g := termui.NewGauge()
g.Percent = 40
g.Width = 50
g.Height = 3
g.Border.Label = "Slim Gauge"
g.BarColor = termui.ColorRed
g.PercentColor = termui.ColorBlue
*/
type Gauge struct {
Block
Percent int
BarColor Attribute
PercentColor Attribute
}
// NewGauge return a new gauge with current theme.
func NewGauge() *Gauge {
g := &Gauge{
Block: *NewBlock(),
PercentColor: theme.GaugePercent,
BarColor: theme.GaugeBar}
g.Width = 12
g.Height = 5
return g
}
// Buffer implements Bufferer interface.
func (g *Gauge) Buffer() []Point {
ps := g.Block.Buffer()
w := g.Percent * g.innerWidth / 100
s := strconv.Itoa(g.Percent) + "%"
rs := str2runes(s)
prx := g.innerX + g.innerWidth/2 - 1
pry := g.innerY + g.innerHeight/2
// plot bar
for i := 0; i < g.innerHeight; i++ {
for j := 0; j < w; j++ {
p := Point{}
p.X = g.innerX + j
p.Y = g.innerY + i
p.Ch = ' '
p.Bg = g.BarColor
if p.Bg == ColorDefault {
p.Bg |= AttrReverse
}
ps = append(ps, p)
}
}
// plot percentage
for i, v := range rs {
p := Point{}
p.X = prx + i
p.Y = pry
p.Ch = v
p.Fg = g.PercentColor
if w > g.innerWidth/2-1+i {
p.Bg = g.BarColor
if p.Bg == ColorDefault {
p.Bg |= AttrReverse
}
} else {
p.Bg = g.Block.BgColor
}
ps = append(ps, p)
}
return g.Block.chopOverflow(ps)
}

17
grid.go
View File

@ -160,8 +160,8 @@ func (r *Row) SetWidth(w int) {
// Buffer implements Bufferer interface,
// recursively merge all widgets buffer
func (r *Row) Buffer() []Point {
merged := []Point{}
func (r *Row) Buffer() Buffer {
merged := Buffer{}
if r.isRenderableLeaf() {
return r.Widget.Buffer()
@ -169,13 +169,13 @@ func (r *Row) Buffer() []Point {
// for those are not leaves but have a renderable widget
if r.Widget != nil {
merged = append(merged, r.Widget.Buffer()...)
merged.Union(r.Widget.Buffer())
}
// collect buffer from children
if !r.isLeaf() {
for _, c := range r.Cols {
merged = append(merged, c.Buffer()...)
merged.Union(c.Buffer())
}
}
@ -267,12 +267,13 @@ func (g *Grid) Align() {
}
// Buffer implments Bufferer interface.
func (g Grid) Buffer() []Point {
ps := []Point{}
func (g Grid) Buffer() Buffer {
buf := Buffer{}
for _, r := range g.Rows {
ps = append(ps, r.Buffer()...)
buf.Union(r.Buffer())
}
return ps
return buf
}
// Body corresponds to the entire terminal display region.

View File

@ -148,3 +148,60 @@ func StringToAttribute(text string) Attribute {
return result
}
// TextCells returns a coloured text cells []Cell
func TextCells(s string, fg, bg Attribute) []Cell {
cs := make([]Cell, 0, len(s))
// sequence := MarkdownTextRendererFactory{}.TextRenderer(s).Render(fg, bg)
// runes := []rune(sequence.NormalizedText)
runes := str2runes(s)
for n := range runes {
// point, _ := sequence.PointAt(n, 0, 0)
// cs = append(cs, Cell{point.Ch, point.Fg, point.Bg})
cs = append(cs, Cell{runes[n], fg, bg})
}
return cs
}
// Width returns the actual screen space the cell takes (usually 1 or 2).
func (c Cell) Width() int {
return charWidth(c.Ch)
}
// Copy return a copy of c
func (c Cell) Copy() Cell {
return c
}
// TrimTxCells trims the overflowed text cells sequence.
func TrimTxCells(cs []Cell, w int) []Cell {
if len(cs) <= w {
return cs
}
return cs[:w]
}
// DTrimTxCls trims the overflowed text cells sequence and append dots at the end.
func DTrimTxCls(cs []Cell, w int) []Cell {
l := len(cs)
if l <= 0 {
return []Cell{}
}
rt := make([]Cell, 0, w)
csw := 0
for i := 0; i < l && csw <= w; i++ {
c := cs[i]
cw := c.Width()
if cw+csw <= w {
rt = append(rt, c)
} else {
rt = append(rt, Cell{'…', c.Fg, c.Bg})
}
}
return rt
}

View File

@ -1,28 +0,0 @@
// 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.
package termui
// Point stands for a single cell in terminal.
type Point struct {
Ch rune
Bg Attribute
Fg Attribute
X int
Y int
}
func newPoint(c rune, x, y int) (p Point) {
p.Ch = c
p.X = x
p.Y = y
return
}
func newPointWithAttrs(c rune, x, y int, fg, bg Attribute) Point {
p := newPoint(c, x, y)
p.Bg = bg
p.Fg = fg
return p
}

View File

@ -8,7 +8,7 @@ import tm "github.com/nsf/termbox-go"
// Bufferer should be implemented by all renderable components.
type Bufferer interface {
Buffer() []Point
Buffer() Buffer
}
// Init initializes termui library. This function should be called before any others.
@ -46,13 +46,18 @@ func TermHeight() int {
// Render renders all Bufferer in the given order from left to right,
// right could overlap on left ones.
func Render(rs ...Bufferer) {
func Render(bs ...Bufferer) {
// set tm bg
tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg))
for _, r := range rs {
buf := r.Buffer()
for _, v := range buf {
tm.SetCell(v.X, v.Y, v.Ch, toTmAttr(v.Fg), toTmAttr(v.Bg))
for _, b := range bs {
buf := b.Buffer()
// set cels in buf
for p, c := range buf.CellMap {
if true { //}p.In(buf.Area) {
tm.SetCell(p.X, p.Y, c.Ch, toTmAttr(c.Fg), toTmAttr(c.Bg))
}
}
}
// render
tm.Flush()
}

View File

@ -1,3 +1,5 @@
// +build ignore
package termui
import (

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
//+build ignore
package termui
import (

67
widget/gauge.go Normal file
View File

@ -0,0 +1,67 @@
// 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.
package widget
import "github.com/gizak/termui"
import "strconv"
// Gauge is a progress bar like widget.
// A simple example:
/*
g := termui.NewGauge()
g.Percent = 40
g.Width = 50
g.Height = 3
g.Border.Label = "Slim Gauge"
g.BarColor = termui.ColorRed
g.PercentColor = termui.ColorBlue
*/
type Gauge struct {
termui.Block
Percent int
BarColor termui.Attribute
PercentColor termui.Attribute
}
// NewGauge return a new gauge with current theme.
func NewGauge() *Gauge {
g := &Gauge{
Block: *termui.NewBlock(),
PercentColor: termui.Theme().GaugePercent,
BarColor: termui.Theme().GaugeBar}
g.Width = 12
g.Height = 3
return g
}
// Buffer implements Bufferer interface.
func (g *Gauge) Buffer() termui.Buffer {
buf := g.Block.Buffer()
inner := g.InnerBounds()
w := g.Percent * (inner.Dx() + 1) / 100
s := strconv.Itoa(g.Percent) + "%"
tx := termui.TextCells(s, g.PercentColor, g.Bg)
prx := inner.Min.X + (inner.Dx()+1)/2 - 1
pry := inner.Min.Y + (inner.Dy()+1)/2
// plot bar
for i := 0; i <= inner.Dy(); i++ {
for j := 0; j < w; j++ {
c := termui.Cell{' ', g.BarColor, g.BarColor}
buf.Set(inner.Min.X+j, inner.Min.Y+i, c)
}
}
// plot percentage
for i, v := range tx {
if w > (inner.Dx()+1)/2-1+i {
v.Bg = g.BarColor
}
buf.Set(prx+i, pry, v)
}
return buf
}

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.

View File

@ -1,3 +1,5 @@
// +build ignore
// 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.