Smash Border into Block

This commit is contained in:
gizak 2015-04-26 00:13:49 -04:00
parent 7aed750f64
commit b65224cdc9
6 changed files with 208 additions and 218 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ _testmain.go
*.exe
*.test
*.prof
.DS_Store

228
block.go
View File

@ -6,17 +6,116 @@ package termui
import "image"
// 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.
// Hline is a horizontal line.
type Hline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
// Vline is a vertical line.
type Vline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
// Buffer draws a horizontal line.
func (l Hline) Buffer() Buffer {
if l.Len <= 0 {
return NewBuffer()
}
return NewFilledBuffer(l.X, l.Y, l.X+l.Len, l.Y, HORIZONTAL_LINE, l.Fg, l.Bg)
}
// Buffer draws a vertical line.
func (l Vline) Buffer() Buffer {
if l.Len <= 0 {
return NewBuffer()
}
return NewFilledBuffer(l.X, l.Y, l.X, l.Y+l.Len, VERTICAL_LINE, l.Fg, l.Bg)
}
// Buffer draws a box border.
func (b Block) drawBorder(buf Buffer) {
if !b.Border {
return
}
min := b.area.Min
max := b.area.Max
x0 := min.X
y0 := min.Y
x1 := max.X
y1 := max.Y
// draw lines
if b.BorderTop {
buf.Merge(Hline{x0, y0, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
}
if b.BorderBottom {
buf.Merge(Hline{x0, y1, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
}
if b.BorderLeft {
buf.Merge(Vline{x0, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
}
if b.BorderRight {
buf.Merge(Vline{x1, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
}
// draw corners
if b.BorderTop && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 0 {
buf.Set(x0, y0, Cell{TOP_LEFT, b.BorderFg, b.BorderBg})
}
if b.BorderTop && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 0 {
buf.Set(x1, y0, Cell{TOP_RIGHT, b.BorderFg, b.BorderBg})
}
if b.BorderBottom && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 1 {
buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.BorderFg, b.BorderBg})
}
if b.BorderBottom && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 1 {
buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.BorderFg, b.BorderBg})
}
}
func (b Block) drawBorderLabel(buf Buffer) {
maxTxtW := b.area.Dx() - 2
tx := DTrimTxCls(TextCells(b.BorderLabel, b.BorderLabelFg, b.BorderLabelBg), maxTxtW)
for i, w := 0, 0; i < len(tx); i++ {
buf.Set(b.area.Min.X+1+w, b.area.Min.Y, tx[i])
w += tx[i].Width()
}
}
// 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
area image.Rectangle
innerArea image.Rectangle
X int
Y int
Border LabeledBorder
IsDisplay bool
HasBorder bool
Border bool
BorderFg Attribute
BorderBg Attribute
BorderLeft bool
BorderRight bool
BorderTop bool
BorderBottom bool
BorderLabel string
BorderLabelFg Attribute
BorderLabelBg Attribute
Display bool
Bg Attribute
Width int
Height int
@ -28,103 +127,90 @@ type Block struct {
// NewBlock returns a *Block which inherits styles from current theme.
func NewBlock() *Block {
d := Block{}
d.IsDisplay = true
d.HasBorder = theme.HasBorder
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
b := Block{}
b.Display = true
b.Border = theme.HasBorder
b.BorderLeft = true
b.BorderRight = true
b.BorderTop = true
b.BorderBottom = true
b.BorderBg = theme.BorderBg
b.BorderFg = theme.BorderFg
b.BorderLabelBg = theme.BorderLabelTextBg
b.BorderLabelFg = theme.BorderLabelTextFg
b.Bg = theme.BlockBg
b.Width = 2
b.Height = 2
return &b
}
// 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
// Align computes box mob.l
func (b *Block) Align() {
b.area.Min.X = b.X
b.area.Min.Y = b.Y
b.area.Max.X = b.X + b.Width - 1
b.area.Max.Y = b.Y + b.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
b.innerArea.Min.X = b.X + b.PaddingLeft
b.innerArea.Min.Y = b.Y + b.PaddingTop
b.innerArea.Max.X = b.area.Max.X - b.PaddingRight
b.innerArea.Max.Y = b.area.Max.Y - b.PaddingBottom
d.Border.Area = d.Area
if d.HasBorder {
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 b.Border {
if b.BorderLeft {
b.innerArea.Min.X++
}
if b.BorderRight {
b.innerArea.Max.X--
}
if b.BorderTop {
b.innerArea.Min.Y++
}
if b.BorderBottom {
b.innerArea.Max.Y--
}
}
}
// InnerBounds returns the internal bounds of the block after aligning and
// calculating the padding and border, if any.
func (d *Block) InnerBounds() image.Rectangle {
d.Align()
return d.innerArea
func (b *Block) InnerBounds() image.Rectangle {
b.Align()
return b.innerArea
}
// Buffer implements Bufferer interface.
// Draw background and border (if any).
func (d *Block) Buffer() Buffer {
d.Align()
func (b *Block) Buffer() Buffer {
b.Align()
buf := NewBuffer()
buf.Area = d.Area
if !d.IsDisplay {
return buf
}
buf.SetArea(b.area)
buf.Fill(' ', ColorDefault, b.Bg)
// render border
if d.HasBorder {
buf.Union(d.Border.Buffer())
}
b.drawBorder(buf)
b.drawBorderLabel(buf)
// render background
for p := range buf.CellMap {
if p.In(d.innerArea) {
buf.CellMap[p] = Cell{' ', ColorDefault, d.Bg}
}
}
return buf
}
// GetHeight implements GridBufferer.
// It returns current height of the block.
func (d Block) GetHeight() int {
return d.Height
func (b Block) GetHeight() int {
return b.Height
}
// SetX implements GridBufferer interface, which sets block's x position.
func (d *Block) SetX(x int) {
d.X = x
func (b *Block) SetX(x int) {
b.X = x
}
// SetY implements GridBufferer interface, it sets y position for block.
func (d *Block) SetY(y int) {
d.Y = y
func (b *Block) SetY(y int) {
b.Y = y
}
// SetWidth implements GridBuffer interface, it sets block's width.
func (d *Block) SetWidth(w int) {
d.Width = w
func (b *Block) SetWidth(w int) {
b.Width = w
}

123
box.go
View File

@ -1,123 +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 "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
Len int
Fg Attribute
Bg Attribute
}
type Vline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
// 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})
}
buf.Align()
return buf
}
// 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})
}
buf.Align()
return buf
}
// 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
}
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
}

View File

@ -15,7 +15,7 @@ type Cell struct {
// Buffer is a renderable rectangle cell data container.
type Buffer struct {
Area image.Rectangle // selected drawing area
Area *image.Rectangle // selected drawing area
CellMap map[image.Point]Cell
}
@ -33,23 +33,31 @@ func (b Buffer) Set(x, y int, c Cell) {
func (b Buffer) Bounds() image.Rectangle {
x0, y0, x1, y1 := 0, 0, 0, 0
for p := range b.CellMap {
switch {
case p.X > x1:
if p.X > x1 {
x1 = p.X
case p.X < x0:
}
if p.X < x0 {
x0 = p.X
case p.Y > y1:
}
if p.Y > y1 {
y1 = p.Y
case p.Y < y0:
}
if 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()
// SetArea assigns a new rect area to Buffer b.
func (b Buffer) SetArea(r image.Rectangle) {
b.Area.Max = r.Max
b.Area.Min = r.Min
}
// Sync sets drawing area to the buffer's bound
func (b Buffer) Sync() {
b.SetArea(b.Bounds())
}
// NewCell returns a new cell
@ -57,23 +65,16 @@ 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)
// Merge merges bs Buffers onto b
func (b Buffer) Merge(bs ...Buffer) {
for _, buf := range bs {
for p, v := range buf.CellMap {
b.Set(p.X, p.Y, v)
}
b.SetArea(b.Area.Union(*buf.Area))
}
}
// 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
@ -85,5 +86,30 @@ type Point struct {
// NewBuffer returns a new Buffer
func NewBuffer() Buffer {
return Buffer{CellMap: make(map[image.Point]Cell)}
return Buffer{
CellMap: make(map[image.Point]Cell),
Area: &image.Rectangle{}}
}
// Fill fills the Buffer b with ch,fg and bg.
func (b Buffer) Fill(ch rune, fg, bg Attribute) {
for x := b.Area.Min.X; x < b.Area.Max.X; x++ {
for y := b.Area.Min.Y; y < b.Area.Max.Y; y++ {
b.Set(x, y, Cell{ch, fg, bg})
}
}
}
// NewFilledBuffer returns a new Buffer filled with ch, fb and bg.
func NewFilledBuffer(x0, y0, x1, y1 int, ch rune, fg, bg Attribute) Buffer {
buf := NewBuffer()
buf.Area.Min = image.Pt(x0, y0)
buf.Area.Max = image.Pt(x1, y1)
for x := buf.Area.Min.X; x < buf.Area.Max.X; x++ {
for y := buf.Area.Min.Y; y < buf.Area.Max.Y; y++ {
buf.Set(x, y, Cell{ch, fg, bg})
}
}
return buf
}