From b65224cdc91baceceb9fd543fa1c0c9a53bd4673 Mon Sep 17 00:00:00 2001 From: gizak Date: Sun, 26 Apr 2015 00:13:49 -0400 Subject: [PATCH] Smash Border into Block --- .gitignore | 1 + block.go | 228 ++++++++++++++++++++--------- box_others.go => block_common.go | 0 box_windows.go => block_windows.go | 0 box.go | 123 ---------------- buffer.go | 74 +++++++--- 6 files changed, 208 insertions(+), 218 deletions(-) rename box_others.go => block_common.go (100%) rename box_windows.go => block_windows.go (100%) delete mode 100644 box.go diff --git a/.gitignore b/.gitignore index daf913b..eb1369f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ _testmain.go *.exe *.test *.prof +.DS_Store diff --git a/block.go b/block.go index 14b7819..c479140 100644 --- a/block.go +++ b/block.go @@ -6,17 +6,116 @@ package termui import "image" +// Copyright 2015 Zack Guo . 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 } diff --git a/box_others.go b/block_common.go similarity index 100% rename from box_others.go rename to block_common.go diff --git a/box_windows.go b/block_windows.go similarity index 100% rename from box_windows.go rename to block_windows.go diff --git a/box.go b/box.go deleted file mode 100644 index 561811f..0000000 --- a/box.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2015 Zack Guo . 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 -} diff --git a/buffer.go b/buffer.go index 2e0892f..ea2064a 100644 --- a/buffer.go +++ b/buffer.go @@ -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 }