mirror of https://github.com/mum4k/termdash.git
Adding the draw library.
And a function that draws boxes.
This commit is contained in:
parent
dc1f2c5a29
commit
6b592b7d34
|
@ -2,7 +2,10 @@ package container
|
|||
|
||||
// options.go defines container options.
|
||||
|
||||
import "github.com/mum4k/termdash/widget"
|
||||
import (
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/widget"
|
||||
)
|
||||
|
||||
// Option is used to provide options.
|
||||
type Option interface {
|
||||
|
@ -25,7 +28,7 @@ type options struct {
|
|||
vAlign vAlignType
|
||||
|
||||
// border is the border around the container.
|
||||
border borderType
|
||||
border draw.LineStyle
|
||||
}
|
||||
|
||||
// option implements Option.
|
||||
|
@ -113,19 +116,10 @@ func VerticalAlignBottom() Option {
|
|||
})
|
||||
}
|
||||
|
||||
// BorderNone configures the container to have no border.
|
||||
// This is the default if none of the Border options is specified.
|
||||
func BorderNone() Option {
|
||||
// Border configures the container to have a border of the specified style.
|
||||
func Border(ls draw.LineStyle) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.border = borderTypeNone
|
||||
})
|
||||
}
|
||||
|
||||
// BorderSolid configures the container to have a border made with a solid
|
||||
// line.
|
||||
func BorderSolid() Option {
|
||||
return option(func(opts *options) {
|
||||
opts.border = borderTypeSolid
|
||||
opts.border = ls
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -198,25 +192,3 @@ const (
|
|||
vAlignTypeMiddle
|
||||
vAlignTypeBottom
|
||||
)
|
||||
|
||||
// borderType represents
|
||||
type borderType int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (bt borderType) String() string {
|
||||
if n, ok := borderTypeNames[bt]; ok {
|
||||
return n
|
||||
}
|
||||
return "borderTypeUnknown"
|
||||
}
|
||||
|
||||
// borderTypeNames maps borderType values to human readable names.
|
||||
var borderTypeNames = map[borderType]string{
|
||||
borderTypeNone: "borderTypeNone",
|
||||
borderTypeSolid: "borderTypeSolid",
|
||||
}
|
||||
|
||||
const (
|
||||
borderTypeNone borderType = iota
|
||||
borderTypeSolid
|
||||
)
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package draw
|
||||
|
||||
// box.go contains code that draws boxes.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/area"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
)
|
||||
|
||||
// boxChar returns the correct box character from the parts for the use at the
|
||||
// specified point of the box. Returns -1 if no character should be at this point.
|
||||
func boxChar(p image.Point, box image.Rectangle, parts map[linePart]rune) rune {
|
||||
switch {
|
||||
case p.X == box.Min.X && p.Y == box.Min.Y:
|
||||
return parts[topLeftCorner]
|
||||
case p.X == box.Max.X-1 && p.Y == box.Min.Y:
|
||||
return parts[topRightCorner]
|
||||
case p.X == box.Min.X && p.Y == box.Max.Y-1:
|
||||
return parts[bottomLeftCorner]
|
||||
case p.X == box.Max.X-1 && p.Y == box.Max.Y-1:
|
||||
return parts[bottomRightCorner]
|
||||
case p.X == box.Min.X || p.X == box.Max.X-1:
|
||||
return parts[vLine]
|
||||
case p.Y == box.Min.Y || p.Y == box.Max.Y-1:
|
||||
return parts[hLine]
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Box draws a box on the canvas.
|
||||
func Box(c *canvas.Canvas, box image.Rectangle, ls LineStyle, opts ...cell.Option) error {
|
||||
ar, err := area.FromSize(c.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !box.In(ar) {
|
||||
return fmt.Errorf("the requested box %+v falls outside of the provided canvas %+v", box, ar)
|
||||
}
|
||||
|
||||
const minSize = 2
|
||||
if box.Dx() < minSize || box.Dy() < minSize {
|
||||
return fmt.Errorf("the smallest supported box is %dx%d, got: %dx%d", minSize, minSize, box.Dx(), box.Dy())
|
||||
}
|
||||
|
||||
parts, err := lineParts(ls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for col := box.Min.X; col < box.Max.X; col++ {
|
||||
for row := box.Min.Y; row < box.Max.Y; row++ {
|
||||
p := image.Point{col, row}
|
||||
r := boxChar(p, box, parts)
|
||||
if r == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.SetCell(p, r, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
package draw
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/faketerm"
|
||||
)
|
||||
|
||||
func TestBox(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
canvas image.Rectangle
|
||||
box image.Rectangle
|
||||
ls LineStyle
|
||||
opts []cell.Option
|
||||
want cell.Buffer
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "box is larger than canvas",
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
box: image.Rect(0, 0, 2, 2),
|
||||
ls: LineStyleLight,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "box is too small",
|
||||
canvas: image.Rect(0, 0, 2, 2),
|
||||
box: image.Rect(0, 0, 1, 1),
|
||||
ls: LineStyleLight,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "unsupported line style",
|
||||
canvas: image.Rect(0, 0, 4, 4),
|
||||
box: image.Rect(0, 0, 2, 2),
|
||||
ls: lineStyleUnknown,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "draws box around the canvas",
|
||||
canvas: image.Rect(0, 0, 4, 4),
|
||||
box: image.Rect(0, 0, 4, 4),
|
||||
ls: LineStyleLight,
|
||||
want: cell.Buffer{
|
||||
{
|
||||
cell.New(lineStyleChars[LineStyleLight][topLeftCorner]),
|
||||
cell.New(lineStyleChars[LineStyleLight][vLine]),
|
||||
cell.New(lineStyleChars[LineStyleLight][vLine]),
|
||||
cell.New(lineStyleChars[LineStyleLight][bottomLeftCorner]),
|
||||
},
|
||||
{
|
||||
cell.New(lineStyleChars[LineStyleLight][hLine]),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(lineStyleChars[LineStyleLight][hLine]),
|
||||
},
|
||||
{
|
||||
cell.New(lineStyleChars[LineStyleLight][hLine]),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(lineStyleChars[LineStyleLight][hLine]),
|
||||
},
|
||||
{
|
||||
cell.New(lineStyleChars[LineStyleLight][topRightCorner]),
|
||||
cell.New(lineStyleChars[LineStyleLight][vLine]),
|
||||
cell.New(lineStyleChars[LineStyleLight][vLine]),
|
||||
cell.New(lineStyleChars[LineStyleLight][bottomRightCorner]),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws box in the canvas",
|
||||
canvas: image.Rect(0, 0, 4, 4),
|
||||
box: image.Rect(1, 1, 3, 3),
|
||||
ls: LineStyleLight,
|
||||
want: cell.Buffer{
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
},
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(lineStyleChars[LineStyleLight][topLeftCorner]),
|
||||
cell.New(lineStyleChars[LineStyleLight][bottomLeftCorner]),
|
||||
cell.New(0),
|
||||
},
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(lineStyleChars[LineStyleLight][topRightCorner]),
|
||||
cell.New(lineStyleChars[LineStyleLight][bottomRightCorner]),
|
||||
cell.New(0),
|
||||
},
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws box with cell options",
|
||||
canvas: image.Rect(0, 0, 4, 4),
|
||||
box: image.Rect(1, 1, 3, 3),
|
||||
ls: LineStyleLight,
|
||||
opts: []cell.Option{
|
||||
cell.FgColor(cell.ColorRed),
|
||||
},
|
||||
want: cell.Buffer{
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
},
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(
|
||||
lineStyleChars[LineStyleLight][topLeftCorner],
|
||||
cell.FgColor(cell.ColorRed),
|
||||
),
|
||||
cell.New(
|
||||
lineStyleChars[LineStyleLight][bottomLeftCorner],
|
||||
cell.FgColor(cell.ColorRed),
|
||||
),
|
||||
cell.New(0),
|
||||
},
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(
|
||||
lineStyleChars[LineStyleLight][topRightCorner],
|
||||
cell.FgColor(cell.ColorRed),
|
||||
),
|
||||
cell.New(
|
||||
lineStyleChars[LineStyleLight][bottomRightCorner],
|
||||
cell.FgColor(cell.ColorRed),
|
||||
),
|
||||
cell.New(0),
|
||||
},
|
||||
{
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
cell.New(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c, err := canvas.New(tc.canvas)
|
||||
if err != nil {
|
||||
t.Fatalf("canvas.New => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = Box(c, tc.box, tc.ls, tc.opts...)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("Box => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ft, err := faketerm.New(c.Size())
|
||||
if err != nil {
|
||||
t.Fatalf("faketerm.New => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := c.Apply(ft); err != nil {
|
||||
t.Fatalf("Apply => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got := ft.BackBuffer()
|
||||
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||
t.Logf("Box => got output:\n%s", ft)
|
||||
t.Errorf("Box => unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// Package draw provides functions that draw lines, shapes, etc on 2-D terminal
|
||||
// like canvases.
|
||||
package draw
|
|
@ -0,0 +1,77 @@
|
|||
package draw
|
||||
|
||||
import "fmt"
|
||||
|
||||
// line_style.go contains the Unicode characters used for drawing lines of
|
||||
// different styles.
|
||||
|
||||
// lineStyleChars maps the line styles to the corresponding component characters.
|
||||
var lineStyleChars = map[LineStyle]map[linePart]rune{
|
||||
LineStyleLight: map[linePart]rune{
|
||||
hLine: '─',
|
||||
vLine: '│',
|
||||
topLeftCorner: '┌',
|
||||
topRightCorner: '┐',
|
||||
bottomLeftCorner: '└',
|
||||
bottomRightCorner: '┘',
|
||||
},
|
||||
}
|
||||
|
||||
// lineParts returns the line component characters for the provided line style.
|
||||
func lineParts(ls LineStyle) (map[linePart]rune, error) {
|
||||
parts, ok := lineStyleChars[ls]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported line style %v", ls)
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// LineStyle defines the supported line styles.Q
|
||||
type LineStyle int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (ls LineStyle) String() string {
|
||||
if n, ok := lineStyleNames[ls]; ok {
|
||||
return n
|
||||
}
|
||||
return "LineStyleUnknown"
|
||||
}
|
||||
|
||||
// lineStyleNames maps LineStyle values to human readable names.
|
||||
var lineStyleNames = map[LineStyle]string{
|
||||
LineStyleLight: "LineStyleLight",
|
||||
}
|
||||
|
||||
const (
|
||||
lineStyleUnknown LineStyle = iota
|
||||
LineStyleLight
|
||||
)
|
||||
|
||||
// linePart identifies individual line parts.
|
||||
type linePart int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (lp linePart) String() string {
|
||||
if n, ok := linePartNames[lp]; ok {
|
||||
return n
|
||||
}
|
||||
return "linePartUnknown"
|
||||
}
|
||||
|
||||
// linePartNames maps linePart values to human readable names.
|
||||
var linePartNames = map[linePart]string{
|
||||
vLine: "linePartVLine",
|
||||
topLeftCorner: "linePartTopLeftCorner",
|
||||
topRightCorner: "linePartTopRightCorner",
|
||||
bottomLeftCorner: "linePartBottomLeftCorner",
|
||||
bottomRightCorner: "linePartBottomRightCorner",
|
||||
}
|
||||
|
||||
const (
|
||||
hLine linePart = iota
|
||||
vLine
|
||||
topLeftCorner
|
||||
topRightCorner
|
||||
bottomLeftCorner
|
||||
bottomRightCorner
|
||||
)
|
|
@ -2,6 +2,7 @@
|
|||
package faketerm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -55,6 +56,25 @@ func (t *Terminal) BackBuffer() cell.Buffer {
|
|||
return t.buffer
|
||||
}
|
||||
|
||||
// String prints out the buffer into a string.
|
||||
// TODO(mum4k): Support printing of options.
|
||||
// Implements fmt.Stringer.
|
||||
func (t *Terminal) String() string {
|
||||
size := t.Size()
|
||||
var b bytes.Buffer
|
||||
for row := 0; row < size.Y; row++ {
|
||||
for col := 0; col < size.X; col++ {
|
||||
r := t.buffer[col][row].Rune
|
||||
if r == 0 {
|
||||
r = ' '
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Implements terminalapi.Terminal.Size.
|
||||
func (t *Terminal) Size() image.Point {
|
||||
return t.buffer.Size()
|
||||
|
|
Loading…
Reference in New Issue